From 6f38c91b1639b78f489c7868c0fef715ca3bdec2 Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 17 Jul 2023 18:19:27 -0400 Subject: [PATCH 01/31] Merge in initial code for explict basis --- Project.toml | 6 +- src/Polynomials.jl | 12 + src/abstract-polynomial.jl | 308 ++++++++++++++++++ src/abstract.jl | 4 +- src/basis-utils.jl | 57 ++++ src/common.jl | 2 +- src/polynomial-basetypes/dense-polynomial.jl | 262 +++++++++++++++ .../immutable-polynomial.jl | 281 ++++++++++++++++ src/polynomial-basetypes/sparse-polynomial.jl | 230 +++++++++++++ src/standard-basis/standard-basis.jl | 125 +++++++ src/standard-basis/standard-dense.jl | 104 ++++++ src/standard-basis/standard-immutable.jl | 115 +++++++ src/standard-basis/standard-sparse.jl | 35 ++ 13 files changed, 1536 insertions(+), 5 deletions(-) create mode 100644 src/abstract-polynomial.jl create mode 100644 src/basis-utils.jl create mode 100644 src/polynomial-basetypes/dense-polynomial.jl create mode 100644 src/polynomial-basetypes/immutable-polynomial.jl create mode 100644 src/polynomial-basetypes/sparse-polynomial.jl create mode 100644 src/standard-basis/standard-basis.jl create mode 100644 src/standard-basis/standard-dense.jl create mode 100644 src/standard-basis/standard-immutable.jl create mode 100644 src/standard-basis/standard-sparse.jl diff --git a/Project.toml b/Project.toml index cf2cd6c8..74b536de 100644 --- a/Project.toml +++ b/Project.toml @@ -10,6 +10,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MakieCore = "20f20a25-4f0e-4fdf-b5d1-57303727442b" MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" [weakdeps] ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" @@ -26,16 +27,17 @@ ChainRulesCore = "1" MakieCore = "0.6" MutableArithmetics = "1" RecipesBase = "0.7, 0.8, 1" +Setfield = "1" julia = "1.6" [extras] ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -MakieCore = "20f20a25-4f0e-4fdf-b5d1-57303727442b" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" DualNumbers = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" +MakieCore = "20f20a25-4f0e-4fdf-b5d1-57303727442b" MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" +OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 6d0a1f7b..93e34cb1 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -3,6 +3,7 @@ module Polynomials # using GenericLinearAlgebra ## remove for now. cf: https://github.com/JuliaLinearAlgebra/GenericLinearAlgebra.jl/pull/71#issuecomment-743928205 using LinearAlgebra import Base: evalpoly +using Setfield include("abstract.jl") include("show.jl") @@ -31,6 +32,17 @@ include("rational-functions/fit.jl") #include("rational-functions/rational-transfer-function.jl") include("rational-functions/plot-recipes.jl") +# polynomials with explicit basis +include("abstract-polynomial.jl") +include("basis-utils.jl") +include("polynomial-basetypes/dense-polynomial.jl") +include("polynomial-basetypes/immutable-polynomial.jl") +include("polynomial-basetypes/sparse-polynomial.jl") +include("standard-basis/standard-basis.jl") +include("standard-basis/standard-dense.jl") +include("standard-basis/standard-immutable.jl") +include("standard-basis/standard-sparse.jl") + # compat; opt-in with `using Polynomials.PolyCompat` include("polynomials/Poly.jl") diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl new file mode 100644 index 00000000..8cc4e4bd --- /dev/null +++ b/src/abstract-polynomial.jl @@ -0,0 +1,308 @@ +""" + Abstract type for polynomials with an explicit basis. +""" +abstract type AbstractUnivariatePolynomial{B, T, X} <: AbstractPolynomial{T,X} end +abstract type AbstractBasis end + + +basistype(p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = B +Base.eltype(p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = T +indeterminate(p::P) where {P <: AbstractUnivariatePolynomial} = indeterminate(P) +_indeterminate(::Type{P}) where {P <: AbstractUnivariatePolynomial} = nothing +_indeterminate(::Type{P}) where {B,T, X, P <: AbstractUnivariatePolynomial{B,T,X}} = X +indeterminate(::Type{P}) where {P <: AbstractUnivariatePolynomial} = something(_indeterminate(P), :x) + +constructorof(::Type{<:AbstractUnivariatePolynomial}) = XXX() +⟒(P::Type{<:AbstractUnivariatePolynomial}) = constructorof(P) +⟒(p::P) where {P <: AbstractUnivariatePolynomial} = ⟒(P) + +## Julia generics treating coefficients as an abstract vector +# pairs should iterate i => cᵢ where basis(P,i) is the basis vector +# * may not be in increasing or decreasing i +# * for standardbasis i -> xⁱ +# * possibly skipping when iszero(cᵢ) +Base.keys(p::AbstractUnivariatePolynomial) = Base.Generator(first, pairs(p)) +Base.values(p::AbstractUnivariatePolynomial) = Base.Generator(last, pairs(p)) +Base.firstindex(p::AbstractUnivariatePolynomial{B, T, X}) where {B,T,X} = XXX() +Base.lastindex(p::AbstractUnivariatePolynomial{B, T, X}) where {B,T,X} = XXX() +Base.iterate(p::AbstractUnivariatePolynomial, args...) = Base.iterate(p.coeffs, args...) +Base.pairs(p::AbstractUnivariatePolynomial) = XXX() + +Base.eltype(::Type{<:AbstractUnivariatePolynomial}) = Float64 +Base.eltype(::Type{<:AbstractUnivariatePolynomial{B,T}}) where {B,T} = T + +Base.size(p::AbstractUnivariatePolynomial) = (length(p),) +Base.size(p::AbstractUnivariatePolynomial, i::Integer) = i <= 1 ? size(p)[i] : 1 + + +hasnan(p::AbstractUnivariatePolynomial) = any(hasnan, p) + + +function LinearAlgebra.norm(q::AbstractUnivariatePolynomial, p::Real = 2) + vs = values(q) + return norm(vs, p) # if vs=() must be handled in special type +end +# norm(q1 - q2) +function normΔ(q1::AbstractUnivariatePolynomial, q2::AbstractUnivariatePolynomial, p::Real = 2) + iszero(q1) && return norm(q2, p) + iszero(q2) && return norm(q1, p) + r = zero(q1[end] + q2[end]) + tot = zero(r) + for i ∈ union(keys(q1), keys(q2)) + @inbounds tot += (q1[i] - q2[i])^p + end + return tot^(1/p) +end + +# need to promote Number -> Poly +# Base.isapprox(p1::AbstractUnivariatePolynomial, p2::Number; kwargs...) = isapprox(promote(p1, p2)...; kwargs...) +# Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(promote(p1, p2)...; kwargs...) +function Base.isapprox(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial; kwargs...) + isapprox(promote(p1, p2)...; kwargs...) +end + +function assert_same_variable(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial) + (isconstant(p1) || isconstant(p2) ) && return true + indeterminate(p1) == indeterminate(p2) && return true + throw(ArgumentError("Polynomials have different indeterminates")) +end +function Base.isapprox(p1::AbstractUnivariatePolynomial{B}, p2::AbstractUnivariatePolynomial{B}; kwargs...) where {B} + if isconstant(p1) + isconstant(p2) && return constantterm(p1) == constantterm(p2) + return false + elseif isconstant(p2) + return false + end + assert_same_variable(p1, p2) || return false + isapprox(promote(p1, p2)...; kwargs...) +end + +function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, + p2::AbstractUnivariatePolynomial{B,T,X}; + rtol::Real = (Base.rtoldefault(T,T,0)), + atol::Real = 0,) where {B,T,X} + (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons + # copy over from abstractarray.jl + Δ = normΔ(p1,p2) + if isfinite(Δ) + return Δ <= max(atol, rtol * max(norm(p1), norm(p2))) + else + for i in 0:max(degree(p1), degree(p2)) + isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false + end + return true + end +end + +function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, p2::Number; kwargs...) where {B,T,X} + q = p2 * one(⟒(p1){B,T,X}) + isapprox(p1, q; kwargs...) +end +Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(p2, p1; kwargs...) + +# map Polynomial terms -> vector terms +degree(p::AbstractUnivariatePolynomial) = iszero(p) ? -1 : lastindex(p) +order(p::AbstractUnivariatePolynomial) = firstindex(p) + +_zeros(p::P, z, N) where {P <: AbstractUnivariatePolynomial} = _zeros(P, z, N) + +check_same_variable(p::AbstractUnivariatePolynomial, q::AbstractUnivariatePolynomial) = indeterminate(p) == indeterminate(q) + +# The zero polynomial. Typically has no coefficients +Base.zero(p::P,args...) where {P <: AbstractUnivariatePolynomial} = zero(P,args...) +Base.zero(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){B,eltype(P),indeterminate(P)}) +Base.zero(::Type{P},var::SymbolLike) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){B,eltype(P),Symbol(var)}) + +# the polynomial 1 +Base.one(p::P,args...) where {P <: AbstractUnivariatePolynomial} = one(P,args...) +Base.one(::Type{P}) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){B,eltype(P),indeterminate(P)}) +Base.one(::Type{P}, var::SymbolLike) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){B,eltype(P),Symbol(var)}) + +# the variable x +variable(p::P) where {P <: AbstractUnivariatePolynomial} = variable(P) +variable(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = variable(⟒(P){B,eltype(P),indeterminate(P)}) +variable(::Type{P}, var::SymbolLike) where {B,P<:AbstractUnivariatePolynomial{B}} = variable(⟒(P){B,eltype(P),Var(var)}) + +# i -> basis polynomial +basis(p::P, i) where {P <: AbstractUnivariatePolynomial} = basis(P, i) +basis(::Type{P}, i) where {B,P <: AbstractUnivariatePolynomial{B}} = basis(⟒(P){B,eltype(P),indeterminate(P)}, i) + +# return dense coefficients (vector or tuple) +coeffs(p::AbstractUnivariatePolynomial) = [p[i] for i ∈ firstindex(p):lastindex(p)] + +function isconstant(p::AbstractUnivariatePolynomial) + p₀ = trim_trailing_zeros(p) + return (firstindex(p₀) == lastindex(p₀) == 0) +end + +# chop chops right (and left side +# can pass tolerances +Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = XXX() +chop!(p::AbstractUnivariatePolynomial; kwargs...) = XXX() + + +# arithmetic dispatch +struct ConstantTerm{T} + x::T +end +function ConstantTerm(p::AbstractUnivariatePolynomial) + isconstant(p) || throw(ArgumentError("Non-constant polynomial")) + convert(ConstantTerm, p) +end +Base.getindex(b::ConstantTerm) = b.x +Base.show(io::IO, c::ConstantTerm) = print(io, c.x) +Base.iszero(b::ConstantTerm) = iszero(b.x) +isconstant(::ConstantTerm) = true +Base.convert(::Type{ConstantTerm}, p::AbstractUnivariatePolynomial) = ConstantTerm(constantterm(p)) +Base.convert(::Type{ConstantTerm{T}}, p::AbstractUnivariatePolynomial) where {T} = ConstantTerm(T(constantterm(p))) + +#= Comparisons =# +Base.isequal(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} = hash(p1) == hash(p2) +function Base.:(==)(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} + iszero(p1) && iszero(p2) && return true + lastindex(p1) == lastindex(p2) || return false + # coeffs(p1) == coeffs(p2), but non-allocating + for i ∈ union(keys(p1), keys(p2)) + p1[i] == p2[i] || return false +# for ((i,pᵢ), (j, pⱼ)) ∈ zip(pairs(p1), pairs(p2)) +# i == j && pᵢ == pⱼ || return false + end + return true +end +function Base.:(==)(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial) + if isconstant(p1) + isconstant(p2) && return constantterm(p1) == constantterm(p2) + return false + elseif isconstant(p2) + return false # p1 is not constant + end + check_same_variable(p1, p2) || return false + ==(promote(p1,p2)...) +end +Base.:(==)(p::AbstractUnivariatePolynomial, n::Number) = degree(p) <= 0 && constantterm(p) == n +Base.:(==)(n::Number, p::AbstractUnivariatePolynomial) = p == n + + +Base.:-(p::AbstractUnivariatePolynomial) = scalar_mul(-1, p) + +Base.:+(c::Number, p::AbstractUnivariatePolynomial) = scalar_add(p, c) +Base.:+(p::AbstractUnivariatePolynomial, c::Number) = scalar_add(p, c) +Base.:+(c::ConstantTerm, p::AbstractUnivariatePolynomial) = scalar_add(p, c[]) +Base.:+(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_add(p, c[]) +scalar_add(p::AbstractUnivariatePolynomial, c) = scalar_add(c,p) # scalar addition is commutative + +Base.:+(p::AbstractUnivariatePolynomial) = p +Base.:+(p::AbstractUnivariatePolynomial{B, T, X}, + q::AbstractUnivariatePolynomial{B, S, X}) where {B,T,S,X} = + +(promote(p,q)...) +Base.:+(p::AbstractUnivariatePolynomial{B, T, X}, + q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = + _mixed_symbol_op(+, p, q) + + + +Base.:-(c::Number, p::AbstractUnivariatePolynomial) = c + (-p) +Base.:-(p::AbstractUnivariatePolynomial, c::Number) = p + (-c) +Base.:-(c::ConstantTerm, p::AbstractUnivariatePolynomial) = (-c[]) + p +Base.:-(p::AbstractUnivariatePolynomial, c::ConstantTerm) = p - c[] + +Base.:-(p::AbstractUnivariatePolynomial{B, T, X}, + q::AbstractUnivariatePolynomial{B, S, X}) where {B,T,S,X} = p + (-q) +Base.:-(p::AbstractUnivariatePolynomial{B, T, X}, + q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = + _mixed_symbol_op(-, p, q) + +Base.:*(c::Number, p::ConstantTerm) = ConstantTerm(c*p[]) +Base.:*(p::ConstantTerm, c::Number) = ConstantTerm(p[] * c) + +Base.:*(c::Number, p::AbstractUnivariatePolynomial) = scalar_mul(c, p) +Base.:*(c::ConstantTerm, p::AbstractUnivariatePolynomial) = scalar_mul(c[], p) +Base.:*(p::AbstractUnivariatePolynomial, c::Number) = scalar_mul(p, c) +Base.:*(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_mul(p, c[]) + +Base.:*(p::AbstractUnivariatePolynomial{B, T, X}, + q::AbstractUnivariatePolynomial{B, S, X}) where {B,T,S,X} = + ⊗(p, q) +Base.:*(p::AbstractUnivariatePolynomial{B, T, X}, + q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = + _mixed_symbol_op(*, p, q) + +Base.:/(p::AbstractUnivariatePolynomial, c::Number) = scalar_mul(p, one(eltype(p))/c) +Base.:/(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_mul(p, one(eltype(p))/c) + +Base.:^(p::AbstractUnivariatePolynomial, n::Integer) = Base.power_by_squaring(p, n) + +# treat constant polynomials as ConstantTerm when symbols mixed +function _mixed_symbol_op(op, + p::AbstractUnivariatePolynomial{B, T, X}, + q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} + X == Y && throw(ArgumentError("dispatch")) + if isconstant(p) + return op(convert(ConstantTerm, p), q) + elseif isconstant(q) + return op(p, convert(ConstantTerm, q)) + end + throw(ArgumentError("Operation with non-constant polynomials having different indeterminates")) +end + +# only need to define differentiate(p::PolyType) +function derivative(p::AbstractUnivariatePolynomial, n::Int=0) + n <= 0 && return 1p + p′ = differentiate(p) + for i ∈ 2:n + p′ = differentiate(p′) + end + p′ +end +const differentiate = derivative + +# only need to define integrate(p::PolyType) +function integrate(p::AbstractUnivariatePolynomial, c) + integrate(p) + c +end + + + + + +macro poly_register(name) + poly = esc(name) + quote + #Base.convert(::Type{P}, p::P) where {P<:$poly} = p + Base.promote(p::P, q::Q) where {B, X, T, P <:$poly{B,T,X}, Q <: $poly{B,T,X}} = p,q + Base.promote_rule(::Type{<:$poly{B,T,X}}, ::Type{<:$poly{B,S,X}}) where {B,T,S,X} = $poly{B,promote_type(T, S),X} + Base.promote_rule(::Type{<:$poly{B,T,X}}, ::Type{S}) where {B,T,S<:Number,X} = + $poly{B,promote_type(T, S), X} + + # $poly{B,T}(x::AbstractVector{S}, var::SymbolLike=Var(:x)) where {B,T,S} = + # $poly{B, T, Symbol(var)}(collect(T,x)) + # $poly{B}(coeffs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {B,T} = + # $poly{B, T, Symbol(var)}(coeffs) + + # function $poly{B,T}(coeffs::G, var::SymbolLike=Var(x)) where {B,T,G} + # !Base.isiterable(G) && throw(ArgumentError("coeffs is not iterable")) + # cs = collect(T, coeffs) + # $poly{B, T, Symbol(var)}(cs) + # end + # function $poly{B}(coeffs::G, var::SymbolLike=Var(:x)) where {B,G} + # !Base.isiterable(G) && throw(ArgumentError("coeffs is not iterable")) + # cs = collect(promote(coeffs...)) + # $poly{B, eltype(cs), Symbol(var)}(cs) + # end + + # $poly{B,T,X}(c::AbstractUnivariatePolynomial{B′,S,Y}) where {B,B′,T,X,S,Y} = convert($poly{B,T,X}, c) + # $poly{B,T}(c::AbstractUnivariatePolynomial{B′,S,Y}) where {B,B′,T,S,Y} = convert($poly{B,T}, c) + # $poly{B}(c::AbstractUnivariatePolynomial{B′,S,Y}) where {B,B′,S,Y} = convert($poly{B}, c) + + $poly{B,T,X}(n::S) where {B, T, X, S<:Number} = + T(n) * one($poly{B, T, X}) + $poly{B, T}(n::S, var::SymbolLike = Var(:x)) where {B, T, S<:Number} = + T(n) * one($poly{B, T, Symbol(var)}) + $poly{B}(n::S, var::SymbolLike = Var(:x)) where {B, S <: Number} = n * one($poly{B, S, Symbol(var)}) + + $poly{B,T}(var::SymbolLike=Var(:x)) where {B,T} = variable($poly{B, T, Symbol(var)}) + $poly{B}(var::SymbolLike=Var(:x)) where {B} = variable($poly{B}, Symbol(var)) + + (p::$poly)(x) = _evalpoly(p, x) + end +end diff --git a/src/abstract.jl b/src/abstract.jl index 2227cd8c..54bdb5aa 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -1,5 +1,5 @@ export AbstractPolynomial - +export AbstractUnivariatePolynomial # *internal* means to pass variable symbol to constructor through 2nd position and keep type stability struct Var{T} end @@ -13,7 +13,7 @@ Base.Symbol(::Val{T}) where {T} = Symbol(T) """ AbstractPolynomial{T,X} -An abstract type for various polynomials. +An abstract type for various polynomials with an *implicit* basis. A polynomial type holds an indeterminate `X`; coefficients of type `T`, stored in some container type; and an implicit basis, such as the standard basis. diff --git a/src/basis-utils.jl b/src/basis-utils.jl new file mode 100644 index 00000000..c04186f4 --- /dev/null +++ b/src/basis-utils.jl @@ -0,0 +1,57 @@ +# container of zeros +# make generic so that _set can be used generically + +_set(c::Vector, i, val) = (c[i] = val; c) +_set(c::AbstractDict, i, val) = (c[i] = val; c) +function _set(c::Tuple, i, val) + @set! c[i] = val + c +end + + +_norm(x,p=2) = real(sqrt(sum(xᵢ^2 for xᵢ ∈ x))) +gtτ(x, τ) = abs(x) > τ + + +# return index or nothing of last non "zdero" +# chop! then considers cases i==nothing, i=length(x), i < length(x) +function chop_right_index(x::AbstractVector{T}; rtol=nothing, atol=nothing) where {T} + + isempty(x) && return nothing + δ = something(rtol,0) + ϵ = something(atol,0) + τ = max(ϵ, _norm(x,2) * δ) + i = findlast(Base.Fix2(gtτ, τ), x) + i + +end + + + +function chop_left_index(x::AbstractVector{T}; rtol=nothing, atol=nothing) where {T} + isempty(x) && return nothing + δ = something(rtol,0) + ϵ = something(atol,0) + τ = max(ϵ, _norm(x,2) * δ) + i = findfirst(Base.Fix2(gtτ,τ), x) + i +end + +## put here, not with type definition, in case reuse is possible +## `conv` can be used with matrix entries, unlike `fastconv` +function XXXconv(p::Vector{T}, q::Vector{S}) where {T,S} + (isempty(p) || isempty(q)) && return promote_type(T, S)[] + as = [p[1]*q[1]] + z = zero(as[1]) + n,m = length(p)-1, length(q)-1 + for i ∈ 1:n+m + Σ = z + for j ∈ max(0, i-m):min(i,n) + Σ = muladd(p[1+j], q[1 + i-j], Σ) + end + push!(as, Σ) + end + as +end + +XXX() = throw(ArgumentError("Method not defined")) diff --git a/src/common.jl b/src/common.jl index 75240808..56607d89 100644 --- a/src/common.jl +++ b/src/common.jl @@ -712,7 +712,7 @@ function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T} idx > M && return zero(T) p.coeffs[idx - m + 1] end -Base.getindex(p::AbstractPolynomial, idx::Number) = getindex(p, convert(Int, idx)) +#XXXBase.getindex(p::AbstractPolynomial, idx::Number) = getindex(p, convert(Int, idx)) Base.getindex(p::AbstractPolynomial, indices) = [p[i] for i in indices] Base.getindex(p::AbstractPolynomial, ::Colon) = coeffs(p) diff --git a/src/polynomial-basetypes/dense-polynomial.jl b/src/polynomial-basetypes/dense-polynomial.jl new file mode 100644 index 00000000..f2e7fb5b --- /dev/null +++ b/src/polynomial-basetypes/dense-polynomial.jl @@ -0,0 +1,262 @@ +# * has order +# * leading 0s are trimmed +# * pass Val(true) to bypass trimmings +struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} + coeffs::Vector{T} + order::Int # lowest degree, typically 0 + function MutableDensePolynomial{B,T,X}(cs, order::Int=0) where {B,T,X} + if Base.has_offset_axes(cs) + @warn "Using the axis offset of the coefficient vector" + cs, order = cs.parent, first(cs.offsets) + end + + i = findlast(!iszero, cs) + if i == nothing + xs = T[] + else + j = findfirst(!iszero, cs) + xs = T[cs[i] for i ∈ j:i] + order = order + j - 1 + end + new{B,T,Symbol(X)}(xs, order) + end + function MutableDensePolynomial{B,T,X}(checked::Val{false}, cs::Vector{T}, order::Int=0) where {B,T,X} + if Base.has_offset_axes(cs) + @warn "Using the axis offset of the coefficient vector" + cs, order = cs.parent, first(cs.offsets) + end + new{B,T,Symbol(X)}(cs, order) + end +end + + +MutableDensePolynomial{B,T,X}(checked::Val{true}, cs::Vector{T}, order::Int=0) where {B,T,X} = MutableDensePolynomial{B,T,X}(cs, order) + +function MutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int=0, var::SymbolLike=:x) where {T, S, B} + cs = convert(Vector{T}, xs) + cs = trim_trailing_zeros(cs) + MutableDensePolynomial{B,T,Symbol(var)}(Val(true),cs, order) +end + +# function MutableDensePolynomial{B,X}(xs::AbstractVector{T}, order::Int) where {T, B,X} +# MutableDensePolynomial{B,T,X}(xs,order) +# end + +# function MutableDensePolynomial{B,X}(xs, order::Int) where {B,X} +# cs = collect(xs) +# T = eltype(cs) +# MutableDensePolynomial{B,T,X}(cs, order) +# end + +function MutableDensePolynomial{B,T}(xs, var::SymbolLike) where {B,T} + cs = convert(Vector{T}, xs) + MutableDensePolynomial{B,T,Symbol(var)}(cs, 0) +end + +# allow specification of codefficients, order, symbol or coefficients, symbol +function MutableDensePolynomial{B}(xs, order::Int=0, var::SymbolLike=Var(:x)) where {B} + cs = collect(xs) + T = eltype(cs) + MutableDensePolynomial{B, T, Symbol(var)}(cs, order) +end + +function MutableDensePolynomial{B}(xs, var::SymbolLike) where {B} + cs = collect(xs) + T = eltype(cs) + MutableDensePolynomial{B, T, Symbol(var)}(cs) +end + +function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDensePolynomial{B,T,X}, S} + R = eltype(as) + Q = MutableDensePolynomial{B, R, X} + as = trim_trailing_zeros(as) + Q(Val(false), as, p.order) +end + +@poly_register MutableDensePolynomial +constructorof(::Type{<:MutableDensePolynomial}) = MutableDensePolynomial + + + +## Generics for polynomials +function Base.convert(::Type{MutableDensePolynomial{B,T,X}}, q::MutableDensePolynomial{B,T′,X′}) where {B,T,T′,X,X′} + MutableDensePolynomial{B,T,X}(Val(false), convert(Vector{T},q.coeffs), q.order) +end + +# function Base.convert(::Type{MutableDensePolynomial{B,T}}, q::MutableDensePolynomial{B,T′,X′}) where {B,T,T′,X′} +# convert(Val(false), MutableDensePolynomial{B,T,X′}, q) +# end + + +Base.copy(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = + MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs), order(p)) + +# This is B <: StandardBasis? +Base.firstindex(p::MutableDensePolynomial) = p.order +Base.length(p::MutableDensePolynomial) = length(p.coeffs) +offset(p::MutableDensePolynomial) = 1 - firstindex(p) +function Base.getindex(p::MutableDensePolynomial{B,T,X}, i::Int) where {B,T,X} + (i < firstindex(p) || i > lastindex(p)) && return zero(T) + p.coeffs[i + offset(p)] +end +function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDensePolynomial{B,T,X}} + a,b = firstindex(p), lastindex(p) + o = a + iszero(p) && return P([value], i) + z = zero(p.coeffs) + if i > b + append!(p.coeffs, _zeros(p, z, i - b)) + p.coeffs[end] = value + elseif i < a + prepend!(p.coeffs, zeros(T, a-i)) + p.coeffs[1] = value + o = i + else + p.coeffs[i + offset(p)] = value + end + P(p.coeffs, o) +end + +Base.lastindex(p::MutableDensePolynomial) = firstindex(p) + length(p) - 1 +Base.eachindex(p::MutableDensePolynomial) = firstindex(p):1:lastindex(p) +Base.iterate(p::MutableDensePolynomial, args...) = Base.iterate(p.coeffs, args...) +Base.pairs(p::MutableDensePolynomial) = + Base.Generator(=>, firstindex(p):lastindex(p), p.coeffs) +# (i - offset(p) => cᵢ for (i, cᵢ) ∈ enumerate(p)) +# return a container of zeros based on basis type +_zeros(::Type{<:MutableDensePolynomial}, z, N) = fill(z, N) + +# iszero, isconstant +Base.iszero(p::MutableDensePolynomial) = iszero(p.coeffs)::Bool + +# zero, one, variable, basis +Base.zero(::Type{MutableDensePolynomial{B,T,X}}) where {B,T,X} = + MutableDensePolynomial{B,T,X}(T[]) + +coeffs(p::MutableDensePolynomial) = p.coeffs + + +function trim_trailing_zeros(cs::Vector{T}) where {T} + isempty(cs) && return cs + !iszero(last(cs)) && return cs + i = findlast(!iszero, cs) + if isnothing(i) + empty!(cs) + else + n = length(cs) + while n > i + pop!(cs) + end + end + cs +end + + + +Base.chop(p::MutableDensePolynomial{B,T,X}; kwargs...) where {B,T,X} = chop!(copy(p);kwargs...) + +function chop!(p::MutableDensePolynomial{B,T,X}; + atol=nothing, rtol=Base.rtoldefault(float(real(T)))) where {B,T,X} + iᵣ = chop_right_index(p.coeffs; atol=atol, rtol=rtol) # nothing -- nothing to chop + iᵣ === nothing && return zero(p) + iₗ = chop_left_index(p.coeffs; atol=atol, rtol=rtol) + iₗ === nothing && return zero(p) + iₗ == 1 && iᵣ == length(p.coeffs) && return p + + N = length(p.coeffs) + + o = order(p) + for i ∈ 1:(iₗ-1) + popfirst!(p.coeffs) + o += 1 + end + + for i ∈ (iᵣ+1):N + pop!(p.coeffs) + end + + MutableDensePolynomial{B,T,X}(p.coeffs, o) +end + +function normΔ(q1::MutableDensePolynomial{B}, q2::MutableDensePolynomial{B}, p::Real = 2) where {B} + iszero(q1) && return norm(q2, p) + iszero(q2) && return norm(q1, p) + r = zero(q1[end] + q2[end]) + tot = zero(r) + for i ∈ minimum(firstindex,(q1,q2)):maximum(lastindex, (q1,q2)) + @inbounds tot += (q1[i] - q2[i])^p + end + return tot^(1/p) +end + + +# vector ops +, -, c*x +## unary +Base.:-(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = + MutableDensePolynomial{B,T,X}(Val(false), -p.coeffs, p.order) + +## binary +Base.:+(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = + offset_vector_combine(+, p, q) +Base.:-(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = + offset_vector_combine(-, p, q) +# handle +, - +# modified from https://github.com/jmichel7/LaurentPolynomials.jl/ +function offset_vector_combine(op, p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} + R = promote_type(T,S) + P = MutableDensePolynomial{B,R,X} + + iszero(p) && return convert(P, op(q)) + iszero(q) && return convert(P, p) + + a₁, a₂ = firstindex(p), firstindex(q) + b₁, b₂ = lastindex(p), lastindex(q) + a, b = min(a₁, a₂), max(b₁, b₂) + + N = b - a + 1 + z = zero(first(p.coeffs) + first(q.coeffs)) + x = _zeros(p, z, N) + + Δp, Δq = a₁ - a₂, 0 + if a₁ < a₂ + Δq, Δp = -Δp, Δq + end + # zip faster than `pairs` + @inbounds for (i, cᵢ) ∈ zip((1+Δp):(length(p) + Δp), p.coeffs) + x[i] = cᵢ + end + @inbounds for (i, cᵢ) ∈ zip((1+Δq):(length(q) + Δq), q.coeffs) + x[i] = op(x[i], cᵢ) + end + + b₁ == b₂ && (x = trim_trailing_zeros(x)) + P(Val(false), x, a) + +end + +# slower +# function Base.:+(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where {B,T,S,X} +# a = min(firstindex(p), firstindex(q)) +# b = max(lastindex(p), lastindex(q)) +# x = collect(p[i] - q[i] for i ∈ a:b) +# MutableDensePolynomial{B,eltype(x),X}(x,a) +# end + +# scalar +function scalar_mul(p::MutableDensePolynomial{B,T,X}, c::S) where {B,T,X,S} + cs = p.coeffs .* (c,) # works with T[] + return _polynomial(p, cs) +end +function scalar_mul(c::S, p::MutableDensePolynomial{B,T,X}) where {B,T,X,S} + cs = (c,) .* p.coeffs + return _polynomial(p, cs) +end + +#XXX need to add LinearAlgebra +# function LinearAlgebra.lmul!(c::Number, p::MutableDensePolynomial{B,T,X}) where {B,T,X} +# MutableDensePolynomial{B,T,X}(Val(false), lmul!(c, p.coeffs), firstindex(p)) +# end + +# function LinearAlgebra.rmul!(c::Number, p::MutableDensePolynomial{B,T,X}) where {B,T,X} +# MutableDensePolynomial{B,T,X}(rmul!(c, p.coeffs), firstindex(p)) +# end diff --git a/src/polynomial-basetypes/immutable-polynomial.jl b/src/polynomial-basetypes/immutable-polynomial.jl new file mode 100644 index 00000000..e22173b2 --- /dev/null +++ b/src/polynomial-basetypes/immutable-polynomial.jl @@ -0,0 +1,281 @@ +# Keep order=0 +# Try to keep length based on N,M so no chopping by default +struct ImmutableDensePolynomial{B,T,X,N} <: AbstractUnivariatePolynomial{B,T,X} + coeffs::NTuple{N,T} + function ImmutableDensePolynomial{B,T,X,N}(cs::NTuple{N,T}) where {B,N,T,X} + if Base.has_offset_axes(cs) + @warn "Ignoring the axis offset of the coefficient vector" + end + new{B,T,Symbol(X),N}(cs) + end +end + +ImmutableDensePolynomial{B,T,X,N}(::Type{Val{false}}, cs::NTuple{N,T}) where {B,N, T,X} = + ImmutableDensePolynomial{B,T,X}(cs) + +ImmutableDensePolynomial{B,T,X,N}(::Type{Val{true}}, cs::NTuple{N,T}) where {B,N, T,X} = + ImmutableDensePolynomial{B,T,X,N}(cs) + +# tuple +function ImmutableDensePolynomial{B,T,X}(xs::NTuple{N,S}) where {B,X,T,N,S} + cs = convert(NTuple{N,T}, xs) + cs = trim_trailing_zeros(cs) + N′ = length(cs) + ImmutableDensePolynomial{B,T,X,N′}(cs) +end + +function ImmutableDensePolynomial{B,T}(xs::NTuple{N,S}, var::SymbolLike=Var(:x)) where {B,T,S,N} + ImmutableDensePolynomial{B,T,Var(var)}(xs) +end + +function ImmutableDensePolynomial{B}(xs::NTuple{N,T}, var::SymbolLike=Var(:x)) where {B,T,N} + ImmutableDensePolynomial{B,T,Var(var)}(xs) +end + +# abstract vector +function ImmutableDensePolynomial{B,T,X}(xs::AbstractVector{S}) where {B,T,X,S} + N = length(xs) + cs = ntuple(Base.Fix1(getindex,xs), Val(N)) + ImmutableDensePolynomial{B,T,X}(cs) +end + +function ImmutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::SymbolLike=Var(:x)) where {B,T,S} + ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) +end + +function ImmutableDensePolynomial{B,T}(xs::AbstractVector{S}, var::SymbolLike) where {B,T,S} + ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) +end + +function ImmutableDensePolynomial{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} + ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) +end + +function ImmutableDensePolynomial{B}(xs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {B,T} + ImmutableDensePolynomial{B,T,Symbol(var)}(xs) +end + +function ImmutableDensePolynomial{B,T,X}(xs) where {B,T,X} + cs = collect(T,xs) + ImmutableDensePolynomial{B,T,X}(cs) +end + +function ImmutableDensePolynomial{B,T}(xs; var::SymbolLike=Var(:x)) where {B,T} + cs = collect(T,xs) + ImmutableDensePolynomial{B,T,X}(cs) +end + + +function ImmutableDensePolynomial{B}(xs, var::SymbolLike=Var(:x)) where {B} + cs = collect(xs) + T = eltype(cs) + ImmutableDensePolynomial{B,T,Symbol(var)}(cs) +end + +@poly_register ImmutableDensePolynomial +constructorof(::Type{<:ImmutableDensePolynomial}) = ImmutableDensePolynomial + + +function Base.convert(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, + p::ImmutableDensePolynomial{B,T′,X,N′}) where {B,T,T′,X,N,N′} + N < N′ && throw(ArgumentError("Wrong size")) + N > N′ && return ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) + ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) +# convert(ImmutableDensePolynomial{B,T,X}, p) +end + +Base.copy(p::ImmutableDensePolynomial) = p + +## chop +function Base.chop(p::ImmutableDensePolynomial{B,T,X,N}; kwargs...) where {B,T,X,N} + i = chop_right_index(p.coeffs; kwargs) + if i == nothing + xs = () + N′ = 0 + else + N′ = i + xs = ntuple(Base.Fix1(getindex, xs), Val(N′)) + end + ImmutableDensePolynomial{B,T,X,N′}(xs) +end + +# not type stable, as N is value dependent +function trim_trailing_zeros(cs::Tuple) + isempty(cs) && return cs + !iszero(last(cs)) && return cs + i = findlast(!iszero, cs) + i == nothing && return () + xs = ntuple(Base.Fix1(getindex,cs), Val(i)) + xs +end + +# doesn't match +# function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, +# p2::AbstractUnivariatePolynomial{B,T,X}; +# rtol::Real = (Base.rtoldefault(T,T,0)), +# atol::Real = 0,) where {B,T,X} + +function normΔ(q1::ImmutableDensePolynomial{B}, q2::ImmutableDensePolynomial{B}, p::Real = 2) where {B} + iszero(q1) && return norm(q2, p) + iszero(q2) && return norm(q1, p) + r = zero(q1[end] + q2[end]) + tot = zero(r) + for i ∈ 1:maximum(lastindex, (q1,q2)) + @inbounds tot += (q1[i] - q2[i])^p + end + return tot^(1/p) +end + +function Base.isapprox(p1::ImmutableDensePolynomial{B,T,X}, p2::ImmutableDensePolynomial{B,T′,X}; + atol=nothing, rtol = nothing + ) where {B,T,T′,X} + + (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons + R = real(float(promote_type(T,T′))) + atol = something(atol, zero(R)) + rtol = something(rtol, Base.rtoldefault(R)) + # copy over from abstractarray.jl + Δ = normΔ(p1,p2) + if isfinite(Δ) + return Δ <= max(atol, rtol * max(norm(p1), norm(p2))) + else + for i in 0:max(degree(p1), degree(p2)) + isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false + end + return true + end +end + +## --- + +_zeros(::Type{<:ImmutableDensePolynomial}, z::S, N) where {S} = + ntuple(_ -> zero(S), Val(N)) + +Base.iszero(p::ImmutableDensePolynomial) = all(iszero,p.coeffs) +Base.zero(::Type{<:ImmutableDensePolynomial{B,T,X}}) where {B,T,X} = + ImmutableDensePolynomial{B,T,X,0}(()) + +function isconstant(p::ImmutableDensePolynomial) + cs = trim_trailing_zeros(p.coeffs) + length(cs) ≤ 1 +end + +Base.firstindex(p::ImmutableDensePolynomial) = 0 +Base.lastindex(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = N - 1 +Base.eachindex(p::ImmutableDensePolynomial) = firstindex(p):lastindex(p) +Base.pairs(p::ImmutableDensePolynomial) = + Base.Generator(=>, eachindex(p), p.coeffs) +Base.length(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = N +offset(p::ImmutableDensePolynomial) = 1 + +# function Base.getindex(p::ImmutableDensePolynomial{B,T,X,N}, i) where {B,T,X,N} +# (i < firstindex(p) || i > lastindex(p)) && return zero(T) +# p.coeffs[i + offset(p)] +# end +Base.setindex!(p::ImmutableDensePolynomial, value, i::Int) = + throw(ArgumentError("ImmutableDensePolynomial has no setindex! method")) + +# can't promote to same N if trailing zeros +function Base.:(==)(p1::ImmutableDensePolynomial{B}, p2::ImmutableDensePolynomial{B}) where {B} + if isconstant(p1) + isconstant(p2) && return constantterm(p1) == constantterm(p2) + return false + elseif isconstant(p2) + return false # p1 is not constant + end + check_same_variable(p1, p2) || return false + for i ∈ union(eachindex(p1), eachindex(p2)) + p1[i] == p2[i] || return false + end + return true +end + + +## --- +function _evalpoly(p::ImmutableDensePolynomial{B,T,X,N}, x) where {B,T,X,N} + N == 0 && return zero(T) * zero(x) + z = zero(x * zero(p[0])) + typeof(z)(Base.evalpoly(x, p.coeffs)) +end + +# zero, one +Base.zero(::Type{ImmutableDensePolynomial{B,T,X,N}}) where {B,T,X,N} = + ImmutableDensePolynomial{B,T,X,0}(()) + +function basis(P::Type{<:ImmutableDensePolynomial{B,T,X}}, i) where {B,T,X} + xs = zeros(T, i + 1) + xs[end] = 1 + ImmutableDensePolynomial{B,T,X}(xs) +end + +coeffs(p::ImmutableDensePolynomial) = p.coeffs + + + +## Vector space operations +# vector ops +, -, c*x +## unary +Base.:-(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = + ImmutableDensePolynomial{B,T,X,N}(map(-, p.coeffs)) + +## binary +function Base.:+(p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomial{B,S,X,M}) where{B,X,T,S,N,M} + N < M && return q + p + _tuple_combine(+, p, q) +end +function Base.:-(p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomial{B,S,X,M}) where{B,X,T,S,N,M} + N < M && return (-q) + p + _tuple_combine(-, p, q) +end + +# handle +, -; Assum N >= M +function _tuple_combine(op, p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomial{B,S,X,M}) where{B,X,T,S,N,M} + + @assert N >= M + R = promote_type(T,S) + P = ImmutableDensePolynomial{B,R,X} + + iszero(p) && return zero(P) + #xs = ntuple(i -> i <= M ? R(op(p.coeffs[i],q.coeffs[i])) : R(p.coeffs[i]), Val(N)) + xs = _tuple_combine(op, p.coeffs, q.coeffs) + P{N}(xs) + +end + +# Padded vector combination of two homogeneous tuples assuming N ≥ M +@generated function _tuple_combine(op, 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] = :(op(p1[$i],p2[$i])) + end + for i in M+1:N + exprs[i] =:(p1[$i]) + end + + return quote + Base.@_inline_meta + #Base.@inline + tuple($(exprs...)) + end + +end + +# scalar + +function scalar_mul(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S,N} + R = promote_type(T,S) + P = ImmutableDensePolynomial{B,R,X} + iszero(N) && return zero(P) + iszero(c) && return convert(P, p) + cs = p.coeffs .* (c,) + return ImmutableDensePolynomial{B,R,X,N}(cs) +end + +function scalar_mul(c::S, p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,S,N} + iszero(N) && return zero(ImmutableDensePolynomial{B,T,X}) + iszero(c) && ImmutableDensePolynomial{B,X}([c .* p[0]]) + cs = (c,) .* p.coeffs + R = eltype(cs) + return ImmutableDensePolynomial{B,R,X,N}(cs) +end diff --git a/src/polynomial-basetypes/sparse-polynomial.jl b/src/polynomial-basetypes/sparse-polynomial.jl new file mode 100644 index 00000000..57080565 --- /dev/null +++ b/src/polynomial-basetypes/sparse-polynomial.jl @@ -0,0 +1,230 @@ +struct SparseUnivariatePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B, T,X} + coeffs::Dict{Int, T} + function SparseUnivariatePolynomial{B,T,X}(coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} + for (i, cᵢ) ∈ pairs(coeffs) + iszero(cᵢ) && delete!(coeffs, i) + end + new{B,T,Symbol(X)}(coeffs) + end + function SparseUnivariatePolynomial{B,T,X}(checked::Val{:false}, coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} + new{B,T,Symbol(X)}(coeffs) + end +end + +function SparseUnivariatePolynomial{B,T,X}(checked::Val{:true}, coeffs::AbstractDict{Int,T}) where {B,T,X<:Symbol} + SparseUnivariatePolynomial{B,T,X}(coeffs) +end + +# Dict +function SparseUnivariatePolynomial{B,T,X}(coeffs::AbstractDict{Int,S}) where {B,T,S,X} + cs = convert(Dict{Int,T}, coeffs) + SparseUnivariatePolynomial{B,T,X}(cs) +end + +function SparseUnivariatePolynomial{B}(cs::AbstractDict{Int,T}, var::SymbolLike=:x) where {B,T} + SparseUnivariatePolynomial{B,T,Symbol(var)}(cs) +end + +# abstract vector has order/symbol +function SparseUnivariatePolynomial{B,T,X}(coeffs::AbstractVector{S}, order::Int=0) where {B,T,S,X} + + P = SparseUnivariatePolynomial{B,T,X} + n = length(coeffs) + iszero(n) && zero(P) + xs = convert(Vector{T}, coeffs) + d = Dict{Int, T}(Base.Generator(=>, order:(order+n-1), xs)) + P(d) +end + +function SparseUnivariatePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::SymbolLike=Var(:x)) where {B,T,S} + SparseUnivariatePolynomial{B,T,Symbol(var)}(xs) +end + +function SparseUnivariatePolynomial{B,T}(coeffs::AbstractVector{S}, var::SymbolLike) where {B,T,S} + SparseUnivariatePolynomial{B,T,Symbol(var)}(xs) +end + +function SparseUnivariatePolynomial{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} + SparseUnivariatePolynomial{B,T,Symbol(var)}(xs) +end + +function SparseUnivariatePolynomial{B}(xs::AbstractVector{T}, var::SymbolLike) where {B,T} + SparseUnivariatePolynomial{B,T,Symbol(var)}(xs) +end + +function SparseUnivariatePolynomial{B,T}(xs, var::SymbolLike=Var(:x)) where {B,T} + cs = collect(T, xs) + cs = trim_trailing_zeros(cs) + SparseUnivariatePolynomial{B,T,Symbol(var)}(cs) +end + +function SparseUnivariatePolynomial{B}(xs, var::SymbolLike=Var(:x)) where {B} + cs = collect(xs) + cs = trim_trailing_zeros(cs) + SparseUnivariatePolynomial{B,T,Symbol(var)}(cs) +end + + +@poly_register SparseUnivariatePolynomial +constructorof(::Type{<:SparseUnivariatePolynomial}) = SparseUnivariatePolynomial + + +# cs iterable of pairs +# XXX enure tight value of T +function SparseUnivariatePolynomial{B}(cs::Tuple, var::SymbolLike=:x) where {B} + isempty(cs) && throw(ArgumentError("No type attached")) + X = Var(var) + if length(cs) == 1 + c = only(cs) + d = Dict(first(c) => last(c)) + T = eltype(last(c)) + return SparseUnivariatePolynomial{B,T,X}(d) + else + c₁, c... = cs + T = typeof(last(c₁)) + for (a,b) ∈ c + T = promote_type(T, typeof(b)) + end + ks = Base.Generator(first, cs) + vs = Base.Generator(last, cs) + d = Dict{Int,T}(Base.Generator(=>, ks, vs)) + return SparseUnivariatePolynomial{B,T,X}(d) + end +end + + + +Base.copy(p::SparseUnivariatePolynomial{B,T,X}) where {B,T,X} = SparseUnivariatePolynomial{B,T,X}(copy(p.coeffs)) + +function Base.convert(::Type{SparseUnivariatePolynomial{B,T,X}}, p::SparseUnivariatePolynomial{B,S,X}) where {B,T,S,X} + d = Dict{Int,T}(k => v for (k,v) ∈ pairs(p.coeffs)) + SparseUnivariatePolynomial{B,T,X}(Val(false), d) +end +# --- + +function Base.firstindex(p::SparseUnivariatePolynomial) + isempty(p.coeffs) && return 0 + i = minimum(keys(p.coeffs)) +end +function Base.lastindex(p::SparseUnivariatePolynomial) + isempty(p.coeffs) && return 0 + maximum(keys(p.coeffs)) +end +function Base.getindex(p::SparseUnivariatePolynomial{B,T,X}, i::Int) where {B,T,X} + get(p.coeffs, i, zero(T)) +end + +function Base.setindex!(p::SparseUnivariatePolynomial{B,T,X}, value, i::Int) where {B,T,X} + iszero(value) && delete!(p.coeffs, i) + p.coeffs[i] = value +end + +hasnan(p::SparseUnivariatePolynomial) = any(hasnan, values(p.coeffs)) +Base.iterate(p::SparseUnivariatePolynomial, args...) = Base.iterate(p.coeffs, args...) +Base.pairs(p::SparseUnivariatePolynomial) = pairs(p.coeffs) + +## Not properly named!!! truncate? chop? skip? +function trim_trailing_zeros(d::Dict) + for (k,v) ∈ pairs(d) + iszero(v) && deletat!(d, k) + end + d +end + +function Base.chop(p::SparseUnivariatePolynomial; atol=nothing, rtol=nothing) + δ = something(rtol,0) + ϵ = something(atol,0) + τ = max(ϵ, _norm(x,2) * δ) + for (i,pᵢ) ∈ pairs(p) + abs(pᵢ) ≤ τ && delete!(p.coeffs, i) + end + p +end + +_zeros(::Type{SparseUnivariatePolynomial{B,T,X}}, z::S, N) where {B,T,X,S} = Dict{Int, S}() + +Base.iszero(p::SparseUnivariatePolynomial) = all(iszero, values(p.coeffs)) + +Base.zero(::Type{SparseUnivariatePolynomial{B,T,X}}) where {B,T,X} = SparseUnivariatePolynomial{B,T,X}(Dict{Int,T}()) +## --- + +function _evalpoly(p::SparseUnivariatePolynomial, x) + + tot = zero(p[0]*x) + for (i, cᵢ) ∈ p.coeffs + tot = muladd(cᵢ, x^i, tot) + end + return tot +end + +offset(p::SparseUnivariatePolynomial) = 0 +function isconstant(p::SparseUnivariatePolynomial) + n = length(p.coeffs) + n == 0 && return true + n == 1 && haskey(p.coeffs, 0) +end + +# much faster than default +function scalar_add(c::S, p::SparseUnivariatePolynomial{B,T,X}) where {B,T,X,S} + c₀ = c + p[0] + R = eltype(c₀) + P = SparseUnivariatePolynomial{B,R,X} + D = convert(Dict{Int, R}, copy(p.coeffs)) + if iszero(c₀) + delete!(D,0) + else + @inbounds D[0] = c₀ + end + return P(Val(false), D) +end + +function scalar_mul(c::S, p::SparseUnivariatePolynomial{B,T,X}) where {B,T,X,S} + + R = promote_type(T,S) + P = SparseUnivariatePolynomial{B,R,X} + (iszero(p) || iszero(c)) && return(zero(P)) + + d = convert(Dict{Int, R}, copy(p.coeffs)) + for (k, pₖ) ∈ pairs(d) + @inbounds d[k] = c .* d[k] + end + + return P(Val(false), d) + +end + +function scalar_mul(p::SparseUnivariatePolynomial{B,T,X}, c::S) where {B,T,X,S} + R = promote_type(T,S) + P = SparseUnivariatePolynomial{B,R,X} + (iszero(p) || iszero(c)) && return(zero(P)) + + d = convert(Dict{Int, R}, copy(p.coeffs)) + for (k, pₖ) ∈ pairs(d) + @inbounds d[k] = d[k] .* c + end + + return P(Val(false), d) +end + +Base.:+(p::SparseUnivariatePolynomial{B,T,X}, q::SparseUnivariatePolynomial{B,S,X}) where{B,X,T,S} = + _dict_combine(+, p, q) +Base.:-(p::SparseUnivariatePolynomial{B,T,X}, q::SparseUnivariatePolynomial{B,S,X}) where{B,X,T,S} = + _dict_combine(-, p, q) + +function _dict_combine(op, p::SparseUnivariatePolynomial{B,T,X}, q::SparseUnivariatePolynomial{B,S,X}) where{B,X,T,S} + + R = promote_type(T,S) + P = SparseUnivariatePolynomial{B,R,X} + D = convert(Dict{Int, R}, copy(p.coeffs)) + for (i, qᵢ) ∈ pairs(q.coeffs) + pᵢ = get(D, i, zero(R)) + pqᵢ = op(pᵢ, qᵢ) + if iszero(pqᵢ) + delete!(D, i) # will be zero + else + D[i] = pqᵢ + end + end + return P(Val(false), D) + +end diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl new file mode 100644 index 00000000..3b2ccdac --- /dev/null +++ b/src/standard-basis/standard-basis.jl @@ -0,0 +1,125 @@ +struct StandardBasis <: AbstractBasis end + +function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where {B<:StandardBasis, T, P<:AbstractUnivariatePolynomial{B,T}} + + if _iszero(pj) return false end + + pj = printsign(io, pj, first, mimetype) + + if hasone(T) + if !(_isone(pj) && !(showone(T) || j == 0)) + printcoefficient(io, pj, j, mimetype) + end + else + printcoefficient(io, pj, j, mimetype) + end + + printproductsign(io, pj, j, mimetype) + printexponent(io, var, j, mimetype) + return true +end + + +function print_basis(io::IO, p::AbstractUnivariatePolynomial{<:StandardBasis, T, X}, i) where {T,X} + print(io, X) + print_unicode_exponent(io, i) +end + +function Base.one(::Type{P}) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} + ⟒(P){B,T,X}([1]) +end + +function variable(P::Type{<:AbstractUnivariatePolynomial{B,T,X}}) where {B<:StandardBasis,T,X} + basis(P, 1) +end + +function basis(P::Type{<:AbstractUnivariatePolynomial{B, T, X}}, i) where {B<:StandardBasis,T,X} + cs = ones(T,1) + P(cs, i) +end + +constantterm(p::AbstractUnivariatePolynomial{B}) where {B <: StandardBasis} = p[0] + + + +# # storage independent scalar add +# # faster alternatives for basic types +# function scalar_add(c::S, p::AbstractUnivariatePolynomial{B,T,X}) where {B<:StandardBasis, S, T, X} +# R = promote_type(T,S) +# P = ⟒(p){B,R,X} + +# iszero(p) && return P((c,), 0) +# iszero(c) && return convert(P, p) + +# a,b = firstindex(p), lastindex(p) +# a′ = min(0,a) +# z = zero(last(first(p.coeffs)) + c) +# cs = _zeros(p, zero(z), length(a′:b)) + +# # i -> idx mapping +# o = offset(p)*(-a+1) # offset for dict is 0; o/w -a + 1 +# for (i, cᵢ) ∈ pairs(p) +# cs = _set(cs, i+o, R(cᵢ)) +# end +# cs = _set(cs, 0+o, cs[0+o] + R(c)) +# cs = trim_trailing_zeros(cs) +# P(Val(false), cs, a′) +# end + +# special cases are faster +function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, + q::AbstractUnivariatePolynomial{B,S,X}) where {B <: StandardBasis, T,S,X} + # simple convolution with order shifted + R = promote_type(T,S) + P = ⟒(p){B,R,X} + + iszero(p) && return zero(P) + iszero(q) && return zero(P) + + cs = fastconv(p.coeffs, q.coeffs) + R = eltype(P) + a = firstindex(p) + firstindex(q) + + P(cs, a) +end + +# maybe do same for, say, Laguerre? Though that may push everything to Dense +function differentiate(p::AbstractUnivariatePolynomial{B,T,X}) where {B<:StandardBasis,T,X} + N = lastindex(p) - firstindex(p) + 1 + R = promote_type(T, Int) + P = ⟒(p){B,T,X} + iszero(p) && return zero(P) + z = zero(1 * p[1]) + cs = _zeros(p, z, N) + os = offset(p) + @inbounds for (i, cᵢ) ∈ pairs(p) + iszero(i) && continue + #cs[i - 1 + os] = i * cᵢ + cs = _set(cs, i - 1 + os, i * cᵢ) + end + + o = firstindex(p) + o = o < 0 ? o - 1 : max(0, o - 1) + ⟒(p){B,T,X}(cs, o) +end + + + +function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardBasis,T,X} + # no offset! XXX + + iszero(p) && return p/1 + + N = lastindex(p) - firstindex(p) + 1 + R = typeof(one(T)/1) + z = zero(R) + P = ⟒(p){B,R,X} + cs = _zeros(p, z, N+1) + os = offset(p) + @inbounds for (i, cᵢ) ∈ pairs(p) + i == -1 && throw(ArgumentError("Laurent polynomial with 1/x term")) + #cs[i + os] = cᵢ / (i+1) + cs = _set(cs, i + 1 + os, cᵢ / (i+1)) + end + P(cs, firstindex(p)) +end diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl new file mode 100644 index 00000000..669e4081 --- /dev/null +++ b/src/standard-basis/standard-dense.jl @@ -0,0 +1,104 @@ +# Dense + StandardBasis +#const Polynomial = MutableDensePolynomial{StandardBasis} # const is important! +#export Polynomial + +#LaurentPolynomial = MutableDensePolynomial{StandardBasis} +#export LaurentPolynomial + +constantterm(p:: MutableDensePolynomial{StandardBasis}) = p[0] + +function evalpoly(c, p::MutableDensePolynomial{StandardBasis,T,X}) where {T,X} + iszero(p) && return zero(T)*zero(c) + EvalPoly.evalpoly(c, p.coeffs) * c^p.order +end + +function _evalpoly(p:: MutableDensePolynomial{StandardBasis,T,X}, c) where {T,X} + iszero(p) && return zero(T)*zero(c) + Base.evalpoly(c, p.coeffs) * c^p.order +end + +function isconstant(p:: MutableDensePolynomial{StandardBasis}) + firstindex(p) != 0 && return false + i = findlast(!iszero, p.coeffs) + i == nothing && return true + i == 1 && return true +end + +# scalar add +function scalar_add(c::S, p:: MutableDensePolynomial{StandardBasis,T,X}) where {S, T, X} + R = promote_type(T,S) + P = MutableDensePolynomial{StandardBasis,R,X} + + iszero(p) && return P([c], 0) + iszero(c) && return convert(P, p) + + a,b = firstindex(p), lastindex(p) + a′ = min(0,a) + cs = _zeros(p, zero(first(p.coeffs)+c), length(a′:b)) + o = offset(p) + a - a′ + for (i, cᵢ) ∈ pairs(p) + cs[i+o] = cᵢ + end + cs[0+o] += c + iszero(last(cs)) && (cs = trim_trailing_zeros(cs)) + P(Val(false), cs, a′) +end + + + +function ⊗(p:: MutableDensePolynomial{StandardBasis,T,X}, + q:: MutableDensePolynomial{StandardBasis,S,X}) where {T,S,X} + # simple convolution + R = promote_type(T,S) + P = MutableDensePolynomial{StandardBasis,R,X} + + iszero(p) && return zero(P) + iszero(q) && return zero(P) + + a₁, a₂ = firstindex(p), firstindex(q) + b₁, b₂ = lastindex(p), lastindex(q) + a, b = a₁ + a₂, b₁ + b₂ + + z = zero(first(p) * first(q)) + cs = _zeros(p, z, length(a:b)) + + # convolve and shift order + @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) + for (j, qⱼ) ∈ enumerate(q.coeffs) + ind = i + j - 1 + cs[ind] = muladd(pᵢ, qⱼ, cs[ind]) + end + end + if iszero(last(cs)) + cs = trime_trailing_zeros(cs) + end + P(Val(false), cs, a) +end + +# function ⊗(p:: MutableDensePolynomial{StandardBasis}{T,X}, +# q:: MutableDensePolynomial{StandardBasis}{S,X}) where {T,S,X} +# # simple convolution +# R = promote_type(T,S) +# P = MutableDensePolynomial{StandardBasis}{R,X} + +# iszero(p) && return zero(P) +# iszero(q) && return zero(P) + +# a₁, a₂ = firstindex(p), firstindex(q) +# b₁, b₂ = lastindex(p), lastindex(q) +# a, b = a₁ + a₂, b₁ + b₂ + +# z = zero(first(p) * first(q)) +# cs = _zeros(p, z, length(a:b)) + +# # convolve and shift order +# @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) +# for (j, qⱼ) ∈ enumerate(q.coeffs) +# ind = i + j - 1 +# cs[ind] += pᵢ * qⱼ +# end +# end + +# iszero(last(cs)) && chop_right!(cs) +# P(Val(false), cs, a) +# end diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl new file mode 100644 index 00000000..c1acdaa1 --- /dev/null +++ b/src/standard-basis/standard-immutable.jl @@ -0,0 +1,115 @@ +#const ImmutablePolynomial = ImmutableDensePolynomial{StandardBasis} +#export ImmutablePolynomial + +function evalpoly(x, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} + N == 0 && return zero(T) * zero(x) + z = zero(x * zero(p[0])) + typeof(z)(EvalPoly.evalpoly(x, p.coeffs)) +end + +# Padded vector sum of two tuples assuming N ≥ M +@generated function tuple_sum(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 + #Base.@inline + tuple($(exprs...)) + end + +end + + + +function scalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B<:StandardBasis,T,X,S,N} + R = promote_type(T,S) + P = ImmutableDensePolynomial{B,R,X} + iszero(c) && return P{N}(convert(NTuple{N,R}, p.coeffs)) + N == 0 && return P{1}(NTuple{1,R}(c)) + N == 1 && return P{N}((p[0]+c,)) + + #cs = tuple_sum(convert(NTuple{N,R}, p.coeffs), NTuple{1,R}(c)) + cs = _tuple_combine(+, convert(NTuple{N,R}, p.coeffs), NTuple{1,R}(c)) + q = P{N}(cs) + + return q + + + P = ImmutableDensePolynomial{B,R,X} + iszero(N) && return P{1}((c,)) + + xs = convert(NTuple{N,R}, p.coeffs) + @set! xs[1] = xs[1] + c + P{N}(xs) +end + + +function XXscalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S,N} + R = promote_type(T,S) + P = ImmutableDensePolynomial{B,R,X} + iszero(N) && return P{1}((c,)) + + xs = convert(NTuple{N,R}, p.coeffs) + @set! xs[1] = xs[1] + c + P{N}(xs) +end + +# return N*M +function ⊗(p::ImmutableDensePolynomial{StandardBasis,T,X,N}, + q::ImmutableDensePolynomial{StandardBasis,S,X,M}) where {T,S,X,N,M} + + # simple convolution + R = promote_type(T,S) + P = ImmutableDensePolynomial{StandardBasis,R,X} + + (iszero(N) || iszero(M)) && return zero(P) + + cs = fastconv(p.coeffs, q.coeffs) + P{N+M-1}(cs) +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 fastconv(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 isnothing(exprs[k]) + exprs[k] = :(p1[$i] * p2[$j]) + else + exprs[k] = :(muladd(p1[$i], p2[$j], $(exprs[k]))) + end + end + end + + return quote + Base.@_inline_meta # 1.8 deprecation + tuple($(exprs...)) + end + +end + + +function differentiate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X,N} + N == 0 && return 1p + cs = ntuple(i -> i*p.coeffs[i+1], Val(N-1)) + R = eltype(cs) + ImmutableDensePolynomial{StandardBasis,R,X,N-1}(cs) +end + + +function integrate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X,N} + cs = ntuple(i -> i > 1 ? p.coeffs[i-1]/(i-1) : zero(T)/1, Val(N+1)) + R = eltype(cs) + ImmutableDensePolynomial{StandardBasis,R,X,N+1}(cs) +end diff --git a/src/standard-basis/standard-sparse.jl b/src/standard-basis/standard-sparse.jl new file mode 100644 index 00000000..78fac26d --- /dev/null +++ b/src/standard-basis/standard-sparse.jl @@ -0,0 +1,35 @@ +#const SparsePolynomial = SparseUnivariatePolynomial{StandardBasis} # const is important! +#export SparsePolynomial + +function evalpoly(x, p::SparseUnivariatePolynomial) + + tot = zero(p[0]*x) + for (i, cᵢ) ∈ p.coeffs + tot = muladd(cᵢ, x^i, tot) + end + return tot +end + + +function constantterm(p::SparseUnivariatePolynomial{B,T,X}) where {B,T,X} + get(p.coeffs, 0, zero(T)) +end + +function ⊗(p::SparseUnivariatePolynomial{StandardBasis,T,X}, + q::SparseUnivariatePolynomial{StandardBasis,S,X}) where {T,S,X} + # simple convolution + R = promote_type(T,S) + P = SparseUnivariatePolynomial{StandardBasis,R,X} + + z = zero(R) + cs = Dict{Int, R}() + + @inbounds for (i, pᵢ) ∈ pairs(p) + for (j, qⱼ) ∈ pairs(q) + cᵢⱼ = get(cs, i+j, z) + cs[i+j] = muladd(pᵢ, qⱼ, cᵢⱼ) + end + end + + P(cs) +end From 0015c2fded398d4fb12e0eb863515238a8f1545c Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 17 Jul 2023 21:50:33 -0400 Subject: [PATCH 02/31] adding in explicit basis code --- src/Polynomials.jl | 6 +- src/abstract-polynomial.jl | 138 +++++------ src/abstract.jl | 5 + src/basis-utils.jl | 11 - src/common.jl | 14 +- ...omial.jl => immutable-dense-polynomial.jl} | 12 +- ...ynomial.jl => mutable-dense-polynomial.jl} | 4 +- .../mutable-sparse-polynomial.jl | 230 ++++++++++++++++++ src/polynomial-basetypes/sparse-polynomial.jl | 230 ------------------ src/standard-basis/standard-basis.jl | 2 +- src/standard-basis/standard-dense.jl | 12 +- src/standard-basis/standard-sparse.jl | 10 +- 12 files changed, 331 insertions(+), 343 deletions(-) rename src/polynomial-basetypes/{immutable-polynomial.jl => immutable-dense-polynomial.jl} (95%) rename src/polynomial-basetypes/{dense-polynomial.jl => mutable-dense-polynomial.jl} (98%) create mode 100644 src/polynomial-basetypes/mutable-sparse-polynomial.jl delete mode 100644 src/polynomial-basetypes/sparse-polynomial.jl diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 93e34cb1..2e0aa942 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -35,9 +35,9 @@ include("rational-functions/plot-recipes.jl") # polynomials with explicit basis include("abstract-polynomial.jl") include("basis-utils.jl") -include("polynomial-basetypes/dense-polynomial.jl") -include("polynomial-basetypes/immutable-polynomial.jl") -include("polynomial-basetypes/sparse-polynomial.jl") +include("polynomial-basetypes/mutable-dense-polynomial.jl") +include("polynomial-basetypes/immutable-dense-polynomial.jl") +include("polynomial-basetypes/mutable-sparse-polynomial.jl") include("standard-basis/standard-basis.jl") include("standard-basis/standard-dense.jl") include("standard-basis/standard-immutable.jl") diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 8cc4e4bd..b3ac20db 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -28,20 +28,15 @@ Base.lastindex(p::AbstractUnivariatePolynomial{B, T, X}) where {B,T,X} = XXX() Base.iterate(p::AbstractUnivariatePolynomial, args...) = Base.iterate(p.coeffs, args...) Base.pairs(p::AbstractUnivariatePolynomial) = XXX() -Base.eltype(::Type{<:AbstractUnivariatePolynomial}) = Float64 +#Base.eltype(::Type{<:AbstractUnivariatePolynomial}) = Float64 Base.eltype(::Type{<:AbstractUnivariatePolynomial{B,T}}) where {B,T} = T Base.size(p::AbstractUnivariatePolynomial) = (length(p),) Base.size(p::AbstractUnivariatePolynomial, i::Integer) = i <= 1 ? size(p)[i] : 1 -hasnan(p::AbstractUnivariatePolynomial) = any(hasnan, p) +#hasnan(p::AbstractUnivariatePolynomial) = any(hasnan, p) - -function LinearAlgebra.norm(q::AbstractUnivariatePolynomial, p::Real = 2) - vs = values(q) - return norm(vs, p) # if vs=() must be handled in special type -end # norm(q1 - q2) function normΔ(q1::AbstractUnivariatePolynomial, q2::AbstractUnivariatePolynomial, p::Real = 2) iszero(q1) && return norm(q2, p) @@ -54,92 +49,56 @@ function normΔ(q1::AbstractUnivariatePolynomial, q2::AbstractUnivariatePolynomi return tot^(1/p) end -# need to promote Number -> Poly -# Base.isapprox(p1::AbstractUnivariatePolynomial, p2::Number; kwargs...) = isapprox(promote(p1, p2)...; kwargs...) -# Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(promote(p1, p2)...; kwargs...) -function Base.isapprox(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial; kwargs...) - isapprox(promote(p1, p2)...; kwargs...) -end - -function assert_same_variable(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial) - (isconstant(p1) || isconstant(p2) ) && return true - indeterminate(p1) == indeterminate(p2) && return true - throw(ArgumentError("Polynomials have different indeterminates")) -end -function Base.isapprox(p1::AbstractUnivariatePolynomial{B}, p2::AbstractUnivariatePolynomial{B}; kwargs...) where {B} - if isconstant(p1) - isconstant(p2) && return constantterm(p1) == constantterm(p2) - return false - elseif isconstant(p2) - return false - end - assert_same_variable(p1, p2) || return false - isapprox(promote(p1, p2)...; kwargs...) -end - -function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, - p2::AbstractUnivariatePolynomial{B,T,X}; - rtol::Real = (Base.rtoldefault(T,T,0)), - atol::Real = 0,) where {B,T,X} - (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons - # copy over from abstractarray.jl - Δ = normΔ(p1,p2) - if isfinite(Δ) - return Δ <= max(atol, rtol * max(norm(p1), norm(p2))) - else - for i in 0:max(degree(p1), degree(p2)) - isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false - end - return true - end -end - -function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, p2::Number; kwargs...) where {B,T,X} - q = p2 * one(⟒(p1){B,T,X}) - isapprox(p1, q; kwargs...) -end -Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(p2, p1; kwargs...) # map Polynomial terms -> vector terms degree(p::AbstractUnivariatePolynomial) = iszero(p) ? -1 : lastindex(p) -order(p::AbstractUnivariatePolynomial) = firstindex(p) +# order(p::AbstractUnivariatePolynomial) = firstindex(p) XXX conflicts with DataFrames.order +# this helps, along with _set, make some storage-generic methods _zeros(p::P, z, N) where {P <: AbstractUnivariatePolynomial} = _zeros(P, z, N) +_set(c::Vector, i, val) = (c[i] = val; c) +_set(c::AbstractDict, i, val) = (c[i] = val; c) +function _set(c::Tuple, i, val) + @set! c[i] = val + c +end -check_same_variable(p::AbstractUnivariatePolynomial, q::AbstractUnivariatePolynomial) = indeterminate(p) == indeterminate(q) +#check_same_variable(p::AbstractUnivariatePolynomial, q::AbstractUnivariatePolynomial) = indeterminate(p) == indeterminate(q) # The zero polynomial. Typically has no coefficients -Base.zero(p::P,args...) where {P <: AbstractUnivariatePolynomial} = zero(P,args...) +#Base.zero(p::P,args...) where {P <: AbstractUnivariatePolynomial} = zero(P,args...) Base.zero(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){B,eltype(P),indeterminate(P)}) Base.zero(::Type{P},var::SymbolLike) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){B,eltype(P),Symbol(var)}) # the polynomial 1 -Base.one(p::P,args...) where {P <: AbstractUnivariatePolynomial} = one(P,args...) +#Base.one(p::P,args...) where {P <: AbstractUnivariatePolynomial} = one(P,args...) Base.one(::Type{P}) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){B,eltype(P),indeterminate(P)}) Base.one(::Type{P}, var::SymbolLike) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){B,eltype(P),Symbol(var)}) # the variable x -variable(p::P) where {P <: AbstractUnivariatePolynomial} = variable(P) +#variable(p::P) where {P <: AbstractUnivariatePolynomial} = variable(P) variable(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = variable(⟒(P){B,eltype(P),indeterminate(P)}) variable(::Type{P}, var::SymbolLike) where {B,P<:AbstractUnivariatePolynomial{B}} = variable(⟒(P){B,eltype(P),Var(var)}) # i -> basis polynomial -basis(p::P, i) where {P <: AbstractUnivariatePolynomial} = basis(P, i) -basis(::Type{P}, i) where {B,P <: AbstractUnivariatePolynomial{B}} = basis(⟒(P){B,eltype(P),indeterminate(P)}, i) +basis(p::P, i::Int) where {P <: AbstractUnivariatePolynomial} = basis(P, i) +basis(::Type{P}, i::Int) where {B,P <: AbstractUnivariatePolynomial{B}} = basis(⟒(P){B,eltype(P),indeterminate(P)}, i) # return dense coefficients (vector or tuple) coeffs(p::AbstractUnivariatePolynomial) = [p[i] for i ∈ firstindex(p):lastindex(p)] -function isconstant(p::AbstractUnivariatePolynomial) - p₀ = trim_trailing_zeros(p) - return (firstindex(p₀) == lastindex(p₀) == 0) -end +# function isconstant(p::AbstractUnivariatePolynomial) +# p₀ = trim_trailing_zeros(p) +# return (firstindex(p₀) == lastindex(p₀) == 0) +# end -# chop chops right (and left side +# chop chops right side of p +# use trunc for left and right # can pass tolerances Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = XXX() chop!(p::AbstractUnivariatePolynomial; kwargs...) = XXX() +## --- constant term --- # arithmetic dispatch struct ConstantTerm{T} @@ -156,7 +115,50 @@ isconstant(::ConstantTerm) = true Base.convert(::Type{ConstantTerm}, p::AbstractUnivariatePolynomial) = ConstantTerm(constantterm(p)) Base.convert(::Type{ConstantTerm{T}}, p::AbstractUnivariatePolynomial) where {T} = ConstantTerm(T(constantterm(p))) +## --- + #= Comparisons =# +# need to promote Number -> Poly +# Base.isapprox(p1::AbstractUnivariatePolynomial, p2::Number; kwargs...) = isapprox(promote(p1, p2)...; kwargs...) +# Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(promote(p1, p2)...; kwargs...) +function Base.isapprox(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial; kwargs...) + isapprox(promote(p1, p2)...; kwargs...) +end + +function Base.isapprox(p1::AbstractUnivariatePolynomial{B}, p2::AbstractUnivariatePolynomial{B}; kwargs...) where {B} + if isconstant(p1) + isconstant(p2) && return constantterm(p1) == constantterm(p2) + return false + elseif isconstant(p2) + return false + end + assert_same_variable(p1, p2) || return false + isapprox(promote(p1, p2)...; kwargs...) +end + +function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, + p2::AbstractUnivariatePolynomial{B,T,X}; + rtol::Real = (Base.rtoldefault(T,T,0)), + atol::Real = 0,) where {B,T,X} + (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons + # copy over from abstractarray.jl + Δ = normΔ(p1,p2) + if isfinite(Δ) + return Δ <= max(atol, rtol * max(norm(p1), norm(p2))) + else + for i in 0:max(degree(p1), degree(p2)) + isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false + end + return true + end +end + +function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, p2::Number; kwargs...) where {B,T,X} + q = p2 * one(⟒(p1){B,T,X}) + isapprox(p1, q; kwargs...) +end +Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(p2, p1; kwargs...) + Base.isequal(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} = hash(p1) == hash(p2) function Base.:(==)(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} iszero(p1) && iszero(p2) && return true @@ -182,6 +184,7 @@ end Base.:(==)(p::AbstractUnivariatePolynomial, n::Number) = degree(p) <= 0 && constantterm(p) == n Base.:(==)(n::Number, p::AbstractUnivariatePolynomial) = p == n +## --- arithmetic operations --- Base.:-(p::AbstractUnivariatePolynomial) = scalar_mul(-1, p) @@ -199,8 +202,6 @@ Base.:+(p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = _mixed_symbol_op(+, p, q) - - Base.:-(c::Number, p::AbstractUnivariatePolynomial) = c + (-p) Base.:-(p::AbstractUnivariatePolynomial, c::Number) = p + (-c) Base.:-(c::ConstantTerm, p::AbstractUnivariatePolynomial) = (-c[]) + p @@ -236,15 +237,16 @@ Base.:^(p::AbstractUnivariatePolynomial, n::Integer) = Base.power_by_squaring(p, function _mixed_symbol_op(op, p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} - X == Y && throw(ArgumentError("dispatch")) + X == Y && throw(ArgumentError("dispatch should catch this case")) if isconstant(p) return op(convert(ConstantTerm, p), q) elseif isconstant(q) return op(p, convert(ConstantTerm, q)) end - throw(ArgumentError("Operation with non-constant polynomials having different indeterminates")) + assert_same_variable(X,Y) end + # only need to define differentiate(p::PolyType) function derivative(p::AbstractUnivariatePolynomial, n::Int=0) n <= 0 && return 1p diff --git a/src/abstract.jl b/src/abstract.jl index 54bdb5aa..5ae9c9cf 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -3,7 +3,12 @@ export AbstractUnivariatePolynomial # *internal* means to pass variable symbol to constructor through 2nd position and keep type stability struct Var{T} end +Var(x::Var) = x Var(x::Symbol) = Var{x}() +Var(x::Type{Var{u}}) where {u} = x +Var(x::AbstractString) = Var(Symbol(x)) +Var(x::Char) = Var(Symbol(x)) + Symbol(::Var{T}) where {T} = T const SymbolLike = Union{AbstractString,Char,Symbol, Var{T} where T} diff --git a/src/basis-utils.jl b/src/basis-utils.jl index c04186f4..7a41ebcb 100644 --- a/src/basis-utils.jl +++ b/src/basis-utils.jl @@ -1,14 +1,3 @@ -# container of zeros -# make generic so that _set can be used generically - -_set(c::Vector, i, val) = (c[i] = val; c) -_set(c::AbstractDict, i, val) = (c[i] = val; c) -function _set(c::Tuple, i, val) - @set! c[i] = val - c -end - - _norm(x,p=2) = real(sqrt(sum(xᵢ^2 for xᵢ ∈ x))) gtτ(x, τ) = abs(x) > τ diff --git a/src/common.jl b/src/common.jl index 56607d89..3bc15149 100644 --- a/src/common.jl +++ b/src/common.jl @@ -456,7 +456,7 @@ check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) = (isconstant(p) || 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")) + check_same_variable(p,q) || throw(ArgumentError("Non-constant polynomials have different indeterminates")) end function assert_same_variable(X::Symbol, Y::Symbol) @@ -622,7 +622,7 @@ hasnan(x) = isnan(x) Is the polynomial `p` a constant. """ -isconstant(p::AbstractPolynomial) = degree(p) <= 0 +isconstant(p::AbstractPolynomial) = degree(p) <= 0 && firstindex(p) == 0 """ coeffs(::AbstractPolynomial) @@ -805,11 +805,9 @@ Base.hash(p::AbstractPolynomial, h::UInt) = hash(indeterminate(p), hash(coeffs(p # get symbol of polynomial. (e.g. `:x` from 1x^2 + 2x^3... _indeterminate(::Type{P}) where {P <: AbstractPolynomial} = nothing _indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T,X}} = X -function indeterminate(::Type{P}) where {P <: AbstractPolynomial} - X = _indeterminate(P) - isnothing(X) ? :x : X -end +indeterminate(::Type{P}) where {P <: AbstractPolynomial} = something(_indeterminate(P), :x) indeterminate(p::P) where {P <: AbstractPolynomial} = _indeterminate(P) + function indeterminate(PP::Type{P}, p::AbstractPolynomial{T,Y}) where {P <: AbstractPolynomial, T,Y} X = _indeterminate(PP) isnothing(X) && return Y @@ -817,9 +815,7 @@ function indeterminate(PP::Type{P}, p::AbstractPolynomial{T,Y}) where {P <: Abst return X #X = isnothing(_indeterminate(PP)) ? indeterminate(p) : _indeterminate(PP) end -function indeterminate(PP::Type{P}, x::Symbol) where {P <: AbstractPolynomial} - X = isnothing(_indeterminate(PP)) ? x : _indeterminate(PP) -end +indeterminate(PP::Type{P}, x::Symbol) where {P <: AbstractPolynomial} = something(_indeterminate(PP), x) #= zero, one, variable, basis =# diff --git a/src/polynomial-basetypes/immutable-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl similarity index 95% rename from src/polynomial-basetypes/immutable-polynomial.jl rename to src/polynomial-basetypes/immutable-dense-polynomial.jl index e22173b2..c02ef3f8 100644 --- a/src/polynomial-basetypes/immutable-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -81,7 +81,6 @@ function Base.convert(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, N < N′ && throw(ArgumentError("Wrong size")) N > N′ && return ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) -# convert(ImmutableDensePolynomial{B,T,X}, p) end Base.copy(p::ImmutableDensePolynomial) = p @@ -109,12 +108,7 @@ function trim_trailing_zeros(cs::Tuple) xs end -# doesn't match -# function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, -# p2::AbstractUnivariatePolynomial{B,T,X}; -# rtol::Real = (Base.rtoldefault(T,T,0)), -# atol::Real = 0,) where {B,T,X} - +# isapprox helper function normΔ(q1::ImmutableDensePolynomial{B}, q2::ImmutableDensePolynomial{B}, p::Real = 2) where {B} iszero(q1) && return norm(q2, p) iszero(q2) && return norm(q1, p) @@ -156,8 +150,8 @@ Base.zero(::Type{<:ImmutableDensePolynomial{B,T,X}}) where {B,T,X} = ImmutableDensePolynomial{B,T,X,0}(()) function isconstant(p::ImmutableDensePolynomial) - cs = trim_trailing_zeros(p.coeffs) - length(cs) ≤ 1 + i = findlast(!iszero, p.coeffs) + return i ≤ 1 end Base.firstindex(p::ImmutableDensePolynomial) = 0 diff --git a/src/polynomial-basetypes/dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl similarity index 98% rename from src/polynomial-basetypes/dense-polynomial.jl rename to src/polynomial-basetypes/mutable-dense-polynomial.jl index f2e7fb5b..7295eeee 100644 --- a/src/polynomial-basetypes/dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -89,7 +89,7 @@ end Base.copy(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = - MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs), order(p)) + MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs), firstindex(p)) # This is B <: StandardBasis? Base.firstindex(p::MutableDensePolynomial) = p.order @@ -129,6 +129,8 @@ _zeros(::Type{<:MutableDensePolynomial}, z, N) = fill(z, N) # iszero, isconstant Base.iszero(p::MutableDensePolynomial) = iszero(p.coeffs)::Bool +degree(p::MutableDensePolynomial) = lastindex(p) + # zero, one, variable, basis Base.zero(::Type{MutableDensePolynomial{B,T,X}}) where {B,T,X} = MutableDensePolynomial{B,T,X}(T[]) diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl new file mode 100644 index 00000000..584aa7c3 --- /dev/null +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -0,0 +1,230 @@ +struct MutableSparsePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B, T,X} + coeffs::Dict{Int, T} + function MutableSparsePolynomial{B,T,X}(coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} + for (i, cᵢ) ∈ pairs(coeffs) + iszero(cᵢ) && delete!(coeffs, i) + end + new{B,T,Symbol(X)}(coeffs) + end + function MutableSparsePolynomial{B,T,X}(checked::Val{:false}, coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} + new{B,T,Symbol(X)}(coeffs) + end +end + +function MutableSparsePolynomial{B,T,X}(checked::Val{:true}, coeffs::AbstractDict{Int,T}) where {B,T,X<:Symbol} + MutableSparsePolynomial{B,T,X}(coeffs) +end + +# Dict +function MutableSparsePolynomial{B,T,X}(coeffs::AbstractDict{Int,S}) where {B,T,S,X} + cs = convert(Dict{Int,T}, coeffs) + MutableSparsePolynomial{B,T,X}(cs) +end + +function MutableSparsePolynomial{B}(cs::AbstractDict{Int,T}, var::SymbolLike=:x) where {B,T} + MutableSparsePolynomial{B,T,Symbol(var)}(cs) +end + +# abstract vector has order/symbol +function MutableSparsePolynomial{B,T,X}(coeffs::AbstractVector{S}, order::Int=0) where {B,T,S,X} + + P = MutableSparsePolynomial{B,T,X} + n = length(coeffs) + iszero(n) && zero(P) + xs = convert(Vector{T}, coeffs) + d = Dict{Int, T}(Base.Generator(=>, order:(order+n-1), xs)) + P(d) +end + +function MutableSparsePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::SymbolLike=Var(:x)) where {B,T,S} + MutableSparsePolynomial{B,T,Symbol(var)}(xs) +end + +function MutableSparsePolynomial{B,T}(coeffs::AbstractVector{S}, var::SymbolLike) where {B,T,S} + MutableSparsePolynomial{B,T,Symbol(var)}(xs) +end + +function MutableSparsePolynomial{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} + MutableSparsePolynomial{B,T,Symbol(var)}(xs) +end + +function MutableSparsePolynomial{B}(xs::AbstractVector{T}, var::SymbolLike) where {B,T} + MutableSparsePolynomial{B,T,Symbol(var)}(xs) +end + +function MutableSparsePolynomial{B,T}(xs, var::SymbolLike=Var(:x)) where {B,T} + cs = collect(T, xs) + cs = trim_trailing_zeros(cs) + MutableSparsePolynomial{B,T,Symbol(var)}(cs) +end + +function MutableSparsePolynomial{B}(xs, var::SymbolLike=Var(:x)) where {B} + cs = collect(xs) + cs = trim_trailing_zeros(cs) + MutableSparsePolynomial{B,T,Symbol(var)}(cs) +end + + +@poly_register MutableSparsePolynomial +constructorof(::Type{<:MutableSparsePolynomial}) = MutableSparsePolynomial + + +# cs iterable of pairs +# XXX enure tight value of T +function MutableSparsePolynomial{B}(cs::Tuple, var::SymbolLike=:x) where {B} + isempty(cs) && throw(ArgumentError("No type attached")) + X = Var(var) + if length(cs) == 1 + c = only(cs) + d = Dict(first(c) => last(c)) + T = eltype(last(c)) + return MutableSparsePolynomial{B,T,X}(d) + else + c₁, c... = cs + T = typeof(last(c₁)) + for (a,b) ∈ c + T = promote_type(T, typeof(b)) + end + ks = Base.Generator(first, cs) + vs = Base.Generator(last, cs) + d = Dict{Int,T}(Base.Generator(=>, ks, vs)) + return MutableSparsePolynomial{B,T,X}(d) + end +end + + + +Base.copy(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(copy(p.coeffs)) + +function Base.convert(::Type{MutableSparsePolynomial{B,T,X}}, p::MutableSparsePolynomial{B,S,X}) where {B,T,S,X} + d = Dict{Int,T}(k => v for (k,v) ∈ pairs(p.coeffs)) + MutableSparsePolynomial{B,T,X}(Val(false), d) +end +# --- + +function Base.firstindex(p::MutableSparsePolynomial) + isempty(p.coeffs) && return 0 + i = minimum(keys(p.coeffs)) +end +function Base.lastindex(p::MutableSparsePolynomial) + isempty(p.coeffs) && return 0 + maximum(keys(p.coeffs)) +end +function Base.getindex(p::MutableSparsePolynomial{B,T,X}, i::Int) where {B,T,X} + get(p.coeffs, i, zero(T)) +end + +function Base.setindex!(p::MutableSparsePolynomial{B,T,X}, value, i::Int) where {B,T,X} + iszero(value) && delete!(p.coeffs, i) + p.coeffs[i] = value +end + +hasnan(p::MutableSparsePolynomial) = any(hasnan, values(p.coeffs)) +Base.iterate(p::MutableSparsePolynomial, args...) = Base.iterate(p.coeffs, args...) +Base.pairs(p::MutableSparsePolynomial) = pairs(p.coeffs) + +## Not properly named!!! truncate? chop? skip? +function trim_trailing_zeros(d::Dict) + for (k,v) ∈ pairs(d) + iszero(v) && deletat!(d, k) + end + d +end + +function Base.chop(p::MutableSparsePolynomial; atol=nothing, rtol=nothing) + δ = something(rtol,0) + ϵ = something(atol,0) + τ = max(ϵ, _norm(x,2) * δ) + for (i,pᵢ) ∈ pairs(p) + abs(pᵢ) ≤ τ && delete!(p.coeffs, i) + end + p +end + +_zeros(::Type{MutableSparsePolynomial{B,T,X}}, z::S, N) where {B,T,X,S} = Dict{Int, S}() + +Base.iszero(p::MutableSparsePolynomial) = all(iszero, values(p.coeffs)) + +Base.zero(::Type{MutableSparsePolynomial{B,T,X}}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(Dict{Int,T}()) +## --- + +function _evalpoly(p::MutableSparsePolynomial, x) + + tot = zero(p[0]*x) + for (i, cᵢ) ∈ p.coeffs + tot = muladd(cᵢ, x^i, tot) + end + return tot +end + +offset(p::MutableSparsePolynomial) = 0 +function isconstant(p::MutableSparsePolynomial) + n = length(p.coeffs) + n == 0 && return true + n == 1 && haskey(p.coeffs, 0) +end + +# much faster than default +function scalar_add(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} + c₀ = c + p[0] + R = eltype(c₀) + P = MutableSparsePolynomial{B,R,X} + D = convert(Dict{Int, R}, copy(p.coeffs)) + if iszero(c₀) + delete!(D,0) + else + @inbounds D[0] = c₀ + end + return P(Val(false), D) +end + +function scalar_mul(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} + + R = promote_type(T,S) + P = MutableSparsePolynomial{B,R,X} + (iszero(p) || iszero(c)) && return(zero(P)) + + d = convert(Dict{Int, R}, copy(p.coeffs)) + for (k, pₖ) ∈ pairs(d) + @inbounds d[k] = c .* d[k] + end + + return P(Val(false), d) + +end + +function scalar_mul(p::MutableSparsePolynomial{B,T,X}, c::S) where {B,T,X,S} + R = promote_type(T,S) + P = MutableSparsePolynomial{B,R,X} + (iszero(p) || iszero(c)) && return(zero(P)) + + d = convert(Dict{Int, R}, copy(p.coeffs)) + for (k, pₖ) ∈ pairs(d) + @inbounds d[k] = d[k] .* c + end + + return P(Val(false), d) +end + +Base.:+(p::MutableSparsePolynomial{B,T,X}, q::MutableSparsePolynomial{B,S,X}) where{B,X,T,S} = + _dict_combine(+, p, q) +Base.:-(p::MutableSparsePolynomial{B,T,X}, q::MutableSparsePolynomial{B,S,X}) where{B,X,T,S} = + _dict_combine(-, p, q) + +function _dict_combine(op, p::MutableSparsePolynomial{B,T,X}, q::MutableSparsePolynomial{B,S,X}) where{B,X,T,S} + + R = promote_type(T,S) + P = MutableSparsePolynomial{B,R,X} + D = convert(Dict{Int, R}, copy(p.coeffs)) + for (i, qᵢ) ∈ pairs(q.coeffs) + pᵢ = get(D, i, zero(R)) + pqᵢ = op(pᵢ, qᵢ) + if iszero(pqᵢ) + delete!(D, i) # will be zero + else + D[i] = pqᵢ + end + end + return P(Val(false), D) + +end diff --git a/src/polynomial-basetypes/sparse-polynomial.jl b/src/polynomial-basetypes/sparse-polynomial.jl deleted file mode 100644 index 57080565..00000000 --- a/src/polynomial-basetypes/sparse-polynomial.jl +++ /dev/null @@ -1,230 +0,0 @@ -struct SparseUnivariatePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B, T,X} - coeffs::Dict{Int, T} - function SparseUnivariatePolynomial{B,T,X}(coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} - for (i, cᵢ) ∈ pairs(coeffs) - iszero(cᵢ) && delete!(coeffs, i) - end - new{B,T,Symbol(X)}(coeffs) - end - function SparseUnivariatePolynomial{B,T,X}(checked::Val{:false}, coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} - new{B,T,Symbol(X)}(coeffs) - end -end - -function SparseUnivariatePolynomial{B,T,X}(checked::Val{:true}, coeffs::AbstractDict{Int,T}) where {B,T,X<:Symbol} - SparseUnivariatePolynomial{B,T,X}(coeffs) -end - -# Dict -function SparseUnivariatePolynomial{B,T,X}(coeffs::AbstractDict{Int,S}) where {B,T,S,X} - cs = convert(Dict{Int,T}, coeffs) - SparseUnivariatePolynomial{B,T,X}(cs) -end - -function SparseUnivariatePolynomial{B}(cs::AbstractDict{Int,T}, var::SymbolLike=:x) where {B,T} - SparseUnivariatePolynomial{B,T,Symbol(var)}(cs) -end - -# abstract vector has order/symbol -function SparseUnivariatePolynomial{B,T,X}(coeffs::AbstractVector{S}, order::Int=0) where {B,T,S,X} - - P = SparseUnivariatePolynomial{B,T,X} - n = length(coeffs) - iszero(n) && zero(P) - xs = convert(Vector{T}, coeffs) - d = Dict{Int, T}(Base.Generator(=>, order:(order+n-1), xs)) - P(d) -end - -function SparseUnivariatePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::SymbolLike=Var(:x)) where {B,T,S} - SparseUnivariatePolynomial{B,T,Symbol(var)}(xs) -end - -function SparseUnivariatePolynomial{B,T}(coeffs::AbstractVector{S}, var::SymbolLike) where {B,T,S} - SparseUnivariatePolynomial{B,T,Symbol(var)}(xs) -end - -function SparseUnivariatePolynomial{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} - SparseUnivariatePolynomial{B,T,Symbol(var)}(xs) -end - -function SparseUnivariatePolynomial{B}(xs::AbstractVector{T}, var::SymbolLike) where {B,T} - SparseUnivariatePolynomial{B,T,Symbol(var)}(xs) -end - -function SparseUnivariatePolynomial{B,T}(xs, var::SymbolLike=Var(:x)) where {B,T} - cs = collect(T, xs) - cs = trim_trailing_zeros(cs) - SparseUnivariatePolynomial{B,T,Symbol(var)}(cs) -end - -function SparseUnivariatePolynomial{B}(xs, var::SymbolLike=Var(:x)) where {B} - cs = collect(xs) - cs = trim_trailing_zeros(cs) - SparseUnivariatePolynomial{B,T,Symbol(var)}(cs) -end - - -@poly_register SparseUnivariatePolynomial -constructorof(::Type{<:SparseUnivariatePolynomial}) = SparseUnivariatePolynomial - - -# cs iterable of pairs -# XXX enure tight value of T -function SparseUnivariatePolynomial{B}(cs::Tuple, var::SymbolLike=:x) where {B} - isempty(cs) && throw(ArgumentError("No type attached")) - X = Var(var) - if length(cs) == 1 - c = only(cs) - d = Dict(first(c) => last(c)) - T = eltype(last(c)) - return SparseUnivariatePolynomial{B,T,X}(d) - else - c₁, c... = cs - T = typeof(last(c₁)) - for (a,b) ∈ c - T = promote_type(T, typeof(b)) - end - ks = Base.Generator(first, cs) - vs = Base.Generator(last, cs) - d = Dict{Int,T}(Base.Generator(=>, ks, vs)) - return SparseUnivariatePolynomial{B,T,X}(d) - end -end - - - -Base.copy(p::SparseUnivariatePolynomial{B,T,X}) where {B,T,X} = SparseUnivariatePolynomial{B,T,X}(copy(p.coeffs)) - -function Base.convert(::Type{SparseUnivariatePolynomial{B,T,X}}, p::SparseUnivariatePolynomial{B,S,X}) where {B,T,S,X} - d = Dict{Int,T}(k => v for (k,v) ∈ pairs(p.coeffs)) - SparseUnivariatePolynomial{B,T,X}(Val(false), d) -end -# --- - -function Base.firstindex(p::SparseUnivariatePolynomial) - isempty(p.coeffs) && return 0 - i = minimum(keys(p.coeffs)) -end -function Base.lastindex(p::SparseUnivariatePolynomial) - isempty(p.coeffs) && return 0 - maximum(keys(p.coeffs)) -end -function Base.getindex(p::SparseUnivariatePolynomial{B,T,X}, i::Int) where {B,T,X} - get(p.coeffs, i, zero(T)) -end - -function Base.setindex!(p::SparseUnivariatePolynomial{B,T,X}, value, i::Int) where {B,T,X} - iszero(value) && delete!(p.coeffs, i) - p.coeffs[i] = value -end - -hasnan(p::SparseUnivariatePolynomial) = any(hasnan, values(p.coeffs)) -Base.iterate(p::SparseUnivariatePolynomial, args...) = Base.iterate(p.coeffs, args...) -Base.pairs(p::SparseUnivariatePolynomial) = pairs(p.coeffs) - -## Not properly named!!! truncate? chop? skip? -function trim_trailing_zeros(d::Dict) - for (k,v) ∈ pairs(d) - iszero(v) && deletat!(d, k) - end - d -end - -function Base.chop(p::SparseUnivariatePolynomial; atol=nothing, rtol=nothing) - δ = something(rtol,0) - ϵ = something(atol,0) - τ = max(ϵ, _norm(x,2) * δ) - for (i,pᵢ) ∈ pairs(p) - abs(pᵢ) ≤ τ && delete!(p.coeffs, i) - end - p -end - -_zeros(::Type{SparseUnivariatePolynomial{B,T,X}}, z::S, N) where {B,T,X,S} = Dict{Int, S}() - -Base.iszero(p::SparseUnivariatePolynomial) = all(iszero, values(p.coeffs)) - -Base.zero(::Type{SparseUnivariatePolynomial{B,T,X}}) where {B,T,X} = SparseUnivariatePolynomial{B,T,X}(Dict{Int,T}()) -## --- - -function _evalpoly(p::SparseUnivariatePolynomial, x) - - tot = zero(p[0]*x) - for (i, cᵢ) ∈ p.coeffs - tot = muladd(cᵢ, x^i, tot) - end - return tot -end - -offset(p::SparseUnivariatePolynomial) = 0 -function isconstant(p::SparseUnivariatePolynomial) - n = length(p.coeffs) - n == 0 && return true - n == 1 && haskey(p.coeffs, 0) -end - -# much faster than default -function scalar_add(c::S, p::SparseUnivariatePolynomial{B,T,X}) where {B,T,X,S} - c₀ = c + p[0] - R = eltype(c₀) - P = SparseUnivariatePolynomial{B,R,X} - D = convert(Dict{Int, R}, copy(p.coeffs)) - if iszero(c₀) - delete!(D,0) - else - @inbounds D[0] = c₀ - end - return P(Val(false), D) -end - -function scalar_mul(c::S, p::SparseUnivariatePolynomial{B,T,X}) where {B,T,X,S} - - R = promote_type(T,S) - P = SparseUnivariatePolynomial{B,R,X} - (iszero(p) || iszero(c)) && return(zero(P)) - - d = convert(Dict{Int, R}, copy(p.coeffs)) - for (k, pₖ) ∈ pairs(d) - @inbounds d[k] = c .* d[k] - end - - return P(Val(false), d) - -end - -function scalar_mul(p::SparseUnivariatePolynomial{B,T,X}, c::S) where {B,T,X,S} - R = promote_type(T,S) - P = SparseUnivariatePolynomial{B,R,X} - (iszero(p) || iszero(c)) && return(zero(P)) - - d = convert(Dict{Int, R}, copy(p.coeffs)) - for (k, pₖ) ∈ pairs(d) - @inbounds d[k] = d[k] .* c - end - - return P(Val(false), d) -end - -Base.:+(p::SparseUnivariatePolynomial{B,T,X}, q::SparseUnivariatePolynomial{B,S,X}) where{B,X,T,S} = - _dict_combine(+, p, q) -Base.:-(p::SparseUnivariatePolynomial{B,T,X}, q::SparseUnivariatePolynomial{B,S,X}) where{B,X,T,S} = - _dict_combine(-, p, q) - -function _dict_combine(op, p::SparseUnivariatePolynomial{B,T,X}, q::SparseUnivariatePolynomial{B,S,X}) where{B,X,T,S} - - R = promote_type(T,S) - P = SparseUnivariatePolynomial{B,R,X} - D = convert(Dict{Int, R}, copy(p.coeffs)) - for (i, qᵢ) ∈ pairs(q.coeffs) - pᵢ = get(D, i, zero(R)) - pqᵢ = op(pᵢ, qᵢ) - if iszero(pqᵢ) - delete!(D, i) # will be zero - else - D[i] = pqᵢ - end - end - return P(Val(false), D) - -end diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 3b2ccdac..a7a1dee9 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -33,7 +33,7 @@ function variable(P::Type{<:AbstractUnivariatePolynomial{B,T,X}}) where {B<:Stan basis(P, 1) end -function basis(P::Type{<:AbstractUnivariatePolynomial{B, T, X}}, i) where {B<:StandardBasis,T,X} +function basis(P::Type{<:AbstractUnivariatePolynomial{B, T, X}}, i::Int) where {B<:StandardBasis,T,X} cs = ones(T,1) P(cs, i) end diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 669e4081..632587c3 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -17,12 +17,12 @@ function _evalpoly(p:: MutableDensePolynomial{StandardBasis,T,X}, c) where {T,X} Base.evalpoly(c, p.coeffs) * c^p.order end -function isconstant(p:: MutableDensePolynomial{StandardBasis}) - firstindex(p) != 0 && return false - i = findlast(!iszero, p.coeffs) - i == nothing && return true - i == 1 && return true -end +# function isconstant(p:: MutableDensePolynomial{StandardBasis}) +# firstindex(p) != 0 && return false +# i = findlast(!iszero, p.coeffs) +# i == nothing && return true +# i == 1 && return true +# end # scalar add function scalar_add(c::S, p:: MutableDensePolynomial{StandardBasis,T,X}) where {S, T, X} diff --git a/src/standard-basis/standard-sparse.jl b/src/standard-basis/standard-sparse.jl index 78fac26d..049a91c4 100644 --- a/src/standard-basis/standard-sparse.jl +++ b/src/standard-basis/standard-sparse.jl @@ -1,7 +1,7 @@ #const SparsePolynomial = SparseUnivariatePolynomial{StandardBasis} # const is important! #export SparsePolynomial -function evalpoly(x, p::SparseUnivariatePolynomial) +function evalpoly(x, p::MutableSparsePolynomial) tot = zero(p[0]*x) for (i, cᵢ) ∈ p.coeffs @@ -11,15 +11,15 @@ function evalpoly(x, p::SparseUnivariatePolynomial) end -function constantterm(p::SparseUnivariatePolynomial{B,T,X}) where {B,T,X} +function constantterm(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} get(p.coeffs, 0, zero(T)) end -function ⊗(p::SparseUnivariatePolynomial{StandardBasis,T,X}, - q::SparseUnivariatePolynomial{StandardBasis,S,X}) where {T,S,X} +function ⊗(p::MutableSparsePolynomial{StandardBasis,T,X}, + q::MutableSparsePolynomial{StandardBasis,S,X}) where {T,S,X} # simple convolution R = promote_type(T,S) - P = SparseUnivariatePolynomial{StandardBasis,R,X} + P = MutableSparsePolynomial{StandardBasis,R,X} z = zero(R) cs = Dict{Int, R}() From 13d6fe37166c8bc1134539cbd7db4fe3a4df8492 Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 19 Jul 2023 15:28:52 -0400 Subject: [PATCH 03/31] WIP: tests pass --- src/abstract-polynomial.jl | 112 ++++----- src/basis-utils.jl | 4 +- src/common.jl | 49 ++-- .../immutable-dense-polynomial.jl | 122 +++++++--- .../mutable-dense-polynomial.jl | 32 ++- .../mutable-sparse-polynomial.jl | 25 +- src/polynomials/LaurentPolynomial.jl | 19 +- src/polynomials/standard-basis.jl | 6 +- src/standard-basis/standard-basis.jl | 220 ++++++++++++++---- src/standard-basis/standard-dense.jl | 8 +- src/standard-basis/standard-immutable.jl | 29 ++- src/standard-basis/standard-sparse.jl | 12 +- test/StandardBasis.jl | 40 ++-- 13 files changed, 452 insertions(+), 226 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index b3ac20db..80e57f0b 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -4,6 +4,10 @@ abstract type AbstractUnivariatePolynomial{B, T, X} <: AbstractPolynomial{T,X} end abstract type AbstractBasis end +## idea is vector space stuff (scalar_add, scalar_mult, vector +/-, ^) goes here +## connection (convert, transform) is specific to a basis (storage) +## ⊗(p::P{T,X}, q::P{S,Y}) is specic to basis/storage + basistype(p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = B Base.eltype(p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = T @@ -13,7 +17,7 @@ _indeterminate(::Type{P}) where {B,T, X, P <: AbstractUnivariatePolynomial{B,T,X indeterminate(::Type{P}) where {P <: AbstractUnivariatePolynomial} = something(_indeterminate(P), :x) constructorof(::Type{<:AbstractUnivariatePolynomial}) = XXX() -⟒(P::Type{<:AbstractUnivariatePolynomial}) = constructorof(P) +⟒(P::Type{<:AbstractUnivariatePolynomial}) = constructorof(P) # returns the Storage{Basis} partially constructed type ⟒(p::P) where {P <: AbstractUnivariatePolynomial} = ⟒(P) ## Julia generics treating coefficients as an abstract vector @@ -34,6 +38,11 @@ Base.eltype(::Type{<:AbstractUnivariatePolynomial{B,T}}) where {B,T} = T Base.size(p::AbstractUnivariatePolynomial) = (length(p),) Base.size(p::AbstractUnivariatePolynomial, i::Integer) = i <= 1 ? size(p)[i] : 1 +Base.copy(p::AbstractUnivariatePolynomial) = XXX() + +# dense collection +Base.iterate(p::AbstractUnivariatePolynomial, state = firstindex(p)) = _iterate(p, state) # _iterate in common.jl + #hasnan(p::AbstractUnivariatePolynomial) = any(hasnan, p) @@ -67,22 +76,26 @@ end # The zero polynomial. Typically has no coefficients #Base.zero(p::P,args...) where {P <: AbstractUnivariatePolynomial} = zero(P,args...) -Base.zero(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){B,eltype(P),indeterminate(P)}) -Base.zero(::Type{P},var::SymbolLike) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){B,eltype(P),Symbol(var)}) +Base.zero(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),indeterminate(P)}) +Base.zero(::Type{P},var::SymbolLike) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),Symbol(var)}) # the polynomial 1 #Base.one(p::P,args...) where {P <: AbstractUnivariatePolynomial} = one(P,args...) -Base.one(::Type{P}) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){B,eltype(P),indeterminate(P)}) -Base.one(::Type{P}, var::SymbolLike) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){B,eltype(P),Symbol(var)}) +Base.one(::Type{P}) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){eltype(P),indeterminate(P)}) +Base.one(::Type{P}, var::SymbolLike) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){eltype(P),Symbol(var)}) # the variable x #variable(p::P) where {P <: AbstractUnivariatePolynomial} = variable(P) -variable(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = variable(⟒(P){B,eltype(P),indeterminate(P)}) -variable(::Type{P}, var::SymbolLike) where {B,P<:AbstractUnivariatePolynomial{B}} = variable(⟒(P){B,eltype(P),Var(var)}) +variable(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = variable(⟒(P){eltype(P),indeterminate(P)}) +variable(::Type{P}, var::SymbolLike) where {B,P<:AbstractUnivariatePolynomial{B}} = variable(⟒(P){eltype(P),Var(var)}) # i -> basis polynomial basis(p::P, i::Int) where {P <: AbstractUnivariatePolynomial} = basis(P, i) -basis(::Type{P}, i::Int) where {B,P <: AbstractUnivariatePolynomial{B}} = basis(⟒(P){B,eltype(P),indeterminate(P)}, i) +basis(::Type{P}, i::Int) where {B,P <: AbstractUnivariatePolynomial{B}} = basis(⟒(P){eltype(P),indeterminate(P)}, i) + +_convert(p::P, as) where {B,T,X,P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(as) +copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {B,T, X, S, Y, P <:AbstractUnivariatePolynomial{B,S,Y}} = + ⟒(P){T, Symbol(X)}(p.coeffs) # return dense coefficients (vector or tuple) coeffs(p::AbstractUnivariatePolynomial) = [p[i] for i ∈ firstindex(p):lastindex(p)] @@ -95,7 +108,7 @@ coeffs(p::AbstractUnivariatePolynomial) = [p[i] for i ∈ firstindex(p):lastinde # chop chops right side of p # use trunc for left and right # can pass tolerances -Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = XXX() +Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = chop!(copy(p)) chop!(p::AbstractUnivariatePolynomial; kwargs...) = XXX() ## --- constant term --- @@ -115,6 +128,7 @@ isconstant(::ConstantTerm) = true Base.convert(::Type{ConstantTerm}, p::AbstractUnivariatePolynomial) = ConstantTerm(constantterm(p)) Base.convert(::Type{ConstantTerm{T}}, p::AbstractUnivariatePolynomial) where {T} = ConstantTerm(T(constantterm(p))) + ## --- #= Comparisons =# @@ -154,7 +168,7 @@ function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, end function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, p2::Number; kwargs...) where {B,T,X} - q = p2 * one(⟒(p1){B,T,X}) + q = p2 * one(⟒(p1){T,X}) isapprox(p1, q; kwargs...) end Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(p2, p1; kwargs...) @@ -185,11 +199,18 @@ Base.:(==)(p::AbstractUnivariatePolynomial, n::Number) = degree(p) <= 0 && const Base.:(==)(n::Number, p::AbstractUnivariatePolynomial) = p == n ## --- arithmetic operations --- - -Base.:-(p::AbstractUnivariatePolynomial) = scalar_mul(-1, p) - -Base.:+(c::Number, p::AbstractUnivariatePolynomial) = scalar_add(p, c) -Base.:+(p::AbstractUnivariatePolynomial, c::Number) = scalar_add(p, c) +## implement +## * unary - : here using scalar_mutl +## * scalar_add : with basis +## * scalar_mult : with storage type +## * scalar division: here using scalar_mult +## * polynomial addition: with storage type +## * polynomial multiplication: resolstorage type + basis +## +Base.:-(p::AbstractUnivariatePolynomial) = scalar_mult(-1, p) + +Base.:+(c::Scalar, p::AbstractUnivariatePolynomial) = scalar_add(p, c) +Base.:+(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_add(p, c) Base.:+(c::ConstantTerm, p::AbstractUnivariatePolynomial) = scalar_add(p, c[]) Base.:+(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_add(p, c[]) scalar_add(p::AbstractUnivariatePolynomial, c) = scalar_add(c,p) # scalar addition is commutative @@ -202,8 +223,8 @@ Base.:+(p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = _mixed_symbol_op(+, p, q) -Base.:-(c::Number, p::AbstractUnivariatePolynomial) = c + (-p) -Base.:-(p::AbstractUnivariatePolynomial, c::Number) = p + (-c) +Base.:-(c::Scalar, p::AbstractUnivariatePolynomial) = c + (-p) +Base.:-(p::AbstractUnivariatePolynomial, c::Scalar) = p + (-c) Base.:-(c::ConstantTerm, p::AbstractUnivariatePolynomial) = (-c[]) + p Base.:-(p::AbstractUnivariatePolynomial, c::ConstantTerm) = p - c[] @@ -213,23 +234,24 @@ Base.:-(p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = _mixed_symbol_op(-, p, q) -Base.:*(c::Number, p::ConstantTerm) = ConstantTerm(c*p[]) -Base.:*(p::ConstantTerm, c::Number) = ConstantTerm(p[] * c) +Base.:*(c::Scalar, p::ConstantTerm) = ConstantTerm(c*p[]) +Base.:*(p::ConstantTerm, c::Scalar) = ConstantTerm(p[] * c) -Base.:*(c::Number, p::AbstractUnivariatePolynomial) = scalar_mul(c, p) -Base.:*(c::ConstantTerm, p::AbstractUnivariatePolynomial) = scalar_mul(c[], p) -Base.:*(p::AbstractUnivariatePolynomial, c::Number) = scalar_mul(p, c) -Base.:*(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_mul(p, c[]) +Base.:*(c::Scalar, p::AbstractUnivariatePolynomial) = scalar_mult(c, p) +Base.:*(c::ConstantTerm, p::AbstractUnivariatePolynomial) = scalar_mult(c[], p) +Base.:*(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_mult(p, c) +Base.:*(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_mult(p, c[]) Base.:*(p::AbstractUnivariatePolynomial{B, T, X}, - q::AbstractUnivariatePolynomial{B, S, X}) where {B,T,S,X} = + q::AbstractUnivariatePolynomial{B, S, X}) where {B,T,S,X} = *(promote(p,q)...) +Base.:*(p::P, q::P) where {B,T,X,P <: AbstractUnivariatePolynomial{B,T,X}} = ⊗(p, q) Base.:*(p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = _mixed_symbol_op(*, p, q) -Base.:/(p::AbstractUnivariatePolynomial, c::Number) = scalar_mul(p, one(eltype(p))/c) -Base.:/(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_mul(p, one(eltype(p))/c) +Base.:/(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_mult(p, one(eltype(p))/c) +Base.:/(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_mult(p, one(eltype(p))/c) Base.:^(p::AbstractUnivariatePolynomial, n::Integer) = Base.power_by_squaring(p, n) @@ -248,8 +270,9 @@ end # only need to define differentiate(p::PolyType) -function derivative(p::AbstractUnivariatePolynomial, n::Int=0) - n <= 0 && return 1p +function derivative(p::AbstractUnivariatePolynomial, n::Int=1) + n < 0 && throw(ArgumentError("n must be non-negative")) + iszero(n) && return p p′ = differentiate(p) for i ∈ 2:n p′ = differentiate(p′) @@ -260,7 +283,7 @@ const differentiate = derivative # only need to define integrate(p::PolyType) function integrate(p::AbstractUnivariatePolynomial, c) - integrate(p) + c + scalar_add(integrate(p), c) end @@ -273,38 +296,19 @@ macro poly_register(name) #Base.convert(::Type{P}, p::P) where {P<:$poly} = p Base.promote(p::P, q::Q) where {B, X, T, P <:$poly{B,T,X}, Q <: $poly{B,T,X}} = p,q Base.promote_rule(::Type{<:$poly{B,T,X}}, ::Type{<:$poly{B,S,X}}) where {B,T,S,X} = $poly{B,promote_type(T, S),X} - Base.promote_rule(::Type{<:$poly{B,T,X}}, ::Type{S}) where {B,T,S<:Number,X} = + Base.promote_rule(::Type{<:$poly{B,T,X}}, ::Type{S}) where {B,T,S<:Scalar,X} = $poly{B,promote_type(T, S), X} - # $poly{B,T}(x::AbstractVector{S}, var::SymbolLike=Var(:x)) where {B,T,S} = - # $poly{B, T, Symbol(var)}(collect(T,x)) - # $poly{B}(coeffs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {B,T} = - # $poly{B, T, Symbol(var)}(coeffs) - - # function $poly{B,T}(coeffs::G, var::SymbolLike=Var(x)) where {B,T,G} - # !Base.isiterable(G) && throw(ArgumentError("coeffs is not iterable")) - # cs = collect(T, coeffs) - # $poly{B, T, Symbol(var)}(cs) - # end - # function $poly{B}(coeffs::G, var::SymbolLike=Var(:x)) where {B,G} - # !Base.isiterable(G) && throw(ArgumentError("coeffs is not iterable")) - # cs = collect(promote(coeffs...)) - # $poly{B, eltype(cs), Symbol(var)}(cs) - # end - - # $poly{B,T,X}(c::AbstractUnivariatePolynomial{B′,S,Y}) where {B,B′,T,X,S,Y} = convert($poly{B,T,X}, c) - # $poly{B,T}(c::AbstractUnivariatePolynomial{B′,S,Y}) where {B,B′,T,S,Y} = convert($poly{B,T}, c) - # $poly{B}(c::AbstractUnivariatePolynomial{B′,S,Y}) where {B,B′,S,Y} = convert($poly{B}, c) - - $poly{B,T,X}(n::S) where {B, T, X, S<:Number} = + # constants + $poly{B,T,X}(n::S) where {B, T, X, S<:Scalar} = T(n) * one($poly{B, T, X}) - $poly{B, T}(n::S, var::SymbolLike = Var(:x)) where {B, T, S<:Number} = + $poly{B, T}(n::S, var::SymbolLike = Var(:x)) where {B, T, S<:Scalar} = T(n) * one($poly{B, T, Symbol(var)}) - $poly{B}(n::S, var::SymbolLike = Var(:x)) where {B, S <: Number} = n * one($poly{B, S, Symbol(var)}) + $poly{B}(n::S, var::SymbolLike = Var(:x)) where {B, S <: Scalar} = n * one($poly{B, S, Symbol(var)}) $poly{B,T}(var::SymbolLike=Var(:x)) where {B,T} = variable($poly{B, T, Symbol(var)}) $poly{B}(var::SymbolLike=Var(:x)) where {B} = variable($poly{B}, Symbol(var)) - (p::$poly)(x) = _evalpoly(p, x) + (p::$poly)(x) = evalpoly(x, p) end end diff --git a/src/basis-utils.jl b/src/basis-utils.jl index 7a41ebcb..1c29a721 100644 --- a/src/basis-utils.jl +++ b/src/basis-utils.jl @@ -4,7 +4,7 @@ gtτ(x, τ) = abs(x) > τ # return index or nothing of last non "zdero" # chop! then considers cases i==nothing, i=length(x), i < length(x) -function chop_right_index(x::AbstractVector{T}; rtol=nothing, atol=nothing) where {T} +function chop_right_index(x; rtol=nothing, atol=nothing) isempty(x) && return nothing δ = something(rtol,0) @@ -17,7 +17,7 @@ end -function chop_left_index(x::AbstractVector{T}; rtol=nothing, atol=nothing) where {T} +function chop_left_index(x; rtol=nothing, atol=nothing) isempty(x) && return nothing δ = something(rtol,0) ϵ = something(atol,0) diff --git a/src/common.jl b/src/common.jl index 3bc15149..5990bbf3 100644 --- a/src/common.jl +++ b/src/common.jl @@ -34,9 +34,9 @@ julia> fromroots(r) Polynomial(6 - 5*x + x^2) ``` """ -function fromroots(P::Type{<:AbstractPolynomial}, roots::AbstractVector; var::SymbolLike = :x) +function fromroots(P::Type{<:AbstractPolynomial}, rs::AbstractVector; var::SymbolLike = :x) x = variable(P, var) - p = prod(x - r for r in roots) + p = prod(x-r for r ∈ rs; init=one(x)) return truncate!(p) end fromroots(r::AbstractVector{<:Number}; var::SymbolLike = :x) = @@ -335,6 +335,7 @@ function truncate!(ps::Dict{Int,T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0,) where {T} + isempty(ps) && return nothing max_coeff = norm(values(ps), Inf) thresh = max_coeff * rtol + atol @@ -346,7 +347,18 @@ function truncate!(ps::Dict{Int,T}; nothing end -truncate!(ps::NTuple; kwargs...) = throw(ArgumentError("`truncate!` not defined.")) +function truncate!(ps::NTuple{N,T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {N,T} + #throw(ArgumentError("`truncate!` not defined.")) + thresh = norm(ps, Inf) * rtol + atol + for (i, pᵢ) ∈ enumerate(ps) + if abs(pᵢ) ≤ thresh + ps = _set(ps, i, zero(pᵢ)) + end + end + ps +end _truncate(ps::NTuple{0}; kwargs...) = ps function _truncate(ps::NTuple{N,T}; @@ -470,9 +482,9 @@ Linear Algebra =# Calculates the p-norm of the polynomial's coefficients """ -function LinearAlgebra.norm(q::AbstractPolynomial, p::Real = 2) - vs = values(q) - return norm(vs, p) # if vs=() must be handled in special type +function LinearAlgebra.norm(q::AbstractPolynomial{T,X}, p::Real = 2) where {T,X} + iszero(q) && return zero(real(T))^(1/p) + return norm(values(q), p) end """ @@ -811,6 +823,7 @@ indeterminate(p::P) where {P <: AbstractPolynomial} = _indeterminate(P) function indeterminate(PP::Type{P}, p::AbstractPolynomial{T,Y}) where {P <: AbstractPolynomial, T,Y} X = _indeterminate(PP) isnothing(X) && return Y + isconstant(p) && return X assert_same_variable(X,Y) return X #X = isnothing(_indeterminate(PP)) ? indeterminate(p) : _indeterminate(PP) @@ -916,17 +929,19 @@ basis(p::P, k::Int, _var=indeterminate(p); var=_var) where {P<:AbstractPolynomia #= arithmetic =# +Scalar = Union{Number, Matrix} + Base.:-(p::P) where {P <: AbstractPolynomial} = _convert(p, -coeffs(p)) -Base.:*(p::AbstractPolynomial, c::Number) = scalar_mult(p, c) -Base.:*(c::Number, p::AbstractPolynomial) = scalar_mult(c, p) +Base.:*(p::AbstractPolynomial, c::Scalar) = scalar_mult(p, c) +Base.:*(c::Scalar, p::AbstractPolynomial) = scalar_mult(c, p) Base.:*(c::T, p::P) where {T, X, P <: AbstractPolynomial{T,X}} = scalar_mult(c, p) Base.:*(p::P, c::T) where {T, X, P <: AbstractPolynomial{T,X}} = scalar_mult(p, c) -# implicitly identify c::Number with a constant polynomials -Base.:+(c::Number, p::AbstractPolynomial) = +(p, c) -Base.:-(p::AbstractPolynomial, c::Number) = +(p, -c) -Base.:-(c::Number, p::AbstractPolynomial) = +(-p, c) +# implicitly identify c::Scalar with a constant polynomials +Base.:+(c::Scalar, p::AbstractPolynomial) = +(p, c) +Base.:-(p::AbstractPolynomial, c::Scalar) = +(p, -c) +Base.:-(c::Scalar, p::AbstractPolynomial) = +(-p, c) # scalar operations # no generic p+c, as polynomial addition falls back to scalar ops @@ -938,13 +953,13 @@ Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) ## 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}} +## +(p::P,c::Scalar) 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 Base.:+(p::AbstractPolynomial) = p -# polynomial + scalar; implicit identification of c with c*one(P) -Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = p + c * one(P) +# polynomial + scalar; implicit identification of c with c*one(p) +Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = scalar_add(p, c)#p + c * one(p) function Base.:+(p::P, c::S) where {T,X, P<:AbstractPolynomial{T,X}, S} R = promote_type(T,S) @@ -1148,8 +1163,8 @@ function Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial) check_same_variable(p1, p2) || return false ==(promote(p1,p2)...) end -Base.:(==)(p::AbstractPolynomial, n::Number) = degree(p) <= 0 && constantterm(p) == n -Base.:(==)(n::Number, p::AbstractPolynomial) = p == n +Base.:(==)(p::AbstractPolynomial, n::Scalar) = degree(p) <= 0 && constantterm(p) == n +Base.:(==)(n::Scalar, p::AbstractPolynomial) = p == n function Base.isapprox(p1::AbstractPolynomial, p2::AbstractPolynomial; kwargs...) if isconstant(p1) diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index c02ef3f8..88fba21d 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -17,7 +17,7 @@ ImmutableDensePolynomial{B,T,X,N}(::Type{Val{true}}, cs::NTuple{N,T}) where {B,N ImmutableDensePolynomial{B,T,X,N}(cs) # tuple -function ImmutableDensePolynomial{B,T,X}(xs::NTuple{N,S}) where {B,X,T,N,S} +function ImmutableDensePolynomial{B,T,X}(xs::NTuple{N,S}) where {B,T,S,X,N} cs = convert(NTuple{N,T}, xs) cs = trim_trailing_zeros(cs) N′ = length(cs) @@ -33,10 +33,15 @@ function ImmutableDensePolynomial{B}(xs::NTuple{N,T}, var::SymbolLike=Var(:x)) w end # abstract vector -function ImmutableDensePolynomial{B,T,X}(xs::AbstractVector{S}) where {B,T,X,S} +function ImmutableDensePolynomial{B,T,X}(xs::AbstractVector{S}, order::Int=0) where {B,T,X,S} + if Base.has_offset_axes(xs) + @warn "ignoring the axis offset of the coefficient vector" + xs = parent(xs) + end + !iszero(order) && @warn "order argument is ignored" N = length(xs) - cs = ntuple(Base.Fix1(getindex,xs), Val(N)) - ImmutableDensePolynomial{B,T,X}(cs) + cs = ntuple(Base.Fix1(T∘getindex,xs), Val(N)) + ImmutableDensePolynomial{B,T,X,N}(cs) end function ImmutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::SymbolLike=Var(:x)) where {B,T,S} @@ -44,7 +49,7 @@ function ImmutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::S end function ImmutableDensePolynomial{B,T}(xs::AbstractVector{S}, var::SymbolLike) where {B,T,S} - ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) + ImmutableDensePolynomial{B,T,Symbol(var)}(xs) end function ImmutableDensePolynomial{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} @@ -62,42 +67,89 @@ end function ImmutableDensePolynomial{B,T}(xs; var::SymbolLike=Var(:x)) where {B,T} cs = collect(T,xs) - ImmutableDensePolynomial{B,T,X}(cs) + ImmutableDensePolynomial{B,T,Var(var)}(cs) end function ImmutableDensePolynomial{B}(xs, var::SymbolLike=Var(:x)) where {B} - cs = collect(xs) + cs = collect(promote(xs...)) T = eltype(cs) ImmutableDensePolynomial{B,T,Symbol(var)}(cs) end +# constant +function ImmutableDensePolynomial{B,T,X,N}(c::S) where {B,T,X,N,S<:Number} + if N == 0 + if iszero(c) + throw(ArgumentError("Can't create zero-length polynomial")) + else + return zero(ImmutableDensePolynomial{B,T,X}) + end + end + cs = ntuple(i -> i == 1 ? T(c) : zero(T), Val(N)) + return ImmutableDensePolynomial{B,T,X,N}(cs) +end + @poly_register ImmutableDensePolynomial -constructorof(::Type{<:ImmutableDensePolynomial}) = ImmutableDensePolynomial +constructorof(::Type{<:ImmutableDensePolynomial{B}}) where {B} = ImmutableDensePolynomial{B} + +Base.promote_rule(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, ::Type{<:ImmutableDensePolynomial{B,S,X,M}}) where {B,T,S,X,N,M} = + ImmutableDensePolynomial{B,promote_type(T,S), X, max(N,M)} +Base.promote_rule(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, ::Type{<:S}) where {B,T,S<:Number,X,N} = + ImmutableDensePolynomial{B,promote_type(T,S), X, N} function Base.convert(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, p::ImmutableDensePolynomial{B,T′,X,N′}) where {B,T,T′,X,N,N′} - N < N′ && throw(ArgumentError("Wrong size")) - N > N′ && return ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) + N′′ = findlast(!iszero, p) + isnothing(N′′) && return zero(ImmutableDensePolynomial{B,T,X,N}) + N < N′′ && throw(ArgumentError("Wrong size")) + N > N′′ && return ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) end + Base.copy(p::ImmutableDensePolynomial) = p +Base.similar(p::ImmutableDensePolynomial, args...) = similar(p.coeffs, args...) ## chop -function Base.chop(p::ImmutableDensePolynomial{B,T,X,N}; kwargs...) where {B,T,X,N} - i = chop_right_index(p.coeffs; kwargs) +function Base.chop(p::ImmutableDensePolynomial{B,T,X,N}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0) where {B,T,X,N} + i = chop_right_index(p.coeffs; rtol=rtol, atol=atol) if i == nothing xs = () N′ = 0 else N′ = i - xs = ntuple(Base.Fix1(getindex, xs), Val(N′)) + xs = ntuple(Base.Fix1(getindex, p.coeffs), Val(N′)) end ImmutableDensePolynomial{B,T,X,N′}(xs) end + +chop!(p::ImmutableDensePolynomial; kwargs...) = chop(p; kwargs...) + +function truncate!(p::ImmutableDensePolynomial{B,T,X,N}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0) where {B,T,X,N} + + ps = p.coeffs + isempty(ps) && return p + max_coeff = norm(ps, Inf) + thresh = max_coeff * rtol + atol + for (i,pᵢ) ∈ pairs(ps) + if abs(pᵢ) <= thresh + @set! ps[i] = zero(T) + end + end + ImmutableDensePolynomial{B,T,X,N}(ps) +end + + + + + # not type stable, as N is value dependent function trim_trailing_zeros(cs::Tuple) isempty(cs) && return cs @@ -151,7 +203,7 @@ Base.zero(::Type{<:ImmutableDensePolynomial{B,T,X}}) where {B,T,X} = function isconstant(p::ImmutableDensePolynomial) i = findlast(!iszero, p.coeffs) - return i ≤ 1 + return isnothing(i) || i ≤ 1 end Base.firstindex(p::ImmutableDensePolynomial) = 0 @@ -162,15 +214,17 @@ Base.pairs(p::ImmutableDensePolynomial) = Base.length(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = N offset(p::ImmutableDensePolynomial) = 1 -# function Base.getindex(p::ImmutableDensePolynomial{B,T,X,N}, i) where {B,T,X,N} -# (i < firstindex(p) || i > lastindex(p)) && return zero(T) -# p.coeffs[i + offset(p)] -# end +function Base.getindex(p::ImmutableDensePolynomial{B,T,X,N}, i::Int) where {B,T,X,N} + N == 0 && return zero(T) + (i < firstindex(p) || i > lastindex(p)) && return zero(p.coeffs[1]) + p.coeffs[i + offset(p)] +end Base.setindex!(p::ImmutableDensePolynomial, value, i::Int) = throw(ArgumentError("ImmutableDensePolynomial has no setindex! method")) # can't promote to same N if trailing zeros function Base.:(==)(p1::ImmutableDensePolynomial{B}, p2::ImmutableDensePolynomial{B}) where {B} + iszero(p1) && iszero(p2) && return true if isconstant(p1) isconstant(p2) && return constantterm(p1) == constantterm(p2) return false @@ -186,17 +240,17 @@ end ## --- -function _evalpoly(p::ImmutableDensePolynomial{B,T,X,N}, x) where {B,T,X,N} - N == 0 && return zero(T) * zero(x) - z = zero(x * zero(p[0])) - typeof(z)(Base.evalpoly(x, p.coeffs)) +function degree(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} + iszero(N) && return -1 + i = findlast(!iszero, p.coeffs) + isnothing(i) && return -1 + return i - 1 end - # zero, one Base.zero(::Type{ImmutableDensePolynomial{B,T,X,N}}) where {B,T,X,N} = ImmutableDensePolynomial{B,T,X,0}(()) -function basis(P::Type{<:ImmutableDensePolynomial{B,T,X}}, i) where {B,T,X} +function basis(P::Type{<:ImmutableDensePolynomial{B,T,X}}, i::Int) where {B,T,X} xs = zeros(T, i + 1) xs[end] = 1 ImmutableDensePolynomial{B,T,X}(xs) @@ -257,18 +311,24 @@ end # scalar -function scalar_mul(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S,N} - R = promote_type(T,S) - P = ImmutableDensePolynomial{B,R,X} - iszero(N) && return zero(P) - iszero(c) && return convert(P, p) +function scalar_mult(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S,N} + iszero(N) && return zero(ImmutableDensePolynomial{B,T,X}) + iszero(c) && ImmutableDensePolynomial{B}([p[0] .* c], X) cs = p.coeffs .* (c,) + R = eltype(cs) return ImmutableDensePolynomial{B,R,X,N}(cs) + + # R = promote_type(T,S) + # P = ImmutableDensePolynomial{B,R,X} + # (iszero(N) || iszero(c)) && return nothing#zero(p) + # #iszero(c) && return zero(p) #convert(P, p) + # cs = p.coeffs .* (c,) + # return ImmutableDensePolynomial{B,R,X,N}(cs) end -function scalar_mul(c::S, p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,S,N} +function scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,S,N} iszero(N) && return zero(ImmutableDensePolynomial{B,T,X}) - iszero(c) && ImmutableDensePolynomial{B,X}([c .* p[0]]) + iszero(c) && ImmutableDensePolynomial{B}([c .* p[0]],X) cs = (c,) .* p.coeffs R = eltype(cs) return ImmutableDensePolynomial{B,R,X,N}(cs) diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index 7295eeee..51efd30c 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -32,12 +32,21 @@ end MutableDensePolynomial{B,T,X}(checked::Val{true}, cs::Vector{T}, order::Int=0) where {B,T,X} = MutableDensePolynomial{B,T,X}(cs, order) -function MutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int=0, var::SymbolLike=:x) where {T, S, B} +function MutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int=0, var::SymbolLike=Var(:x)) where {T, S, B} + if Base.has_offset_axes(xs) + @warn "Using the axis offset of the coefficient vector" + xs, order = xs.parent, first(xs.offsets) + end + cs = convert(Vector{T}, xs) cs = trim_trailing_zeros(cs) MutableDensePolynomial{B,T,Symbol(var)}(Val(true),cs, order) end +function MutableDensePolynomial{B}(xs::AbstractVector{T}, order::Int=0, var::SymbolLike=Var(:x)) where {B, T} + MutableDensePolynomial{B,T}(xs, order, var) +end + # function MutableDensePolynomial{B,X}(xs::AbstractVector{T}, order::Int) where {T, B,X} # MutableDensePolynomial{B,T,X}(xs,order) # end @@ -55,7 +64,7 @@ end # allow specification of codefficients, order, symbol or coefficients, symbol function MutableDensePolynomial{B}(xs, order::Int=0, var::SymbolLike=Var(:x)) where {B} - cs = collect(xs) + cs = collect(promote(xs...)) T = eltype(cs) MutableDensePolynomial{B, T, Symbol(var)}(cs, order) end @@ -74,7 +83,7 @@ function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDensePolyn end @poly_register MutableDensePolynomial -constructorof(::Type{<:MutableDensePolynomial}) = MutableDensePolynomial +constructorof(::Type{<:MutableDensePolynomial{B}}) where {B} = MutableDensePolynomial{B} @@ -103,12 +112,12 @@ function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDensePolynom a,b = firstindex(p), lastindex(p) o = a iszero(p) && return P([value], i) - z = zero(p.coeffs) + z = zero(first(p.coeffs)) if i > b append!(p.coeffs, _zeros(p, z, i - b)) p.coeffs[end] = value elseif i < a - prepend!(p.coeffs, zeros(T, a-i)) + prepend!(p.coeffs, zeros(p, z, a-i)) p.coeffs[1] = value o = i else @@ -126,10 +135,16 @@ Base.pairs(p::MutableDensePolynomial) = # return a container of zeros based on basis type _zeros(::Type{<:MutableDensePolynomial}, z, N) = fill(z, N) +Base.similar(p::MutableDensePolynomial, args...) = similar(p.coeffs, args...) + # iszero, isconstant Base.iszero(p::MutableDensePolynomial) = iszero(p.coeffs)::Bool -degree(p::MutableDensePolynomial) = lastindex(p) +function degree(p::MutableDensePolynomial) + i = findlast(!iszero, p.coeffs) + isnothing(i) && return -1 + firstindex(p) + i - 1 +end # zero, one, variable, basis Base.zero(::Type{MutableDensePolynomial{B,T,X}}) where {B,T,X} = @@ -148,6 +163,7 @@ function trim_trailing_zeros(cs::Vector{T}) where {T} n = length(cs) while n > i pop!(cs) + n -= 1 end end cs @@ -245,11 +261,11 @@ end # end # scalar -function scalar_mul(p::MutableDensePolynomial{B,T,X}, c::S) where {B,T,X,S} +function scalar_mult(p::MutableDensePolynomial{B,T,X}, c::S) where {B,T,X,S} cs = p.coeffs .* (c,) # works with T[] return _polynomial(p, cs) end -function scalar_mul(c::S, p::MutableDensePolynomial{B,T,X}) where {B,T,X,S} +function scalar_mult(c::S, p::MutableDensePolynomial{B,T,X}) where {B,T,X,S} cs = (c,) .* p.coeffs return _polynomial(p, cs) end diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index 584aa7c3..def7be07 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -27,6 +27,10 @@ end # abstract vector has order/symbol function MutableSparsePolynomial{B,T,X}(coeffs::AbstractVector{S}, order::Int=0) where {B,T,S,X} + if Base.has_offset_axes(coeffs) + @warn "ignoring the axis offset of the coefficient vector" + coeffs = parent(coeffs) + end P = MutableSparsePolynomial{B,T,X} n = length(coeffs) @@ -40,7 +44,7 @@ function MutableSparsePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::Sy MutableSparsePolynomial{B,T,Symbol(var)}(xs) end -function MutableSparsePolynomial{B,T}(coeffs::AbstractVector{S}, var::SymbolLike) where {B,T,S} +function MutableSparsePolynomial{B,T}(xs::AbstractVector{S}, var::SymbolLike) where {B,T,S} MutableSparsePolynomial{B,T,Symbol(var)}(xs) end @@ -66,7 +70,7 @@ end @poly_register MutableSparsePolynomial -constructorof(::Type{<:MutableSparsePolynomial}) = MutableSparsePolynomial +constructorof(::Type{<:MutableSparsePolynomial{B}}) where {B} = MutableSparsePolynomial{B} # cs iterable of pairs @@ -82,18 +86,17 @@ function MutableSparsePolynomial{B}(cs::Tuple, var::SymbolLike=:x) where {B} else c₁, c... = cs T = typeof(last(c₁)) - for (a,b) ∈ c + for b ∈ c T = promote_type(T, typeof(b)) end - ks = Base.Generator(first, cs) - vs = Base.Generator(last, cs) + ks = 0:length(cs)-1 + vs = cs d = Dict{Int,T}(Base.Generator(=>, ks, vs)) return MutableSparsePolynomial{B,T,X}(d) end end - Base.copy(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(copy(p.coeffs)) function Base.convert(::Type{MutableSparsePolynomial{B,T,X}}, p::MutableSparsePolynomial{B,S,X}) where {B,T,S,X} @@ -120,7 +123,6 @@ function Base.setindex!(p::MutableSparsePolynomial{B,T,X}, value, i::Int) where end hasnan(p::MutableSparsePolynomial) = any(hasnan, values(p.coeffs)) -Base.iterate(p::MutableSparsePolynomial, args...) = Base.iterate(p.coeffs, args...) Base.pairs(p::MutableSparsePolynomial) = pairs(p.coeffs) ## Not properly named!!! truncate? chop? skip? @@ -131,10 +133,11 @@ function trim_trailing_zeros(d::Dict) d end -function Base.chop(p::MutableSparsePolynomial; atol=nothing, rtol=nothing) +function chop!(p::MutableSparsePolynomial; atol=nothing, rtol=nothing) + isempty(p.coeffs) && return p δ = something(rtol,0) ϵ = something(atol,0) - τ = max(ϵ, _norm(x,2) * δ) + τ = max(ϵ, _norm(values(p.coeffs),2) * δ) for (i,pᵢ) ∈ pairs(p) abs(pᵢ) ≤ τ && delete!(p.coeffs, i) end @@ -178,7 +181,7 @@ function scalar_add(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} return P(Val(false), D) end -function scalar_mul(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} +function scalar_mult(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} R = promote_type(T,S) P = MutableSparsePolynomial{B,R,X} @@ -193,7 +196,7 @@ function scalar_mul(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} end -function scalar_mul(p::MutableSparsePolynomial{B,T,X}, c::S) where {B,T,X,S} +function scalar_mult(p::MutableSparsePolynomial{B,T,X}, c::S) where {B,T,X,S} R = promote_type(T,S) P = MutableSparsePolynomial{B,R,X} (iszero(p) || iszero(c)) && return(zero(P)) diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index 6780174a..b5235c77 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -150,16 +150,19 @@ function LaurentPolynomial{T,X}(p::OffsetCoeffs) where {T, X} LaurentPolynomial{T,X}(p.coeffs, p.m) end -function Base.convert(::Type{P}, q::StandardBasisPolynomial{S}) where {P <:LaurentPolynomial,S} +# function Base.convert(::Type{P}, q::StandardBasisPolynomial{S,X}) where {P <:LaurentPolynomial,S,X} - T = _eltype(P, q) - X = indeterminate(P, q) - ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) - end +# T = _eltype(P, q) +# X = indeterminate(P, q) +# ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) +# end -function Base.convert(::Type{P}, q::AbstractPolynomial{T,X}) where {T,X,P <:LaurentPolynomial} - convert(P, convert(Polynomial, q)) -end + +# #function Base.convert(::Type{P}, q::AbstractPolynomial{T,X}) where {T,X,P <:LaurentPolynomial{T,X}} +# function Base.convert(::Type{P}, q::AbstractPolynomial) where {P <:LaurentPolynomial} +# convert(P, convert(Polynomial, q)) +# end +Base.convert(::Type{LaurentPolynomial{T, X}}, p::LaurentPolynomial{T, X}) where {T, X} = p # Ambiguity, issue #435 function Base.convert(𝑷::Type{P}, p::ArnoldiFit{T, M, X}) where {P<:LaurentPolynomial, T, M, X} diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index d2ad9a13..1a76fb94 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -55,7 +55,7 @@ constantterm(p::StandardBasisPolynomial) = p[0] domain(::Type{<:StandardBasisPolynomial}) = Interval{Open,Open}(-Inf, Inf) mapdomain(::Type{<:StandardBasisPolynomial}, x::AbstractArray) = x -function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) +function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial{T′, X′}) where {T′, X′} if isa(q, P) return q else @@ -63,7 +63,7 @@ function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolyno throw(ArgumentError("a $P can not have a minimum exponent of $(minimumexponent(q))")) T = _eltype(P,q) X = indeterminate(P,q) - return ⟒(P){T,X}([q[i] for i in eachindex(q)]) + return ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) end end @@ -88,7 +88,7 @@ Base.:+(p::P, c::T) where {T, X, P<:StandardBasisPolynomial{T, X}} = p + ⟒(P)( ## 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)) + cs = ⊗(P, p.coeffs, q.coeffs) #coeffs(p), coeffs(q)) P(cs) end diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index a7a1dee9..86835155 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -25,53 +25,59 @@ function print_basis(io::IO, p::AbstractUnivariatePolynomial{<:StandardBasis, T, print_unicode_exponent(io, i) end -function Base.one(::Type{P}) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} - ⟒(P){B,T,X}([1]) +# XXX For now need 3 convert methods for standard basis +function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: AbstractUnivariatePolynomial{B}, Q<:AbstractUnivariatePolynomial{B}} + if isa(q, PP) + return q + else + minimumexponent(P) <= minimumexponent(q) || + throw(ArgumentError("a $P can not have a minimum exponent of $(minimumexponent(q))")) + T = _eltype(P,q) + X = indeterminate(P,q) + return ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) + end end - -function variable(P::Type{<:AbstractUnivariatePolynomial{B,T,X}}) where {B<:StandardBasis,T,X} - basis(P, 1) +function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B<:StandardBasis,T,X, Q<:AbstractUnivariatePolynomial{B,T,X}} + isa(q, PP) && return p + T′ = _eltype(P,q) + X′ = indeterminate(P,q) + if firstindex(q) >= 0 + cs = [q[i] for i ∈ 0:lastindex(q)] + o = 0 + else + cs = [q[i] for i ∈ eachindex(q)] + o = firstindex(q) + end + ⟒(P){T′,X′}(cs, o) end - -function basis(P::Type{<:AbstractUnivariatePolynomial{B, T, X}}, i::Int) where {B<:StandardBasis,T,X} - cs = ones(T,1) - P(cs, i) +function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: AbstractUnivariatePolynomial{B}, Q<:StandardBasisPolynomial} + isa(q, PP) && return p + T = _eltype(P,q) + X = indeterminate(P,q) + ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) end -constantterm(p::AbstractUnivariatePolynomial{B}) where {B <: StandardBasis} = p[0] +Base.one(p::P) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}([one(p[0])]) +Base.one(::Type{P}) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(ones(T,1)) +variable(P::Type{<:AbstractUnivariatePolynomial{B,T,X}}) where {B<:StandardBasis,T,X} = basis(P, 1) +basis(P::Type{<:AbstractUnivariatePolynomial{B, T, X}}, i::Int) where {B<:StandardBasis,T,X} = P(ones(T,1), i) -# # storage independent scalar add -# # faster alternatives for basic types -# function scalar_add(c::S, p::AbstractUnivariatePolynomial{B,T,X}) where {B<:StandardBasis, S, T, X} -# R = promote_type(T,S) -# P = ⟒(p){B,R,X} +domain(::Type{P}) where {B <: StandardBasis, P <: AbstractUnivariatePolynomial{B}} = Interval{Open,Open}(-Inf, Inf) -# iszero(p) && return P((c,), 0) -# iszero(c) && return convert(P, p) +mapdomain(::Type{P}, x::AbstractArray) where {B <: StandardBasis, P <: AbstractUnivariatePolynomial{B}} = x -# a,b = firstindex(p), lastindex(p) -# a′ = min(0,a) -# z = zero(last(first(p.coeffs)) + c) -# cs = _zeros(p, zero(z), length(a′:b)) +constantterm(p::AbstractUnivariatePolynomial{B}) where {B <: StandardBasis} = p[0] -# # i -> idx mapping -# o = offset(p)*(-a+1) # offset for dict is 0; o/w -a + 1 -# for (i, cᵢ) ∈ pairs(p) -# cs = _set(cs, i+o, R(cᵢ)) -# end -# cs = _set(cs, 0+o, cs[0+o] + R(c)) -# cs = trim_trailing_zeros(cs) -# P(Val(false), cs, a′) -# end +## Multiplication # special cases are faster function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, q::AbstractUnivariatePolynomial{B,S,X}) where {B <: StandardBasis, T,S,X} # simple convolution with order shifted R = promote_type(T,S) - P = ⟒(p){B,R,X} + P = ⟒(p){R,X} iszero(p) && return zero(P) iszero(q) && return zero(P) @@ -83,24 +89,34 @@ function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, P(cs, a) end -# maybe do same for, say, Laguerre? Though that may push everything to Dense -function differentiate(p::AbstractUnivariatePolynomial{B,T,X}) where {B<:StandardBasis,T,X} +# dense +function differentiate(p::P′) where {B<:StandardBasis,T,X, P′ <: AbstractUnivariatePolynomial{B,T,X}} + + N = lastindex(p) - firstindex(p) + 1 + R = promote_type(T, Int) + P = ⟒(p){R,X} + hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} + iszero(p) && return P(0*p[0]) + + ps = p.coeffs + cs = [i*pᵢ for (i,pᵢ) ∈ pairs(p)] + return P(cs, p.order-1) +end + +# sparse +function differentiate(p::P′) where {B<:StandardBasis,T,X, P′ <: MutableSparsePolynomial{B,T,X}} N = lastindex(p) - firstindex(p) + 1 R = promote_type(T, Int) - P = ⟒(p){B,T,X} + P = ⟒(p){R,X} + hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} iszero(p) && return zero(P) - z = zero(1 * p[1]) - cs = _zeros(p, z, N) - os = offset(p) - @inbounds for (i, cᵢ) ∈ pairs(p) + + d = Dict{Int,R}() + for (i, pᵢ) ∈ pairs(p) iszero(i) && continue - #cs[i - 1 + os] = i * cᵢ - cs = _set(cs, i - 1 + os, i * cᵢ) + d[i-1] = i*pᵢ end - - o = firstindex(p) - o = o < 0 ? o - 1 : max(0, o - 1) - ⟒(p){B,T,X}(cs, o) + return P(d) end @@ -109,11 +125,12 @@ function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardB # no offset! XXX iszero(p) && return p/1 - N = lastindex(p) - firstindex(p) + 1 R = typeof(one(T)/1) z = zero(R) - P = ⟒(p){B,R,X} + P = ⟒(p){R,X} + hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} + cs = _zeros(p, z, N+1) os = offset(p) @inbounds for (i, cᵢ) ∈ pairs(p) @@ -123,3 +140,114 @@ function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardB end P(cs, firstindex(p)) end + +function Base.divrem(num::P, den::Q) where {B<:StandardBasis, + T, P <: AbstractUnivariatePolynomial{B,T}, + S, Q <: AbstractUnivariatePolynomial{B,S}} + + assert_same_variable(num, den) + @assert ⟒(P) == ⟒(Q) + + X = indeterminate(num) + R = Base.promote_op(/, T, S) + PP = ⟒(P){R,X} + + + n = degree(num) + m = degree(den) + + m == -1 && throw(DivideError()) + if m == 0 && den[0] ≈ 0 throw(DivideError()) end + + R = eltype(one(T)/one(S)) + + deg = n - m + 1 + + if deg ≤ 0 + return zero(P), num + end + + q_coeff = zeros(R, deg) + 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 + @inbounds for j in 0:m + elem = den[j] * q + r_coeff[i - m + j + 1] -= elem + end + end + resize!(r_coeff, min(length(r_coeff), m)) + return PP(q_coeff), PP(r_coeff) + +end + +## XXX copy or pass along to other system for now +function vander(p::Type{<:P}, x::AbstractVector{T}, degs) where {B<:StandardBasis, P<:AbstractUnivariatePolynomial{B}, T <: Number} + vander(StandardBasisPolynomial, x, degs) +end + +function LinearAlgebra.cond(p::P, x) where {B<:StandardBasis, P<:AbstractUnivariatePolynomial{B}} + p̃ = map(abs, p) + p̃(abs(x))/ abs(p(x)) +end + +function ngcd(p::P, q::Q, + args...; + kwargs...) where {B <: StandardBasis, + T,X,P<:AbstractUnivariatePolynomial{B,T,X}, + S,Y,Q<:AbstractUnivariatePolynomial{B,S,Y}} + ngcd(PnPolynomial(p.coeffs), PnPolynomial(q.coeffs), args...; kwargs...) +end + +# XXX p.coeffs isn't right +function Multroot.multroot(p::AbstractUnivariatePolynomial{B}, args...; + kwargs...) where {B<:StandardBasis} + cs = coeffs(p) + if firstindex(p) > 0 + cs = vcat(zeros(firstindex(p)), cs) + elseif firstindex(p) < 0 + @warn "Laurent Polynomial; finding values after factoring out leading term" + end + Multroot.multroot(Polynomial(cs), args...; kwargs...) +end + +Polynomials.Multroot.pejorative_root(q::AbstractUnivariatePolynomial{<:StandardBasis}, zs::Vector{S}, ls; kwargs...) where {S} = + Polynomials.Multroot.pejorative_root(convert(Polynomial, q), zs, ls; kwargs...) + +Polynomials.Multroot.stats(q::AbstractUnivariatePolynomial{<:StandardBasis}, zs::Vector{S}, ls; kwargs...) where {S} = + Polynomials.Multroot.stats(convert(Polynomial, q), zs, ls; kwargs...) + +function fit(::Type{P}, + x::AbstractVector{T}, + y::AbstractVector{T}, + deg::Int; + kwargs...) where {T, P<:AbstractUnivariatePolynomial{<:StandardBasis}} + convert(P, fit(Polynomial, x, y, deg; kwargs...)) +end + +# for one ambigous test! +function fit(::Type{P}, + x::AbstractVector{T}, + y::AbstractVector{T}, + deg::AbstractVector; + kwargs...) where {T, P<:AbstractUnivariatePolynomial{<:StandardBasis}} + convert(P, fit(Polynomial, x, y, deg; kwargs...)) +end + +function fit(::Type{P}, + x::AbstractVector{T}, + y::AbstractVector{T}, + deg::AbstractVector, + cs::Dict; + kwargs...) where {T, P<:AbstractUnivariatePolynomial{<:StandardBasis}} + convert(P, fit(Polynomial, x, y, deg, cs; kwargs...)) +end + +# new constructors taking order in second position +SparsePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T, X, S} = SparsePolynomial{T,X}(coeffs) +Polynomial{T, X}(coeffs::AbstractVector{S},order::Int) where {T, X, S} = Polynomial{T,X}(coeffs) +ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T,X,S} = ImmutablePolynomial{T,X}(coeffs) +FactoredPolynomial{T,X}(coeffs::AbstractVector{S}, order::Int) where {T,S,X} = FactoredPolynomial{T,X}(coeffs) +PnPolynomial{T, X}(coeffs::AbstractVector, order::Int) where {T, X} = PnPolynomial(coeffs) # for generic programming diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 632587c3..d8a8c79d 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -5,17 +5,11 @@ #LaurentPolynomial = MutableDensePolynomial{StandardBasis} #export LaurentPolynomial -constantterm(p:: MutableDensePolynomial{StandardBasis}) = p[0] - function evalpoly(c, p::MutableDensePolynomial{StandardBasis,T,X}) where {T,X} iszero(p) && return zero(T)*zero(c) EvalPoly.evalpoly(c, p.coeffs) * c^p.order end -function _evalpoly(p:: MutableDensePolynomial{StandardBasis,T,X}, c) where {T,X} - iszero(p) && return zero(T)*zero(c) - Base.evalpoly(c, p.coeffs) * c^p.order -end # function isconstant(p:: MutableDensePolynomial{StandardBasis}) # firstindex(p) != 0 && return false @@ -70,7 +64,7 @@ function ⊗(p:: MutableDensePolynomial{StandardBasis,T,X}, end end if iszero(last(cs)) - cs = trime_trailing_zeros(cs) + cs = trim_trailing_zeros(cs) end P(Val(false), cs, a) end diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl index c1acdaa1..6e9112c3 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/standard-immutable.jl @@ -1,5 +1,3 @@ -#const ImmutablePolynomial = ImmutableDensePolynomial{StandardBasis} -#export ImmutablePolynomial function evalpoly(x, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} N == 0 && return zero(T) * zero(x) @@ -27,7 +25,7 @@ end end - +# faster function scalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B<:StandardBasis,T,X,S,N} R = promote_type(T,S) P = ImmutableDensePolynomial{B,R,X} @@ -36,7 +34,7 @@ function scalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B<:Standa N == 1 && return P{N}((p[0]+c,)) #cs = tuple_sum(convert(NTuple{N,R}, p.coeffs), NTuple{1,R}(c)) - cs = _tuple_combine(+, convert(NTuple{N,R}, p.coeffs), NTuple{1,R}(c)) + cs = _tuple_combine(+, convert(NTuple{N,R}, p.coeffs), NTuple{1,R}((c,))) q = P{N}(cs) return q @@ -51,17 +49,12 @@ function scalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B<:Standa end -function XXscalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S,N} - R = promote_type(T,S) - P = ImmutableDensePolynomial{B,R,X} - iszero(N) && return P{1}((c,)) - - xs = convert(NTuple{N,R}, p.coeffs) - @set! xs[1] = xs[1] + c - P{N}(xs) -end - # return N*M +# intercept promotion call +function Base.:*(p::ImmutableDensePolynomial{StandardBasis,T,X,N}, + q::ImmutableDensePolynomial{StandardBasis,S,X,M}) where {T,S,X,N,M} + ⊗(p,q) +end function ⊗(p::ImmutableDensePolynomial{StandardBasis,T,X,N}, q::ImmutableDensePolynomial{StandardBasis,S,X,M}) where {T,S,X,N,M} @@ -101,7 +94,8 @@ end function differentiate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X,N} - N == 0 && return 1p + N == 0 && return p + hasnan(p) && return ⟒(p)(zero(T)/zero(T),X) # NaN{T} cs = ntuple(i -> i*p.coeffs[i+1], Val(N-1)) R = eltype(cs) ImmutableDensePolynomial{StandardBasis,R,X,N-1}(cs) @@ -109,7 +103,10 @@ end function integrate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X,N} - cs = ntuple(i -> i > 1 ? p.coeffs[i-1]/(i-1) : zero(T)/1, Val(N+1)) + N == 0 && return p # different type + hasnan(p) && return ⟒(p)(zero(T)/zero(T), X) # NaN{T} + z = zero(first(p.coeffs)) + cs = ntuple(i -> i > 1 ? p.coeffs[i-1]/(i-1) : z/1, Val(N+1)) R = eltype(cs) ImmutableDensePolynomial{StandardBasis,R,X,N+1}(cs) end diff --git a/src/standard-basis/standard-sparse.jl b/src/standard-basis/standard-sparse.jl index 049a91c4..60a266d3 100644 --- a/src/standard-basis/standard-sparse.jl +++ b/src/standard-basis/standard-sparse.jl @@ -17,19 +17,21 @@ end function ⊗(p::MutableSparsePolynomial{StandardBasis,T,X}, q::MutableSparsePolynomial{StandardBasis,S,X}) where {T,S,X} - # simple convolution + + # simple convolution same as ⊗(Polynomial, p.coeffs, q.coeffs) (DRY) R = promote_type(T,S) P = MutableSparsePolynomial{StandardBasis,R,X} z = zero(R) cs = Dict{Int, R}() - @inbounds for (i, pᵢ) ∈ pairs(p) + for (i, pᵢ) ∈ pairs(p) for (j, qⱼ) ∈ pairs(q) cᵢⱼ = get(cs, i+j, z) - cs[i+j] = muladd(pᵢ, qⱼ, cᵢⱼ) + val = muladd(pᵢ, qⱼ, cᵢⱼ) + iszero(val) && continue + @inbounds cs[i+j] = val end end - - P(cs) + P(Val(false), cs) end diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index eae101fa..e726f7ad 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -1,6 +1,8 @@ using LinearAlgebra using OffsetArrays, StaticArrays import Polynomials: indeterminate +import Polynomials: ImmutableDensePolynomial, StandardBasis,MutableSparsePolynomial, MutableDensePolynomial + ## Test standard basis polynomials with (nearly) the same tests # compare upto trailing zeros @@ -23,11 +25,13 @@ upto_z(as, bs) = upto_tz(filter(!iszero,as), filter(!iszero,bs)) ==ᵟ(a,b) = (a == b) ==ᵟ(a::FactoredPolynomial, b::FactoredPolynomial) = a ≈ b -_isimmutable(::Type{P}) where {P <: Union{ImmutablePolynomial, FactoredPolynomial}} = true +_isimmutable(::Type{P}) where {P <: Union{ImmutablePolynomial, FactoredPolynomial, ImmutableDensePolynomial{StandardBasis}}} = true _isimmutable(P) = false -Ps = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, FactoredPolynomial) +Ps = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, FactoredPolynomial, + MutableDensePolynomial{StandardBasis},ImmutableDensePolynomial{StandardBasis}, MutableSparsePolynomial{StandardBasis} + ) @testset "Construction" begin @testset for coeff in Any[ @@ -371,7 +375,7 @@ end @testset for P in Ps # LaurentPolynomial accepts OffsetArrays; others throw warning - if P == LaurentPolynomial + if P ∈ (LaurentPolynomial, MutableDensePolynomial{StandardBasis}) @test LaurentPolynomial(as) == LaurentPolynomial(bs, 3) else @test P(as) == P(bs) @@ -409,7 +413,7 @@ end pR = P([3 // 4, -2 // 1, 1 // 1]) # type stability of the default constructor without variable name - if !(P ∈ (LaurentPolynomial, ImmutablePolynomial, FactoredPolynomial)) + if !(P ∈ (LaurentPolynomial, ImmutablePolynomial, FactoredPolynomial, ImmutableDensePolynomial{StandardBasis})) @inferred P([1, 2, 3]) @inferred P([1,2,3], Polynomials.Var(:x)) end @@ -493,7 +497,7 @@ end # issue #395 @testset for P ∈ Ps - P ∈ (FactoredPolynomial, ImmutablePolynomial) && continue + P ∈ (FactoredPolynomial, ImmutablePolynomial, ImmutableDensePolynomial{StandardBasis} ) && continue p = P([2,1], :s) @inferred -p # issue #395 @inferred 2p @@ -516,7 +520,7 @@ end # evaluation at special cases different number types @testset for P ∈ Ps - P ∈ (SparsePolynomial, FactoredPolynomial) && continue + P ∈ (SparsePolynomial, FactoredPolynomial, MutableSparsePolynomial{StandardBasis}) && continue # vector coefficients v₀, v₁ = [1,1,1], [1,2,3] p₁ = P([v₀]) @@ -541,7 +545,7 @@ end # p - p requires a zero @testset for P ∈ Ps P ∈ (LaurentPolynomial, SparsePolynomial, - FactoredPolynomial) && continue + FactoredPolynomial, MutableSparsePolynomial{StandardBasis}) && continue for v ∈ ([1,2,3], [[1,2,3],[1,2,3]], [[1 2;3 4], [3 4; 5 6]] @@ -559,7 +563,7 @@ end @testset "Divrem" begin @testset for P in Ps - + P == FactoredPolynomial && continue p0 = P([0]) p1 = P([1]) p2 = P([5, 6, -3, 2 ,4]) @@ -618,7 +622,7 @@ end # Check for isequal p1 = P([1.0, -0.0, 5.0, Inf]) p2 = P([1.0, 0.0, 5.0, Inf]) - !(P ∈ (FactoredPolynomial, SparsePolynomial)) && (@test p1 == p2 && !isequal(p1, p2)) # SparsePolynomial doesn't store -0.0, 0.0. + !(P ∈ (FactoredPolynomial, SparsePolynomial, MutableSparsePolynomial{StandardBasis})) && (@test p1 == p2 && !isequal(p1, p2)) # SparsePolynomial doesn't store -0.0, 0.0. p3 = P([0, NaN]) @test p3 === p3 && p3 ≠ p3 && isequal(p3, p3) @@ -831,7 +835,7 @@ end f(x) = (x - 1)^20 p = f(x) e₁ = abs( (f(4/3) - p(4/3))/ p(4/3) ) - e₂ = abs( (f(4/3) - Polynomials.compensated_horner(p, 4/3))/ p(4/3) ) + e₂ = abs( (f(4/3) - Polynomials.compensated_horner(coeffs(p), 4/3))/ p(4/3) ) λ = cond(p, 4/3) u = eps()/2 @test λ > 1/u @@ -845,7 +849,7 @@ end X = :x @testset for P in Ps - if !(P == ImmutablePolynomial) + if !(P ∈ (ImmutablePolynomial, ImmutableDensePolynomial{StandardBasis})) p = P([0,one(Float64)]) @test P{Complex{Float64},X} == typeof(p + 1im) @test P{Complex{Float64},X} == typeof(1im - p) @@ -1056,7 +1060,7 @@ end p = P(rand(1:5, 6)) @test degree(truncate(p - integrate(derivative(p)), atol=1e-13)) <= 0 @test degree(truncate(p - derivative(integrate(p)), atol=1e-13)) <= 0 - end + end # Handling of `NaN`s p = P([NaN, 1, 5]) @@ -1094,8 +1098,8 @@ end @test q isa Vector{typeof(p1)} @test p isa Vector{typeof(p2)} else - @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 + @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 @@ -1151,14 +1155,14 @@ end @test !issymmetric(A) U = A * A' @test U[1,2] ≈ U[2,1] # issymmetric with some allowed error for FactoredPolynomial - diagm(0 => [1, p^3], 1=>[p^2], -1=>[p]) + P != Polynomials.ImmutableDensePolynomial{Polynomials.StandardBasis} && diagm(0 => [1, p^3], 1=>[p^2], -1=>[p]) end # issue 206 with mixed variable types and promotion @testset for P in Ps λ = P([0,1],:λ) A = [1 λ; λ^2 λ^3] - @test A == diagm(0 => [1, λ^3], 1=>[λ], -1=>[λ^2]) + P != Polynomials.ImmutableDensePolynomial{Polynomials.StandardBasis} && @test A == diagm(0 => [1, λ^3], 1=>[λ], -1=>[λ^2]) # XXX diagm + ImmutableDensePolynomial{StandardBasis} isn't working @test all([1 -λ]*[λ^2 λ; λ 1] .== 0) @test [λ 1] + [1 λ] == (λ+1) .* [1 1] # (λ+1) not a number, so we broadcast end @@ -1624,7 +1628,7 @@ end T1,T2 = Ts[i],Ts[i+1] @testset for P in Ps P <: FactoredPolynomial && continue - if P != ImmutablePolynomial + if !(P ∈ (ImmutablePolynomial, ImmutableDensePolynomial{StandardBasis})) p = P{T2}(T1.(rand(1:3,3))) @test typeof(p) == P{T2, :x} else @@ -1640,7 +1644,7 @@ end # test P{T}(...) is P{T} (not always the case for FactoredPolynomial) @testset for P in Ps P <: FactoredPolynomial && continue - if P != ImmutablePolynomial + if !(P ∈ (ImmutablePolynomial, ImmutableDensePolynomial{StandardBasis})) @testset for T in (Int32, Int64, BigInt) p₁ = P{T}(Float64.(rand(1:3,5))) @test typeof(p₁) == P{T,:x} # conversion works From 12ae7dafe327a3a0e3e8797e170439ee7a0ee1e8 Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 19 Jul 2023 21:45:40 -0400 Subject: [PATCH 04/31] WIP: tests passing, cleaned up --- src/abstract-polynomial.jl | 95 +++++++++++-------- src/basis-utils.jl | 26 +---- src/common.jl | 10 +- .../immutable-dense-polynomial.jl | 90 ++++++++---------- .../mutable-dense-polynomial.jl | 90 +++++++----------- .../mutable-sparse-polynomial.jl | 61 ++++++------ src/polynomials/LaurentPolynomial.jl | 24 +++-- src/polynomials/standard-basis.jl | 3 +- src/standard-basis/standard-basis.jl | 76 ++------------- src/standard-basis/standard-dense.jl | 65 +++++-------- src/standard-basis/standard-immutable.jl | 13 ++- src/standard-basis/standard-sparse.jl | 22 ++++- test/StandardBasis.jl | 4 +- 13 files changed, 242 insertions(+), 337 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 80e57f0b..9f3946ff 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -1,9 +1,30 @@ +# XXX todo, merge in with common.jl """ Abstract type for polynomials with an explicit basis. """ abstract type AbstractUnivariatePolynomial{B, T, X} <: AbstractPolynomial{T,X} end abstract type AbstractBasis end +function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where {B, T, P<:AbstractUnivariatePolynomial{B,T}} + + if _iszero(pj) return false end + + pj = printsign(io, pj, first, mimetype) + + if hasone(T) + if !(_isone(pj) && !(showone(T) || j == 0)) + printcoefficient(io, pj, j, mimetype) + end + else + printcoefficient(io, pj, j, mimetype) + end + + printproductsign(io, pj, j, mimetype) + printexponent(io, var, j, mimetype) + return true +end + + ## idea is vector space stuff (scalar_add, scalar_mult, vector +/-, ^) goes here ## connection (convert, transform) is specific to a basis (storage) ## ⊗(p::P{T,X}, q::P{S,Y}) is specic to basis/storage @@ -76,16 +97,17 @@ end # The zero polynomial. Typically has no coefficients #Base.zero(p::P,args...) where {P <: AbstractUnivariatePolynomial} = zero(P,args...) -Base.zero(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),indeterminate(P)}) -Base.zero(::Type{P},var::SymbolLike) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),Symbol(var)}) +#Base.zero(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),indeterminate(P)}) +#Base.zero(::Type{P},var::SymbolLike) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),Symbol(var)}) # the polynomial 1 -#Base.one(p::P,args...) where {P <: AbstractUnivariatePolynomial} = one(P,args...) +# one(P) is basis dependent +Base.one(p::P,args...) where {P <: AbstractUnivariatePolynomial} = one(P,args...) Base.one(::Type{P}) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){eltype(P),indeterminate(P)}) Base.one(::Type{P}, var::SymbolLike) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){eltype(P),Symbol(var)}) # the variable x -#variable(p::P) where {P <: AbstractUnivariatePolynomial} = variable(P) +variable(p::P) where {P <: AbstractUnivariatePolynomial} = variable(P) variable(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = variable(⟒(P){eltype(P),indeterminate(P)}) variable(::Type{P}, var::SymbolLike) where {B,P<:AbstractUnivariatePolynomial{B}} = variable(⟒(P){eltype(P),Var(var)}) @@ -133,8 +155,6 @@ Base.convert(::Type{ConstantTerm{T}}, p::AbstractUnivariatePolynomial) where {T} #= Comparisons =# # need to promote Number -> Poly -# Base.isapprox(p1::AbstractUnivariatePolynomial, p2::Number; kwargs...) = isapprox(promote(p1, p2)...; kwargs...) -# Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(promote(p1, p2)...; kwargs...) function Base.isapprox(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial; kwargs...) isapprox(promote(p1, p2)...; kwargs...) end @@ -167,36 +187,34 @@ function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, end end -function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, p2::Number; kwargs...) where {B,T,X} - q = p2 * one(⟒(p1){T,X}) - isapprox(p1, q; kwargs...) -end -Base.isapprox(p1::Number, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(p2, p1; kwargs...) - -Base.isequal(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} = hash(p1) == hash(p2) -function Base.:(==)(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} - iszero(p1) && iszero(p2) && return true - lastindex(p1) == lastindex(p2) || return false - # coeffs(p1) == coeffs(p2), but non-allocating - for i ∈ union(keys(p1), keys(p2)) - p1[i] == p2[i] || return false -# for ((i,pᵢ), (j, pⱼ)) ∈ zip(pairs(p1), pairs(p2)) -# i == j && pᵢ == pⱼ || return false - end - return true -end -function Base.:(==)(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial) - if isconstant(p1) - isconstant(p2) && return constantterm(p1) == constantterm(p2) - return false - elseif isconstant(p2) - return false # p1 is not constant - end - check_same_variable(p1, p2) || return false - ==(promote(p1,p2)...) -end -Base.:(==)(p::AbstractUnivariatePolynomial, n::Number) = degree(p) <= 0 && constantterm(p) == n -Base.:(==)(n::Number, p::AbstractUnivariatePolynomial) = p == n +# function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, p2::Scalar; kwargs...) where {B,T,X} +# q = p2 * one(⟒(p1){T,X}) +# isapprox(p1, q; kwargs...) +# end +# Base.isapprox(p1::Scalar, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(p2, p1; kwargs...) + +#Base.isequal(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} = hash(p1) == hash(p2) +# function Base.:(==)(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} +# iszero(p1) && iszero(p2) && return true +# lastindex(p1) == lastindex(p2) || return false +# # coeffs(p1) == coeffs(p2), but non-allocating +# for i ∈ union(keys(p1), keys(p2)) +# p1[i] == p2[i] || return false +# end +# return true +# end +# function Base.:(==)(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial) +# if isconstant(p1) +# isconstant(p2) && return constantterm(p1) == constantterm(p2) +# return false +# elseif isconstant(p2) +# return false # p1 is not constant +# end +# check_same_variable(p1, p2) || return false +# ==(promote(p1,p2)...) +# end +#Base.:(==)(p::AbstractUnivariatePolynomial, n::Scalar) = isconstant(p) && constantterm(p) == n +#Base.:(==)(n::Scalar, p::AbstractUnivariatePolynomial) = p == n ## --- arithmetic operations --- ## implement @@ -286,10 +304,7 @@ function integrate(p::AbstractUnivariatePolynomial, c) scalar_add(integrate(p), c) end - - - - +# promote, promote_rule, handle constants macro poly_register(name) poly = esc(name) quote diff --git a/src/basis-utils.jl b/src/basis-utils.jl index 1c29a721..33557717 100644 --- a/src/basis-utils.jl +++ b/src/basis-utils.jl @@ -1,7 +1,6 @@ -_norm(x,p=2) = real(sqrt(sum(xᵢ^2 for xᵢ ∈ x))) +## XXX move to contrib.jl gtτ(x, τ) = abs(x) > τ - # return index or nothing of last non "zdero" # chop! then considers cases i==nothing, i=length(x), i < length(x) function chop_right_index(x; rtol=nothing, atol=nothing) @@ -9,38 +8,19 @@ function chop_right_index(x; rtol=nothing, atol=nothing) isempty(x) && return nothing δ = something(rtol,0) ϵ = something(atol,0) - τ = max(ϵ, _norm(x,2) * δ) + τ = max(ϵ, norm(x,2) * δ) i = findlast(Base.Fix2(gtτ, τ), x) i end - - function chop_left_index(x; rtol=nothing, atol=nothing) isempty(x) && return nothing δ = something(rtol,0) ϵ = something(atol,0) - τ = max(ϵ, _norm(x,2) * δ) + τ = max(ϵ, norm(x,2) * δ) i = findfirst(Base.Fix2(gtτ,τ), x) i end -## put here, not with type definition, in case reuse is possible -## `conv` can be used with matrix entries, unlike `fastconv` -function XXXconv(p::Vector{T}, q::Vector{S}) where {T,S} - (isempty(p) || isempty(q)) && return promote_type(T, S)[] - as = [p[1]*q[1]] - z = zero(as[1]) - n,m = length(p)-1, length(q)-1 - for i ∈ 1:n+m - Σ = z - for j ∈ max(0, i-m):min(i,n) - Σ = muladd(p[1+j], q[1 + i-j], Σ) - end - push!(as, Σ) - end - as -end - XXX() = throw(ArgumentError("Method not defined")) diff --git a/src/common.jl b/src/common.jl index 5990bbf3..90eca83d 100644 --- a/src/common.jl +++ b/src/common.jl @@ -558,7 +558,7 @@ copy_with_eltype(::Type{T}, p::P) where {T, S, Y, P <:AbstractPolynomial{S,Y}} = #copy_with_eltype(::Type{T}, p::P) where {T, S, X, P<:AbstractPolynomial{S,X}} = # copy_with_eltype(Val(T), Val(X), p) -Base.iszero(p::AbstractPolynomial) = all(iszero, p) +Base.iszero(p::AbstractPolynomial) = all(iszero, values(p)) # See discussions in https://github.com/JuliaMath/Polynomials.jl/issues/258 @@ -1163,7 +1163,7 @@ function Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial) check_same_variable(p1, p2) || return false ==(promote(p1,p2)...) end -Base.:(==)(p::AbstractPolynomial, n::Scalar) = degree(p) <= 0 && constantterm(p) == n +Base.:(==)(p::AbstractPolynomial, n::Scalar) = isconstant(p) && constantterm(p) == n Base.:(==)(n::Scalar, p::AbstractPolynomial) = p == n function Base.isapprox(p1::AbstractPolynomial, p2::AbstractPolynomial; kwargs...) @@ -1178,9 +1178,9 @@ function Base.isapprox(p1::AbstractPolynomial, p2::AbstractPolynomial; kwargs... end function Base.isapprox(p1::AbstractPolynomial{T,X}, - p2::AbstractPolynomial{T,X}; - rtol::Real = (Base.rtoldefault(T,T,0)), - atol::Real = 0,) where {T,X} + p2::AbstractPolynomial{S,X}; + rtol::Real = (Base.rtoldefault(T,S,0)), + atol::Real = 0,) where {T,S,X} (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons # copy over from abstractarray.jl Δ = norm(p1-p2) diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index 88fba21d..8ae1d6ee 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -1,5 +1,5 @@ -# Keep order=0 -# Try to keep length based on N,M so no chopping by default +# Try to keep length based on N,M so no removal of trailing zeros by default +# order is ignored, firstindex is always 0 struct ImmutableDensePolynomial{B,T,X,N} <: AbstractUnivariatePolynomial{B,T,X} coeffs::NTuple{N,T} function ImmutableDensePolynomial{B,T,X,N}(cs::NTuple{N,T}) where {B,N,T,X} @@ -10,26 +10,28 @@ struct ImmutableDensePolynomial{B,T,X,N} <: AbstractUnivariatePolynomial{B,T,X} end end -ImmutableDensePolynomial{B,T,X,N}(::Type{Val{false}}, cs::NTuple{N,T}) where {B,N, T,X} = +ImmutableDensePolynomial{B,T,X,N}(::Type{Val{false}}, cs::NTuple{N,T}) where {B,N,T,X} = ImmutableDensePolynomial{B,T,X}(cs) ImmutableDensePolynomial{B,T,X,N}(::Type{Val{true}}, cs::NTuple{N,T}) where {B,N, T,X} = ImmutableDensePolynomial{B,T,X,N}(cs) -# tuple +# tuple with mis-matched size +function ImmutableDensePolynomial{B,T,X,N}(xs::NTuple{M,S}) where {B,T,S,X,N,M} + convert(ImmutableDensePolynomial{B,T,X,N}, ImmutableDensePolynomial{B,T,X,M}(xs)) +end + function ImmutableDensePolynomial{B,T,X}(xs::NTuple{N,S}) where {B,T,S,X,N} cs = convert(NTuple{N,T}, xs) - cs = trim_trailing_zeros(cs) - N′ = length(cs) - ImmutableDensePolynomial{B,T,X,N′}(cs) + ImmutableDensePolynomial{B,T,X,N}(cs) end function ImmutableDensePolynomial{B,T}(xs::NTuple{N,S}, var::SymbolLike=Var(:x)) where {B,T,S,N} - ImmutableDensePolynomial{B,T,Var(var)}(xs) + ImmutableDensePolynomial{B,T,Var(var),N}(xs) end function ImmutableDensePolynomial{B}(xs::NTuple{N,T}, var::SymbolLike=Var(:x)) where {B,T,N} - ImmutableDensePolynomial{B,T,Var(var)}(xs) + ImmutableDensePolynomial{B,T,Var(var),N}(xs) end # abstract vector @@ -53,7 +55,7 @@ function ImmutableDensePolynomial{B,T}(xs::AbstractVector{S}, var::SymbolLike) w end function ImmutableDensePolynomial{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} - ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) + ImmutableDensePolynomial{B,T,Symbol(var)}(xs) end function ImmutableDensePolynomial{B}(xs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {B,T} @@ -93,6 +95,9 @@ end @poly_register ImmutableDensePolynomial constructorof(::Type{<:ImmutableDensePolynomial{B}}) where {B} = ImmutableDensePolynomial{B} +## ---- + +# need to promote to larger Base.promote_rule(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, ::Type{<:ImmutableDensePolynomial{B,S,X,M}}) where {B,T,S,X,N,M} = ImmutableDensePolynomial{B,promote_type(T,S), X, max(N,M)} Base.promote_rule(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, ::Type{<:S}) where {B,T,S<:Number,X,N} = @@ -110,7 +115,7 @@ end Base.copy(p::ImmutableDensePolynomial) = p -Base.similar(p::ImmutableDensePolynomial, args...) = similar(p.coeffs, args...) +Base.similar(p::ImmutableDensePolynomial, args...) = p.coeffs ## chop function Base.chop(p::ImmutableDensePolynomial{B,T,X,N}; @@ -127,7 +132,7 @@ function Base.chop(p::ImmutableDensePolynomial{B,T,X,N}; ImmutableDensePolynomial{B,T,X,N′}(xs) end - +# misnamed! chop!(p::ImmutableDensePolynomial; kwargs...) = chop(p; kwargs...) function truncate!(p::ImmutableDensePolynomial{B,T,X,N}; @@ -147,9 +152,6 @@ function truncate!(p::ImmutableDensePolynomial{B,T,X,N}; end - - - # not type stable, as N is value dependent function trim_trailing_zeros(cs::Tuple) isempty(cs) && return cs @@ -172,39 +174,31 @@ function normΔ(q1::ImmutableDensePolynomial{B}, q2::ImmutableDensePolynomial{B} return tot^(1/p) end -function Base.isapprox(p1::ImmutableDensePolynomial{B,T,X}, p2::ImmutableDensePolynomial{B,T′,X}; - atol=nothing, rtol = nothing - ) where {B,T,T′,X} - - (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons - R = real(float(promote_type(T,T′))) - atol = something(atol, zero(R)) - rtol = something(rtol, Base.rtoldefault(R)) - # copy over from abstractarray.jl - Δ = normΔ(p1,p2) - if isfinite(Δ) - return Δ <= max(atol, rtol * max(norm(p1), norm(p2))) - else - for i in 0:max(degree(p1), degree(p2)) - isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false - end - return true - end -end +# function Base.isapprox(p1::ImmutableDensePolynomial{B,T,X}, p2::ImmutableDensePolynomial{B,T′,X}; +# atol=nothing, rtol = nothing +# ) where {B,T,T′,X} + +# (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons +# R = real(float(promote_type(T,T′))) +# atol = something(atol, zero(R)) +# rtol = something(rtol, Base.rtoldefault(R)) +# # copy over from abstractarray.jl +# Δ = normΔ(p1,p2) +# if isfinite(Δ) +# return Δ <= max(atol, rtol * max(norm(p1), norm(p2))) +# else +# for i in 0:max(degree(p1), degree(p2)) +# isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false +# end +# return true +# end +# end ## --- _zeros(::Type{<:ImmutableDensePolynomial}, z::S, N) where {S} = ntuple(_ -> zero(S), Val(N)) -Base.iszero(p::ImmutableDensePolynomial) = all(iszero,p.coeffs) -Base.zero(::Type{<:ImmutableDensePolynomial{B,T,X}}) where {B,T,X} = - ImmutableDensePolynomial{B,T,X,0}(()) - -function isconstant(p::ImmutableDensePolynomial) - i = findlast(!iszero, p.coeffs) - return isnothing(i) || i ≤ 1 -end Base.firstindex(p::ImmutableDensePolynomial) = 0 Base.lastindex(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = N - 1 @@ -219,6 +213,7 @@ function Base.getindex(p::ImmutableDensePolynomial{B,T,X,N}, i::Int) where {B,T, (i < firstindex(p) || i > lastindex(p)) && return zero(p.coeffs[1]) p.coeffs[i + offset(p)] end + Base.setindex!(p::ImmutableDensePolynomial, value, i::Int) = throw(ArgumentError("ImmutableDensePolynomial has no setindex! method")) @@ -246,7 +241,11 @@ function degree(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} isnothing(i) && return -1 return i - 1 end + # zero, one +Base.zero(::Type{<:ImmutableDensePolynomial{B,T,X}}) where {B,T,X} = + ImmutableDensePolynomial{B,T,X,0}(()) + Base.zero(::Type{ImmutableDensePolynomial{B,T,X,N}}) where {B,T,X,N} = ImmutableDensePolynomial{B,T,X,0}(()) @@ -317,13 +316,6 @@ function scalar_mult(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S, cs = p.coeffs .* (c,) R = eltype(cs) return ImmutableDensePolynomial{B,R,X,N}(cs) - - # R = promote_type(T,S) - # P = ImmutableDensePolynomial{B,R,X} - # (iszero(N) || iszero(c)) && return nothing#zero(p) - # #iszero(c) && return zero(p) #convert(P, p) - # cs = p.coeffs .* (c,) - # return ImmutableDensePolynomial{B,R,X,N}(cs) end function scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,S,N} diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index 51efd30c..7d0b1293 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -1,6 +1,6 @@ # * has order # * leading 0s are trimmed -# * pass Val(true) to bypass trimmings +# * pass checked::Val(false) to bypass trimmings struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} coeffs::Vector{T} order::Int # lowest degree, typically 0 @@ -27,42 +27,24 @@ struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} end new{B,T,Symbol(X)}(cs, order) end + function MutableDensePolynomial{B,T,X}(checked::Val{true}, cs::Vector{T}, order::Int=0) where {B,T,X} + MutableDensePolynomial{B,T,X}(cs, order) + end end - -MutableDensePolynomial{B,T,X}(checked::Val{true}, cs::Vector{T}, order::Int=0) where {B,T,X} = MutableDensePolynomial{B,T,X}(cs, order) - function MutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int=0, var::SymbolLike=Var(:x)) where {T, S, B} - if Base.has_offset_axes(xs) - @warn "Using the axis offset of the coefficient vector" - xs, order = xs.parent, first(xs.offsets) - end - - cs = convert(Vector{T}, xs) - cs = trim_trailing_zeros(cs) - MutableDensePolynomial{B,T,Symbol(var)}(Val(true),cs, order) + MutableDensePolynomial{B,T,Symbol(var)}(xs, order) end function MutableDensePolynomial{B}(xs::AbstractVector{T}, order::Int=0, var::SymbolLike=Var(:x)) where {B, T} - MutableDensePolynomial{B,T}(xs, order, var) + MutableDensePolynomial{B,T,Symbol(var)}(xs, order) end -# function MutableDensePolynomial{B,X}(xs::AbstractVector{T}, order::Int) where {T, B,X} -# MutableDensePolynomial{B,T,X}(xs,order) -# end - -# function MutableDensePolynomial{B,X}(xs, order::Int) where {B,X} -# cs = collect(xs) -# T = eltype(cs) -# MutableDensePolynomial{B,T,X}(cs, order) -# end -function MutableDensePolynomial{B,T}(xs, var::SymbolLike) where {B,T} - cs = convert(Vector{T}, xs) - MutableDensePolynomial{B,T,Symbol(var)}(cs, 0) +function MutableDensePolynomial{B,T}(xs, var::SymbolLike=Var(:x)) where {B,T} + MutableDensePolynomial{B,T,Symbol(var)}(xs, 0) end -# allow specification of codefficients, order, symbol or coefficients, symbol function MutableDensePolynomial{B}(xs, order::Int=0, var::SymbolLike=Var(:x)) where {B} cs = collect(promote(xs...)) T = eltype(cs) @@ -70,9 +52,7 @@ function MutableDensePolynomial{B}(xs, order::Int=0, var::SymbolLike=Var(:x)) wh end function MutableDensePolynomial{B}(xs, var::SymbolLike) where {B} - cs = collect(xs) - T = eltype(cs) - MutableDensePolynomial{B, T, Symbol(var)}(cs) + MutableDensePolynomial{B}(xs, 0, var) end function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDensePolynomial{B,T,X}, S} @@ -85,25 +65,19 @@ end @poly_register MutableDensePolynomial constructorof(::Type{<:MutableDensePolynomial{B}}) where {B} = MutableDensePolynomial{B} - +## --- ## Generics for polynomials function Base.convert(::Type{MutableDensePolynomial{B,T,X}}, q::MutableDensePolynomial{B,T′,X′}) where {B,T,T′,X,X′} MutableDensePolynomial{B,T,X}(Val(false), convert(Vector{T},q.coeffs), q.order) end -# function Base.convert(::Type{MutableDensePolynomial{B,T}}, q::MutableDensePolynomial{B,T′,X′}) where {B,T,T′,X′} -# convert(Val(false), MutableDensePolynomial{B,T,X′}, q) -# end - - Base.copy(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs), firstindex(p)) -# This is B <: StandardBasis? Base.firstindex(p::MutableDensePolynomial) = p.order Base.length(p::MutableDensePolynomial) = length(p.coeffs) -offset(p::MutableDensePolynomial) = 1 - firstindex(p) +Base.lastindex(p::MutableDensePolynomial) = firstindex(p) + length(p) - 1 function Base.getindex(p::MutableDensePolynomial{B,T,X}, i::Int) where {B,T,X} (i < firstindex(p) || i > lastindex(p)) && return zero(T) p.coeffs[i + offset(p)] @@ -126,18 +100,18 @@ function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDensePolynom P(p.coeffs, o) end -Base.lastindex(p::MutableDensePolynomial) = firstindex(p) + length(p) - 1 +offset(p::MutableDensePolynomial) = 1 - firstindex(p) Base.eachindex(p::MutableDensePolynomial) = firstindex(p):1:lastindex(p) Base.iterate(p::MutableDensePolynomial, args...) = Base.iterate(p.coeffs, args...) Base.pairs(p::MutableDensePolynomial) = Base.Generator(=>, firstindex(p):lastindex(p), p.coeffs) -# (i - offset(p) => cᵢ for (i, cᵢ) ∈ enumerate(p)) + # return a container of zeros based on basis type _zeros(::Type{<:MutableDensePolynomial}, z, N) = fill(z, N) Base.similar(p::MutableDensePolynomial, args...) = similar(p.coeffs, args...) -# iszero, isconstant +# iszero Base.iszero(p::MutableDensePolynomial) = iszero(p.coeffs)::Bool function degree(p::MutableDensePolynomial) @@ -147,8 +121,8 @@ function degree(p::MutableDensePolynomial) end # zero, one, variable, basis -Base.zero(::Type{MutableDensePolynomial{B,T,X}}) where {B,T,X} = - MutableDensePolynomial{B,T,X}(T[]) +# Base.zero(::Type{MutableDensePolynomial{B,T,X}}) where {B,T,X} = +# MutableDensePolynomial{B,T,X}(T[]) coeffs(p::MutableDensePolynomial) = p.coeffs @@ -173,6 +147,7 @@ end Base.chop(p::MutableDensePolynomial{B,T,X}; kwargs...) where {B,T,X} = chop!(copy(p);kwargs...) +# trims left **and right** function chop!(p::MutableDensePolynomial{B,T,X}; atol=nothing, rtol=Base.rtoldefault(float(real(T)))) where {B,T,X} iᵣ = chop_right_index(p.coeffs; atol=atol, rtol=rtol) # nothing -- nothing to chop @@ -252,15 +227,8 @@ function offset_vector_combine(op, p::MutableDensePolynomial{B,T,X}, q::MutableD end -# slower -# function Base.:+(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where {B,T,S,X} -# a = min(firstindex(p), firstindex(q)) -# b = max(lastindex(p), lastindex(q)) -# x = collect(p[i] - q[i] for i ∈ a:b) -# MutableDensePolynomial{B,eltype(x),X}(x,a) -# end -# scalar +# scalar mult function scalar_mult(p::MutableDensePolynomial{B,T,X}, c::S) where {B,T,X,S} cs = p.coeffs .* (c,) # works with T[] return _polynomial(p, cs) @@ -270,11 +238,19 @@ function scalar_mult(c::S, p::MutableDensePolynomial{B,T,X}) where {B,T,X,S} return _polynomial(p, cs) end -#XXX need to add LinearAlgebra -# function LinearAlgebra.lmul!(c::Number, p::MutableDensePolynomial{B,T,X}) where {B,T,X} -# MutableDensePolynomial{B,T,X}(Val(false), lmul!(c, p.coeffs), firstindex(p)) -# end +## --- +function LinearAlgebra.lmul!(c::Scalar, p::MutableDensePolynomial{B,T,X}) where {B,T,X} + if iszero(c) + empty!(p.coeffs) + else + MutableDensePolynomial{B,T,X}(Val(false), lmul!(c, p.coeffs), firstindex(p)) + end +end -# function LinearAlgebra.rmul!(c::Number, p::MutableDensePolynomial{B,T,X}) where {B,T,X} -# MutableDensePolynomial{B,T,X}(rmul!(c, p.coeffs), firstindex(p)) -# end +function LinearAlgebra.rmul!(p::MutableDensePolynomial{B,T,X}, c::Scalar) where {B,T,X} + if iszero(c) + empty!(p.coeffs) + else + MutableDensePolynomial{B,T,X}(Val(false), rmul!(c, p.coeffs), firstindex(p)) + end +end diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index def7be07..a17c8798 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -1,9 +1,10 @@ +# dictionary to store (i, cᵢ) +# ensure cᵢ ≠ 0 in constructor struct MutableSparsePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B, T,X} coeffs::Dict{Int, T} - function MutableSparsePolynomial{B,T,X}(coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} - for (i, cᵢ) ∈ pairs(coeffs) - iszero(cᵢ) && delete!(coeffs, i) - end + function MutableSparsePolynomial{B,T,X}(cs::AbstractDict{Int,S},order::Int=0) where {B,T,S,X} + coeffs = convert(Dict{Int,T}, cs) + chop_exact_zeros!(coeffs) new{B,T,Symbol(X)}(coeffs) end function MutableSparsePolynomial{B,T,X}(checked::Val{:false}, coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} @@ -16,12 +17,11 @@ function MutableSparsePolynomial{B,T,X}(checked::Val{:true}, coeffs::AbstractDic end # Dict -function MutableSparsePolynomial{B,T,X}(coeffs::AbstractDict{Int,S}) where {B,T,S,X} - cs = convert(Dict{Int,T}, coeffs) - MutableSparsePolynomial{B,T,X}(cs) +function MutableSparsePolynomial{B,T}(coeffs::AbstractDict{Int,S}, var::SymbolLike=Var(:x)) where {B,T,S} + MutableSparsePolynomial{B,T,Symbol(var)}(coeffs) end -function MutableSparsePolynomial{B}(cs::AbstractDict{Int,T}, var::SymbolLike=:x) where {B,T} +function MutableSparsePolynomial{B}(cs::AbstractDict{Int,T}, var::SymbolLike=Var(:x)) where {B,T} MutableSparsePolynomial{B,T,Symbol(var)}(cs) end @@ -56,6 +56,7 @@ function MutableSparsePolynomial{B}(xs::AbstractVector{T}, var::SymbolLike) wher MutableSparsePolynomial{B,T,Symbol(var)}(xs) end +# iterable function MutableSparsePolynomial{B,T}(xs, var::SymbolLike=Var(:x)) where {B,T} cs = collect(T, xs) cs = trim_trailing_zeros(cs) @@ -65,16 +66,11 @@ end function MutableSparsePolynomial{B}(xs, var::SymbolLike=Var(:x)) where {B} cs = collect(xs) cs = trim_trailing_zeros(cs) - MutableSparsePolynomial{B,T,Symbol(var)}(cs) + MutableSparsePolynomial{B,eltype(cs),Symbol(var)}(cs) end -@poly_register MutableSparsePolynomial -constructorof(::Type{<:MutableSparsePolynomial{B}}) where {B} = MutableSparsePolynomial{B} - - -# cs iterable of pairs -# XXX enure tight value of T +# cs iterable of pairs; ensuring tight value of T function MutableSparsePolynomial{B}(cs::Tuple, var::SymbolLike=:x) where {B} isempty(cs) && throw(ArgumentError("No type attached")) X = Var(var) @@ -96,6 +92,11 @@ function MutableSparsePolynomial{B}(cs::Tuple, var::SymbolLike=:x) where {B} end end +@poly_register MutableSparsePolynomial + +constructorof(::Type{<:MutableSparsePolynomial{B}}) where {B} = MutableSparsePolynomial{B} + +## --- Base.copy(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(copy(p.coeffs)) @@ -103,16 +104,19 @@ function Base.convert(::Type{MutableSparsePolynomial{B,T,X}}, p::MutableSparsePo d = Dict{Int,T}(k => v for (k,v) ∈ pairs(p.coeffs)) MutableSparsePolynomial{B,T,X}(Val(false), d) end + # --- function Base.firstindex(p::MutableSparsePolynomial) isempty(p.coeffs) && return 0 i = minimum(keys(p.coeffs)) end + function Base.lastindex(p::MutableSparsePolynomial) isempty(p.coeffs) && return 0 maximum(keys(p.coeffs)) end + function Base.getindex(p::MutableSparsePolynomial{B,T,X}, i::Int) where {B,T,X} get(p.coeffs, i, zero(T)) end @@ -125,42 +129,37 @@ end hasnan(p::MutableSparsePolynomial) = any(hasnan, values(p.coeffs)) Base.pairs(p::MutableSparsePolynomial) = pairs(p.coeffs) -## Not properly named!!! truncate? chop? skip? -function trim_trailing_zeros(d::Dict) +offset(p::MutableSparsePolynomial) = 0 + +## --- + +function chop_exact_zeros!(d::Dict) for (k,v) ∈ pairs(d) - iszero(v) && deletat!(d, k) + iszero(v) && delete!(d, k) end d end +trim_trailing_zeros(d::Dict) = chop_exact_zeros!(d) # Not properly named, but what is expected in other constructors function chop!(p::MutableSparsePolynomial; atol=nothing, rtol=nothing) isempty(p.coeffs) && return p δ = something(rtol,0) ϵ = something(atol,0) - τ = max(ϵ, _norm(values(p.coeffs),2) * δ) + τ = max(ϵ, norm(values(p.coeffs),2) * δ) for (i,pᵢ) ∈ pairs(p) abs(pᵢ) ≤ τ && delete!(p.coeffs, i) end p end -_zeros(::Type{MutableSparsePolynomial{B,T,X}}, z::S, N) where {B,T,X,S} = Dict{Int, S}() +## --- -Base.iszero(p::MutableSparsePolynomial) = all(iszero, values(p.coeffs)) +_zeros(::Type{MutableSparsePolynomial{B,T,X}}, z::S, N) where {B,T,X,S} = Dict{Int, S}() Base.zero(::Type{MutableSparsePolynomial{B,T,X}}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(Dict{Int,T}()) -## --- - -function _evalpoly(p::MutableSparsePolynomial, x) - tot = zero(p[0]*x) - for (i, cᵢ) ∈ p.coeffs - tot = muladd(cᵢ, x^i, tot) - end - return tot -end +## --- -offset(p::MutableSparsePolynomial) = 0 function isconstant(p::MutableSparsePolynomial) n = length(p.coeffs) n == 0 && return true diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl index b5235c77..0918aae0 100644 --- a/src/polynomials/LaurentPolynomial.jl +++ b/src/polynomials/LaurentPolynomial.jl @@ -150,18 +150,24 @@ function LaurentPolynomial{T,X}(p::OffsetCoeffs) where {T, X} LaurentPolynomial{T,X}(p.coeffs, p.m) end -# function Base.convert(::Type{P}, q::StandardBasisPolynomial{S,X}) where {P <:LaurentPolynomial,S,X} -# T = _eltype(P, q) -# X = indeterminate(P, q) -# ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) -# end +function Base.convert(::Type{P}, q::AbstractPolynomial{T,X}) where {T,X,P <:LaurentPolynomial} + convert(P, convert(Polynomial, q)) +end + +function Base.convert(::Type{P}, q::StandardBasisPolynomial{T′,X′}) where {P <:LaurentPolynomial,T′,X′} + T = _eltype(P, q) + X = indeterminate(P, q) + ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) +end + +# resolve ambiguity +function Base.convert(::Type{P}, q::Polynomial{T, X}) where {T, X, P<:LaurentPolynomial} + p = convert(Polynomial, q) + LaurentPolynomial{eltype(p), indeterminate(p)}(p.coeffs, 0) +end -# #function Base.convert(::Type{P}, q::AbstractPolynomial{T,X}) where {T,X,P <:LaurentPolynomial{T,X}} -# function Base.convert(::Type{P}, q::AbstractPolynomial) where {P <:LaurentPolynomial} -# convert(P, convert(Polynomial, q)) -# end Base.convert(::Type{LaurentPolynomial{T, X}}, p::LaurentPolynomial{T, X}) where {T, X} = p # Ambiguity, issue #435 diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index 1a76fb94..e7e5f4a4 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -55,7 +55,7 @@ constantterm(p::StandardBasisPolynomial) = p[0] domain(::Type{<:StandardBasisPolynomial}) = Interval{Open,Open}(-Inf, Inf) mapdomain(::Type{<:StandardBasisPolynomial}, x::AbstractArray) = x -function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial{T′, X′}) where {T′, X′} +function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolynomial) if isa(q, P) return q else @@ -67,6 +67,7 @@ function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolyno end end + # treat p as a *vector* of coefficients Base.similar(p::StandardBasisPolynomial, args...) = similar(coeffs(p), args...) diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 86835155..9416c0e6 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -1,26 +1,6 @@ struct StandardBasis <: AbstractBasis end -function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where {B<:StandardBasis, T, P<:AbstractUnivariatePolynomial{B,T}} - - if _iszero(pj) return false end - - pj = printsign(io, pj, first, mimetype) - - if hasone(T) - if !(_isone(pj) && !(showone(T) || j == 0)) - printcoefficient(io, pj, j, mimetype) - end - else - printcoefficient(io, pj, j, mimetype) - end - - printproductsign(io, pj, j, mimetype) - printexponent(io, var, j, mimetype) - return true -end - - -function print_basis(io::IO, p::AbstractUnivariatePolynomial{<:StandardBasis, T, X}, i) where {T,X} +function print_basis(io::IO, p::AbstractUnivariatePolynomial{<:StandardBasis}, i) print(io, X) print_unicode_exponent(io, i) end @@ -58,71 +38,28 @@ function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: Abstract end Base.one(p::P) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}([one(p[0])]) +Base.one(::Type{P}) where {B<:StandardBasis,T,P <: AbstractUnivariatePolynomial{B,T}} = ⟒(P){T}(ones(T,1), indeterminate(P)) Base.one(::Type{P}) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(ones(T,1)) variable(P::Type{<:AbstractUnivariatePolynomial{B,T,X}}) where {B<:StandardBasis,T,X} = basis(P, 1) basis(P::Type{<:AbstractUnivariatePolynomial{B, T, X}}, i::Int) where {B<:StandardBasis,T,X} = P(ones(T,1), i) +constantterm(p::AbstractUnivariatePolynomial{B}) where {B <: StandardBasis} = p[0] + domain(::Type{P}) where {B <: StandardBasis, P <: AbstractUnivariatePolynomial{B}} = Interval{Open,Open}(-Inf, Inf) mapdomain(::Type{P}, x::AbstractArray) where {B <: StandardBasis, P <: AbstractUnivariatePolynomial{B}} = x -constantterm(p::AbstractUnivariatePolynomial{B}) where {B <: StandardBasis} = p[0] - - ## Multiplication # special cases are faster function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, q::AbstractUnivariatePolynomial{B,S,X}) where {B <: StandardBasis, T,S,X} # simple convolution with order shifted - R = promote_type(T,S) - P = ⟒(p){R,X} - - iszero(p) && return zero(P) - iszero(q) && return zero(P) - - cs = fastconv(p.coeffs, q.coeffs) - R = eltype(P) - a = firstindex(p) + firstindex(q) - - P(cs, a) + XXX() end -# dense -function differentiate(p::P′) where {B<:StandardBasis,T,X, P′ <: AbstractUnivariatePolynomial{B,T,X}} - - N = lastindex(p) - firstindex(p) + 1 - R = promote_type(T, Int) - P = ⟒(p){R,X} - hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} - iszero(p) && return P(0*p[0]) - - ps = p.coeffs - cs = [i*pᵢ for (i,pᵢ) ∈ pairs(p)] - return P(cs, p.order-1) -end - -# sparse -function differentiate(p::P′) where {B<:StandardBasis,T,X, P′ <: MutableSparsePolynomial{B,T,X}} - N = lastindex(p) - firstindex(p) + 1 - R = promote_type(T, Int) - P = ⟒(p){R,X} - hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} - iszero(p) && return zero(P) - - d = Dict{Int,R}() - for (i, pᵢ) ∈ pairs(p) - iszero(i) && continue - d[i-1] = i*pᵢ - end - return P(d) -end - - - function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardBasis,T,X} - # no offset! XXX iszero(p) && return p/1 N = lastindex(p) - firstindex(p) + 1 @@ -183,7 +120,8 @@ function Base.divrem(num::P, den::Q) where {B<:StandardBasis, end -## XXX copy or pass along to other system for now +## XXX This needs resolving! +## XXX copy or pass along to other system for now where things are defined fro StandardBasisPolynomial function vander(p::Type{<:P}, x::AbstractVector{T}, degs) where {B<:StandardBasis, P<:AbstractUnivariatePolynomial{B}, T <: Number} vander(StandardBasisPolynomial, x, degs) end diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index d8a8c79d..3865fbd2 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -1,23 +1,10 @@ # Dense + StandardBasis -#const Polynomial = MutableDensePolynomial{StandardBasis} # const is important! -#export Polynomial - -#LaurentPolynomial = MutableDensePolynomial{StandardBasis} -#export LaurentPolynomial function evalpoly(c, p::MutableDensePolynomial{StandardBasis,T,X}) where {T,X} - iszero(p) && return zero(T)*zero(c) + iszero(p) && return zero(T) * zero(c) EvalPoly.evalpoly(c, p.coeffs) * c^p.order end - -# function isconstant(p:: MutableDensePolynomial{StandardBasis}) -# firstindex(p) != 0 && return false -# i = findlast(!iszero, p.coeffs) -# i == nothing && return true -# i == 1 && return true -# end - # scalar add function scalar_add(c::S, p:: MutableDensePolynomial{StandardBasis,T,X}) where {S, T, X} R = promote_type(T,S) @@ -43,6 +30,7 @@ end function ⊗(p:: MutableDensePolynomial{StandardBasis,T,X}, q:: MutableDensePolynomial{StandardBasis,S,X}) where {T,S,X} # simple convolution + # This is ⊗(P,p,q) from polynomial standard-basis R = promote_type(T,S) P = MutableDensePolynomial{StandardBasis,R,X} @@ -69,30 +57,25 @@ function ⊗(p:: MutableDensePolynomial{StandardBasis,T,X}, P(Val(false), cs, a) end -# function ⊗(p:: MutableDensePolynomial{StandardBasis}{T,X}, -# q:: MutableDensePolynomial{StandardBasis}{S,X}) where {T,S,X} -# # simple convolution -# R = promote_type(T,S) -# P = MutableDensePolynomial{StandardBasis}{R,X} - -# iszero(p) && return zero(P) -# iszero(q) && return zero(P) - -# a₁, a₂ = firstindex(p), firstindex(q) -# b₁, b₂ = lastindex(p), lastindex(q) -# a, b = a₁ + a₂, b₁ + b₂ - -# z = zero(first(p) * first(q)) -# cs = _zeros(p, z, length(a:b)) - -# # convolve and shift order -# @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) -# for (j, qⱼ) ∈ enumerate(q.coeffs) -# ind = i + j - 1 -# cs[ind] += pᵢ * qⱼ -# end -# end - -# iszero(last(cs)) && chop_right!(cs) -# P(Val(false), cs, a) -# end + +function derivative(p::MutableDensePolynomial{B,T,X}) where {B<:StandardBasis,T,X} + + N = lastindex(p) - firstindex(p) + 1 + R = promote_type(T, Int) + P = ⟒(p){R,X} + hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} + iszero(p) && return P(0*p[0]) + + ps = p.coeffs + cs = [i*pᵢ for (i,pᵢ) ∈ pairs(p)] + return P(cs, p.order-1) +end + + +## XXX ---- + +# resolve ambiguity +function Base.convert(::Type{P}, q::Q) where {T, X, P<:LaurentPolynomial, B<:StandardBasis, Q<:AbstractUnivariatePolynomial{B, T, X}} + p = convert(Polynomial, q) + LaurentPolynomial{eltype(p), indeterminate(p)}(p.coeffs, 0) +end diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl index 6e9112c3..321165c2 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/standard-immutable.jl @@ -1,10 +1,14 @@ +evalpoly(x, p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = zero(T)*zero(x) function evalpoly(x, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} - N == 0 && return zero(T) * zero(x) - z = zero(x * zero(p[0])) - typeof(z)(EvalPoly.evalpoly(x, p.coeffs)) +# z = zero(x * zero(p[0])) +# typeof(z)(EvalPoly.evalpoly(x, p.coeffs)) + EvalPoly.evalpoly(x, p.coeffs) end +constantterm(p::ImmutableDensePolynomial{B,T,X,N}) where {B <: StandardBasis,T,X,N} = p.coeffs[1] # this is oddly slow +constantterm(p::ImmutableDensePolynomial{B,T,X,0}) where {B <: StandardBasis,T,X} = zero(T) + # Padded vector sum of two tuples assuming N ≥ M @generated function tuple_sum(p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} @@ -33,7 +37,6 @@ function scalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B<:Standa N == 0 && return P{1}(NTuple{1,R}(c)) N == 1 && return P{N}((p[0]+c,)) - #cs = tuple_sum(convert(NTuple{N,R}, p.coeffs), NTuple{1,R}(c)) cs = _tuple_combine(+, convert(NTuple{N,R}, p.coeffs), NTuple{1,R}((c,))) q = P{N}(cs) @@ -93,7 +96,7 @@ end end -function differentiate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X,N} +function derivative(p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} N == 0 && return p hasnan(p) && return ⟒(p)(zero(T)/zero(T),X) # NaN{T} cs = ntuple(i -> i*p.coeffs[i+1], Val(N-1)) diff --git a/src/standard-basis/standard-sparse.jl b/src/standard-basis/standard-sparse.jl index 60a266d3..48afb710 100644 --- a/src/standard-basis/standard-sparse.jl +++ b/src/standard-basis/standard-sparse.jl @@ -10,11 +10,6 @@ function evalpoly(x, p::MutableSparsePolynomial) return tot end - -function constantterm(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} - get(p.coeffs, 0, zero(T)) -end - function ⊗(p::MutableSparsePolynomial{StandardBasis,T,X}, q::MutableSparsePolynomial{StandardBasis,S,X}) where {T,S,X} @@ -35,3 +30,20 @@ function ⊗(p::MutableSparsePolynomial{StandardBasis,T,X}, end P(Val(false), cs) end + + +# sparse +function derivative(p:: MutableSparsePolynomial{B,T,X}) where {B<:StandardBasis,T,X} + N = lastindex(p) - firstindex(p) + 1 + R = promote_type(T, Int) + P = ⟒(p){R,X} + hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} + iszero(p) && return zero(P) + + d = Dict{Int,R}() + for (i, pᵢ) ∈ pairs(p) + iszero(i) && continue + d[i-1] = i*pᵢ + end + return P(d) +end diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index e726f7ad..fc3a3bb9 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -525,7 +525,7 @@ end v₀, v₁ = [1,1,1], [1,2,3] p₁ = P([v₀]) @test p₁(0) == v₀ == Polynomials.constantterm(p₁) - @test_throws MethodError (0 * p₁)(0) # no zero(Vector{Int}) + P != ImmutableDensePolynomial{StandardBasis} && @test_throws MethodError (0 * p₁)(0) # no zero(Vector{Int}) # XXX p₂ = P([v₀, v₁]) @test p₂(0) == v₀ == Polynomials.constantterm(p₂) @test p₂(2) == v₀ + 2v₁ @@ -1163,7 +1163,7 @@ end λ = P([0,1],:λ) A = [1 λ; λ^2 λ^3] P != Polynomials.ImmutableDensePolynomial{Polynomials.StandardBasis} && @test A == diagm(0 => [1, λ^3], 1=>[λ], -1=>[λ^2]) # XXX diagm + ImmutableDensePolynomial{StandardBasis} isn't working - @test all([1 -λ]*[λ^2 λ; λ 1] .== 0) + @test iszero([1 -λ]*[λ^2 λ; λ 1]) @test [λ 1] + [1 λ] == (λ+1) .* [1 1] # (λ+1) not a number, so we broadcast end From 0d90ff6f29a689410c640f5c2d29c7f8edde0ad1 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 20 Jul 2023 06:16:22 -0400 Subject: [PATCH 05/31] add polynomial_composition code path --- src/abstract-polynomial.jl | 1 + src/abstract.jl | 1 + src/common.jl | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 9f3946ff..ffc89953 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -324,6 +324,7 @@ macro poly_register(name) $poly{B,T}(var::SymbolLike=Var(:x)) where {B,T} = variable($poly{B, T, Symbol(var)}) $poly{B}(var::SymbolLike=Var(:x)) where {B} = variable($poly{B}, Symbol(var)) + (p::$poly)(x::AbstractPolynomial) = polynomial_composition(p, x) (p::$poly)(x) = evalpoly(x, p) end end diff --git a/src/abstract.jl b/src/abstract.jl index 5ae9c9cf..521ed1d5 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -125,6 +125,7 @@ macro register(name) $poly{T}(var::SymbolLike=Var(:x)) where {T} = variable($poly{T, Symbol(var)}) $poly(var::SymbolLike=Var(:x)) = variable($poly, Symbol(var)) + (p::$poly)(x::AbstractPolynomial) = polynomial_composition(p, x) (p::$poly)(x) = evalpoly(x, p) end end diff --git a/src/common.jl b/src/common.jl index 90eca83d..e4861687 100644 --- a/src/common.jl +++ b/src/common.jl @@ -927,6 +927,14 @@ function basis(::Type{P}, k::Int, _var::SymbolLike; var=_var) where {P <: Abstra end basis(p::P, k::Int, _var=indeterminate(p); var=_var) where {P<:AbstractPolynomial} = basis(P, k, var) +#= composition =# +""" + polynomial_composition(p, q) + +Evaluate `p(q)`, possibly exploiting a faster evaluation scheme, defaulting to `evalpoly`. +""" +polynomial_composition(p::AbstractPolynomial, q::AbstractPolynomial) = p(q) + #= arithmetic =# Scalar = Union{Number, Matrix} From dac8895f377aac446a1ff4a529249cc114e15a16 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 20 Jul 2023 06:27:55 -0400 Subject: [PATCH 06/31] WIP --- src/common.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/common.jl b/src/common.jl index e4861687..4dd51a09 100644 --- a/src/common.jl +++ b/src/common.jl @@ -927,13 +927,19 @@ function basis(::Type{P}, k::Int, _var::SymbolLike; var=_var) where {P <: Abstra end basis(p::P, k::Int, _var=indeterminate(p); var=_var) where {P<:AbstractPolynomial} = basis(P, k, var) -#= composition =# +#= +composition +cf. https://github.com/JuliaMath/Polynomials.jl/issues/511 for a paper with implentations + +=# """ polynomial_composition(p, q) Evaluate `p(q)`, possibly exploiting a faster evaluation scheme, defaulting to `evalpoly`. """ -polynomial_composition(p::AbstractPolynomial, q::AbstractPolynomial) = p(q) +function polynomial_composition(p::AbstractPolynomial, q::AbstractPolynomial) + evalpoly(q, p) +end #= arithmetic =# From 24a9959c76cb8d0d98ef7bec920c540525660fc3 Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 21 Jul 2023 07:53:21 -0400 Subject: [PATCH 07/31] WIP --- src/Polynomials.jl | 24 ++-- src/abstract-polynomial.jl | 2 +- .../immutable-dense-polynomial.jl | 2 + .../mutable-dense-polynomial.jl | 8 +- .../mutable-sparse-polynomial.jl | 2 + src/polynomials/ChebyshevT.jl | 2 + src/polynomials/SparsePolynomial.jl | 19 ++- src/polynomials/standard-basis.jl | 6 +- src/standard-basis/standard-basis.jl | 9 +- src/standard-basis/standard-dense.jl | 124 +++++++++++++++++- src/standard-basis/standard-immutable.jl | 5 + src/standard-basis/standard-sparse.jl | 23 +++- test/StandardBasis.jl | 15 ++- 13 files changed, 210 insertions(+), 31 deletions(-) diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 2e0aa942..5f8b1946 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -16,22 +16,16 @@ 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/ImmutablePolynomial.jl") +#include("polynomials/SparsePolynomial.jl") +#include("polynomials/LaurentPolynomial.jl") + include("polynomials/pi_n_polynomial.jl") include("polynomials/factored_polynomial.jl") include("polynomials/ngcd.jl") include("polynomials/multroot.jl") include("polynomials/ChebyshevT.jl") -# Rational functions -include("rational-functions/common.jl") -include("rational-functions/rational-function.jl") -include("rational-functions/fit.jl") -#include("rational-functions/rational-transfer-function.jl") -include("rational-functions/plot-recipes.jl") - # polynomials with explicit basis include("abstract-polynomial.jl") include("basis-utils.jl") @@ -44,6 +38,16 @@ include("standard-basis/standard-immutable.jl") include("standard-basis/standard-sparse.jl") + + + +# Rational functions +include("rational-functions/common.jl") +include("rational-functions/rational-function.jl") +include("rational-functions/fit.jl") +#include("rational-functions/rational-transfer-function.jl") +include("rational-functions/plot-recipes.jl") + # compat; opt-in with `using Polynomials.PolyCompat` include("polynomials/Poly.jl") diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index ffc89953..9078c1c2 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -79,7 +79,6 @@ function normΔ(q1::AbstractUnivariatePolynomial, q2::AbstractUnivariatePolynomi return tot^(1/p) end - # map Polynomial terms -> vector terms degree(p::AbstractUnivariatePolynomial) = iszero(p) ? -1 : lastindex(p) # order(p::AbstractUnivariatePolynomial) = firstindex(p) XXX conflicts with DataFrames.order @@ -324,6 +323,7 @@ macro poly_register(name) $poly{B,T}(var::SymbolLike=Var(:x)) where {B,T} = variable($poly{B, T, Symbol(var)}) $poly{B}(var::SymbolLike=Var(:x)) where {B} = variable($poly{B}, Symbol(var)) + $poly{B}(c::AbstractPolynomial{S,Y}) where {B,S,Y} = convert($poly{B}, c) (p::$poly)(x::AbstractPolynomial) = polynomial_composition(p, x) (p::$poly)(x) = evalpoly(x, p) end diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index 8ae1d6ee..aa2968aa 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -233,6 +233,8 @@ function Base.:(==)(p1::ImmutableDensePolynomial{B}, p2::ImmutableDensePolynomia return true end +minimumexponent(::Type{<:ImmutableDensePolynomial}) = 0 + ## --- function degree(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index 7d0b1293..f2126d4c 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -7,7 +7,7 @@ struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} function MutableDensePolynomial{B,T,X}(cs, order::Int=0) where {B,T,X} if Base.has_offset_axes(cs) @warn "Using the axis offset of the coefficient vector" - cs, order = cs.parent, first(cs.offsets) + cs, order = cs.parent, firstindex(cs) end i = findlast(!iszero, cs) @@ -65,6 +65,9 @@ end @poly_register MutableDensePolynomial constructorof(::Type{<:MutableDensePolynomial{B}}) where {B} = MutableDensePolynomial{B} +Base.promote_rule(::Type{P},::Type{Q}) where {B,T, X, P <: AbstractUnivariatePolynomial{B,T,X}, S, Q <: AbstractUnivariatePolynomial{B, S, X}} = MutableDensePolynomial{B,promote_type(T, S), X} + + ## --- ## Generics for polynomials @@ -182,6 +185,9 @@ function normΔ(q1::MutableDensePolynomial{B}, q2::MutableDensePolynomial{B}, p: return tot^(1/p) end +minimumexponent(::Type{<:MutableDensePolynomial}) = typemin(Int) + + # vector ops +, -, c*x ## unary diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index a17c8798..f2faa5ba 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -98,6 +98,8 @@ constructorof(::Type{<:MutableSparsePolynomial{B}}) where {B} = MutableSparsePol ## --- +minimumexponent(::Type{<:MutableSparsePolynomial}) = typemin(Int) + Base.copy(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(copy(p.coeffs)) function Base.convert(::Type{MutableSparsePolynomial{B,T,X}}, p::MutableSparsePolynomial{B,S,X}) where {B,T,S,X} diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index f66837f5..be9f8e40 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -83,10 +83,12 @@ function Base.convert(C::Type{<:ChebyshevT}, p::Polynomial) p(x) end +#= XXX Base.promote_rule(::Type{P},::Type{Q}) where { T, X, P <: LaurentPolynomial{T,X}, S, Q <: ChebyshevT{S, X}} = LaurentPolynomial{promote_type(T, S), X} +=# domain(::Type{<:ChebyshevT}) = Interval(-1, 1) function Base.one(::Type{P}) where {P<:ChebyshevT} diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl index f024b360..0fd204c7 100644 --- a/src/polynomials/SparsePolynomial.jl +++ b/src/polynomials/SparsePolynomial.jl @@ -99,7 +99,8 @@ function coeffs(p::SparsePolynomial{T}) where {T} n = degree(p) cs = zeros(T, length(p)) keymin = firstindex(p) - for (k,v) in p.coeffs + for k in sort(collect(keys(p.coeffs))) + v = p.coeffs[k] cs[k - keymin + 1] = v end cs @@ -259,3 +260,19 @@ function derivative(p::SparsePolynomial{T,X}, order::Integer = 1) where {T,X} return dpn end + +function integrate(p:: SparsePolynomial{T,X}) where {T,X} + + R = Base.promote_op(/, T, Int) + P = SparsePolynomial{R,X} + hasnan(p) && return ⟒(P)(NaN) + iszero(p) && return zero(p)/1 + + d = Dict{Int, R}() + for (i, pᵢ) ∈ pairs(p.coeffs) + i == -1 && throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term")) + cᵢ₊₁ = pᵢ/(i+1) + !iszero(cᵢ₊₁) && (d[i+1] = cᵢ₊₁) + end + return P(d) +end diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index e7e5f4a4..4e76376b 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -59,8 +59,10 @@ function Base.convert(P::Type{<:StandardBasisPolynomial}, q::StandardBasisPolyno if isa(q, P) return q else - minimumexponent(P) <= minimumexponent(q) || - throw(ArgumentError("a $P can not have a minimum exponent of $(minimumexponent(q))")) + # minimumexponent(P) <= minimumexponent(q) || + # throw(ArgumentError("a $P can not have a minimum exponent of $(minimumexponent(q))")) + minimumexponent(P) > firstindex(q) && + throw(ArgumentError("Can't convert to a polynomial of type $(⟒(P)) as the degree of the polynomial is too small")) T = _eltype(P,q) X = indeterminate(P,q) return ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 9416c0e6..379e9793 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -18,6 +18,9 @@ function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: Abstract end end function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B<:StandardBasis,T,X, Q<:AbstractUnivariatePolynomial{B,T,X}} + minimumexponent(P) > firstindex(q) && + throw(ArgumentError("Degree of polynomial less than minimum degree of polynomial type $(⟒(P))")) + isa(q, PP) && return p T′ = _eltype(P,q) X′ = indeterminate(P,q) @@ -71,7 +74,7 @@ function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardB cs = _zeros(p, z, N+1) os = offset(p) @inbounds for (i, cᵢ) ∈ pairs(p) - i == -1 && throw(ArgumentError("Laurent polynomial with 1/x term")) + i == -1 && (iszero(cᵢ) ? continue : throw(ArgumentError("Laurent polynomial with 1/x term"))) #cs[i + os] = cᵢ / (i+1) cs = _set(cs, i + 1 + os, cᵢ / (i+1)) end @@ -184,8 +187,8 @@ function fit(::Type{P}, end # new constructors taking order in second position -SparsePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T, X, S} = SparsePolynomial{T,X}(coeffs) +#SparsePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T, X, S} = SparsePolynomial{T,X}(coeffs) Polynomial{T, X}(coeffs::AbstractVector{S},order::Int) where {T, X, S} = Polynomial{T,X}(coeffs) -ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T,X,S} = ImmutablePolynomial{T,X}(coeffs) +#ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T,X,S} = ImmutablePolynomial{T,X}(coeffs) FactoredPolynomial{T,X}(coeffs::AbstractVector{S}, order::Int) where {T,S,X} = FactoredPolynomial{T,X}(coeffs) PnPolynomial{T, X}(coeffs::AbstractVector, order::Int) where {T, X} = PnPolynomial(coeffs) # for generic programming diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 3865fbd2..824eebba 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -1,10 +1,12 @@ # Dense + StandardBasis + function evalpoly(c, p::MutableDensePolynomial{StandardBasis,T,X}) where {T,X} iszero(p) && return zero(T) * zero(c) EvalPoly.evalpoly(c, p.coeffs) * c^p.order end + # scalar add function scalar_add(c::S, p:: MutableDensePolynomial{StandardBasis,T,X}) where {S, T, X} R = promote_type(T,S) @@ -71,11 +73,127 @@ function derivative(p::MutableDensePolynomial{B,T,X}) where {B<:StandardBasis,T, return P(cs, p.order-1) end +# LaurentPolynomials have `inv` defined for monomials +function Base.inv(p::MutableDensePolynomial{StandardBasis}) + m,n = firstindex(p), lastindex(p) + m != n && throw(ArgumentError("Only monomials can be inverted")) + cs = [1/p for p in p.coeffs] + LaurentPolynomial{eltype(cs), indeterminate(p)}(cs, -m) +end ## XXX ---- +#const Polynomial = MutableDensePolynomial{StandardBasis} +#export Polynomial + +const LaurentPolynomial = MutableDensePolynomial{StandardBasis} +export LaurentPolynomial + +""" + paraconj(p) + +[cf.](https://ccrma.stanford.edu/~jos/filters/Paraunitary_FiltersC_3.html) + +Call `p̂ = paraconj(p)` and `p̄` = conj(p)`, then this satisfies +`conj(p(z)) = p̂(1/conj(z))` or `p̂(z) = p̄(1/z) = (conj ∘ p ∘ conj ∘ inf)(z)`. + +Examples: + +```jldoctest laurent +julia> using Polynomials; + +julia> z = variable(LaurentPolynomial, :z) +LaurentPolynomial(z) + +julia> h = LaurentPolynomial([1,1], -1, :z) +LaurentPolynomial(z⁻¹ + 1) + +julia> Polynomials.paraconj(h)(z) ≈ 1 + z ≈ LaurentPolynomial([1,1], 0, :z) +true + +julia> h = LaurentPolynomial([3,2im,1], -2, :z) +LaurentPolynomial(3*z⁻² + 2im*z⁻¹ + 1) + +julia> Polynomials.paraconj(h)(z) ≈ 1 - 2im*z + 3z^2 ≈ LaurentPolynomial([1, -2im, 3], 0, :z) +true + +julia> Polynomials.paraconj(h)(z) ≈ (conj ∘ h ∘ conj ∘ inv)(z) +true +""" +function paraconj(p::LaurentPolynomial) + cs = p.coeffs + ds = adjoint.(cs) + n = degree(p) + LaurentPolynomial(reverse(ds), -n, indeterminate(p)) +end + +""" + cconj(p) + +Conjugation of a polynomial with respect to the imaginary axis. + +The `cconj` of a polynomial, `p̃`, conjugates the coefficients and applies `s -> -s`. That is `cconj(p)(s) = conj(p)(-s)`. + +This satisfies for *imaginary* `s`: `conj(p(s)) = p̃(s) = (conj ∘ p)(s) = cconj(p)(s) ` + +[ref](https://github.com/hurak/PolynomialEquations.jl#symmetrix-conjugate-equation-continuous-time-case) + +Examples: +```jldoctest laurent +julia> using Polynomials; + +julia> s = 2im +0 + 2im + +julia> p = LaurentPolynomial([im,-1, -im, 1], 1, :s) +LaurentPolynomial(im*s - s² - im*s³ + s⁴) + +julia> Polynomials.cconj(p)(s) ≈ conj(p(s)) +true + +julia> a = LaurentPolynomial([-0.12, -0.29, 1],:s) +LaurentPolynomial(-0.12 - 0.29*s + 1.0*s²) + +julia> b = LaurentPolynomial([1.86, -0.34, -1.14, -0.21, 1.19, -1.12],:s) +LaurentPolynomial(1.86 - 0.34*s - 1.14*s² - 0.21*s³ + 1.19*s⁴ - 1.12*s⁵) + +julia> x = LaurentPolynomial([-15.5, 50.0096551724139, 1.19], :s) +LaurentPolynomial(-15.5 + 50.0096551724139*s + 1.19*s²) + +julia> Polynomials.cconj(a) * x + a * Polynomials.cconj(x) ≈ b + Polynomials.cconj(b) +true +``` + +""" +function cconj(p::LaurentPolynomial) + ps = conj.(coeffs(p)) + m,n = (extrema ∘ degreerange)(p) + for i in m:n + if isodd(i) + ps[i+1-m] *= -1 + end + end + LaurentPolynomial(ps, m, indeterminate(p)) +end + + # resolve ambiguity -function Base.convert(::Type{P}, q::Q) where {T, X, P<:LaurentPolynomial, B<:StandardBasis, Q<:AbstractUnivariatePolynomial{B, T, X}} - p = convert(Polynomial, q) - LaurentPolynomial{eltype(p), indeterminate(p)}(p.coeffs, 0) +# function Base.convert(::Type{P}, q::Q) where {T, X, P<:LaurentPolynomial, B<:StandardBasis, Q<:AbstractUnivariatePolynomial{B, T, X}} +# p = convert(Polynomial, q) +# LaurentPolynomial{eltype(p), indeterminate(p)}(p.coeffs, 0) +# end + +## ---- +## XXX needs to be incorporated if Polynomial = MutableDensePolynomial{StandardBasis} +function roots(p::P; kwargs...) where {T, X, P <: MutableDensePolynomial{StandardBasis,T,X}} + iszero(p) && return float(T)[] + c = coeffs(p) + r = degreerange(p) + d = r[end] - min(0, r[1]) + 1 # Length of the coefficient vector, taking into consideration + # the case when the lower degree is strictly positive + # (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{T,X}(z) # The root is then the root of the numerator polynomial. + return roots(a; kwargs...) end diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl index 321165c2..9db4adde 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/standard-immutable.jl @@ -113,3 +113,8 @@ function integrate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X, R = eltype(cs) ImmutableDensePolynomial{StandardBasis,R,X,N+1}(cs) end + + +## --- +ImmutablePolynomial = ImmutableDensePolynomial{StandardBasis} +export ImmutablePolynomial diff --git a/src/standard-basis/standard-sparse.jl b/src/standard-basis/standard-sparse.jl index 48afb710..3b9d384c 100644 --- a/src/standard-basis/standard-sparse.jl +++ b/src/standard-basis/standard-sparse.jl @@ -1,6 +1,3 @@ -#const SparsePolynomial = SparseUnivariatePolynomial{StandardBasis} # const is important! -#export SparsePolynomial - function evalpoly(x, p::MutableSparsePolynomial) tot = zero(p[0]*x) @@ -47,3 +44,23 @@ function derivative(p:: MutableSparsePolynomial{B,T,X}) where {B<:StandardBasis, end return P(d) end + +function integrate(p:: MutableSparsePolynomial{B,T,X}) where {B<:StandardBasis,T,X} + + R = Base.promote_op(/, T, Int) + P = MutableSparsePolynomial{B,R,X} + hasnan(p) && return ⟒(P)(NaN) + iszero(p) && return zero(p)/1 + + d = Dict{Int, R}() + for (i, pᵢ) ∈ pairs(p.coeffs) + i == -1 && throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term")) + cᵢ₊₁ = pᵢ/(i+1) + !iszero(cᵢ₊₁) && (d[i+1] = cᵢ₊₁) + end + return P(d) +end + +## --- +const SparsePolynomial = MutableSparsePolynomial{StandardBasis} # const is important! +export SparsePolynomial diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index fc3a3bb9..d6945b29 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -1,6 +1,6 @@ using LinearAlgebra using OffsetArrays, StaticArrays -import Polynomials: indeterminate +import Polynomials: indeterminate, ⟒ import Polynomials: ImmutableDensePolynomial, StandardBasis,MutableSparsePolynomial, MutableDensePolynomial ## Test standard basis polynomials with (nearly) the same tests @@ -375,7 +375,7 @@ end @testset for P in Ps # LaurentPolynomial accepts OffsetArrays; others throw warning - if P ∈ (LaurentPolynomial, MutableDensePolynomial{StandardBasis}) + if P ∈ (LaurentPolynomial,) @test LaurentPolynomial(as) == LaurentPolynomial(bs, 3) else @test P(as) == P(bs) @@ -895,6 +895,7 @@ end @testset for P1 in Ps p = P1(c) @testset for P2 in Psexact + @show P1, P2 @test convert(P2, p) == p end @test convert(FactoredPolynomial, p) ≈ p @@ -1039,7 +1040,7 @@ end der = derivative(p) @test coeffs(der) ==ᵗ⁰ [2, 6, 12] int = integrate(der, 1) - @test coeffs(int) ==ᵗ⁰ c + @test coeffs(int)[2:end] ==ᵗ⁰ c[2:end] @test derivative(pR) == P([-2 // 1,2 // 1]) @@ -1546,7 +1547,7 @@ end p = P([1 + im, 1 - im, -1 + im, -1 - im])# minus signs @test repr(p) == "$P((1 + im) + (1 - im)x - (1 - im)x^2 - (1 + im)x^3)" p = P([1.0, 0 + NaN * im, NaN, Inf, 0 - Inf * im]) # handle NaN or Inf appropriately - @test repr(p) == "$P(1.0 + NaN*im*x + NaN*x^2 + Inf*x^3 - Inf*im*x^4)" + @test repr(p) == "$(P)(1.0 + NaN*im*x + NaN*x^2 + Inf*x^3 - Inf*im*x^4)" p = P([1,2,3]) @@ -1669,9 +1670,9 @@ end @testset "empty" begin p = SparsePolynomial(Float64[0]) @test eltype(p) == Float64 - @test eltype(keys(p)) == Int - @test eltype(values(p)) == Float64 - @test collect(p) == Float64[] + @test eltype(collect(keys(p))) == Int + @test eltype(collect(values(p))) == Float64 + @test collect(p) ==ᵗ⁰ Float64[] @test collect(keys(p)) == Int[] @test collect(values(p)) == Float64[] @test p == Polynomial(0) From 2bbf30286cbd551e9fa52dc219b15812184726a9 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sat, 22 Jul 2023 10:21:30 -0400 Subject: [PATCH 08/31] WIP: tests passing --- src/abstract-polynomial.jl | 23 +++- src/abstract.jl | 2 +- src/common.jl | 2 +- .../immutable-dense-polynomial.jl | 121 +++++------------- .../mutable-dense-polynomial.jl | 31 +---- .../mutable-sparse-polynomial.jl | 29 ----- src/standard-basis/standard-basis.jl | 11 +- src/standard-basis/standard-dense.jl | 25 ++-- src/standard-basis/standard-immutable.jl | 5 +- test/StandardBasis.jl | 71 ++++++++-- 10 files changed, 138 insertions(+), 182 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 9078c1c2..c03633aa 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -24,6 +24,7 @@ function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where return true end +_convert(p::P, as) where {B,T,X,P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(as, firstindex(p)) ## idea is vector space stuff (scalar_add, scalar_mult, vector +/-, ^) goes here ## connection (convert, transform) is specific to a basis (storage) @@ -114,7 +115,6 @@ variable(::Type{P}, var::SymbolLike) where {B,P<:AbstractUnivariatePolynomial{B} basis(p::P, i::Int) where {P <: AbstractUnivariatePolynomial} = basis(P, i) basis(::Type{P}, i::Int) where {B,P <: AbstractUnivariatePolynomial{B}} = basis(⟒(P){eltype(P),indeterminate(P)}, i) -_convert(p::P, as) where {B,T,X,P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(as) copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {B,T, X, S, Y, P <:AbstractUnivariatePolynomial{B,S,Y}} = ⟒(P){T, Symbol(X)}(p.coeffs) @@ -298,10 +298,6 @@ function derivative(p::AbstractUnivariatePolynomial, n::Int=1) end const differentiate = derivative -# only need to define integrate(p::PolyType) -function integrate(p::AbstractUnivariatePolynomial, c) - scalar_add(integrate(p), c) -end # promote, promote_rule, handle constants macro poly_register(name) @@ -313,6 +309,23 @@ macro poly_register(name) Base.promote_rule(::Type{<:$poly{B,T,X}}, ::Type{S}) where {B,T,S<:Scalar,X} = $poly{B,promote_type(T, S), X} + # vector + $poly{B,T}(xs::AbstractVector{S}, order::Int, var::SymbolLike=Var(:x)) where {B,T,S} = $poly{B,T,Symbol(var)}(xs,order) + $poly{B,T}(xs::AbstractVector{S}, var::SymbolLike) where {B,T,S} = $poly{B,T,Symbol(var)}(xs,0) + $poly{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} = $poly{B,T,Symbol(var)}(xs,order) + $poly{B}(xs::AbstractVector{T}, var::SymbolLike) where {B,T} = $poly{B,T,Symbol(var)}(xs,0) + + # untyped + $poly{B,T,X}(xs, order::Int=0) where {B,T,X} = $poly{B,T,X}(collect(T,xs), order) + $poly{B,T}(xs, order::Int=0, var::SymbolLike=Var(:x)) where {B,T} = $poly{B,T,Var(var)}(collect(T,xs), order) + $poly{B,T}(xs, var::SymbolLike) where {B,T} = $poly{B,T}(xs, 0, var) + function $poly{B}(xs, order::Int=0, var::SymbolLike=Var(:x)) where {B} + cs = collect(promote(xs...)) + T = eltype(cs) + $poly{B,T,Symbol(var)}(cs, order) + end + $poly{B}(xs, var::SymbolLike) where {B} = $poly{B}(xs, 0, var) + # constants $poly{B,T,X}(n::S) where {B, T, X, S<:Scalar} = T(n) * one($poly{B, T, X}) diff --git a/src/abstract.jl b/src/abstract.jl index 521ed1d5..936d0f44 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -63,7 +63,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 {T,X,P <: AbstractPolynomial{T,X}} = ⟒(P)(as, Var(X)) +_convert(p::P, as) where {T,X,P <: AbstractPolynomial{T,X}} = ⟒(P)(as, Var(X)) """ diff --git a/src/common.jl b/src/common.jl index 4dd51a09..dfecc43c 100644 --- a/src/common.jl +++ b/src/common.jl @@ -587,7 +587,7 @@ Transform coefficients of `p` by applying a function (or other callables) `fn` t You can implement `real`, etc., to a `Polynomial` by using `map`. """ -Base.map(fn, p::P, args...) where {P<:AbstractPolynomial} = _convert(p, map(fn, coeffs(p), args...)) +Base.map(fn, p::P, args...) where {P<:AbstractPolynomial} = _convert(p, map(fn, coeffs(p),args...)) """ diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index aa2968aa..0d571df4 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -2,39 +2,40 @@ # order is ignored, firstindex is always 0 struct ImmutableDensePolynomial{B,T,X,N} <: AbstractUnivariatePolynomial{B,T,X} coeffs::NTuple{N,T} - function ImmutableDensePolynomial{B,T,X,N}(cs::NTuple{N,T}) where {B,N,T,X} - if Base.has_offset_axes(cs) - @warn "Ignoring the axis offset of the coefficient vector" - end + function ImmutableDensePolynomial{B,T,X,N}(cs::NTuple{N,S}) where {B,N,T,X,S} new{B,T,Symbol(X),N}(cs) end end -ImmutableDensePolynomial{B,T,X,N}(::Type{Val{false}}, cs::NTuple{N,T}) where {B,N,T,X} = +ImmutableDensePolynomial{B,T,X,N}(check::Type{Val{false}}, cs::NTuple{N,T}) where {B,N,T,X} = ImmutableDensePolynomial{B,T,X}(cs) -ImmutableDensePolynomial{B,T,X,N}(::Type{Val{true}}, cs::NTuple{N,T}) where {B,N, T,X} = +ImmutableDensePolynomial{B,T,X,N}(check::Type{Val{true}}, cs::NTuple{N,T}) where {B,N, T,X} = ImmutableDensePolynomial{B,T,X,N}(cs) # tuple with mis-matched size function ImmutableDensePolynomial{B,T,X,N}(xs::NTuple{M,S}) where {B,T,S,X,N,M} + p = ImmutableDensePolynomial{B,S,X,M}(xs) convert(ImmutableDensePolynomial{B,T,X,N}, ImmutableDensePolynomial{B,T,X,M}(xs)) end -function ImmutableDensePolynomial{B,T,X}(xs::NTuple{N,S}) where {B,T,S,X,N} - cs = convert(NTuple{N,T}, xs) - ImmutableDensePolynomial{B,T,X,N}(cs) -end - -function ImmutableDensePolynomial{B,T}(xs::NTuple{N,S}, var::SymbolLike=Var(:x)) where {B,T,S,N} - ImmutableDensePolynomial{B,T,Var(var),N}(xs) -end - -function ImmutableDensePolynomial{B}(xs::NTuple{N,T}, var::SymbolLike=Var(:x)) where {B,T,N} - ImmutableDensePolynomial{B,T,Var(var),N}(xs) +# constant +function ImmutableDensePolynomial{B,T,X,N}(c::S) where {B,T,X,N,S<:Scalar} + if N == 0 + if iszero(c) + throw(ArgumentError("Can't create zero-length polynomial")) + else + return zero(ImmutableDensePolynomial{B,T,X}) + end + end + cs = ntuple(i -> i == 1 ? T(c) : zero(T), Val(N)) + return ImmutableDensePolynomial{B,T,X,N}(cs) end +ImmutableDensePolynomial{B,T,X}(xs::NTuple{N,S}) where {B,T,S,X,N} = ImmutableDensePolynomial{B,T,X,N}(convert(NTuple{N,T}, xs)) +ImmutableDensePolynomial{B,T}(xs::NTuple{N,S}, var::SymbolLike=Var(:x)) where {B,T,S,N} = ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) +ImmutableDensePolynomial{B}(xs::NTuple{N,T}, var::SymbolLike=Var(:x)) where {B,T,N} = ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) -# abstract vector +# abstract vector. Must eat order function ImmutableDensePolynomial{B,T,X}(xs::AbstractVector{S}, order::Int=0) where {B,T,X,S} if Base.has_offset_axes(xs) @warn "ignoring the axis offset of the coefficient vector" @@ -46,52 +47,6 @@ function ImmutableDensePolynomial{B,T,X}(xs::AbstractVector{S}, order::Int=0) wh ImmutableDensePolynomial{B,T,X,N}(cs) end -function ImmutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::SymbolLike=Var(:x)) where {B,T,S} - ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) -end - -function ImmutableDensePolynomial{B,T}(xs::AbstractVector{S}, var::SymbolLike) where {B,T,S} - ImmutableDensePolynomial{B,T,Symbol(var)}(xs) -end - -function ImmutableDensePolynomial{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} - ImmutableDensePolynomial{B,T,Symbol(var)}(xs) -end - -function ImmutableDensePolynomial{B}(xs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {B,T} - ImmutableDensePolynomial{B,T,Symbol(var)}(xs) -end - -function ImmutableDensePolynomial{B,T,X}(xs) where {B,T,X} - cs = collect(T,xs) - ImmutableDensePolynomial{B,T,X}(cs) -end - -function ImmutableDensePolynomial{B,T}(xs; var::SymbolLike=Var(:x)) where {B,T} - cs = collect(T,xs) - ImmutableDensePolynomial{B,T,Var(var)}(cs) -end - - -function ImmutableDensePolynomial{B}(xs, var::SymbolLike=Var(:x)) where {B} - cs = collect(promote(xs...)) - T = eltype(cs) - ImmutableDensePolynomial{B,T,Symbol(var)}(cs) -end - -# constant -function ImmutableDensePolynomial{B,T,X,N}(c::S) where {B,T,X,N,S<:Number} - if N == 0 - if iszero(c) - throw(ArgumentError("Can't create zero-length polynomial")) - else - return zero(ImmutableDensePolynomial{B,T,X}) - end - end - cs = ntuple(i -> i == 1 ? T(c) : zero(T), Val(N)) - return ImmutableDensePolynomial{B,T,X,N}(cs) -end - @poly_register ImmutableDensePolynomial constructorof(::Type{<:ImmutableDensePolynomial{B}}) where {B} = ImmutableDensePolynomial{B} @@ -109,7 +64,6 @@ function Base.convert(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, N′′ = findlast(!iszero, p) isnothing(N′′) && return zero(ImmutableDensePolynomial{B,T,X,N}) N < N′′ && throw(ArgumentError("Wrong size")) - N > N′′ && return ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) end @@ -174,25 +128,6 @@ function normΔ(q1::ImmutableDensePolynomial{B}, q2::ImmutableDensePolynomial{B} return tot^(1/p) end -# function Base.isapprox(p1::ImmutableDensePolynomial{B,T,X}, p2::ImmutableDensePolynomial{B,T′,X}; -# atol=nothing, rtol = nothing -# ) where {B,T,T′,X} - -# (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons -# R = real(float(promote_type(T,T′))) -# atol = something(atol, zero(R)) -# rtol = something(rtol, Base.rtoldefault(R)) -# # copy over from abstractarray.jl -# Δ = normΔ(p1,p2) -# if isfinite(Δ) -# return Δ <= max(atol, rtol * max(norm(p1), norm(p2))) -# else -# for i in 0:max(degree(p1), degree(p2)) -# isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false -# end -# return true -# end -# end ## --- @@ -237,8 +172,8 @@ minimumexponent(::Type{<:ImmutableDensePolynomial}) = 0 ## --- +degree(p::ImmutableDensePolynomial{B,T,X,0}) where {B,T,X} = -1 function degree(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} - iszero(N) && return -1 i = findlast(!iszero, p.coeffs) isnothing(i) && return -1 return i - 1 @@ -248,13 +183,15 @@ end Base.zero(::Type{<:ImmutableDensePolynomial{B,T,X}}) where {B,T,X} = ImmutableDensePolynomial{B,T,X,0}(()) -Base.zero(::Type{ImmutableDensePolynomial{B,T,X,N}}) where {B,T,X,N} = - ImmutableDensePolynomial{B,T,X,0}(()) +function Base.zero(P::Type{ImmutableDensePolynomial{B,T,X,N}}) where {B,T,X,N} + xs = _zeros(P, zero(T), N) + ImmutableDensePolynomial{B,T,X,N}(xs) +end -function basis(P::Type{<:ImmutableDensePolynomial{B,T,X}}, i::Int) where {B,T,X} - xs = zeros(T, i + 1) - xs[end] = 1 - ImmutableDensePolynomial{B,T,X}(xs) +function basis(P::Type{<:ImmutableDensePolynomial{B}}, i::Int) where {B} + xs = _zeros(P, zero(eltype(P)), i + 1) + @set! xs[end] = 1 + ImmutableDensePolynomial{B,eltype(P),indeterminate(P)}(xs) end coeffs(p::ImmutableDensePolynomial) = p.coeffs @@ -284,7 +221,7 @@ function _tuple_combine(op, p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDe R = promote_type(T,S) P = ImmutableDensePolynomial{B,R,X} - iszero(p) && return zero(P) + iszero(p) && return zero(P{N}) #xs = ntuple(i -> i <= M ? R(op(p.coeffs[i],q.coeffs[i])) : R(p.coeffs[i]), Val(N)) xs = _tuple_combine(op, p.coeffs, q.coeffs) P{N}(xs) diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index f2126d4c..48c06139 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -1,10 +1,10 @@ # * has order # * leading 0s are trimmed -# * pass checked::Val(false) to bypass trimmings +# * pass check::Val(false) to bypass trimmings struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} coeffs::Vector{T} order::Int # lowest degree, typically 0 - function MutableDensePolynomial{B,T,X}(cs, order::Int=0) where {B,T,X} + function MutableDensePolynomial{B,T,X}(cs::AbstractVector{S}, order::Int=0) where {B,T,X,S} if Base.has_offset_axes(cs) @warn "Using the axis offset of the coefficient vector" cs, order = cs.parent, firstindex(cs) @@ -20,41 +20,18 @@ struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} end new{B,T,Symbol(X)}(xs, order) end - function MutableDensePolynomial{B,T,X}(checked::Val{false}, cs::Vector{T}, order::Int=0) where {B,T,X} + function MutableDensePolynomial{B,T,X}(check::Val{false}, cs::Vector{T}, order::Int=0) where {B,T,X} if Base.has_offset_axes(cs) @warn "Using the axis offset of the coefficient vector" cs, order = cs.parent, first(cs.offsets) end new{B,T,Symbol(X)}(cs, order) end - function MutableDensePolynomial{B,T,X}(checked::Val{true}, cs::Vector{T}, order::Int=0) where {B,T,X} + function MutableDensePolynomial{B,T,X}(check::Val{true}, cs::Vector{T}, order::Int=0) where {B,T,X} MutableDensePolynomial{B,T,X}(cs, order) end end -function MutableDensePolynomial{B,T}(xs::AbstractVector{S}, order::Int=0, var::SymbolLike=Var(:x)) where {T, S, B} - MutableDensePolynomial{B,T,Symbol(var)}(xs, order) -end - -function MutableDensePolynomial{B}(xs::AbstractVector{T}, order::Int=0, var::SymbolLike=Var(:x)) where {B, T} - MutableDensePolynomial{B,T,Symbol(var)}(xs, order) -end - - -function MutableDensePolynomial{B,T}(xs, var::SymbolLike=Var(:x)) where {B,T} - MutableDensePolynomial{B,T,Symbol(var)}(xs, 0) -end - -function MutableDensePolynomial{B}(xs, order::Int=0, var::SymbolLike=Var(:x)) where {B} - cs = collect(promote(xs...)) - T = eltype(cs) - MutableDensePolynomial{B, T, Symbol(var)}(cs, order) -end - -function MutableDensePolynomial{B}(xs, var::SymbolLike) where {B} - MutableDensePolynomial{B}(xs, 0, var) -end - function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDensePolynomial{B,T,X}, S} R = eltype(as) Q = MutableDensePolynomial{B, R, X} diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index f2faa5ba..087368c0 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -40,35 +40,6 @@ function MutableSparsePolynomial{B,T,X}(coeffs::AbstractVector{S}, order::Int=0) P(d) end -function MutableSparsePolynomial{B,T}(xs::AbstractVector{S}, order::Int, var::SymbolLike=Var(:x)) where {B,T,S} - MutableSparsePolynomial{B,T,Symbol(var)}(xs) -end - -function MutableSparsePolynomial{B,T}(xs::AbstractVector{S}, var::SymbolLike) where {B,T,S} - MutableSparsePolynomial{B,T,Symbol(var)}(xs) -end - -function MutableSparsePolynomial{B}(xs::AbstractVector{T}, order::Int, var::SymbolLike=Var(:x)) where {B,T} - MutableSparsePolynomial{B,T,Symbol(var)}(xs) -end - -function MutableSparsePolynomial{B}(xs::AbstractVector{T}, var::SymbolLike) where {B,T} - MutableSparsePolynomial{B,T,Symbol(var)}(xs) -end - -# iterable -function MutableSparsePolynomial{B,T}(xs, var::SymbolLike=Var(:x)) where {B,T} - cs = collect(T, xs) - cs = trim_trailing_zeros(cs) - MutableSparsePolynomial{B,T,Symbol(var)}(cs) -end - -function MutableSparsePolynomial{B}(xs, var::SymbolLike=Var(:x)) where {B} - cs = collect(xs) - cs = trim_trailing_zeros(cs) - MutableSparsePolynomial{B,eltype(cs),Symbol(var)}(cs) -end - # cs iterable of pairs; ensuring tight value of T function MutableSparsePolynomial{B}(cs::Tuple, var::SymbolLike=:x) where {B} diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 379e9793..81200fea 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -5,12 +5,15 @@ function print_basis(io::IO, p::AbstractUnivariatePolynomial{<:StandardBasis}, i print_unicode_exponent(io, i) end +Base.promote_rule(p::Type{<:AbstractUnivariatePolynomial{B}}, q::Type{AbstractUnivariatePolynomial{B′}}) where {B,B′} = + MutableDensePolynomial{StandardBasis} + # XXX For now need 3 convert methods for standard basis function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: AbstractUnivariatePolynomial{B}, Q<:AbstractUnivariatePolynomial{B}} if isa(q, PP) return q else - minimumexponent(P) <= minimumexponent(q) || + minimumexponent(P) <= firstindex(q) || throw(ArgumentError("a $P can not have a minimum exponent of $(minimumexponent(q))")) T = _eltype(P,q) X = indeterminate(P,q) @@ -188,7 +191,11 @@ end # new constructors taking order in second position #SparsePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T, X, S} = SparsePolynomial{T,X}(coeffs) -Polynomial{T, X}(coeffs::AbstractVector{S},order::Int) where {T, X, S} = Polynomial{T,X}(coeffs) #ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T,X,S} = ImmutablePolynomial{T,X}(coeffs) + + +Polynomial{T, X}(coeffs::AbstractVector{S},order::Int) where {T, X, S} = Polynomial{T,X}(coeffs) FactoredPolynomial{T,X}(coeffs::AbstractVector{S}, order::Int) where {T,S,X} = FactoredPolynomial{T,X}(coeffs) PnPolynomial{T, X}(coeffs::AbstractVector, order::Int) where {T, X} = PnPolynomial(coeffs) # for generic programming +PnPolynomial{T}(coeffs::AbstractVector, order::Int,var) where {T} = PnPolynomial(coeffs,var) # for generic programming +PnPolynomial(coeffs::AbstractVector, order::Int,var) = PnPolynomial(coeffs,var) # for generic programming diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 824eebba..943c0272 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -81,13 +81,6 @@ function Base.inv(p::MutableDensePolynomial{StandardBasis}) LaurentPolynomial{eltype(cs), indeterminate(p)}(cs, -m) end -## XXX ---- -#const Polynomial = MutableDensePolynomial{StandardBasis} -#export Polynomial - -const LaurentPolynomial = MutableDensePolynomial{StandardBasis} -export LaurentPolynomial - """ paraconj(p) @@ -119,11 +112,11 @@ true julia> Polynomials.paraconj(h)(z) ≈ (conj ∘ h ∘ conj ∘ inv)(z) true """ -function paraconj(p::LaurentPolynomial) +function paraconj(p::MutableDensePolynomial{B,T,X}) where {B <: StandardBasis, T,X} cs = p.coeffs ds = adjoint.(cs) n = degree(p) - LaurentPolynomial(reverse(ds), -n, indeterminate(p)) + MutableDensePolynomial{B,T,X}(reverse(ds), -n) end """ @@ -164,19 +157,27 @@ true ``` """ -function cconj(p::LaurentPolynomial) +function cconj(p::MutableDensePolynomial{B,T,X}) where {B <: StandardBasis, T,X} ps = conj.(coeffs(p)) - m,n = (extrema ∘ degreerange)(p) + m,n = firstindex(p), lastindex(p) for i in m:n if isodd(i) ps[i+1-m] *= -1 end end - LaurentPolynomial(ps, m, indeterminate(p)) + MutableDensePolynomial{B,T,X}(ps, m) end +## XXX ---- +#const Polynomial = MutableDensePolynomial{StandardBasis} +#export Polynomial + +const LaurentPolynomial = MutableDensePolynomial{StandardBasis} +export LaurentPolynomial + + # resolve ambiguity # function Base.convert(::Type{P}, q::Q) where {T, X, P<:LaurentPolynomial, B<:StandardBasis, Q<:AbstractUnivariatePolynomial{B, T, X}} # p = convert(Polynomial, q) diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl index 9db4adde..401960cd 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/standard-immutable.jl @@ -95,7 +95,7 @@ end end - +derivative(p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = p function derivative(p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} N == 0 && return p hasnan(p) && return ⟒(p)(zero(T)/zero(T),X) # NaN{T} @@ -104,7 +104,8 @@ function derivative(p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasi ImmutableDensePolynomial{StandardBasis,R,X,N-1}(cs) end - +integrate(p::ImmutableDensePolynomial{StandardBasis,T,X,0}) where {T,X} = + ImmutableDensePolynomial{StandardBasis,Base.promote_op(/,T,Int),X,1}((0/1,)) function integrate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X,N} N == 0 && return p # different type hasnan(p) && return ⟒(p)(zero(T)/zero(T), X) # NaN{T} diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index d6945b29..929de882 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -432,13 +432,13 @@ end @test pNULL^3 == pNULL @test pNULL * pNULL == pNULL - if P === Polynomial - # type stability of multiplication - @inferred 10 * pNULL - @inferred 10 * p0 - @inferred p2 * p2 - @inferred p2 * p2 - end + # if P === Polynomial + # # type stability of multiplication + # @inferred 10 * pNULL + # @inferred 10 * p0 + # @inferred p2 * p2 + # @inferred p2 * p2 + # end @test pNULL + 2 == p0 + 2 == 2 + p0 == P([2]) @test p2 - 2 == -2 + p2 == P([-1,1]) @@ -446,6 +446,55 @@ end end + # test inferrability + @testset "Inferrability" for P ∈ (ImmutablePolynomial, LaurentPolynomial, SparsePolynomial, Polynomial) + + x = [1,2,3] + T, S = Float64, Int + + @testset "constructors" begin + x = [1,2,3] + T, S = Float64, Int + if P == ImmutablePolynomial + x = (1,2,3) + @inferred P{T,:x,4}(x) + @inferred P{S,:x,4}(x) + @inferred P{T,:x,3}(x) + @inferred P{S,:x,3}(x) + end + + @inferred P{T,:x}(x) + @inferred P{S,:x}(x) + @inferred P{T}(x) + @inferred P{S}(x) + @inferred P(x) + end + + @testset "arithmetic" begin + for p ∈ (P(x), zero(P)) + q = P(x)^2 + @inferred -p + @inferred p + 2 + @inferred p * 2 + @inferred 2 * p + @inferred p/2 + @inferred p + q + @inferred p * q + @inferred p^2 + end + end + + if P != Polynomial # XXX + @testset "integrate/differentiation" begin + p = P(x) + @inferred integrate(p) + @inferred derivative(p) + end + end + + end + + @testset "generic arithmetics" begin P = Polynomial # define a set algebra @@ -895,7 +944,6 @@ end @testset for P1 in Ps p = P1(c) @testset for P2 in Psexact - @show P1, P2 @test convert(P2, p) == p end @test convert(FactoredPolynomial, p) ≈ p @@ -1179,7 +1227,8 @@ end @testset for P in (Polynomial, ImmutablePolynomial, SparsePolynomial, LaurentPolynomial) p,q = P([1,2], :x), P([1,2], :y) - P′′ = P == LaurentPolynomial ? P : P′ # different promotion rule + #P′′ = P == LaurentPolynomial ? P : P′ # different promotion rule + P′′ = P′ #XXX treat LaurentPolynomial no differently # * should promote to Polynomial type if mixed (save Laurent Polynomial) @testset "promote mixed polys" begin @@ -1527,7 +1576,7 @@ end p = Polynomial{Rational{Int}}([1, 4]) @test sprint(show, p) == "Polynomial(1//1 + 4//1*x)" - @testset for P in (Polynomial, ImmutablePolynomial) + @testset for P in (Polynomial, )# ImmutablePolynomial) # ImmutablePolynomial prints with Basis! p = P([1, 2, 3]) @test sprint(show, p) == "$P(1 + 2*x + 3*x^2)" @@ -1577,7 +1626,7 @@ end @test printpoly_to_string(Polynomial(BigInt[1,0,1], :y)) == "1 + y^2" # negative indices - @test printpoly_to_string(LaurentPolynomial([-1:3;], -2)) == "-x⁻² + 1 + 2*x + 3*x²" + @test printpoly_to_string(LaurentPolynomial([-1:3;], -2)) == "-x^-2 + 1 + 2*x + 3*x^2" # "-x⁻² + 1 + 2*x + 3*x²" @test printpoly_to_string(SparsePolynomial(Dict(.=>(-2:2, -1:3)))) == "-x^-2 + 1 + 2*x + 3*x^2" end From 987b7a159cdafb100c938dee0f1957f075c0e3d3 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sun, 23 Jul 2023 22:56:14 -0400 Subject: [PATCH 09/31] cleanup --- .github/workflows/downstream.yml | 7 +++++-- src/common.jl | 4 +++- src/promotions.jl | 31 ++----------------------------- 3 files changed, 10 insertions(+), 32 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index d3157ab9..5482f4f0 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -17,9 +17,12 @@ jobs: julia-version: [1,1.6] os: [ubuntu-latest] package: - - {user: jverzani, repo: SpecialPolynomials.jl, group: All} - {user: JuliaControl, repo: ControlSystems.jl, group: All} - + - {user: andreasvarga, repo: DescriptorSystems.jl, group: All} + - {user: JuliaDSP, repo: DSP.jl, group: All} + - {user: tkluck, repo: GaloisFields.jl, group: All} + - {user: jverzani, repo: SpecialPolynomials.jl, group: All} + - {user: JuliaGNI, repo: QuadratureRules.jl, group: All} steps: - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 diff --git a/src/common.jl b/src/common.jl index 6eb6e783..f7578e60 100644 --- a/src/common.jl +++ b/src/common.jl @@ -971,7 +971,9 @@ Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) Base.:+(p::AbstractPolynomial) = p # polynomial + scalar; implicit identification of c with c*one(p) -Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = scalar_add(p, c)#p + c * one(p) +Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = scalar_add(p, c) +scalar_add(p::AbstractPolynomial, c) = p + c * one(p) + function Base.:+(p::P, c::S) where {T,X, P<:AbstractPolynomial{T,X}, S} R = promote_type(T,S) diff --git a/src/promotions.jl b/src/promotions.jl index 278333ef..d43fbb3d 100644 --- a/src/promotions.jl +++ b/src/promotions.jl @@ -1,33 +1,6 @@ - -# do both ways to avoid an issue with the next set of promotion rules Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, - P<:MutableDensePolynomial{B,T,X}, - Q<:MutableSparsePolynomial{B,S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} -Base.promote_rule(::Type{Q}, ::Type{P}) where {B,T,S,X, - P<:MutableDensePolynomial{B,T,X}, - Q<:MutableSparsePolynomial{B,S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} -Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X,N, - P<:MutableDensePolynomial{B,T,X}, - Q<:ImmutableDensePolynomial{B,S,X,N}} = MutableDensePolynomial{B,promote_type(T,S),X} -Base.promote_rule(::Type{Q}, ::Type{P}) where {B,T,S,X,N, - P<:MutableDensePolynomial{B,T,X}, - Q<:ImmutableDensePolynomial{B,S,X,N}} = MutableDensePolynomial{B,promote_type(T,S),X} -Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X,N, - P<:MutableSparsePolynomial{B,T,X}, - Q<:ImmutableDensePolynomial{B,S,X,N}} = MutableSparsePolynomial{B,promote_type(T,S),X} -Base.promote_rule(::Type{Q}, ::Type{P}) where {B,T,S,X,N, - P<:MutableSparsePolynomial{B,T,X}, - Q<:ImmutableDensePolynomial{B,S,X,N}} = MutableSparsePolynomial{B,promote_type(T,S),X} - - -# XXX need both to work around more general promotion to Polynomial type -# Base.promote_rule(::Type{P},::Type{Q}) where {B<:StandardBasis,T,X, P<:AbstractUnivariatePolynomial{B,T,X}, -# S, Q<:AbstractPolynomial{S,X}} = -# MutableDensePolynomial{StandardBasis, promote_type(T, S), X} -# Base.promote_rule(::Type{Q},::Type{P}) where {B<:StandardBasis,T,X, P<:AbstractUnivariatePolynomial{B,T,X}, -# S, Q<:AbstractPolynomial{S,X}} = -# MutableDensePolynomial{StandardBasis, promote_type(T, S), X} - + P<:AbstractUnivariatePolynomial{B,T,X}, + Q<:AbstractUnivariatePolynomial{B,S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} # Methods to ensure that matrices of polynomials behave as desired Base.promote_rule(::Type{P},::Type{Q}) where {T,X, P<:AbstractPolynomial{T,X}, From 89e8e9f517d201ba3c0c956e304f0ceb19e6cb0e Mon Sep 17 00:00:00 2001 From: jverzani Date: Mon, 24 Jul 2023 08:20:51 -0400 Subject: [PATCH 10/31] fix bug in scalar_add --- src/standard-basis/standard-dense.jl | 7 +++--- test/StandardBasis.jl | 32 +++++++++++++++++++--------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 943c0272..52168453 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -8,16 +8,17 @@ end # scalar add -function scalar_add(c::S, p:: MutableDensePolynomial{StandardBasis,T,X}) where {S, T, X} +function scalar_add(c::S, p:: MutableDensePolynomial{B,T,X}) where {B<:StandardBasis, S, T, X} R = promote_type(T,S) - P = MutableDensePolynomial{StandardBasis,R,X} + P = MutableDensePolynomial{B,R,X} iszero(p) && return P([c], 0) iszero(c) && return convert(P, p) a,b = firstindex(p), lastindex(p) a′ = min(0,a) - cs = _zeros(p, zero(first(p.coeffs)+c), length(a′:b)) + b′ = max(0,b) + cs = _zeros(p, zero(first(p.coeffs)+c), length(a′:b′)) o = offset(p) + a - a′ for (i, cᵢ) ∈ pairs(p) cs[i+o] = cᵢ diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 2fd95350..2a3504ac 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -446,6 +446,7 @@ end end + # test inferrability @testset "Inferrability" for P ∈ (ImmutablePolynomial, LaurentPolynomial, SparsePolynomial, Polynomial) @@ -532,16 +533,27 @@ end @test p*q ==ᵟ P(im*[1,2,3]) end - # Laurent polynomials and scalar operations - cs = [1,2,3,4] - p = LaurentPolynomial(cs, -3) - @test p*3 == LaurentPolynomial(cs .* 3, -3) - @test 3*p == LaurentPolynomial(3 .* cs, -3) - - # LaurentPolynomial has an inverse for monomials - x = variable(LaurentPolynomial) - @test Polynomials.isconstant(x * inv(x)) - @test_throws ArgumentError inv(x + x^2) + @testset "Laurent" begin + P = LaurentPolynomial + x = variable(P) + x⁻ = inv(x) + p = P([1,2,3], -4) + @test p + 4 == P([1,2,3,0,4],-4) + p = P([1,2,3], 4) + @test p + 4 == P([4,0,0,0,1,2,3]) + @test P([1,2,3],-4) + P([1,2,3]) == P([1,2,3,0,1,2,3],-4) + + # Laurent polynomials and scalar operations + cs = [1,2,3,4] + p = LaurentPolynomial(cs, -3) + @test p*3 == LaurentPolynomial(cs .* 3, -3) + @test 3*p == LaurentPolynomial(3 .* cs, -3) + + # LaurentPolynomial has an inverse for monomials + x = variable(LaurentPolynomial) + @test Polynomials.isconstant(x * inv(x)) + @test_throws ArgumentError inv(x + x^2) + end # issue #395 @testset for P ∈ Ps From 16a6d0330570fa7507582e6cdf2b96dcfe231d52 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 27 Jul 2023 07:51:49 -0400 Subject: [PATCH 11/31] WIP: cleanup --- src/Polynomials.jl | 2 +- src/abstract-polynomial.jl | 61 ++++- src/abstract.jl | 2 +- src/common.jl | 27 +-- src/contrib.jl | 66 +++++- .../immutable-dense-polynomial.jl | 118 ++++++---- .../mutable-dense-polynomial.jl | 28 +-- .../mutable-sparse-polynomial.jl | 35 +-- src/polynomials/ChebyshevT.jl | 68 +----- src/polynomials/chebyshev.jl | 216 ++++++++++++++++++ src/polynomials/standard-basis.jl | 2 +- src/show.jl | 12 +- src/standard-basis/standard-basis.jl | 29 +-- src/standard-basis/standard-dense.jl | 22 +- src/standard-basis/standard-immutable.jl | 83 ++----- src/standard-basis/standard-sparse.jl | 23 +- test/StandardBasis.jl | 18 +- 17 files changed, 546 insertions(+), 266 deletions(-) create mode 100644 src/polynomials/chebyshev.jl diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 00191296..b0bf6823 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -42,7 +42,7 @@ include("standard-basis/standard-sparse.jl") include("promotions.jl") - +include("polynomials/chebyshev.jl") # wrong place # Rational functions diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index c03633aa..e84ede5a 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -6,11 +6,9 @@ abstract type AbstractUnivariatePolynomial{B, T, X} <: AbstractPolynomial{T,X} e abstract type AbstractBasis end function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where {B, T, P<:AbstractUnivariatePolynomial{B,T}} - if _iszero(pj) return false end pj = printsign(io, pj, first, mimetype) - if hasone(T) if !(_isone(pj) && !(showone(T) || j == 0)) printcoefficient(io, pj, j, mimetype) @@ -20,10 +18,17 @@ function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where end printproductsign(io, pj, j, mimetype) - printexponent(io, var, j, mimetype) + printbasis(io, P, j, mimetype) return true end +# overload basis_symbol for most types +# overload printbasis to see a subscript, as Tᵢ... or if there are parameters in basis +function printbasis(io::IO, ::Type{P}, j::Int, m::MIME) where {P <: AbstractUnivariatePolynomial} + print(io, basis_symbol(P)) + print(io, subscript_text(j, m)) +end + _convert(p::P, as) where {B,T,X,P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(as, firstindex(p)) ## idea is vector space stuff (scalar_add, scalar_mult, vector +/-, ^) goes here @@ -100,6 +105,7 @@ end #Base.zero(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),indeterminate(P)}) #Base.zero(::Type{P},var::SymbolLike) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),Symbol(var)}) + # the polynomial 1 # one(P) is basis dependent Base.one(p::P,args...) where {P <: AbstractUnivariatePolynomial} = one(P,args...) @@ -118,8 +124,20 @@ basis(::Type{P}, i::Int) where {B,P <: AbstractUnivariatePolynomial{B}} = basis( copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {B,T, X, S, Y, P <:AbstractUnivariatePolynomial{B,S,Y}} = ⟒(P){T, Symbol(X)}(p.coeffs) +# coefficients # return dense coefficients (vector or tuple) -coeffs(p::AbstractUnivariatePolynomial) = [p[i] for i ∈ firstindex(p):lastindex(p)] +# if laurent type, coefficients are just stored values, there may be an offset +# if not laurent type, then return coefficients p_0, p_1, ... padding out with zeros, as neede +# return Val(::Bool) to indicate if laurent type. This should compile away, unlike the check +laurenttype(P::Type{<:AbstractPolynomial}) = Val(minimumexponent(P) < 0) + +coeffs(p::P) where {P <: AbstractUnivariatePolynomial} = coeffs(laurenttype(P), p) +coeffs(laurent::Val{true}, p) = p.coeffs +function coeffs(laurent::Val{false}, p) + firstindex(p) == 0 && return p.coeffs + firstindex(p) > 0 && return [p[i] for i ∈ 0:lastindex(p)] + throw(ArgumentError("Polynomial type does not support negative degree terms")) +end # function isconstant(p::AbstractUnivariatePolynomial) # p₀ = trim_trailing_zeros(p) @@ -298,8 +316,36 @@ function derivative(p::AbstractUnivariatePolynomial, n::Int=1) end const differentiate = derivative +function fit(::Type{P}, + x::AbstractVector{T}, + y::AbstractVector{T}, + deg::AbstractVector, + cs::Dict; + kwargs...) where {T, P<:AbstractUnivariatePolynomial} + convert(P, fit(Polynomial, x, y, deg, cs; kwargs...)) +end + -# promote, promote_rule, handle constants +## Interface +## These must be implemented for a storage type / basis +# minimumexponent(::Type{P}) where {B,P<:AbstractUnivariatePolynomial{B}} = XXX() +# Base.one(::Type{P}) where {B,P<:AbstractUnivariatePolynomial{B}} = XXX() +# variable(::Type{P}) where {B,P<:AbstractUnivariatePolynomial{B}} = XXX() +# evalpoly(x, p::P) where {B,P<:AbstractUnivariatePolynomial{B}} = XXX() +# scalar_add(c, p::P) where {B,P<:AbstractUnivariatePolynomial{B}} = XXX() +# ⊗(p::P, q::Q) where {B,P<:AbstractUnivariatePolynomial{B},Q<:AbstractUnivariatePolynomial{B}} = XXX() + +# these *may* be implemented for a basis type +# * basis_symbol/printbasis +# * one, variable, constantterm, domain, mapdomain +# * derivative +# * integrate +# * divrem +# * vander +# * + +# promote, promote_rule, vector specification, untyped specification, handle constants, conversion of Q(p) +# poly composition, calling a polynomial macro poly_register(name) poly = esc(name) quote @@ -336,7 +382,12 @@ macro poly_register(name) $poly{B,T}(var::SymbolLike=Var(:x)) where {B,T} = variable($poly{B, T, Symbol(var)}) $poly{B}(var::SymbolLike=Var(:x)) where {B} = variable($poly{B}, Symbol(var)) + # conversion via P(q) + $poly{B,T,X}(c::AbstractPolynomial{S,Y}) where {B,T,X,S,Y} = convert($poly{B,T,X}, c) + $poly{B,T}(c::AbstractPolynomial{S,Y}) where {B,T,S,Y} = convert($poly{B,T}, c) $poly{B}(c::AbstractPolynomial{S,Y}) where {B,S,Y} = convert($poly{B}, c) + + # poly composition and evaluation (p::$poly)(x::AbstractPolynomial) = polynomial_composition(p, x) (p::$poly)(x) = evalpoly(x, p) end diff --git a/src/abstract.jl b/src/abstract.jl index 936d0f44..521ed1d5 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -63,7 +63,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 {T,X,P <: AbstractPolynomial{T,X}} = ⟒(P)(as, Var(X)) +_convert(p::P, as) where {T,X,P <: AbstractPolynomial{T,X}} = ⟒(P)(as, Var(X)) """ diff --git a/src/common.jl b/src/common.jl index f7578e60..fdbe0ef2 100644 --- a/src/common.jl +++ b/src/common.jl @@ -347,18 +347,19 @@ function truncate!(ps::Dict{Int,T}; nothing end -function truncate!(ps::NTuple{N,T}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {N,T} - #throw(ArgumentError("`truncate!` not defined.")) - thresh = norm(ps, Inf) * rtol + atol - for (i, pᵢ) ∈ enumerate(ps) - if abs(pᵢ) ≤ thresh - ps = _set(ps, i, zero(pᵢ)) - end - end - ps -end +truncate!(ps::NTuple; kwargs...) = throw(ArgumentError("`truncate!` not defined.")) +# function truncate!(ps::NTuple{N,T}; +# rtol::Real = Base.rtoldefault(real(T)), +# atol::Real = 0,) where {N,T} +# #throw(ArgumentError("`truncate!` not defined.")) +# thresh = norm(ps, Inf) * rtol + atol +# for (i, pᵢ) ∈ enumerate(ps) +# if abs(pᵢ) ≤ thresh +# ps = _set(ps, i, zero(pᵢ)) +# end +# end +# ps +# end _truncate(ps::NTuple{0}; kwargs...) = ps function _truncate(ps::NTuple{N,T}; @@ -585,7 +586,7 @@ Transform coefficients of `p` by applying a function (or other callables) `fn` t You can implement `real`, etc., to a `Polynomial` by using `map`. """ -Base.map(fn, p::P, args...) where {P<:AbstractPolynomial} = _convert(p, map(fn, coeffs(p),args...)) +Base.map(fn, p::P, args...) where {P<:AbstractPolynomial} = _convert(p, map(fn, coeffs(p), args...)) """ diff --git a/src/contrib.jl b/src/contrib.jl index e1ca2cac..1db406b0 100644 --- a/src/contrib.jl +++ b/src/contrib.jl @@ -37,8 +37,7 @@ module EvalPoly using LinearAlgebra function evalpoly(x::S, p::Tuple) where {S} if @generated - N = length(p.parameters) - ex = :(p[end]*_one(S)) + ex = :(p[N]*_one(x)) for i in N-1:-1:1 ex = :(_muladd($ex, x, p[$i])) end @@ -52,15 +51,13 @@ evalpoly(x, p::AbstractVector) = _evalpoly(x, p) # https://discourse.julialang.org/t/i-have-a-much-faster-version-of-evalpoly-why-is-it-faster/79899; improvement *and* closes #313 function _evalpoly(x::S, p) where {S} - - i = lastindex(p) + a,i = firstindex(p), lastindex(p) @inbounds out = p[i] * _one(x) i -= 1 - while i >= firstindex(p) + while i >= a #firstindex(p) @inbounds out = _muladd(out, x, p[i]) i -= 1 end - return out end @@ -206,3 +203,60 @@ function Base.in(x, I::Interval{T,L,R}) where {T, L, R} end Base.isopen(I::Interval{T,L,R}) where {T,L,R} = (L != Closed && R != Closed) + + + +#= +zseries -- for ChebyshevT example +=# + +function _c_to_z(cs::AbstractVector{T}) where {T} + n = length(cs) + U = typeof(one(T) / 2) + zs = zeros(U, 2n - 1) + zs[n:end] = cs ./ 2 + return zs .+ reverse(zs) +end + +function _z_to_c(z::AbstractVector{T}) where {T} + n = (length(z) + 1) ÷ 2 + cs = z[n:end] + cs[2:n] *= 2 + return cs +end + +function _z_division(z1::AbstractVector{T}, z2::AbstractVector{S}) where {T,S} + R = eltype(one(T) / one(S)) + length(z1) + length(z2) + if length(z2) == 1 + z1 ./= z2 + return z1, zero(R) + elseif length(z1) < length(z2) + return zero(R), R.(z1) + end + dlen = length(z1) - length(z2) + scl = z2[1] + z2 ./= scl + quo = Vector{R}(undef, dlen + 1) + i = 1 + j = dlen + 1 + while i < j + r = z1[i] + quo[i] = z1[i] + quo[end - i + 1] = r + tmp = r .* z2 + z1[i:i + length(z2) - 1] .-= tmp + z1[j:j + length(z2) - 1] .-= tmp + i += 1 + j -= 1 + end + + r = z1[i] + quo[i] = r + tmp = r * z2 + z1[i:i + length(z2) - 1] .-= tmp + quo ./= scl + rem = z1[i + 1:i - 2 + length(z2)] + return quo, rem +end diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index 0d571df4..48d5563e 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -1,5 +1,15 @@ # Try to keep length based on N,M so no removal of trailing zeros by default # order is ignored, firstindex is always 0 + +""" + ImmutableDensePolynomial{B,T,X,N} + +This polynomial type uses an `NTuple{N,T}` to store the coefficients of a polynomial relative to the basis `B` with indeterminate `X`. +For type stability, these polynomials may have trailing zeros. For example, the polynomial `p-p` will have the same size +coefficient tuple as `p`. The `chop` function will trim off trailing zeros, when desired. + +Immutable is a bit of a misnomer, as using the `@set!` macro from `Setfield.jl` one can modify elements, as in `@set! p[i] = value`. +""" struct ImmutableDensePolynomial{B,T,X,N} <: AbstractUnivariatePolynomial{B,T,X} coeffs::NTuple{N,T} function ImmutableDensePolynomial{B,T,X,N}(cs::NTuple{N,S}) where {B,N,T,X,S} @@ -71,7 +81,18 @@ end Base.copy(p::ImmutableDensePolynomial) = p Base.similar(p::ImmutableDensePolynomial, args...) = p.coeffs -## chop + +# not type stable, as N is value dependent +function trim_trailing_zeros(cs::Tuple) + isempty(cs) && return cs + !iszero(last(cs)) && return cs + i = findlast(!iszero, cs) + i == nothing && return () + xs = ntuple(Base.Fix1(getindex,cs), Val(i)) + xs +end + +## chop. Also, not type stable function Base.chop(p::ImmutableDensePolynomial{B,T,X,N}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) where {B,T,X,N} @@ -86,9 +107,10 @@ function Base.chop(p::ImmutableDensePolynomial{B,T,X,N}; ImmutableDensePolynomial{B,T,X,N′}(xs) end -# misnamed! +# misnamed, should be chop!! chop!(p::ImmutableDensePolynomial; kwargs...) = chop(p; kwargs...) +# truncate!!; keeps length replacing values with zeros function truncate!(p::ImmutableDensePolynomial{B,T,X,N}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) where {B,T,X,N} @@ -106,16 +128,6 @@ function truncate!(p::ImmutableDensePolynomial{B,T,X,N}; end -# not type stable, as N is value dependent -function trim_trailing_zeros(cs::Tuple) - isempty(cs) && return cs - !iszero(last(cs)) && return cs - i = findlast(!iszero, cs) - i == nothing && return () - xs = ntuple(Base.Fix1(getindex,cs), Val(i)) - xs -end - # isapprox helper function normΔ(q1::ImmutableDensePolynomial{B}, q2::ImmutableDensePolynomial{B}, p::Real = 2) where {B} iszero(q1) && return norm(q2, p) @@ -134,6 +146,8 @@ end _zeros(::Type{<:ImmutableDensePolynomial}, z::S, N) where {S} = ntuple(_ -> zero(S), Val(N)) +minimumexponent(::Type{<:ImmutableDensePolynomial}) = 0 +laurenttype(::Type{<:ImmutableDensePolynomial}) = Val(false) Base.firstindex(p::ImmutableDensePolynomial) = 0 Base.lastindex(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = N - 1 @@ -149,8 +163,17 @@ function Base.getindex(p::ImmutableDensePolynomial{B,T,X,N}, i::Int) where {B,T, p.coeffs[i + offset(p)] end +# need to call with Setfield as in +# @set! p[i] = value +function Base.setindex(p::ImmutableDensePolynomial{B,T,X,N}, value, i::Int) where {B,T,X,N} + ps = p.coeffs + @set! ps[i] = value + ImmutableDensePolynomial{B,T,X,N}(ps) +end + Base.setindex!(p::ImmutableDensePolynomial, value, i::Int) = - throw(ArgumentError("ImmutableDensePolynomial has no setindex! method")) + throw(ArgumentError("Use the `@set!` macro from `Setfield` to mutate coefficients.")) + # can't promote to same N if trailing zeros function Base.:(==)(p1::ImmutableDensePolynomial{B}, p2::ImmutableDensePolynomial{B}) where {B} @@ -168,7 +191,6 @@ function Base.:(==)(p1::ImmutableDensePolynomial{B}, p2::ImmutableDensePolynomia return true end -minimumexponent(::Type{<:ImmutableDensePolynomial}) = 0 ## --- @@ -194,8 +216,6 @@ function basis(P::Type{<:ImmutableDensePolynomial{B}}, i::Int) where {B} ImmutableDensePolynomial{B,eltype(P),indeterminate(P)}(xs) end -coeffs(p::ImmutableDensePolynomial) = p.coeffs - ## Vector space operations @@ -214,20 +234,34 @@ function Base.:-(p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomi _tuple_combine(-, p, q) end -# handle +, -; Assum N >= M +# handle +, -; Assume N >= M +_tuple_combine(op, p::ImmutableDensePolynomial{B,T,X,0}, q::ImmutableDensePolynomial{B,S,X,M}) where{B,X,T,S,M} = + zero(ImmutableDensePolynomial{B,T,X,0}) function _tuple_combine(op, p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomial{B,S,X,M}) where{B,X,T,S,N,M} - @assert N >= M - R = promote_type(T,S) - P = ImmutableDensePolynomial{B,R,X} - - iszero(p) && return zero(P{N}) - #xs = ntuple(i -> i <= M ? R(op(p.coeffs[i],q.coeffs[i])) : R(p.coeffs[i]), Val(N)) xs = _tuple_combine(op, p.coeffs, q.coeffs) - P{N}(xs) + R = eltype(xs) + ImmutableDensePolynomial{B,R,X,N}(xs) +end + + +# scalar +scalar_mult(p::ImmutableDensePolynomial{B,T,X,0}, c::S) where {B,T,X,S} = zero(ImmutableDensePolynomial{B,T,X,0}) +function scalar_mult(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S,N} + cs = p.coeffs .* (c,) + R = eltype(cs) + return ImmutableDensePolynomial{B,R,X,N}(cs) +end +scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,0}) where {B,T,X,S} = zero(ImmutableDensePolynomial{B,T,X,0}) +function scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,S,N} + cs = (c,) .* p.coeffs + R = eltype(cs) + return ImmutableDensePolynomial{B,R,X,N}(cs) end + +## --- # Padded vector combination of two homogeneous tuples assuming N ≥ M @generated function _tuple_combine(op, p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} @@ -235,7 +269,7 @@ end for i in 1:M exprs[i] = :(op(p1[$i],p2[$i])) end - for i in M+1:N + for i in (M+1):N exprs[i] =:(p1[$i]) end @@ -247,20 +281,26 @@ end end -# scalar +## 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 fastconv(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 isnothing(exprs[k]) + exprs[k] = :(p1[$i] * p2[$j]) + else + exprs[k] = :(muladd(p1[$i], p2[$j], $(exprs[k]))) + end + end + end -function scalar_mult(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S,N} - iszero(N) && return zero(ImmutableDensePolynomial{B,T,X}) - iszero(c) && ImmutableDensePolynomial{B}([p[0] .* c], X) - cs = p.coeffs .* (c,) - R = eltype(cs) - return ImmutableDensePolynomial{B,R,X,N}(cs) -end + return quote + Base.@_inline_meta # 1.8 deprecation + tuple($(exprs...)) + end -function scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,S,N} - iszero(N) && return zero(ImmutableDensePolynomial{B,T,X}) - iszero(c) && ImmutableDensePolynomial{B}([c .* p[0]],X) - cs = (c,) .* p.coeffs - R = eltype(cs) - return ImmutableDensePolynomial{B,R,X,N}(cs) end diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index e5db5aef..968b476e 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -1,6 +1,13 @@ -# * has order -# * leading 0s are trimmed -# * pass check::Val(false) to bypass trimmings +""" + MutableDensePolynomial{B,T,X} + +This polynomial type essentially uses an offset vector (`Vector{T}`,`order`) to store the coefficients of a polynomial relative to the basis `B` with indeterminate `X`. + +The typical offset is to have `0` as the order, but, say, to accomodate Laurent polynomials, or more efficient storage of basis elements any order may be specified. + +This type trims trailing zeros and when the offset is not 0, trims the leading zeros. + +""" struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} coeffs::Vector{T} order::Int # lowest degree, typically 0 @@ -13,7 +20,9 @@ struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} i = findlast(!iszero, cs) if i == nothing xs = T[] - else + elseif iszero(order) + xs = T[cs[i] for i ∈ 1:i] + else # shift if not 0 j = findfirst(!iszero, cs) xs = T[cs[i] for i ∈ j:i] order = order + j - 1 @@ -42,11 +51,6 @@ end @poly_register MutableDensePolynomial constructorof(::Type{<:MutableDensePolynomial{B}}) where {B} = MutableDensePolynomial{B} -# # promote to mutable dense -# Base.promote_rule(::Type{<:AbstractUnivariatePolynomial{B,T,X}},::Type{<:AbstractUnivariatePolynomial{B,S,X}}) where {B,T,S,X} = -# MutableDensePolynomial{B, promote_type(T, S), X} - - ## --- ## Generics for polynomials @@ -102,12 +106,10 @@ function degree(p::MutableDensePolynomial) firstindex(p) + i - 1 end -# zero, one, variable, basis -# Base.zero(::Type{MutableDensePolynomial{B,T,X}}) where {B,T,X} = -# MutableDensePolynomial{B,T,X}(T[]) -coeffs(p::MutableDensePolynomial) = p.coeffs +laurenttype(::Type{<:MutableDensePolynomial}) = Val(true) +basis(::Type{MutableDensePolynomial{B,T,X}},i::Int) where {B,T,X} = MutableDensePolynomial{B,T,X}([1],i) function trim_trailing_zeros(cs::Vector{T}) where {T} isempty(cs) && return cs diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index 087368c0..ace71215 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -1,5 +1,9 @@ -# dictionary to store (i, cᵢ) -# ensure cᵢ ≠ 0 in constructor +""" + +This polynomial type uses an `Dict{Int,T}` to store the coefficients of a polynomial relative to the basis `B` with indeterminate `X`. +Explicit `0` coefficients are not stored. This type can be used for Laurent polynomials. + +""" struct MutableSparsePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B, T,X} coeffs::Dict{Int, T} function MutableSparsePolynomial{B,T,X}(cs::AbstractDict{Int,S},order::Int=0) where {B,T,S,X} @@ -70,6 +74,7 @@ constructorof(::Type{<:MutableSparsePolynomial{B}}) where {B} = MutableSparsePol ## --- minimumexponent(::Type{<:MutableSparsePolynomial}) = typemin(Int) +laurenttype(::Type{<:MutableSparsePolynomial}) = Val(true) Base.copy(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(copy(p.coeffs)) @@ -99,6 +104,19 @@ function Base.setindex!(p::MutableSparsePolynomial{B,T,X}, value, i::Int) where p.coeffs[i] = value end +# return coeffs as a vector +# use p.coeffs to get Dictionary +function coeffs(p::MutableSparsePolynomial{B,T}) where {B,T} + a,b = firstindex(p), lastindex(p) + cs = zeros(T, length(a:b)) + for k in sort(collect(keys(p.coeffs))) + v = p.coeffs[k] + cs[k - a + 1] = v + end + cs +end + + hasnan(p::MutableSparsePolynomial) = any(hasnan, values(p.coeffs)) Base.pairs(p::MutableSparsePolynomial) = pairs(p.coeffs) @@ -139,19 +157,6 @@ function isconstant(p::MutableSparsePolynomial) n == 1 && haskey(p.coeffs, 0) end -# much faster than default -function scalar_add(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} - c₀ = c + p[0] - R = eltype(c₀) - P = MutableSparsePolynomial{B,R,X} - D = convert(Dict{Int, R}, copy(p.coeffs)) - if iszero(c₀) - delete!(D,0) - else - @inbounds D[0] = c₀ - end - return P(Val(false), D) -end function scalar_mult(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl index 8c6d5ecb..0fa28147 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/ChebyshevT.jl @@ -63,7 +63,7 @@ function Base.convert(P::Type{<:Polynomial}, ch::ChebyshevT) Q = ⟒(P){T,X} if length(ch) < 3 - return Q(ch.coeffs) + return Q(coeffs(ch)) end c0 = Q(ch[end - 1]) @@ -219,7 +219,8 @@ function companion(p::ChebyshevT{T}) where T diag = vcat(√0.5, fill(R(0.5), d - 2)) comp = diagm(1 => diag, -1 => diag) - monics = p.coeffs ./ p.coeffs[end] + ps = coeffs(p) + monics = ps ./ ps[end] comp[:, end] .-= monics[1:d] .* scl ./ scl[end] ./ 2 return R.(comp) end @@ -246,8 +247,8 @@ end 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) + z1 = _c_to_z(coeffs(p1)) + z2 = _c_to_z(coeffs(p2)) prod = fastconv(z1, z2) cs = _z_to_c(prod) ret = ChebyshevT(cs,X) @@ -269,8 +270,8 @@ function Base.divrem(num::ChebyshevT{T,X}, den::ChebyshevT{S,Y}) where {T,X,S,Y} return num ./ den[end], zero(P) end - znum = _c_to_z(num.coeffs) - zden = _c_to_z(den.coeffs) + znum = _c_to_z(coeffs(num)) + zden = _c_to_z(coeffs(den)) quo, rem = _z_division(znum, zden) q_coeff = _z_to_c(quo) r_coeff = _z_to_c(rem) @@ -288,58 +289,3 @@ function showterm(io::IO, ::Type{ChebyshevT{T,X}}, pj::T, var, j, first::Bool, m end return true end - - -#= -zseries =# - -function _c_to_z(cs::AbstractVector{T}) where {T} - n = length(cs) - U = typeof(one(T) / 2) - zs = zeros(U, 2n - 1) - zs[n:end] = cs ./ 2 - return zs .+ reverse(zs) -end - -function _z_to_c(z::AbstractVector{T}) where {T} - n = (length(z) + 1) ÷ 2 - cs = z[n:end] - cs[2:n] *= 2 - return cs -end - -function _z_division(z1::AbstractVector{T}, z2::AbstractVector{S}) where {T,S} - R = eltype(one(T) / one(S)) - length(z1) - length(z2) - if length(z2) == 1 - z1 ./= z2 - return z1, zero(R) - elseif length(z1) < length(z2) - return zero(R), R.(z1) - end - dlen = length(z1) - length(z2) - scl = z2[1] - z2 ./= scl - quo = Vector{R}(undef, dlen + 1) - i = 1 - j = dlen + 1 - while i < j - r = z1[i] - quo[i] = z1[i] - quo[end - i + 1] = r - tmp = r .* z2 - z1[i:i + length(z2) - 1] .-= tmp - z1[j:j + length(z2) - 1] .-= tmp - i += 1 - j -= 1 - end - - r = z1[i] - quo[i] = r - tmp = r * z2 - z1[i:i + length(z2) - 1] .-= tmp - quo ./= scl - rem = z1[i + 1:i - 2 + length(z2)] - return quo, rem -end diff --git a/src/polynomials/chebyshev.jl b/src/polynomials/chebyshev.jl new file mode 100644 index 00000000..9b95c3b0 --- /dev/null +++ b/src/polynomials/chebyshev.jl @@ -0,0 +1,216 @@ +# Example of using mutable dense container with a different basis +struct ChebyshevTBasis <: AbstractBasis end + +# This is the same as ChebyshevT +#const ChebyshevT = MutableDensePolynomial{ChebyshevTBasis} +#export ChebyshevT + +basis_symbol(::Type{<:AbstractUnivariatePolynomial{ChebyshevTBasis}}) = "T" + +function Base.convert(P::Type{<:Polynomial}, ch::MutableDensePolynomial{ChebyshevTBasis}) + + T = _eltype(P,ch) + X = indeterminate(P,ch) + Q = ⟒(P){T,X} + + d = lastindex(ch) + if d ≤ 1 # T₀, T₁ = 1, x + return Q(coeffs(ch)) + end + + c0 = Q(ch[end - 1]) + c1 = Q(ch[end]) + x = variable(Q) + @inbounds for i in d:-1:2 + tmp = c0 + c0 = Q(ch[i - 2]) - c1 + c1 = tmp + c1 * x * 2 + end + return c0 + c1 * x +end + +function Base.convert(C::Type{<:MutableDensePolynomial{ChebyshevTBasis}}, p::Polynomial) + x = variable(C) + isconstant(p) || assert_same_variable(indeterminate(x),indeterminate(p)) + p(x) +end + +# lowest degree is always 0 +laurenttype(::Type{<:MutableDensePolynomial{ChebyshevTBasis}}) = Val(false) +minimumexponent(::Type{<:MutableDensePolynomial{ChebyshevTBasis}}) = 0 +domain(::Type{<:MutableDensePolynomial{ChebyshevTBasis}}) = Interval(-1, 1) + +constantterm(p::MutableDensePolynomial{ChebyshevTBasis}) = p(0) +function Base.one(::Type{P}) where {P<:MutableDensePolynomial{ChebyshevTBasis}} + T,X = eltype(P), indeterminate(P) + ⟒(P){T,X}(ones(T,1)) +end +function variable(::Type{P}) where {P<:MutableDensePolynomial{ChebyshevTBasis}} + T,X = eltype(P), indeterminate(P) + ⟒(P){T,X}([zero(T), one(T)]) +end + +""" + (::MutableDensePolynomial{ChebyshevTBasis})(x) + +Evaluate the Chebyshev polynomial at `x`. If `x` is outside of the domain of [-1, 1], an error will be thrown. The evaluation uses Clenshaw Recursion. + +# Examples +```jldoctest ChebyshevT +julia> using Polynomials + +julia> c = ChebyshevT([2.5, 1.5, 1.0]) +ChebyshevT(2.5⋅T_0(x) + 1.5⋅T_1(x) + 1.0⋅T_2(x)) + +julia> c(0) +1.5 + +julia> c.(-1:0.5:1) +5-element Vector{Float64}: + 2.0 + 1.25 + 1.5 + 2.75 + 5.0 +``` +""" +function evalpoly(x::S, ch::MutableDensePolynomial{ChebyshevTBasis}) where {S} + x ∉ domain(ch) && throw(ArgumentError("$x outside of domain")) + evalpoly(x, ch, false) +end + +function evalpoly(x::AbstractPolynomial, ch::MutableDensePolynomial{ChebyshevTBasis}) + evalpoly(x, ch, false) +end + +# no checking, so can be called directly through any third argument +function evalpoly(x::S, ch::MutableDensePolynomial{ChebyshevTBasis,T}, checked) where {T,S} + R = promote_type(T, S) + length(ch) == 0 && return zero(R) + length(ch) == 1 && return R(ch[0]) + c0 = ch[end - 1] + c1 = ch[end] + @inbounds for i in lastindex(ch) - 2:-1:0 + c0, c1 = ch[i] - c1, c0 + c1 * 2x + end + return R(c0 + c1 * x) +end + +# scalar + +function scalar_add(c::S, p::MutableDensePolynomial{B,T,X}) where {B<:ChebyshevTBasis,T,X, S<:Scalar} + R = promote_type(T,S) + cs = collect(R, values(p)) + cs[1] += c + MutableDensePolynomial{ChebyshevTBasis,R,X}(cs) +end + +# product +function ⊗(p1::MutableDensePolynomial{B,T,X}, p2::MutableDensePolynomial{B,T,X}) where {B<:ChebyshevTBasis,T,X} + z1 = _c_to_z(coeffs(p1)) + z2 = _c_to_z(coeffs(p2)) + prod = fastconv(z1, z2) + cs = _z_to_c(prod) + ret = MutableDensePolynomial{ChebyshevTBasis}(cs,X) + return ret +end + +function derivative(p::P, order::Integer = 1) where {B<:ChebyshevTBasis,T,X,P<:MutableDensePolynomial{B,T,X}} + order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) + R = eltype(one(T)/1) + Q = MutableDensePolynomial{ChebyshevTBasis,R,X} + order == 0 && return convert(Q, p) + hasnan(p) && return Q(R[NaN]) + order > length(p) && return zero(Q) + + + q = convert(P{R,X}, copy(p)) + n = length(p) + der = Vector{R}(undef, n) + + for j in n:-1:3 + der[j] = 2j * q[j] + q[j - 2] += j * q[j] / (j - 2) + end + if n > 1 + der[2] = 4q[2] + end + der[1] = q[1] + + pp = Q(der) + return order > 1 ? derivative(pp, order - 1) : pp + +end + +function integrate(p::P) where {B<:ChebyshevTBasis,T,X,P<:MutableDensePolynomial{B,T,X}} + R = eltype(one(T) / 1) + Q = MutableDensePolynomial{B,R,X} + if hasnan(p) + return Q([NaN]) + end + n = length(p) + if n == 1 + return Q([zero(R), p[0]]) + end + a2 = Vector{R}(undef, n + 1) + a2[1] = zero(R) + a2[2] = p[0] + a2[3] = p[1] / 4 + @inbounds for i in 2:n - 1 + a2[i + 2] = p[i] / (2 * (i + 1)) + a2[i] -= p[i] / (2 * (i - 1)) + end + + return Q(a2) +end + +function vander(P::Type{<:MutableDensePolynomial{ChebyshevTBasis}}, x::AbstractVector{T}, n::Integer) where {T <: Number} + A = Matrix{T}(undef, length(x), n + 1) + A[:, 1] .= one(T) + if n > 0 + A[:, 2] .= x + @inbounds for i in 3:n + 1 + A[:, i] .= A[:, i - 1] .* 2x .- A[:, i - 2] + end + end + return A +end + +function companion(p::MutableDensePolynomial{ChebyshevTBasis,T}) where T + d = length(p) - 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)) + + scl = vcat(1.0, fill(R(√0.5), d - 1)) + + diag = vcat(√0.5, fill(R(0.5), d - 2)) + comp = diagm(1 => diag, + -1 => diag) + monics = coeffs(ps) ./ coeffs(p)[end] + comp[:, end] .-= monics[1:d] .* scl ./ scl[end] ./ 2 + return R.(comp) +end + +function Base.divrem(num::MutableDensePolynomial{ChebyshevTBasis}{T,X}, + den::MutableDensePolynomial{ChebyshevTBasis}{S,Y}) where {T,X,S,Y} + assert_same_variable(num, den) + n = length(num) - 1 + m = length(den) - 1 + + R = typeof(one(T) / one(S)) + P = MutableDensePolynomial{ChebyshevTBasis}{R,X} + + if n < m + return zero(P), convert(P, num) + elseif m == 0 + den[0] ≈ 0 && throw(DivideError()) + return num ./ den[end], zero(P) + end + + znum = _c_to_z(coeffs(num)) + zden = _c_to_z(coeffs(den)) + quo, rem = _z_division(znum, zden) + q_coeff = _z_to_c(quo) + r_coeff = _z_to_c(rem) + return P(q_coeff), P(r_coeff) +end diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index 18509881..2ddf4dc1 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -47,7 +47,7 @@ julia> p.(0:3) function evalpoly(x, p::StandardBasisPolynomial{T}) where {T} # the zero polynomial is a *special case* iszero(p) && return zero(x) * zero(T) - EvalPoly.evalpoly(x, p.coeffs) # allows broadcast issue #209 + EvalPoly.evalpoly(x, coeffs(p)) # allows broadcast issue #209 end constantterm(p::StandardBasisPolynomial) = p[0] diff --git a/src/show.jl b/src/show.jl index 92e17bea..0f773ed4 100644 --- a/src/show.jl +++ b/src/show.jl @@ -306,16 +306,26 @@ end exponent_text(i, ::MIME) = "^$(i)" exponent_text(i, ::MIME"text/html") = "$(i)" exponent_text(i, ::MIME"text/latex") = "^{$(i)}" +subscript_text(i, ::MIME) = "_$(i)" +subscript_text(i, ::MIME"text/html") = "$(i)" +subscript_text(i, ::MIME"text/latex") = "_{$(i)}" + function printexponent(io, var, i, mimetype::MIME) if i == 0 return elseif i == 1 - print(io,var) + print(io, var) else print(io, var, exponent_text(i, mimetype)) end end +function printsubscript(io, var, i, mimetype::MIME) + print(io, var, subscript_text(i, mimetype)) +end + +ascii_exponent(io, j) = print(io, "^", j) +ascii_subscript(io, j) = print(io, "_", j) function unicode_exponent(io, j) a = ("⁻","","","⁰","¹","²","³","⁴","⁵","⁶","⁷","⁸","⁹") diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 048f093e..4dfafba1 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -1,8 +1,11 @@ struct StandardBasis <: AbstractBasis end -function print_basis(io::IO, p::AbstractUnivariatePolynomial{<:StandardBasis}, i) - print(io, X) - print_unicode_exponent(io, i) +basis_symbol(::Type{<:AbstractUnivariatePolynomial{StandardBasis}}) = "x" +function printbasis(io::IO, ::Type{P}, j::Int, m::MIME) where {B<:StandardBasis, P <: AbstractUnivariatePolynomial{B}} + iszero(j) && return # no x^0 + print(io, basis_symbol(P)) + hasone(typeof(j)) && isone(j) && return # no 2x^1, just 2x + print(io, exponent_text(j, m)) end @@ -22,6 +25,7 @@ function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: Abstract return ⟒(P){T,X}([q[i] for i in 0:lastindex(q)]) # full poly end end + function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B<:StandardBasis,T,X, Q<:AbstractUnivariatePolynomial{B,T,X}} minimumexponent(P) > firstindex(q) && throw(ArgumentError("Degree of polynomial less than minimum degree of polynomial type $(⟒(P))")) @@ -38,6 +42,7 @@ function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B end ⟒(P){T′,X′}(cs, o) end + function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: AbstractUnivariatePolynomial{B}, Q<:StandardBasisPolynomial} isa(q, PP) && return p T = _eltype(P,q) @@ -67,6 +72,8 @@ function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, throw(ArgumentError("Method not defined")) end +# implemented derivative case by case + function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardBasis,T,X} iszero(p) && return p/1 @@ -129,7 +136,7 @@ function Base.divrem(num::P, den::Q) where {B<:StandardBasis, end ## XXX This needs resolving! -## XXX copy or pass along to other system for now where things are defined fro StandardBasisPolynomial +## XXX copy or pass along to other system for now where things are defined for StandardBasisPolynomial function vander(p::Type{<:P}, x::AbstractVector{T}, degs) where {B<:StandardBasis, P<:AbstractUnivariatePolynomial{B}, T <: Number} vander(StandardBasisPolynomial, x, degs) end @@ -182,20 +189,8 @@ function fit(::Type{P}, convert(P, fit(Polynomial, x, y, deg; kwargs...)) end -function fit(::Type{P}, - x::AbstractVector{T}, - y::AbstractVector{T}, - deg::AbstractVector, - cs::Dict; - kwargs...) where {T, P<:AbstractUnivariatePolynomial{<:StandardBasis}} - convert(P, fit(Polynomial, x, y, deg, cs; kwargs...)) -end - # new constructors taking order in second position -#SparsePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T, X, S} = SparsePolynomial{T,X}(coeffs) -#ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}, ::Int) where {T,X,S} = ImmutablePolynomial{T,X}(coeffs) - - +# these are Polynomial{T, X}(coeffs::AbstractVector{S},order::Int) where {T, X, S} = Polynomial{T,X}(coeffs) FactoredPolynomial{T,X}(coeffs::AbstractVector{S}, order::Int) where {T,S,X} = FactoredPolynomial{T,X}(coeffs) PnPolynomial{T, X}(coeffs::AbstractVector, order::Int) where {T, X} = PnPolynomial(coeffs) # for generic programming diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 52168453..609e0c38 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -1,12 +1,17 @@ # Dense + StandardBasis +# XXX for now, use older Polynomial type +#const Polynomial = MutableDensePolynomial{StandardBasis} +#export Polynomial + +const LaurentPolynomial = MutableDensePolynomial{StandardBasis} +export LaurentPolynomial function evalpoly(c, p::MutableDensePolynomial{StandardBasis,T,X}) where {T,X} iszero(p) && return zero(T) * zero(c) EvalPoly.evalpoly(c, p.coeffs) * c^p.order end - # scalar add function scalar_add(c::S, p:: MutableDensePolynomial{B,T,X}) where {B<:StandardBasis, S, T, X} R = promote_type(T,S) @@ -28,8 +33,6 @@ function scalar_add(c::S, p:: MutableDensePolynomial{B,T,X}) where {B<:StandardB P(Val(false), cs, a′) end - - function ⊗(p:: MutableDensePolynomial{StandardBasis,T,X}, q:: MutableDensePolynomial{StandardBasis,S,X}) where {T,S,X} # simple convolution @@ -60,7 +63,6 @@ function ⊗(p:: MutableDensePolynomial{StandardBasis,T,X}, P(Val(false), cs, a) end - function derivative(p::MutableDensePolynomial{B,T,X}) where {B<:StandardBasis,T,X} N = lastindex(p) - firstindex(p) + 1 @@ -172,18 +174,6 @@ end ## XXX ---- -#const Polynomial = MutableDensePolynomial{StandardBasis} -#export Polynomial - -const LaurentPolynomial = MutableDensePolynomial{StandardBasis} -export LaurentPolynomial - - -# resolve ambiguity -# function Base.convert(::Type{P}, q::Q) where {T, X, P<:LaurentPolynomial, B<:StandardBasis, Q<:AbstractUnivariatePolynomial{B, T, X}} -# p = convert(Polynomial, q) -# LaurentPolynomial{eltype(p), indeterminate(p)}(p.coeffs, 0) -# end ## ---- ## XXX needs to be incorporated if Polynomial = MutableDensePolynomial{StandardBasis} diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl index 79527263..b61dd1d5 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/standard-immutable.jl @@ -1,37 +1,17 @@ +## Immutable dense / standard basis specific polynomial code +ImmutablePolynomial = ImmutableDensePolynomial{StandardBasis} +export ImmutablePolynomial + evalpoly(x, p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = zero(T)*zero(x) -function evalpoly(x, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} -# z = zero(x * zero(p[0])) -# typeof(z)(EvalPoly.evalpoly(x, p.coeffs)) - EvalPoly.evalpoly(x, p.coeffs) -end +evalpoly(x, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} = EvalPoly.evalpoly(x, p.coeffs) -constantterm(p::ImmutableDensePolynomial{B,T,X,N}) where {B <: StandardBasis,T,X,N} = p.coeffs[1] # this is oddly slow constantterm(p::ImmutableDensePolynomial{B,T,X,0}) where {B <: StandardBasis,T,X} = zero(T) +constantterm(p::ImmutableDensePolynomial{B,T,X,N}) where {B <: StandardBasis,T,X,N} = p.coeffs[1] -# Padded vector sum of two tuples assuming N ≥ M -@generated function tuple_sum(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 - #Base.@inline - tuple($(exprs...)) - end - -end - - -# faster (need special case for inference) scalar_add(p::ImmutableDensePolynomial{B,T,X,0}, c::S) where {B<:StandardBasis,T,X,S} = ImmutableDensePolynomial{B,promote_type(T,S),X,1}((c,)) + function scalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B<:StandardBasis,T,X,S,N} R = promote_type(T,S) P = ImmutableDensePolynomial{B,R,X} @@ -60,42 +40,20 @@ function Base.:*(p::ImmutableDensePolynomial{StandardBasis,T,X,N}, q::ImmutableDensePolynomial{StandardBasis,S,X,M}) where {T,S,X,N,M} ⊗(p,q) end -function ⊗(p::ImmutableDensePolynomial{StandardBasis,T,X,N}, - q::ImmutableDensePolynomial{StandardBasis,S,X,M}) where {T,S,X,N,M} - - # simple convolution - R = promote_type(T,S) - P = ImmutableDensePolynomial{StandardBasis,R,X} - - (iszero(N) || iszero(M)) && return zero(P) +⊗(p::ImmutableDensePolynomial{B,T,X,0}, + q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,M} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) +⊗(p::ImmutableDensePolynomial{B,T,X,N}, + q::ImmutableDensePolynomial{B,S,X,0}) where {B<:StandardBasis,T,S,X,N} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) +⊗(p::ImmutableDensePolynomial{B,T,X,0}, + q::ImmutableDensePolynomial{B,S,X,0}) where {B<:StandardBasis,T,S,X} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) +function ⊗(p::ImmutableDensePolynomial{B,T,X,N}, + q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,N,M} cs = fastconv(p.coeffs, q.coeffs) - P{N+M-1}(cs) + R = eltype(cs) + ImmutableDensePolynomial{B,R,X,N+M-1}(cs) 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 fastconv(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 isnothing(exprs[k]) - exprs[k] = :(p1[$i] * p2[$j]) - else - exprs[k] = :(muladd(p1[$i], p2[$j], $(exprs[k]))) - end - end - end - - return quote - Base.@_inline_meta # 1.8 deprecation - tuple($(exprs...)) - end - -end # function polynomial_composition(p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,N,M} @@ -104,6 +62,8 @@ function polynomial_composition(p::ImmutableDensePolynomial{B,T,X,N}, q::Immutab convert(P, cs) end +# special cases of polynomial composition +# ... TBD ... derivative(p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = p function derivative(p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} @@ -124,8 +84,3 @@ function integrate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X, R = eltype(cs) ImmutableDensePolynomial{StandardBasis,R,X,N+1}(cs) end - - -## --- -ImmutablePolynomial = ImmutableDensePolynomial{StandardBasis} -export ImmutablePolynomial diff --git a/src/standard-basis/standard-sparse.jl b/src/standard-basis/standard-sparse.jl index 3b9d384c..f9a02e7d 100644 --- a/src/standard-basis/standard-sparse.jl +++ b/src/standard-basis/standard-sparse.jl @@ -1,3 +1,7 @@ +## Standard basis + sparse storage +const SparsePolynomial = MutableSparsePolynomial{StandardBasis} # const is important! +export SparsePolynomial + function evalpoly(x, p::MutableSparsePolynomial) tot = zero(p[0]*x) @@ -7,6 +11,21 @@ function evalpoly(x, p::MutableSparsePolynomial) return tot end +# much faster than default +function scalar_add(c::S, p::MutableSparsePolynomial{B,T,X}) where {B<:StandardBasis,T,X,S} + c₀ = c + p[0] + R = eltype(c₀) + P = MutableSparsePolynomial{B,R,X} + D = convert(Dict{Int, R}, copy(p.coeffs)) + if iszero(c₀) + delete!(D,0) + else + @inbounds D[0] = c₀ + end + return P(Val(false), D) +end + + function ⊗(p::MutableSparsePolynomial{StandardBasis,T,X}, q::MutableSparsePolynomial{StandardBasis,S,X}) where {T,S,X} @@ -60,7 +79,3 @@ function integrate(p:: MutableSparsePolynomial{B,T,X}) where {B<:StandardBasis,T end return P(d) end - -## --- -const SparsePolynomial = MutableSparsePolynomial{StandardBasis} # const is important! -export SparsePolynomial diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 2a3504ac..ec2b08ae 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -458,17 +458,17 @@ end T, S = Float64, Int if P == ImmutablePolynomial x = (1,2,3) - @inferred P{T,:x,4}(x) - @inferred P{S,:x,4}(x) - @inferred P{T,:x,3}(x) - @inferred P{S,:x,3}(x) + @inferred P{T,:x,4}(x) == P{T,:x,4}(x) + @inferred P{S,:x,4}(x) == P{S,:x,4}(x) + @inferred P{T,:x,3}(x) == P{T,:x,3}(x) + @inferred P{S,:x,3}(x) == P{S,:x,3}(x) end - @inferred P{T,:x}(x) - @inferred P{S,:x}(x) - @inferred P{T}(x) - @inferred P{S}(x) - @inferred P(x) + @inferred P{T,:x}(x) == P{T,:x}(x) + @inferred P{S,:x}(x) == P{S,:x}(x) + @inferred P{T}(x) == P{T}(x) + @inferred P{S}(x) == P{S}(x) + @inferred P(x) == P(x) end @testset "arithmetic" begin From 99937ae1d8994a95252d36f39c904a893819fada Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 27 Jul 2023 15:18:12 -0400 Subject: [PATCH 12/31] WIP: coeffs --- src/abstract-polynomial.jl | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index e84ede5a..5520e5f3 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -1,4 +1,5 @@ # XXX todo, merge in with common.jl +# used by LaurentPolynomial, ImmutablePolynomial, SparsePolynomial """ Abstract type for polynomials with an explicit basis. """ @@ -124,26 +125,27 @@ basis(::Type{P}, i::Int) where {B,P <: AbstractUnivariatePolynomial{B}} = basis( copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {B,T, X, S, Y, P <:AbstractUnivariatePolynomial{B,S,Y}} = ⟒(P){T, Symbol(X)}(p.coeffs) +# XXX something feels off here... # coefficients # return dense coefficients (vector or tuple) -# if laurent type, coefficients are just stored values, there may be an offset -# if not laurent type, then return coefficients p_0, p_1, ... padding out with zeros, as neede +# if laurent type and of lowest degree (after chopping) 0 or greater, +# *or* not of laurent type return p_0, ..., p_n (padded out on left) +# if laurent type and lowest degree < 0 (after chopping) return p.coeffs (user needs to get offset) # return Val(::Bool) to indicate if laurent type. This should compile away, unlike the check laurenttype(P::Type{<:AbstractPolynomial}) = Val(minimumexponent(P) < 0) coeffs(p::P) where {P <: AbstractUnivariatePolynomial} = coeffs(laurenttype(P), p) -coeffs(laurent::Val{true}, p) = p.coeffs +function coeffs(laurent::Val{true}, p) + q = chop(p) + firstindex(q) ≥ 0 && return [q[i] for i ∈ 0:lastindex(q)] + return q.coeffs +end function coeffs(laurent::Val{false}, p) firstindex(p) == 0 && return p.coeffs firstindex(p) > 0 && return [p[i] for i ∈ 0:lastindex(p)] throw(ArgumentError("Polynomial type does not support negative degree terms")) end -# function isconstant(p::AbstractUnivariatePolynomial) -# p₀ = trim_trailing_zeros(p) -# return (firstindex(p₀) == lastindex(p₀) == 0) -# end - # chop chops right side of p # use trunc for left and right # can pass tolerances @@ -204,6 +206,7 @@ function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, end end +# XXX in common.jl # function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, p2::Scalar; kwargs...) where {B,T,X} # q = p2 * one(⟒(p1){T,X}) # isapprox(p1, q; kwargs...) From 8b3bfb49f8b621dfb04d43b16d6c391527d9a5a9 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 27 Jul 2023 16:03:47 -0400 Subject: [PATCH 13/31] WIP: adjust hash, coeffs --- src/common.jl | 4 ++-- src/standard-basis/standard-dense.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common.jl b/src/common.jl index fdbe0ef2..24899cab 100644 --- a/src/common.jl +++ b/src/common.jl @@ -810,8 +810,8 @@ Base.length(v::Monomials) = length(keys(v.p)) #= 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)) +Base.copy(p::P) where {P <: AbstractPolynomial} = _convert(p, copy(p.coeffs)) +Base.hash(p::AbstractPolynomial{T,X}, h::UInt) where {T,X} = hash(indeterminate(p), hash(p.coeffs, hash(X,h))) # get symbol of polynomial. (e.g. `:x` from 1x^2 + 2x^3... _indeterminate(::Type{P}) where {P <: AbstractPolynomial} = nothing diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 609e0c38..bc0232dd 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -179,7 +179,7 @@ end ## XXX needs to be incorporated if Polynomial = MutableDensePolynomial{StandardBasis} function roots(p::P; kwargs...) where {T, X, P <: MutableDensePolynomial{StandardBasis,T,X}} iszero(p) && return float(T)[] - c = coeffs(p) + c = p.coeffs r = degreerange(p) d = r[end] - min(0, r[1]) + 1 # Length of the coefficient vector, taking into consideration # the case when the lower degree is strictly positive From 266bf47cbd3d116ba1013ed1d4824e46adb5b3a9 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 27 Jul 2023 17:52:55 -0400 Subject: [PATCH 14/31] WIP: coeffs as before --- src/abstract-polynomial.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 5520e5f3..8f4bbe3a 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -127,18 +127,15 @@ copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {B,T, X, S, Y, P <:AbstractUni # XXX something feels off here... # coefficients -# return dense coefficients (vector or tuple) -# if laurent type and of lowest degree (after chopping) 0 or greater, -# *or* not of laurent type return p_0, ..., p_n (padded out on left) +# if laurent type trim, return coeffs +# if not laurent, return all # if laurent type and lowest degree < 0 (after chopping) return p.coeffs (user needs to get offset) # return Val(::Bool) to indicate if laurent type. This should compile away, unlike the check laurenttype(P::Type{<:AbstractPolynomial}) = Val(minimumexponent(P) < 0) coeffs(p::P) where {P <: AbstractUnivariatePolynomial} = coeffs(laurenttype(P), p) function coeffs(laurent::Val{true}, p) - q = chop(p) - firstindex(q) ≥ 0 && return [q[i] for i ∈ 0:lastindex(q)] - return q.coeffs + p.coeffs end function coeffs(laurent::Val{false}, p) firstindex(p) == 0 && return p.coeffs From db43bf364a27fa0a874d9a05dc97a57755c871ad Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 28 Jul 2023 10:23:09 -0400 Subject: [PATCH 15/31] WIP --- src/abstract-polynomial.jl | 2 +- src/standard-basis/standard-basis.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 8f4bbe3a..03271e8a 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -135,7 +135,7 @@ laurenttype(P::Type{<:AbstractPolynomial}) = Val(minimumexponent(P) < 0) coeffs(p::P) where {P <: AbstractUnivariatePolynomial} = coeffs(laurenttype(P), p) function coeffs(laurent::Val{true}, p) - p.coeffs + chop(p).coeffs end function coeffs(laurent::Val{false}, p) firstindex(p) == 0 && return p.coeffs diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 4dfafba1..6f2db9f6 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -27,7 +27,7 @@ function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: Abstract end function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B<:StandardBasis,T,X, Q<:AbstractUnivariatePolynomial{B,T,X}} - minimumexponent(P) > firstindex(q) && + minimumexponent(P) > firstindex(chop(q)) && throw(ArgumentError("Degree of polynomial less than minimum degree of polynomial type $(⟒(P))")) isa(q, PP) && return p From ce64b61326967521cf279d9b95b5c6afa813dcbe Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 28 Jul 2023 11:26:47 -0400 Subject: [PATCH 16/31] WIP: coeffs tweak, mutable order --- src/abstract-polynomial.jl | 2 +- .../mutable-dense-polynomial.jl | 33 +++++++++++-------- src/standard-basis/standard-dense.jl | 4 +-- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 03271e8a..8f4bbe3a 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -135,7 +135,7 @@ laurenttype(P::Type{<:AbstractPolynomial}) = Val(minimumexponent(P) < 0) coeffs(p::P) where {P <: AbstractUnivariatePolynomial} = coeffs(laurenttype(P), p) function coeffs(laurent::Val{true}, p) - chop(p).coeffs + p.coeffs end function coeffs(laurent::Val{false}, p) firstindex(p) == 0 && return p.coeffs diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index 968b476e..e520b570 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -10,7 +10,7 @@ This type trims trailing zeros and when the offset is not 0, trims the leading z """ struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} coeffs::Vector{T} - order::Int # lowest degree, typically 0 + order::Base.RefValue{Int} # lowest degree, typically 0 function MutableDensePolynomial{B,T,X}(cs::AbstractVector{S}, order::Int=0) where {B,T,X,S} if Base.has_offset_axes(cs) @warn "Using the axis offset of the coefficient vector" @@ -27,17 +27,18 @@ struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} xs = T[cs[i] for i ∈ j:i] order = order + j - 1 end - new{B,T,Symbol(X)}(xs, order) + new{B,T,Symbol(X)}(xs, Ref(order)) + end function MutableDensePolynomial{B,T,X}(check::Val{false}, cs::Vector{T}, order::Int=0) where {B,T,X} if Base.has_offset_axes(cs) @warn "Using the axis offset of the coefficient vector" cs, order = cs.parent, first(cs.offsets) end - new{B,T,Symbol(X)}(cs, order) + new{B,T,Symbol(X)}(cs, Ref(order)) end function MutableDensePolynomial{B,T,X}(check::Val{true}, cs::Vector{T}, order::Int=0) where {B,T,X} - MutableDensePolynomial{B,T,X}(cs, order) + MutableDensePolynomial{B,T,X}(cs, Ref(order)) end end @@ -45,7 +46,7 @@ function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDensePolyn R = eltype(as) Q = MutableDensePolynomial{B, R, X} as = trim_trailing_zeros(as) - Q(Val(false), as, p.order) + Q(Val(false), as, p.order[]) end @poly_register MutableDensePolynomial @@ -55,19 +56,20 @@ constructorof(::Type{<:MutableDensePolynomial{B}}) where {B} = MutableDensePolyn ## Generics for polynomials function Base.convert(::Type{MutableDensePolynomial{B,T,X}}, q::MutableDensePolynomial{B,T′,X′}) where {B,T,T′,X,X′} - MutableDensePolynomial{B,T,X}(Val(false), convert(Vector{T},q.coeffs), q.order) + MutableDensePolynomial{B,T,X}(Val(false), convert(Vector{T},q.coeffs), q.order[]) end Base.copy(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs), firstindex(p)) -Base.firstindex(p::MutableDensePolynomial) = p.order +Base.firstindex(p::MutableDensePolynomial) = p.order[] Base.length(p::MutableDensePolynomial) = length(p.coeffs) Base.lastindex(p::MutableDensePolynomial) = firstindex(p) + length(p) - 1 function Base.getindex(p::MutableDensePolynomial{B,T,X}, i::Int) where {B,T,X} (i < firstindex(p) || i > lastindex(p)) && return zero(T) p.coeffs[i + offset(p)] end +# ??? should this call chop! if `iszero(value)`? function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDensePolynomial{B,T,X}} a,b = firstindex(p), lastindex(p) o = a @@ -83,7 +85,8 @@ function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDensePolynom else p.coeffs[i + offset(p)] = value end - P(p.coeffs, o) + p.order[] = o + p end offset(p::MutableDensePolynomial) = 1 - firstindex(p) @@ -151,8 +154,8 @@ function chop!(p::MutableDensePolynomial{B,T,X}; for i ∈ (iᵣ+1):N pop!(p.coeffs) end - - MutableDensePolynomial{B,T,X}(p.coeffs, o) + p.order[] = o + p end function normΔ(q1::MutableDensePolynomial{B}, q2::MutableDensePolynomial{B}, p::Real = 2) where {B} @@ -173,7 +176,7 @@ minimumexponent(::Type{<:MutableDensePolynomial}) = typemin(Int) # vector ops +, -, c*x ## unary Base.:-(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = - MutableDensePolynomial{B,T,X}(Val(false), -p.coeffs, p.order) + MutableDensePolynomial{B,T,X}(Val(false), -p.coeffs, p.order[]) ## binary Base.:+(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = @@ -242,15 +245,19 @@ end function LinearAlgebra.lmul!(c::Scalar, p::MutableDensePolynomial{B,T,X}) where {B,T,X} if iszero(c) empty!(p.coeffs) + p.order[] = 0 else - MutableDensePolynomial{B,T,X}(Val(false), lmul!(c, p.coeffs), firstindex(p)) + lmul!(c, p.coeffs) end + p end function LinearAlgebra.rmul!(p::MutableDensePolynomial{B,T,X}, c::Scalar) where {B,T,X} if iszero(c) empty!(p.coeffs) + p.order[] = 0 else - MutableDensePolynomial{B,T,X}(Val(false), rmul!(c, p.coeffs), firstindex(p)) + rmul!(p.coeffs, c) end + p end diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index bc0232dd..b463cdc6 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -9,7 +9,7 @@ export LaurentPolynomial function evalpoly(c, p::MutableDensePolynomial{StandardBasis,T,X}) where {T,X} iszero(p) && return zero(T) * zero(c) - EvalPoly.evalpoly(c, p.coeffs) * c^p.order + EvalPoly.evalpoly(c, p.coeffs) * c^p.order[] end # scalar add @@ -73,7 +73,7 @@ function derivative(p::MutableDensePolynomial{B,T,X}) where {B<:StandardBasis,T, ps = p.coeffs cs = [i*pᵢ for (i,pᵢ) ∈ pairs(p)] - return P(cs, p.order-1) + return P(cs, p.order[]-1) end # LaurentPolynomials have `inv` defined for monomials From 65f8e077cdb16c4f152a3391da0cac76ac139ba7 Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 28 Jul 2023 14:22:34 -0400 Subject: [PATCH 17/31] WIP: doc adjustments --- docs/src/extending.md | 187 ++++++++++++++++++++++- src/promotions.jl | 8 + src/show.jl | 5 +- src/standard-basis/standard-basis.jl | 6 +- src/standard-basis/standard-dense.jl | 95 ++++++++++++ src/standard-basis/standard-immutable.jl | 55 +++++++ src/standard-basis/standard-sparse.jl | 43 ++++++ 7 files changed, 394 insertions(+), 5 deletions(-) diff --git a/docs/src/extending.md b/docs/src/extending.md index 9a815044..e7e44264 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -1,6 +1,6 @@ # 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; examples follow. The newer [`AbstractUnivariatePolynomial`](@ref) type is illustrated at the end. ```@docs AbstractPolynomial @@ -148,3 +148,188 @@ julia> p .+ 2 ``` The unexported `Polynomials.PnPolynomial` type implements much of this. + + +## Extending the AbstractUnivariatePolynomial type + +An `AbstractUnivariatePolynomial` polynomial consists of a basis and a storage type. The storage type can be mutable dense, mutable sparse, or immutable dense. + +A basis inherits from `Polynomials.AbstractBasis`, in the example our basis type has a parameter. + +### The generalized Laguerre polynomials + +These are orthogonal polynomials parameterized by $\alpha$ and defined recursively by + +```math +\begin{align*} +L^\alpha_1(x) &= 1\\ +L^\alpha_2(x) &= 1 + \alpha - x\\ +L^\alpha_{n+1}(x) &= \frac{2n+1+\alpha -x}{n+1} L^\alpha_n(x) - \frac{n+\alpha}{n+1} L^\alpha_{n-1}(x)\\ +&= (A_nx +B_n) \cdot L^\alpha_n(x) - C_n \cdot L^\alpha_{n-1}(x). +\end{align*} +``` + +There are other [characterizations available](https://en.wikipedia.org/wiki/Laguerre_polynomials). The three-point recursion, described by `A`,`B`, and `C` is used below for evaluation. + +We define the basis with + +```jldoctest abstract_univariate_polynomial +julia> using Polynomials; + +julia> import Polynomials: AbstractUnivariatePolynomial, AbstractBasis, MutableDensePolynomial; + +julia> struct LaguerreBasis{alpha} <: AbstractBasis end + +julia> Polynomials.basis_symbol(::Type{<:AbstractUnivariatePolynomial{LaguerreBasis{α}}}) where {α} = + "L^$(α)" +``` + +The basis symbol has no default. We added a method to `basis_symbol` to show this basis. More generally, `Polynomials.printbasis` can have methods added to adjust for different display types. + +Polynomials can be initiated through specifying a storage type and a basis, say: + +```jldoctest abstract_univariate_polynomial +julia> P = MutableDensePolynomial{LaguerreBasis{0}} +MutableDensePolynomial{LaguerreBasis{0}} + +julia> p = P([1,2,3]) +MutableDensePolynomial(1L^0_0 + 2*L^0_1 + 3*L^0_2) +``` + +Or using other storage types: + +```jldoctest abstract_univariate_polynomial +julia> Polynomials.ImmutableDensePolynomial{LaguerreBasis{1}}((1,2,3)) +Polynomials.ImmutableDensePolynomial(1L^1_0 + 2*L^1_1 + 3*L^1_2) +``` + +All polynomials have vector addition and scalar multiplication defined: + +```jldoctest abstract_univariate_polynomial +julia> q = P([1,2]) +MutableDensePolynomial(1L^0_0 + 2*L^0_1) + +julia> p + q +MutableDensePolynomial(2L^0_0 + 4*L^0_1 + 3*L^0_2) +``` + +```jldoctest abstract_univariate_polynomial +julia> 2p +MutableDensePolynomial(2L^0_0 + 4*L^0_1 + 6*L^0_2) +``` + +For a new basis, there are no default methods for polynomial evaluation, scalar addition, and polynomial multiplication; and no defaults for `one`, and `variable`. + +For the Laguerre Polynomials, Clenshaw recursion can be used for evaluation. Internally, `evalpoly` is called so we forward that method. + +```jldoctest abstract_univariate_polynomial +julia> function ABC(::Type{LaguerreBasis{α}}, n) where {α} + o = one(α) + d = n + o + (A=-o/d, B=(2n + o + α)/d, C=(n+α)/d) + end +ABC (generic function with 1 method) +``` + +```jldoctest abstract_univariate_polynomial +julia> function clenshaw_eval(p::P, x::S) where {α, Bᵅ<: LaguerreBasis{α}, T, P<:AbstractUnivariatePolynomial{Bᵅ,T}, S} + d = degree(p) + R = typeof(((one(α) * one(T)) * one(S)) / 1) + p₀ = one(R) + d == -1 && return zero(R) + d == 0 && return p[0] * one(R) + Δ0 = p[d-1] + Δ1 = p[d] + @inbounds for i in (d - 1):-1:1 + A,B,C = ABC(Bᵅ, i) + Δ0, Δ1 = + p[i] - Δ1 * C, Δ0 + Δ1 * muladd(x, A, B) + end + A,B,C = ABC(Bᵅ, 0) + p₁ = muladd(x, A, B) * p₀ + return Δ0 * p₀ + Δ1 * p₁ + end +clenshaw_eval (generic function with 1 method) +``` + +```jldoctest abstract_univariate_polynomial +julia> Polynomials.evalpoly(x, p::P) where {P<:AbstractUnivariatePolynomial{<:LaguerreBasis}} = + clenshaw_eval(p, x) +``` + +```jldoctest abstract_univariate_polynomial +julia> p = P([0,0,1]) +MutableDensePolynomial(L^0_2) + +julia> x = variable(Polynomial) +Polynomial(1.0*x) + +julia> p(x) +Polynomial(1.0 - 2.0*x + 0.5*x^2) +``` + +We see that conversion to the `Polynomial` type is available through polynomial evaluation. This is used by default, so we have `convert` methods available: + +```jldoctest abstract_univariate_polynomial +julia> convert(ChebyshevT, p) +ChebyshevT(1.25⋅T_0(x) - 2.0⋅T_1(x) + 0.25⋅T_2(x)) +``` + +Or, using some extra annotations to have rational arithmetic used, we can compare to easily found representations in the standard basis: + +```jldoctest abstract_univariate_polynomial +julia> q = Polynomials.basis(MutableDensePolynomial{LaguerreBasis{0//1}, Int}, 5) +MutableDensePolynomial(L^0//1_5) + +julia> x = variable(Polynomial{Int}) +Polynomial(x) + +julia> q(x) +Polynomial(1//1 - 5//1*x + 5//1*x^2 - 5//3*x^3 + 5//24*x^4 - 1//120*x^5) +``` + + +To implement scalar addition, we utilize the fact that ``L_0 = 1`` to manipulate the coefficients. Below we specialize to a container type: + +```jldoctest abstract_univariate_polynomial +julia> function Polynomials.scalar_add(c::S, p::P) where {B<:LaguerreBasis,T,X, + P<:MutableDensePolynomial{B,T,X},S} + R = promote_type(T,S) + iszero(p) && return MutableDensePolynomial{B,R,X}(c) + cs = convert(Vector{R}, copy(p.coeffs)) + cs[1] += c + MutableDensePolynomial{B,R,X}(cs) + end + +julia> p + 3 +MutableDensePolynomial(3L^0_0 + L^0_2) +``` + +The values of `one` and `variable` are straightforward, as ``L_0=1`` and ``L_1=1 - x`` or ``x = 1 - L_1`` + +```jldoctest abstract_univariate_polynomial +julia> Polynomials.one(::Type{P}) where {B<:LaguerreBasis,T,X,P<:AbstractUnivariatePolynomial{B,T,X}} = + P([one(T)]) + +julia> Polynomials.variable(::Type{P}) where {B<:LaguerreBasis,T,X,P<:AbstractUnivariatePolynomial{B,T,X}} = + P([one(T), -one(T)]) +``` + +To see this is correct, we have: + +```jldoctest abstract_univariate_polynomial +julia> variable(P)(x) == x +true +``` + +Finally, we implement polynomial multiplication through conversion to the polynomial type. The [direct formula](https://londmathsoc.onlinelibrary.wiley.com/doi/pdf/10.1112/jlms/s1-36.1.399) could be implemented. + +```jldoctest abstract_univariate_polynomial +julia> function Base.:*(p::MutableDensePolynomial{B,T,X}, + q::MutableDensePolynomial{B,S,X}) where {B<:LaguerreBasis, T,S,X} + x = variable(Polynomial{T,X}) + p(x) * q(x) + end +``` + +Were it defined, a `convert` method from `Polynomial` to the `LaguerreBasis` could be used to implement multiplication. diff --git a/src/promotions.jl b/src/promotions.jl index d43fbb3d..cb4b3c05 100644 --- a/src/promotions.jl +++ b/src/promotions.jl @@ -2,6 +2,14 @@ Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, P<:AbstractUnivariatePolynomial{B,T,X}, Q<:AbstractUnivariatePolynomial{B,S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} +Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, + P<:AbstractPolynomial{T,X}, + Q<:AbstractUnivariatePolynomial{B,S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} + +Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, + P<:AbstractUnivariatePolynomial{B,T,X}, + Q<:AbstractPolynomial{S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} + # Methods to ensure that matrices of polynomials behave as desired Base.promote_rule(::Type{P},::Type{Q}) where {T,X, P<:AbstractPolynomial{T,X}, S, Q<:AbstractPolynomial{S,X}} = diff --git a/src/show.jl b/src/show.jl index 0f773ed4..61b7f338 100644 --- a/src/show.jl +++ b/src/show.jl @@ -59,10 +59,13 @@ showone(::Type{<:AbstractPolynomial{S}}) where {S} = false Common Printing =# +_typealias(::Type{P}) where {P<:AbstractPolynomial} = P.name.wrapper # allows for override + Base.show(io::IO, p::AbstractPolynomial) = show(io, MIME("text/plain"), p) function Base.show(io::IO, mimetype::MIME"text/plain", p::P) where {P<:AbstractPolynomial} - print(io,"$(P.name.wrapper)(") + print(io, _typealias(P)) + print(io, "(") printpoly(io, p, mimetype) print(io,")") end diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 6f2db9f6..b23eb4ce 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -1,6 +1,6 @@ struct StandardBasis <: AbstractBasis end -basis_symbol(::Type{<:AbstractUnivariatePolynomial{StandardBasis}}) = "x" +basis_symbol(::Type{P}) where {P<:AbstractUnivariatePolynomial{StandardBasis}} = string(indeterminate(P)) function printbasis(io::IO, ::Type{P}, j::Int, m::MIME) where {B<:StandardBasis, P <: AbstractUnivariatePolynomial{B}} iszero(j) && return # no x^0 print(io, basis_symbol(P)) @@ -28,7 +28,7 @@ end function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B<:StandardBasis,T,X, Q<:AbstractUnivariatePolynomial{B,T,X}} minimumexponent(P) > firstindex(chop(q)) && - throw(ArgumentError("Degree of polynomial less than minimum degree of polynomial type $(⟒(P))")) + throw(ArgumentError("Lowest degree term of polynomial less than the minimum degree of the polynomial type $(⟒(P))")) isa(q, PP) && return p T′ = _eltype(P,q) @@ -86,7 +86,7 @@ function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardB cs = _zeros(p, z, N+1) os = offset(p) @inbounds for (i, cᵢ) ∈ pairs(p) - i == -1 && (iszero(cᵢ) ? continue : throw(ArgumentError("Laurent polynomial with 1/x term"))) + i == -1 && (iszero(cᵢ) ? continue : throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term"))) #cs[i + os] = cᵢ / (i+1) cs = _set(cs, i + 1 + os, cᵢ / (i+1)) end diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index b463cdc6..8ed0f28f 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -4,9 +4,104 @@ #const Polynomial = MutableDensePolynomial{StandardBasis} #export Polynomial +""" + 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`. + +The `coeffs` specify `a_{m}, a_{m-1}, ..., a_{n}`. +The argument `m` represents the lowest exponent of the variable in the series, and is taken to be zero by default. + +Laurent polynomials and standard basis polynomials promote to Laurent polynomials. Laurent polynomials may be converted to a standard basis polynomial when `m >= 0` +. + +Integration will fail if there is a `x⁻¹` term in the polynomial. + +!!! note + `LaurentPolynomial` is an alias for `MutableDensePolynomial{StandardBasis}`. + +!!! note + `LaurentPolynomial` is axis-aware, unlike the other polynomial types in this package. + +# Examples: +```jldoctest laurent +julia> using Polynomials + +julia> P = LaurentPolynomial; + +julia> p = P([1,1,1], -1) +LaurentPolynomial(x⁻¹ + 1 + x) + +julia> q = P([1,1,1]) +LaurentPolynomial(1 + x + x²) + +julia> pp = Polynomial([1,1,1]) +Polynomial(1 + x + x^2) + +julia> p + q +LaurentPolynomial(x⁻¹ + 2 + 2*x + x²) + +julia> p * q +LaurentPolynomial(x⁻¹ + 2 + 3*x + 2*x² + x³) + +julia> p * pp +LaurentPolynomial(x⁻¹ + 2 + 3*x + 2*x² + x³) + +julia> pp - q +LaurentPolynomial(0) + +julia> derivative(p) +LaurentPolynomial(-x⁻² + 1) + +julia> integrate(q) +LaurentPolynomial(1.0*x + 0.5*x² + 0.3333333333333333*x³) + +julia> integrate(p) # x⁻¹ term is an issue +ERROR: ArgumentError: Can't integrate Laurent polynomial with `x⁻¹` term + +julia> integrate(P([1,1,1], -5)) +LaurentPolynomial(-0.25*x⁻⁴ - 0.3333333333333333*x⁻³ - 0.5*x⁻²) + +julia> x⁻¹ = inv(variable(LaurentPolynomial)) # `inv` defined on monomials +LaurentPolynomial(1.0*x⁻¹) + +julia> p = Polynomial([1,2,3]) +Polynomial(1 + 2*x + 3*x^2) + +julia> x = variable() +Polynomial(x) + +julia> x^degree(p) * p(x⁻¹) # reverses coefficients +LaurentPolynomial(3.0 + 2.0*x + 1.0*x²) +``` +""" const LaurentPolynomial = MutableDensePolynomial{StandardBasis} export LaurentPolynomial +_typealias(::Type{P}) where {P<:LaurentPolynomial} = "LaurentPolynomial" + +# how to show term. Only needed here to get unicode exponents to match old LaurentPolynomial.jl type +function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where {T, P<:MutableDensePolynomial{StandardBasis,T}} + if _iszero(pj) return false end + + pj = printsign(io, pj, first, mimetype) + if hasone(T) + if !(_isone(pj) && !(showone(T) || j == 0)) + printcoefficient(io, pj, j, mimetype) + end + else + printcoefficient(io, pj, j, mimetype) + end + + iszero(j) && return true + printproductsign(io, pj, j, mimetype) + print(io, indeterminate(P)) + j == 1 && return true + unicode_exponent(io, j) # print(io, exponent_text(j, mimetype)) + return true +end + + function evalpoly(c, p::MutableDensePolynomial{StandardBasis,T,X}) where {T,X} iszero(p) && return zero(T) * zero(c) EvalPoly.evalpoly(c, p.coeffs) * c^p.order[] diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl index b61dd1d5..fdc5249c 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/standard-immutable.jl @@ -1,7 +1,62 @@ ## Immutable dense / standard basis specific polynomial code +""" + ImmutablePolynomial{T, X, N}(coeffs) + +Construct an immutable (static) polynomial from its coefficients +`a₀, a₁, …, aₙ`, +lowest order first, optionally in terms of the given variable `x` +where `x` can be a character, symbol, or string. + +If ``p = a_n x^n + \\ldots + a_2 x^2 + a_1 x + a_0``, we construct +this through `ImmutablePolynomial((a_0, a_1, ..., a_n))` (assuming +`a_n ≠ 0`). As well, a vector or number can be used for construction. + + +The usual arithmetic operators are overloaded to work with polynomials +as well as with combinations of polynomials and scalars. However, +operations involving two non-constant polynomials of different variables causes an +error. Unlike other polynomials, `setindex!` is not defined for `ImmutablePolynomials`. + +As the degree of the polynomial (`+1`) is a compile-time constant, +several performance improvements are possible. For example, immutable +polynomials can take advantage of faster polynomial evaluation +provided by `evalpoly` from Julia 1.4; similar methods are also used +for addition and multiplication. + +However, as the degree is included in the type, promotion between +immutable polynomials can not promote to a common type. As such, they +are precluded from use in rational functions. + +!!! note + `ImmutablePolynomial` is not axis-aware, and it treats `coeffs` simply as a list of coefficients with the first + index always corresponding to the constant term. + +# Examples + +```jldoctest +julia> using Polynomials + +julia> ImmutablePolynomial((1, 0, 3, 4)) +ImmutablePolynomial(1 + 3*x^2 + 4*x^3) + +julia> ImmutablePolynomial((1, 2, 3), :s) +ImmutablePolynomial(1 + 2*s + 3*s^2) + +julia> one(ImmutablePolynomial) +ImmutablePolynomial(1.0) +``` + +!!! note + This was modeled after [StaticUnivariatePolynomials](https://github.com/tkoolen/StaticUnivariatePolynomials.jl) by `@tkoolen`. + +!!! note + `ImmutablePolynomial` is an alias for `ImmutableDensePolynomial{StandardBasis}`. + +""" ImmutablePolynomial = ImmutableDensePolynomial{StandardBasis} export ImmutablePolynomial +_typealias(::Type{P}) where {P<:ImmutablePolynomial} = "ImmutablePolynomial" evalpoly(x, p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = zero(T)*zero(x) evalpoly(x, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} = EvalPoly.evalpoly(x, p.coeffs) diff --git a/src/standard-basis/standard-sparse.jl b/src/standard-basis/standard-sparse.jl index f9a02e7d..2a20ca13 100644 --- a/src/standard-basis/standard-sparse.jl +++ b/src/standard-basis/standard-sparse.jl @@ -1,7 +1,50 @@ ## Standard basis + sparse storage + +""" + SparsePolynomial{T, X}(coeffs::Dict{Int,T}) + +Polynomials in the standard basis backed by a dictionary holding the +non-zero coefficients. For polynomials of high degree, this might be +advantageous. + +# Examples: + +```jldoctest +julia> using Polynomials + +julia> P = SparsePolynomial; + +julia> p, q = P([1,2,3]), P([4,3,2,1]) +(SparsePolynomial(1 + 2*x + 3*x^2), SparsePolynomial(4 + 3*x + 2*x^2 + x^3)) + +julia> p + q +SparsePolynomial(5 + 5*x + 5*x^2 + x^3) + +julia> p * q +SparsePolynomial(4 + 11*x + 20*x^2 + 14*x^3 + 8*x^4 + 3*x^5) + +julia> p + 1 +SparsePolynomial(2 + 2*x + 3*x^2) + +julia> q * 2 +SparsePolynomial(8 + 6*x + 4*x^2 + 2*x^3) + +julia> p = Polynomials.basis(P, 10^9) - Polynomials.basis(P,0) # also P(Dict(0=>-1, 10^9=>1)) +SparsePolynomial(-1.0 + 1.0*x^1000000000) + +julia> p(1) +0.0 +``` + +!!! note + `SparsePolynomial` is an alias for `MutableSparsePolynomial{StandardBasis}`. + +""" const SparsePolynomial = MutableSparsePolynomial{StandardBasis} # const is important! export SparsePolynomial +_typealias(::Type{P}) where {P<:SparsePolynomial} = "SparsePolynomial" + function evalpoly(x, p::MutableSparsePolynomial) tot = zero(p[0]*x) From b778db0ed853764b7a4fad451f0d4e48639ccd9c Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 28 Jul 2023 17:23:49 -0400 Subject: [PATCH 18/31] WIP: fiddle with promotion --- src/polynomial-basetypes/mutable-dense-polynomial.jl | 6 ++---- src/promotions.jl | 11 +++++++++++ test/StandardBasis.jl | 5 +++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index e520b570..1d1c45cd 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -5,7 +5,7 @@ This polynomial type essentially uses an offset vector (`Vector{T}`,`order`) to The typical offset is to have `0` as the order, but, say, to accomodate Laurent polynomials, or more efficient storage of basis elements any order may be specified. -This type trims trailing zeros and when the offset is not 0, trims the leading zeros. +This type trims trailing zeros and the leading zeros on construction. """ struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} @@ -20,9 +20,7 @@ struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} i = findlast(!iszero, cs) if i == nothing xs = T[] - elseif iszero(order) - xs = T[cs[i] for i ∈ 1:i] - else # shift if not 0 + else j = findfirst(!iszero, cs) xs = T[cs[i] for i ∈ j:i] order = order + j - 1 diff --git a/src/promotions.jl b/src/promotions.jl index cb4b3c05..0689b1ea 100644 --- a/src/promotions.jl +++ b/src/promotions.jl @@ -10,6 +10,17 @@ Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, P<:AbstractUnivariatePolynomial{B,T,X}, Q<:AbstractPolynomial{S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} +## XXX these are needed for rational-functions +Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, + P<:Polynomial{T,X}, + Q<:AbstractUnivariatePolynomial{B,S,X}} = Polynomial{promote_type(T,S),X} + +Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, + P<:AbstractUnivariatePolynomial{B,T,X}, + Q<:Polynomial{S,X}} = Polynomial{promote_type(T,S),X} + + + # Methods to ensure that matrices of polynomials behave as desired Base.promote_rule(::Type{P},::Type{Q}) where {T,X, P<:AbstractPolynomial{T,X}, S, Q<:AbstractPolynomial{S,X}} = diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index ec2b08ae..06e8a242 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -1237,8 +1237,9 @@ end @testset for P in (Polynomial, ImmutablePolynomial, SparsePolynomial, LaurentPolynomial) p,q = P([1,2], :x), P([1,2], :y) + P′′ = P <: Polynomials.AbstractUnivariatePolynomial ? LaurentPolynomial : Polynomial #P′′ = P == LaurentPolynomial ? P : P′ # different promotion rule - P′′ = P′ #XXX treat LaurentPolynomial no differently + P′′ = Polynomial #XXX # * should promote to Polynomial type if mixed (save Laurent Polynomial) @testset "promote mixed polys" begin @@ -1636,7 +1637,7 @@ end @test printpoly_to_string(Polynomial(BigInt[1,0,1], :y)) == "1 + y^2" # negative indices - @test printpoly_to_string(LaurentPolynomial([-1:3;], -2)) == "-x^-2 + 1 + 2*x + 3*x^2" # "-x⁻² + 1 + 2*x + 3*x²" + @test printpoly_to_string(LaurentPolynomial([-1:3;], -2)) == "-x⁻² + 1 + 2*x + 3*x²" @test printpoly_to_string(SparsePolynomial(Dict(.=>(-2:2, -1:3)))) == "-x^-2 + 1 + 2*x + 3*x^2" end From 555549eba3b3ed6faaab2286a7ad8eabb4eede77 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sat, 29 Jul 2023 09:06:34 -0400 Subject: [PATCH 19/31] WIP: identify DescriptorSystems issue --- src/standard-basis/standard-basis.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index b23eb4ce..36317ca8 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -28,7 +28,7 @@ end function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B<:StandardBasis,T,X, Q<:AbstractUnivariatePolynomial{B,T,X}} minimumexponent(P) > firstindex(chop(q)) && - throw(ArgumentError("Lowest degree term of polynomial less than the minimum degree of the polynomial type $(⟒(P))")) + throw(ArgumentError("Lowest degree term of polynomial ($(q), $(firstindex(q)), $(q.coeffs)) less than the minimum degree of the polynomial type $(⟒(P)), $(minimumexponent(p))")) isa(q, PP) && return p T′ = _eltype(P,q) From 663cf936505995f4b25d8d3c0bfed350b438e0ed Mon Sep 17 00:00:00 2001 From: jverzani Date: Sat, 29 Jul 2023 10:51:29 -0400 Subject: [PATCH 20/31] WIP: More promotions to match old behaviours --- .github/workflows/downstream.yml | 2 +- src/promotions.jl | 9 +++++++++ src/standard-basis/standard-basis.jl | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 5482f4f0..4b9404cc 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - julia-version: [1,1.6] + julia-version: [1] os: [ubuntu-latest] package: - {user: JuliaControl, repo: ControlSystems.jl, group: All} diff --git a/src/promotions.jl b/src/promotions.jl index 0689b1ea..c449f460 100644 --- a/src/promotions.jl +++ b/src/promotions.jl @@ -19,6 +19,15 @@ Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, P<:AbstractUnivariatePolynomial{B,T,X}, Q<:Polynomial{S,X}} = Polynomial{promote_type(T,S),X} +# L,P -> L (also S,P -> L should be defined...) +Base.promote_rule(::Type{P}, ::Type{Q}) where {T,S,X, + P<:Polynomial{T,X}, + Q<:LaurentPolynomial{S,X}} = LaurentPolynomial{promote_type(T,S),X} + +Base.promote_rule(::Type{P}, ::Type{Q}) where {T,S,X, + P<:LaurentPolynomial{T,X}, + Q<:Polynomial{S,X}} = LaurentPolynomial{promote_type(T,S),X} + # Methods to ensure that matrices of polynomials behave as desired diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 36317ca8..95667d19 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -27,8 +27,8 @@ function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: Abstract end function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B<:StandardBasis,T,X, Q<:AbstractUnivariatePolynomial{B,T,X}} - minimumexponent(P) > firstindex(chop(q)) && - throw(ArgumentError("Lowest degree term of polynomial ($(q), $(firstindex(q)), $(q.coeffs)) less than the minimum degree of the polynomial type $(⟒(P)), $(minimumexponent(p))")) + minimumexponent(P) > firstindex(q) && + throw(ArgumentError("Lowest degree term of polynomial is less than the minimum degree of the polynomial type.")) isa(q, PP) && return p T′ = _eltype(P,q) From e2c715ff7d3e064838e0f7084cb7bd0a07f236f7 Mon Sep 17 00:00:00 2001 From: jverzani Date: Sat, 29 Jul 2023 17:03:05 -0400 Subject: [PATCH 21/31] WIP --- test/StandardBasis.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 06e8a242..dbd969de 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -1238,8 +1238,8 @@ end p,q = P([1,2], :x), P([1,2], :y) P′′ = P <: Polynomials.AbstractUnivariatePolynomial ? LaurentPolynomial : Polynomial - #P′′ = P == LaurentPolynomial ? P : P′ # different promotion rule - P′′ = Polynomial #XXX + P′′ = P == LaurentPolynomial ? P : P′ # different promotion rule + #P′′ = Polynomial #XXX # * should promote to Polynomial type if mixed (save Laurent Polynomial) @testset "promote mixed polys" begin From b7a17c4f1f31f4ce855bfbae8597b495f4e8d8bd Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 1 Aug 2023 10:27:03 -0400 Subject: [PATCH 22/31] WIP: view --- src/Polynomials.jl | 16 ++- src/abstract-polynomial.jl | 52 +++------ src/abstract.jl | 1 - .../immutable-dense-polynomial.jl | 4 + .../mutable-dense-view-polynomial.jl | 105 ++++++++++++++++++ .../mutable-sparse-polynomial.jl | 2 +- src/polynomials/multroot.jl | 46 ++++---- src/polynomials/ngcd.jl | 19 +++- src/polynomials/standard-basis.jl | 2 +- src/standard-basis/standard-basis.jl | 39 ++----- src/standard-basis/standard-dense-view.jl | 86 ++++++++++++++ src/standard-basis/standard-immutable.jl | 15 +-- test/StandardBasis.jl | 32 +++--- 13 files changed, 289 insertions(+), 130 deletions(-) create mode 100644 src/polynomial-basetypes/mutable-dense-view-polynomial.jl create mode 100644 src/standard-basis/standard-dense-view.jl diff --git a/src/Polynomials.jl b/src/Polynomials.jl index b0bf6823..00c52b64 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -20,9 +20,9 @@ include("polynomials/Polynomial.jl") #include("polynomials/SparsePolynomial.jl") #include("polynomials/LaurentPolynomial.jl") -include("polynomials/pi_n_polynomial.jl") -include("polynomials/ngcd.jl") -include("polynomials/multroot.jl") +#include("polynomials/pi_n_polynomial.jl") +#include("polynomials/ngcd.jl") +#include("polynomials/multroot.jl") include("polynomials/factored_polynomial.jl") include("polynomials/ChebyshevT.jl") @@ -32,17 +32,21 @@ include("basis-utils.jl") include("polynomial-basetypes/mutable-dense-polynomial.jl") include("polynomial-basetypes/immutable-dense-polynomial.jl") include("polynomial-basetypes/mutable-sparse-polynomial.jl") - +include("polynomial-basetypes/mutable-dense-view-polynomial.jl") include("standard-basis/standard-basis.jl") include("standard-basis/standard-dense.jl") include("standard-basis/standard-immutable.jl") include("standard-basis/standard-sparse.jl") +include("standard-basis/standard-dense-view.jl") include("promotions.jl") -include("polynomials/chebyshev.jl") # wrong place +include("polynomials/ngcd.jl") +include("polynomials/multroot.jl") + +include("polynomials/chebyshev.jl") # Rational functions @@ -61,6 +65,6 @@ if !isdefined(Base, :get_extension) include("../ext/PolynomialsMutableArithmeticsExt.jl") end -include("precompiles.jl") +#include("precompiles.jl") end # module diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 8f4bbe3a..0bdbffc9 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -1,10 +1,11 @@ -# XXX todo, merge in with common.jl +# XXX todo, merge in with common.jl, abstract.jl # used by LaurentPolynomial, ImmutablePolynomial, SparsePolynomial """ Abstract type for polynomials with an explicit basis. """ abstract type AbstractUnivariatePolynomial{B, T, X} <: AbstractPolynomial{T,X} end abstract type AbstractBasis end +export AbstractUnivariatePolynomial function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where {B, T, P<:AbstractUnivariatePolynomial{B,T}} if _iszero(pj) return false end @@ -78,10 +79,10 @@ Base.iterate(p::AbstractUnivariatePolynomial, state = firstindex(p)) = _iterate( function normΔ(q1::AbstractUnivariatePolynomial, q2::AbstractUnivariatePolynomial, p::Real = 2) iszero(q1) && return norm(q2, p) iszero(q2) && return norm(q1, p) - r = zero(q1[end] + q2[end]) + r = abs(zero(q1[end] + q2[end])) tot = zero(r) for i ∈ union(keys(q1), keys(q2)) - @inbounds tot += (q1[i] - q2[i])^p + @inbounds tot += abs(q1[i] - q2[i])^p end return tot^(1/p) end @@ -149,23 +150,6 @@ end Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = chop!(copy(p)) chop!(p::AbstractUnivariatePolynomial; kwargs...) = XXX() -## --- constant term --- - -# arithmetic dispatch -struct ConstantTerm{T} - x::T -end -function ConstantTerm(p::AbstractUnivariatePolynomial) - isconstant(p) || throw(ArgumentError("Non-constant polynomial")) - convert(ConstantTerm, p) -end -Base.getindex(b::ConstantTerm) = b.x -Base.show(io::IO, c::ConstantTerm) = print(io, c.x) -Base.iszero(b::ConstantTerm) = iszero(b.x) -isconstant(::ConstantTerm) = true -Base.convert(::Type{ConstantTerm}, p::AbstractUnivariatePolynomial) = ConstantTerm(constantterm(p)) -Base.convert(::Type{ConstantTerm{T}}, p::AbstractUnivariatePolynomial) where {T} = ConstantTerm(T(constantterm(p))) - ## --- @@ -246,8 +230,6 @@ Base.:-(p::AbstractUnivariatePolynomial) = scalar_mult(-1, p) Base.:+(c::Scalar, p::AbstractUnivariatePolynomial) = scalar_add(p, c) Base.:+(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_add(p, c) -Base.:+(c::ConstantTerm, p::AbstractUnivariatePolynomial) = scalar_add(p, c[]) -Base.:+(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_add(p, c[]) scalar_add(p::AbstractUnivariatePolynomial, c) = scalar_add(c,p) # scalar addition is commutative Base.:+(p::AbstractUnivariatePolynomial) = p @@ -260,8 +242,6 @@ Base.:+(p::AbstractUnivariatePolynomial{B, T, X}, Base.:-(c::Scalar, p::AbstractUnivariatePolynomial) = c + (-p) Base.:-(p::AbstractUnivariatePolynomial, c::Scalar) = p + (-c) -Base.:-(c::ConstantTerm, p::AbstractUnivariatePolynomial) = (-c[]) + p -Base.:-(p::AbstractUnivariatePolynomial, c::ConstantTerm) = p - c[] Base.:-(p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, X}) where {B,T,S,X} = p + (-q) @@ -269,13 +249,8 @@ Base.:-(p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = _mixed_symbol_op(-, p, q) -Base.:*(c::Scalar, p::ConstantTerm) = ConstantTerm(c*p[]) -Base.:*(p::ConstantTerm, c::Scalar) = ConstantTerm(p[] * c) - Base.:*(c::Scalar, p::AbstractUnivariatePolynomial) = scalar_mult(c, p) -Base.:*(c::ConstantTerm, p::AbstractUnivariatePolynomial) = scalar_mult(c[], p) Base.:*(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_mult(p, c) -Base.:*(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_mult(p, c[]) Base.:*(p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, X}) where {B,T,S,X} = *(promote(p,q)...) @@ -286,20 +261,21 @@ Base.:*(p::AbstractUnivariatePolynomial{B, T, X}, _mixed_symbol_op(*, p, q) Base.:/(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_mult(p, one(eltype(p))/c) -Base.:/(p::AbstractUnivariatePolynomial, c::ConstantTerm) = scalar_mult(p, one(eltype(p))/c) Base.:^(p::AbstractUnivariatePolynomial, n::Integer) = Base.power_by_squaring(p, n) -# treat constant polynomials as ConstantTerm when symbols mixed +# treat constant polynomials as when symbols mixed +scalar_op(::typeof(*)) = scalar_mult +scalar_op(::typeof(+)) = scalar_add +scalar_op(::typeof(/)) = scalar_div function _mixed_symbol_op(op, - p::AbstractUnivariatePolynomial{B, T, X}, - q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} + p::P, + q::Q) where {B,T,S,X,Y, + P<:AbstractUnivariatePolynomial{B, T, X}, + Q<:AbstractUnivariatePolynomial{B, S, Y}} X == Y && throw(ArgumentError("dispatch should catch this case")) - if isconstant(p) - return op(convert(ConstantTerm, p), q) - elseif isconstant(q) - return op(p, convert(ConstantTerm, q)) - end + isconstant(p) && return scalar_op(op)(constantterm(p), q) + isconstant(q) && return scalar_op(op)(p, constantterm(q)) assert_same_variable(X,Y) end diff --git a/src/abstract.jl b/src/abstract.jl index 521ed1d5..08df6c27 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -1,5 +1,4 @@ export AbstractPolynomial -export AbstractUnivariatePolynomial # *internal* means to pass variable symbol to constructor through 2nd position and keep type stability struct Var{T} end diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index 48d5563e..c1d94ef4 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -201,6 +201,10 @@ function degree(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} return i - 1 end +function coeffs(p::P) where {P <: ImmutableDensePolynomial} + trim_trailing_zeros(p.coeffs) +end + # zero, one Base.zero(::Type{<:ImmutableDensePolynomial{B,T,X}}) where {B,T,X} = ImmutableDensePolynomial{B,T,X,0}(()) diff --git a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl new file mode 100644 index 00000000..6cb90d79 --- /dev/null +++ b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl @@ -0,0 +1,105 @@ +""" + MutableDenseViewPolynomial{B,T,X} + +Construct a polynomial in `P_n` (or `Πₙ`), the collection of polynomials in the +basis of degree `n` *or less*, using a vector of length +`N+1`. + +* Unlike other polynomial types, this type allows trailing zeros in the coefficient vector. Call `chop!` to trim trailing zeros if desired. +* Unlike other polynomial types, this does not copy the coefficients on construction +* Unlike other polynomial types, this type broadcasts like a vector for in-place vector operations (scalar multiplication, polynomial addition/subtraction of the same size) +* The method inplace `mul!(pq, p, q)` is defined to use precallocated storage for the product of `p` and `q` + +This type is useful for reducing copies and allocations in some algorithms. + +""" +struct MutableDenseViewPolynomial{B,T,X} <: AbstractUnivariatePolynomial{B, T, X} + coeffs::Vector{T} + function MutableDenseViewPolynomial{B, T, X}(coeffs::AbstractVector{S}) where {B, T,S, X} + new{B,T,Symbol(X)}(convert(Vector{T}, coeffs)) + end +end + +MutableDensePolynomial{B,T,X}(check::Val{false}, cs::AbstractVector{S}) where {B,T,S,X} = new{B,T,X}(coeffs) +MutableDensePolynomial{B,T,X}(check::Val{true}, cs::AbstractVector{S}) where {B,T,S,X} = new{B,T,X}(coeffs) + +function MutableDenseViewPolynomial{B, T, X}(coeffs::AbstractVector{S}, order::Int) where {B, T,S, X} + iszero(order) && return MutableDenseViewPolynomial{B,T,X}(coeffs) + order < 0 && throw(ArgumentError("Not a Laurent type")) + prepend!(coeffs, zeros(T,order)) + MutableDenseViewPolynomial{B,T,X}(coeffs) +end + + +@poly_register MutableDenseViewPolynomial +constructorof(::Type{<:MutableDenseViewPolynomial{B}}) where {B} = MutableDenseViewPolynomial{B} +minimumexponent(::Type{<:MutableDenseViewPolynomial}) = 0 +laurenttype(::Type{<:MutableDenseViewPolynomial}) = Val(false) +_zeros(::Type{<:MutableDenseViewPolynomial}, z, N) = fill(z, N) +Base.copy(p::MutableDenseViewPolynomial{B,T,X}) where {B,T,X} = MutableDenseViewPolynomial{B,T,X}(copy(p.coeffs)) +# change broadcast semantics +Base.broadcastable(p::MutableDenseViewPolynomial) = p.coeffs; +Base.ndims(::Type{<:MutableDenseViewPolynomial}) = 1 +Base.copyto!(p::MutableDenseViewPolynomial{B, T, X}, + x::S) where {B, T, X, + S<:Union{AbstractVector, Base.AbstractBroadcasted, Tuple} # to avoid an invalidation. Might need to be more general? + } = copyto!(p.coeffs, x) + +Base.firstindex(p::MutableDenseViewPolynomial) = 0 +Base.lastindex(p::MutableDenseViewPolynomial) = length(p.coeffs)-1 +Base.pairs(p::MutableDenseViewPolynomial) = Base.Generator(=>, 0:(length(p.coeffs)-1), p.coeffs) +function degree(p::MutableDenseViewPolynomial) + i = findlast(!iszero, p.coeffs) + isnothing(i) && return -1 + i - 1 +end + +# trims left **and right** +function chop!(p::MutableDenseViewPolynomial{B,T,X}; + atol=nothing, rtol=Base.rtoldefault(float(real(T)))) where {B,T,X} + iᵣ = chop_right_index(p.coeffs; atol=atol, rtol=rtol) # nothing -- nothing to chop + iᵣ === nothing && return zero(p) + N = length(p.coeffs) + for i ∈ (iᵣ+1):N + pop!(p.coeffs) + end + p +end + +function Base.:-(p::MutableDenseViewPolynomial{B,T,X}) where {B,T,X} + MutableDenseViewPolynomial{B,T,X}(-p.coeffs) # use lmul!(-1,p) for in place +end + +# for same length, can use p .+= q for in place +Base.:+(p::MutableDenseViewPolynomial{B,T,X}, q::MutableDenseViewPolynomial{B,S,X}) where{B,X,T,S} = + _vector_combine(+, p, q) +Base.:-(p::MutableDenseViewPolynomial{B,T,X}, q::MutableDenseViewPolynomial{B,S,X}) where{B,X,T,S} = + _vector_combine(-, p, q) + +function _vector_combine(op, p::MutableDenseViewPolynomial{B,T,X}, q::MutableDenseViewPolynomial{B,S,X}) where {B,T,S,X} + n,m = length(p.coeffs), length(q.coeffs) + R = promote_type(T,S) + if n ≥ m + cs = convert(Vector{R}, p.coeffs) + for (i,qᵢ) ∈ enumerate(q.coeffs) + cs[i] = op(cs[i], qᵢ) + end + else + cs = convert(Vector{R}, q.coeffs) + for (i,pᵢ) ∈ enumerate(p.coeffs) + cs[i] = op(pᵢ, cs[i]) + end + end + MutableDenseViewPolynomial{B,R,X}(cs) +end + + +# pre-allocated multiplication +function LinearAlgebra.lmul!(c::Number, p::MutableDenseViewPolynomial{T,X}) where {T,X} + p.coeffs[:] = (c,) .* p.coeffs + p +end +function LinearAlgebra.rmul!(p::MutableDenseViewPolynomial{T,X}, c::Number) where {T,X} + p.coeffs[:] = p.coeffs .* (c,) + p +end diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index ace71215..5c61fd6b 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -107,7 +107,7 @@ end # return coeffs as a vector # use p.coeffs to get Dictionary function coeffs(p::MutableSparsePolynomial{B,T}) where {B,T} - a,b = firstindex(p), lastindex(p) + a,b = min(0,firstindex(p)), lastindex(p) cs = zeros(T, length(a:b)) for k in sort(collect(keys(p.coeffs))) v = p.coeffs[k] diff --git a/src/polynomials/multroot.jl b/src/polynomials/multroot.jl index 731dce7c..e456afac 100644 --- a/src/polynomials/multroot.jl +++ b/src/polynomials/multroot.jl @@ -3,8 +3,14 @@ module Multroot export multroot using ..Polynomials + using LinearAlgebra + +PnPolynomial = Polynomials.PnPolynomial +StandardBasisType = Polynomials.StandardBasisType +#PnPolynomial = Polynomials.MutableDenseViewPolynomial{Polynomials.StandardBasis} + """ multroot(p; verbose=false, method=:direct, kwargs...) @@ -94,7 +100,7 @@ For polynomials of degree 20 or higher, it is often the case the `l` is misidentified. """ -function multroot(p::Polynomials.StandardBasisPolynomial{T}; verbose=false, +function multroot(p::StandardBasisType{T}; verbose=false, kwargs...) where {T} # degenerate case, constant @@ -133,7 +139,7 @@ end # Better performing :direct method by Florent Bréhard, Adrien Poteaux, and Léo Soudant [Validated root enclosures for interval polynomials with multiplicities](preprint) function pejorative_manifold( - p::Polynomials.StandardBasisPolynomial{T,X}; + p::StandardBasisType{T,X}; #::Polynomials.StandardBasisPolynomial{T,X}; method = :direct, θ = 1e-8, # zero singular-value threshold ρ = 1e-13, # initial residual tolerance, was 1e-10 @@ -142,7 +148,7 @@ function pejorative_manifold( ) where {T,X} S = float(T) - u = Polynomials.PnPolynomial{S,X}(S.(coeffs(p))) + u = PnPolynomial{S,X}(S.(coeffs(p))) nu₂ = norm(u, 2) θ2, ρ2 = θ * nu₂, ρ * nu₂ @@ -173,9 +179,9 @@ end # using the `:iterative` method of Zeng function pejorative_manifold_multiplicities( ::Val{:iterative}, - u::Polynomials.PnPolynomial{T}, - v::Polynomials.PnPolynomial{T}, - w::Polynomials.PnPolynomial{T}, + u::PnPolynomial{T}, + v::PnPolynomial{T}, + w::PnPolynomial{T}, zs, l::Any, ρⱼ,θ, ρ, ϕ; @@ -213,9 +219,9 @@ end # directly from the cofactors v, w s.t. p = u*v and q = u*w function pejorative_manifold_multiplicities( ::Val{:direct}, - u::Polynomials.PnPolynomial{T}, - v::Polynomials.PnPolynomial{T}, - w::Polynomials.PnPolynomial{T}, + u::PnPolynomial{T}, + v::PnPolynomial{T}, + w::PnPolynomial{T}, zs, args...; kwargs...) where {T} @@ -239,7 +245,7 @@ root is a least squares minimizer of `F(z) = W ⋅ [Gₗ(z) - a]`. Here `a ~ (p_ This follows Algorithm 1 of [Zeng](https://www.ams.org/journals/mcom/2005-74-250/S0025-5718-04-01692-8/S0025-5718-04-01692-8.pdf) """ -function pejorative_root(p::Polynomials.StandardBasisPolynomial, +function pejorative_root(p::StandardBasisType, #::Polynomials.StandardBasisPolynomial, zs::Vector{S}, ls; kwargs...) where {S} ps = reverse(coeffs(p)) pejorative_root(ps, zs, ls; kwargs...) @@ -361,7 +367,7 @@ function cond_zl(p, zs::Vector{S}, ls) where {S} 1 / σ end -backward_error(p::AbstractPolynomial, zs::Vector{S}, ls) where {S} = +backward_error(p::StandardBasisType, zs::Vector{S}, ls) where {S} = backward_error(reverse(coeffs(p)), zs, ls) function backward_error(p, z̃s::Vector{S}, ls) where {S} @@ -393,7 +399,7 @@ end # If method=direct and leastsquares=true, compute the cofactors v,w # using least-squares rather than Zeng's AGCD refinement strategy function pejorative_manifold( - p::Polynomials.StandardBasisPolynomial{T,X}, + p::StandardBasisType{T,X}, #::Polynomials.StandardBasisPolynomial{T,X}, k::Int; method = :direct, leastsquares = false, @@ -406,7 +412,7 @@ function pejorative_manifold( error("Does this get called?") S = float(T) - u = Polynomials.PnPolynomial{S,X}(S.(coeffs(p))) + u = PnPolynomial{S,X}(S.(coeffs(p))) nu₂ = norm(u, 2) @@ -437,7 +443,7 @@ end # when the multiplicity structure l is known # If method=direct and leastsquares=true, compute the cofactors v,w # using least-squares rather than Zeng's AGCD refinement strategy -function pejorative_manifold(p::Polynomials.StandardBasisPolynomial{T,X}, +function pejorative_manifold(p::StandardBasisType{T,X}, #Polynomials.StandardBasisPolynomial{T,X}, l::Vector{Int}; method = :direct, leastsquares = false, @@ -448,7 +454,7 @@ function pejorative_manifold(p::Polynomials.StandardBasisPolynomial{T,X}, S = float(T) - u = Polynomials.PnPolynomial{S,X}(S.(coeffs(p))) + u = PnPolynomial{S,X}(S.(coeffs(p))) # number of distinct roots k = sum(l .> 0) @@ -479,9 +485,9 @@ function _ngcd(u, k) x = zeros(S, 2*k-1) Polynomials.NGCD.qrsolve!(x, A, b) # w = n*X^{k-1} + ... - w = Polynomials.PnPolynomial([x[1:k-1]; n]) + w = PnPolynomial([x[1:k-1]; n]) # v = X^k + ... - v = Polynomials.PnPolynomial([-x[k:2*k-1]; 1]) + v = PnPolynomial([-x[k:2*k-1]; 1]) v, w end @@ -495,9 +501,9 @@ end #function pejorative_manifold_iterative_multiplicities( function pejorative_manifold_multiplicities( ::Val{:iterative}, - u::Polynomials.PnPolynomial{T}, - v::Polynomials.PnPolynomial{T}, - w::Polynomials.PnPolynomial{T}, + u::PnPolynomial{T}, + v::PnPolynomial{T}, + w::PnPolynomial{T}, zs, l::Vector{Int}, args...; kwargs...) where {T} diff --git a/src/polynomials/ngcd.jl b/src/polynomials/ngcd.jl index 91a00ba6..9eb580fe 100644 --- a/src/polynomials/ngcd.jl +++ b/src/polynomials/ngcd.jl @@ -10,8 +10,8 @@ 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,X,P<:StandardBasisPolynomial{T,X}, - S,Y,Q<:StandardBasisPolynomial{S,Y}} + kwargs...) where {T,X,P<:StandardBasisType{T,X}, + S,Y,Q<:StandardBasisType{S,Y}} if (degree(q) > degree(p)) u,w,v,Θ,κ = ngcd(q,p,args...;kwargs...) return (u=u,v=v,w=w, Θ=Θ, κ=κ) @@ -45,12 +45,15 @@ function ngcd(p::P, q::Q, end ## call ngcd - p′ = PnPolynomial{R,X}(ps[nz:end]) - q′ = PnPolynomial{R,X}(qs[nz:end]) + #P′ = PnPolynomial + P′ = MutableDenseViewPolynomial{StandardBasis} + p′ = P′{R,X}(ps[nz:end]) + q′ = P′{R,X}(qs[nz:end]) out = NGCD.ngcd(p′, q′, args...; kwargs...) - ## convert to original polynomial type + 𝑷 = Polynomials.constructorof(promote_type(P,Q)){R,X} + 𝑷 = MutableDenseViewPolynomial{StandardBasis,R,X} u,v,w = convert.(𝑷, (out.u,out.v,out.w)) if nz > 1 u *= variable(u)^(nz-1) @@ -91,7 +94,11 @@ end module NGCD using Polynomials, LinearAlgebra -import Polynomials: PnPolynomial, constructorof +import Polynomials: constructorof, MutableDenseViewPolynomial, StandardBasis + +#PnPolynomial = Polynomials.PnPolynomial +PnPolynomial = MutableDenseViewPolynomial{StandardBasis} + """ ngcd(p::PnPolynomial{T,X}, q::PnPolynomial{T,X}, [k::Int]; diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis.jl index 2ddf4dc1..cc8cd538 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis.jl @@ -377,7 +377,7 @@ function uvw(V::Val{:euclidean}, p::P, q::P; kwargs...) where {P <: AbstractPoly end function uvw(::Any, p::P, q::P; kwargs...) where {P <: AbstractPolynomial} - throw(ArgumentError("not defined")) + throw(ArgumentError("not defined")) end # Some things lifted from diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 95667d19..5a8ec08b 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -1,4 +1,5 @@ struct StandardBasis <: AbstractBasis end +const StandardBasisType = Union{Polynomials.StandardBasisPolynomial{T,X}, Polynomials.AbstractUnivariatePolynomial{<:Polynomials.StandardBasis,T,X}} where {T,X} basis_symbol(::Type{P}) where {P<:AbstractUnivariatePolynomial{StandardBasis}} = string(indeterminate(P)) function printbasis(io::IO, ::Type{P}, j::Int, m::MIME) where {B<:StandardBasis, P <: AbstractUnivariatePolynomial{B}} @@ -146,31 +147,13 @@ function LinearAlgebra.cond(p::P, x) where {B<:StandardBasis, P<:AbstractUnivari p̃(abs(x))/ abs(p(x)) end -function ngcd(p::P, q::Q, - args...; - kwargs...) where {B <: StandardBasis, - T,X,P<:AbstractUnivariatePolynomial{B,T,X}, - S,Y,Q<:AbstractUnivariatePolynomial{B,S,Y}} - ngcd(PnPolynomial(coeffs(p)), PnPolynomial(coeffs(q)), args...; kwargs...) -end - -# XXX p.coeffs isn't right -function Multroot.multroot(p::AbstractUnivariatePolynomial{B}, args...; - kwargs...) where {B<:StandardBasis} - cs = coeffs(p) - if firstindex(p) > 0 - cs = vcat(zeros(firstindex(p)), cs) - elseif firstindex(p) < 0 - @warn "Laurent Polynomial; finding values after factoring out leading term" - end - Multroot.multroot(Polynomial(cs), args...; kwargs...) -end - -Polynomials.Multroot.pejorative_root(q::AbstractUnivariatePolynomial{<:StandardBasis}, zs::Vector{S}, ls; kwargs...) where {S} = - Polynomials.Multroot.pejorative_root(convert(Polynomial, q), zs, ls; kwargs...) - -Polynomials.Multroot.stats(q::AbstractUnivariatePolynomial{<:StandardBasis}, zs::Vector{S}, ls; kwargs...) where {S} = - Polynomials.Multroot.stats(convert(Polynomial, q), zs, ls; kwargs...) +# function ngcd(p::P, q::Q, +# args...; +# kwargs...) where {B <: StandardBasis, +# T,X,P<:AbstractUnivariatePolynomial{B,T,X}, +# S,Y,Q<:AbstractUnivariatePolynomial{B,S,Y}} +# ngcd(PnPolynomial(coeffs(p)), PnPolynomial(coeffs(q)), args...; kwargs...) +# end function fit(::Type{P}, x::AbstractVector{T}, @@ -193,6 +176,6 @@ end # these are Polynomial{T, X}(coeffs::AbstractVector{S},order::Int) where {T, X, S} = Polynomial{T,X}(coeffs) FactoredPolynomial{T,X}(coeffs::AbstractVector{S}, order::Int) where {T,S,X} = FactoredPolynomial{T,X}(coeffs) -PnPolynomial{T, X}(coeffs::AbstractVector, order::Int) where {T, X} = PnPolynomial(coeffs) # for generic programming -PnPolynomial{T}(coeffs::AbstractVector, order::Int,var) where {T} = PnPolynomial(coeffs,var) # for generic programming -PnPolynomial(coeffs::AbstractVector, order::Int,var) = PnPolynomial(coeffs,var) # for generic programming +#PnPolynomial{T, X}(coeffs::AbstractVector, order::Int) where {T, X} = PnPolynomial(coeffs) # for generic programming +#PnPolynomial{T}(coeffs::AbstractVector, order::Int,var) where {T} = PnPolynomial(coeffs,var) # for generic programming +#PnPolynomial(coeffs::AbstractVector, order::Int,var) = PnPolynomial(coeffs,var) # for generic programming diff --git a/src/standard-basis/standard-dense-view.jl b/src/standard-basis/standard-dense-view.jl new file mode 100644 index 00000000..bdcae1ef --- /dev/null +++ b/src/standard-basis/standard-dense-view.jl @@ -0,0 +1,86 @@ +""" + PnPolynomial{T,X}(coeffs::Vector{T}) + +Construct a polynomial in `P_n` (or `Πₙ`), the collection of polynomials in the +standard basis of degree `n` *or less*, using a vector of length +`N+1`. + +* Unlike other polynomial types, this type allows trailing zeros in the coefficient vector. Call `chop!` to trim trailing zeros if desired. +* Unlike other polynomial types, this does not copy the coefficients on construction +* Unlike other polynomial types, this type broadcasts like a vector for in-place vector operations (scalar multiplication, polynomial addition/subtraction of the same size) +* The method inplace `mul!(pq, p, q)` is defined to use precallocated storage for the product of `p` and `q` + +This type is useful for reducing copies and allocations in some algorithms. + +""" +const PnPolynomial = MutableDenseViewPolynomial{StandardBasis} + + +function evalpoly(c, p::PnPolynomial{T,X}) where {T,X} + iszero(p) && return zero(T) * zero(c) + EvalPoly.evalpoly(c, p.coeffs) +end + +# scalar add +function scalar_add(c::S, p:: PnPolynomial{T,X}) where {S, T, X} + R = promote_type(T,S) + P = MutableDenseViewPolynomial{B,R,X} + + iszero(p) && return P([c], 0) + cs = convert(Vector{R}, copy(p.coeffs)) + cs[1] += c + P(cs) +end + +function ⊗(p:: PnPolynomial{T,X}, + q:: PnPolynomial{S,X}) where {T,S,X} + R = promote_type(T,S) + P = PnPolynomial{R,X} + + N,M = length(p), length(q) + iszero(N) && return zero(P) + iszero(M) && return zero(P) + + cs = Vector{R}(undef, N+M-1) + pq = P(cs) + mul!(pq, p, q) + return pq +end + +function derivative(p::PnPolynomial{T,X}) where {T,X} + R = promote_type(T, Int) + N = length(p.coeffs) + iszero(N) && return zero(MutableDenseViewPolynomial{B,R,X}) + cs = Vector{R}(undef,N-1) + for (i, pᵢ) ∈ Base.Iterators.drop(pairs(p),1) + cs[i] = i * pᵢ + end + PnPolynomial{R,X}(cs) +end + +function integrate(p::PnPolynomial{T,X}) where {T,X} + R = Base.promote_op(/, T, Int) + N = length(p.coeffs) + cs = Vector{R}(undef,N+1) + cs[1] = zero(R) + for (i, pᵢ) ∈ pairs(p) + cs[i+1+1] = pᵢ/(i+1) + end + PnPolynomial{R,X}(cs) +end + + + +# This is for standard basis XXX +function LinearAlgebra.mul!(pq, p::PnPolynomial{T,X}, q) where {T,X} + m,n = length(p)-1, length(q)-1 + cs = pq.coeffs + cs .= 0 + @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) + for (j, qⱼ) ∈ enumerate(q.coeffs) + ind = i + j - 1 + cs[ind] = muladd(pᵢ, qⱼ, cs[ind]) + end + end + nothing +end diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl index fdc5249c..f5193b7c 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/standard-immutable.jl @@ -66,26 +66,19 @@ constantterm(p::ImmutableDensePolynomial{B,T,X,N}) where {B <: StandardBasis,T,X scalar_add(p::ImmutableDensePolynomial{B,T,X,0}, c::S) where {B<:StandardBasis,T,X,S} = ImmutableDensePolynomial{B,promote_type(T,S),X,1}((c,)) - +function scalar_add(p::ImmutableDensePolynomial{B,T,X,1}, c::S) where {B<:StandardBasis,T,X,S} + R = promote_type(T,S) + ImmutableDensePolynomial{B,R,X}(NTuple{1,R}(p[0] + c)) +end function scalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B<:StandardBasis,T,X,S,N} R = promote_type(T,S) P = ImmutableDensePolynomial{B,R,X} iszero(c) && return P{N}(convert(NTuple{N,R}, p.coeffs)) - N == 0 && return P{1}(NTuple{1,R}(c)) - N == 1 && return P{N}((p[0]+c,)) cs = _tuple_combine(+, convert(NTuple{N,R}, p.coeffs), NTuple{1,R}((c,))) q = P{N}(cs) return q - - - P = ImmutableDensePolynomial{B,R,X} - iszero(N) && return P{1}((c,)) - - xs = convert(NTuple{N,R}, p.coeffs) - @set! xs[1] = xs[1] + c - P{N}(xs) end diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index dbd969de..88a34f41 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -1,7 +1,6 @@ using LinearAlgebra using OffsetArrays, StaticArrays import Polynomials: indeterminate, ⟒ -import Polynomials: ImmutableDensePolynomial, StandardBasis,MutableSparsePolynomial, MutableDensePolynomial ## Test standard basis polynomials with (nearly) the same tests @@ -25,13 +24,11 @@ upto_z(as, bs) = upto_tz(filter(!iszero,as), filter(!iszero,bs)) ==ᵟ(a,b) = (a == b) ==ᵟ(a::FactoredPolynomial, b::FactoredPolynomial) = a ≈ b -_isimmutable(::Type{P}) where {P <: Union{ImmutablePolynomial, FactoredPolynomial, ImmutableDensePolynomial{StandardBasis}}} = true +_isimmutable(::Type{P}) where {P <: Union{ImmutablePolynomial, FactoredPolynomial}} = true _isimmutable(P) = false -Ps = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, FactoredPolynomial, - MutableDensePolynomial{StandardBasis},ImmutableDensePolynomial{StandardBasis}, MutableSparsePolynomial{StandardBasis} - ) +Ps = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, FactoredPolynomial) @testset "Construction" begin @testset for coeff in Any[ @@ -413,7 +410,7 @@ end pR = P([3 // 4, -2 // 1, 1 // 1]) # type stability of the default constructor without variable name - if !(P ∈ (LaurentPolynomial, ImmutablePolynomial, FactoredPolynomial, ImmutableDensePolynomial{StandardBasis})) + if !(P ∈ (LaurentPolynomial, ImmutablePolynomial, FactoredPolynomial)) @inferred P([1, 2, 3]) @inferred P([1,2,3], Polynomials.Var(:x)) end @@ -557,7 +554,7 @@ end # issue #395 @testset for P ∈ Ps - P ∈ (FactoredPolynomial, ImmutablePolynomial, ImmutableDensePolynomial{StandardBasis} ) && continue + P ∈ (FactoredPolynomial, ImmutablePolynomial) && continue p = P([2,1], :s) @inferred -p # issue #395 @inferred 2p @@ -580,12 +577,12 @@ end # evaluation at special cases different number types @testset for P ∈ Ps - P ∈ (SparsePolynomial, FactoredPolynomial, MutableSparsePolynomial{StandardBasis}) && continue + P ∈ (SparsePolynomial, FactoredPolynomial) && continue # vector coefficients v₀, v₁ = [1,1,1], [1,2,3] p₁ = P([v₀]) @test p₁(0) == v₀ == Polynomials.constantterm(p₁) - P != ImmutableDensePolynomial{StandardBasis} && @test_throws MethodError (0 * p₁)(0) # no zero(Vector{Int}) # XXX + P != ImmutablePolynomial && @test_throws MethodError (0 * p₁)(0) # no zero(Vector{Int}) # XXX p₂ = P([v₀, v₁]) @test p₂(0) == v₀ == Polynomials.constantterm(p₂) @test p₂(2) == v₀ + 2v₁ @@ -604,8 +601,7 @@ end # p - p requires a zero @testset for P ∈ Ps - P ∈ (LaurentPolynomial, SparsePolynomial, - FactoredPolynomial, MutableSparsePolynomial{StandardBasis}) && continue + P ∈ (LaurentPolynomial, SparsePolynomial,FactoredPolynomial) && continue for v ∈ ([1,2,3], [[1,2,3],[1,2,3]], [[1 2;3 4], [3 4; 5 6]] @@ -682,7 +678,7 @@ end # Check for isequal p1 = P([1.0, -0.0, 5.0, Inf]) p2 = P([1.0, 0.0, 5.0, Inf]) - !(P ∈ (FactoredPolynomial, SparsePolynomial, MutableSparsePolynomial{StandardBasis})) && (@test p1 == p2 && !isequal(p1, p2)) # SparsePolynomial doesn't store -0.0, 0.0. + !(P ∈ (FactoredPolynomial, SparsePolynomial)) && (@test p1 == p2 && !isequal(p1, p2)) # SparsePolynomial doesn't store -0.0, 0.0. p3 = P([0, NaN]) @test p3 === p3 && p3 ≠ p3 && isequal(p3, p3) @@ -909,7 +905,7 @@ end X = :x @testset for P in Ps - if !(P ∈ (ImmutablePolynomial, ImmutableDensePolynomial{StandardBasis})) + if !(P ∈ (ImmutablePolynomial,)) p = P([0,one(Float64)]) @test P{Complex{Float64},X} == typeof(p + 1im) @test P{Complex{Float64},X} == typeof(1im - p) @@ -1041,7 +1037,7 @@ end @test out.ϵ <= sqrt(eps()) @test out.κ * out.ϵ < sqrt(eps()) # small forward error # one for which the multiplicities are not correctly identified - n = 4 + n = 3 # was 4? q = p^n out = Polynomials.Multroot.multroot(q) @test (out.multiplicities == n*ls) || (out.κ * out.ϵ > sqrt(eps())) # large forward error, l misidentified @@ -1214,14 +1210,14 @@ end @test !issymmetric(A) U = A * A' @test U[1,2] ≈ U[2,1] # issymmetric with some allowed error for FactoredPolynomial - P != Polynomials.ImmutableDensePolynomial{Polynomials.StandardBasis} && diagm(0 => [1, p^3], 1=>[p^2], -1=>[p]) + P != ImmutablePolynomial && diagm(0 => [1, p^3], 1=>[p^2], -1=>[p]) end # issue 206 with mixed variable types and promotion @testset for P in Ps λ = P([0,1],:λ) A = [1 λ; λ^2 λ^3] - P != Polynomials.ImmutableDensePolynomial{Polynomials.StandardBasis} && @test A == diagm(0 => [1, λ^3], 1=>[λ], -1=>[λ^2]) # XXX diagm + ImmutableDensePolynomial{StandardBasis} isn't working + @test A == diagm(0 => [1, λ^3], 1=>[λ], -1=>[λ^2]) @test iszero([1 -λ]*[λ^2 λ; λ 1]) @test [λ 1] + [1 λ] == (λ+1) .* [1 1] # (λ+1) not a number, so we broadcast end @@ -1689,7 +1685,7 @@ end T1,T2 = Ts[i],Ts[i+1] @testset for P in Ps P <: FactoredPolynomial && continue - if !(P ∈ (ImmutablePolynomial, ImmutableDensePolynomial{StandardBasis})) + if !(P ∈ (ImmutablePolynomial,)) p = P{T2}(T1.(rand(1:3,3))) @test typeof(p) == P{T2, :x} else @@ -1705,7 +1701,7 @@ end # test P{T}(...) is P{T} (not always the case for FactoredPolynomial) @testset for P in Ps P <: FactoredPolynomial && continue - if !(P ∈ (ImmutablePolynomial, ImmutableDensePolynomial{StandardBasis})) + if !(P ∈ (ImmutablePolynomial,)) @testset for T in (Int32, Int64, BigInt) p₁ = P{T}(Float64.(rand(1:3,5))) @test typeof(p₁) == P{T,:x} # conversion works From 4c608f5a4bff40759e17b3a59fd5776078ea86b4 Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 1 Aug 2023 20:09:17 -0400 Subject: [PATCH 23/31] WIP: cleanup --- docs/src/extending.md | 4 +- src/Polynomials.jl | 8 +- src/abstract-polynomial.jl | 97 +++++++++++-------- src/basis-utils.jl | 26 ----- src/common.jl | 11 ++- .../immutable-dense-polynomial.jl | 40 +++----- .../mutable-dense-polynomial.jl | 58 +++++++---- .../mutable-dense-view-polynomial.jl | 16 ++- .../mutable-sparse-polynomial.jl | 54 ++++++----- src/polynomials/chebyshev.jl | 52 ++++++---- src/standard-basis/standard-basis.jl | 29 +++--- src/standard-basis/standard-dense-view.jl | 12 +-- src/standard-basis/standard-dense.jl | 10 +- test/ChebyshevT.jl | 17 ++-- 14 files changed, 232 insertions(+), 202 deletions(-) delete mode 100644 src/basis-utils.jl diff --git a/docs/src/extending.md b/docs/src/extending.md index e7e44264..14af95e6 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -180,11 +180,11 @@ julia> import Polynomials: AbstractUnivariatePolynomial, AbstractBasis, MutableD julia> struct LaguerreBasis{alpha} <: AbstractBasis end -julia> Polynomials.basis_symbol(::Type{<:AbstractUnivariatePolynomial{LaguerreBasis{α}}}) where {α} = +julia> Polynomials.basis_symbol(::Type{<:AbstractUnivariatePolynomial{LaguerreBasis{α},T,X}}) where {α,T,X} = "L^$(α)" ``` -The basis symbol has no default. We added a method to `basis_symbol` to show this basis. More generally, `Polynomials.printbasis` can have methods added to adjust for different display types. +The basis symbol has a poor default. The method requires the full type, as the indeterminate, `X`, may part of the desired output. We added a method to `basis_symbol` to show this basis. More generally, `Polynomials.printbasis` can have methods added to adjust for different display types. Polynomials can be initiated through specifying a storage type and a basis, say: diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 00c52b64..3f22af99 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -16,25 +16,23 @@ include("common.jl") # Polynomials include("polynomials/standard-basis.jl") include("polynomials/Polynomial.jl") +include("polynomials/factored_polynomial.jl") + #include("polynomials/ImmutablePolynomial.jl") #include("polynomials/SparsePolynomial.jl") #include("polynomials/LaurentPolynomial.jl") - #include("polynomials/pi_n_polynomial.jl") #include("polynomials/ngcd.jl") #include("polynomials/multroot.jl") -include("polynomials/factored_polynomial.jl") -include("polynomials/ChebyshevT.jl") +#include("polynomials/ChebyshevT.jl") # polynomials with explicit basis include("abstract-polynomial.jl") -include("basis-utils.jl") include("polynomial-basetypes/mutable-dense-polynomial.jl") include("polynomial-basetypes/immutable-dense-polynomial.jl") include("polynomial-basetypes/mutable-sparse-polynomial.jl") include("polynomial-basetypes/mutable-dense-view-polynomial.jl") - include("standard-basis/standard-basis.jl") include("standard-basis/standard-dense.jl") include("standard-basis/standard-immutable.jl") diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 0bdbffc9..36c020a7 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -1,9 +1,15 @@ # XXX todo, merge in with common.jl, abstract.jl -# used by LaurentPolynomial, ImmutablePolynomial, SparsePolynomial +# used by LaurentPolynomial, ImmutablePolynomial, SparsePolynomial, PnPolynomial, ChebyshevT """ Abstract type for polynomials with an explicit basis. """ abstract type AbstractUnivariatePolynomial{B, T, X} <: AbstractPolynomial{T,X} end + +# for 0-based polys +abstract type AbstractDenseUnivariatePolynomial{B, T, X} <: AbstractUnivariatePolynomial{B,T,X} end +# for negative integer +abstract type AbstractLaurentUnivariatePolynomial{B, T, X} <: AbstractUnivariatePolynomial{B,T,X} end + abstract type AbstractBasis end export AbstractUnivariatePolynomial @@ -24,14 +30,18 @@ function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where return true end -# overload basis_symbol for most types # overload printbasis to see a subscript, as Tᵢ... or if there are parameters in basis function printbasis(io::IO, ::Type{P}, j::Int, m::MIME) where {P <: AbstractUnivariatePolynomial} print(io, basis_symbol(P)) print(io, subscript_text(j, m)) end -_convert(p::P, as) where {B,T,X,P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(as, firstindex(p)) +basis_symbol(::Type{AbstractUnivariatePolynomial{B,T,X}}) where {B,T,X} = "Χ($(X))" + +# should use map, but this is used in common.jl +_convert(p::P, as) where {B,T,X,P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){promote_type(T, eltype(as)), X}(as) + + ## idea is vector space stuff (scalar_add, scalar_mult, vector +/-, ^) goes here ## connection (convert, transform) is specific to a basis (storage) @@ -39,12 +49,14 @@ _convert(p::P, as) where {B,T,X,P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒( basistype(p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = B +basistype(::Type{<:AbstractUnivariatePolynomial{B}}) where {B} = B # some default Base.eltype(p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = T indeterminate(p::P) where {P <: AbstractUnivariatePolynomial} = indeterminate(P) _indeterminate(::Type{P}) where {P <: AbstractUnivariatePolynomial} = nothing _indeterminate(::Type{P}) where {B,T, X, P <: AbstractUnivariatePolynomial{B,T,X}} = X indeterminate(::Type{P}) where {P <: AbstractUnivariatePolynomial} = something(_indeterminate(P), :x) +XXX() = throw(ArgumentError("Method not defined")) constructorof(::Type{<:AbstractUnivariatePolynomial}) = XXX() ⟒(P::Type{<:AbstractUnivariatePolynomial}) = constructorof(P) # returns the Storage{Basis} partially constructed type ⟒(p::P) where {P <: AbstractUnivariatePolynomial} = ⟒(P) @@ -58,7 +70,8 @@ Base.keys(p::AbstractUnivariatePolynomial) = Base.Generator(first, pairs(p)) Base.values(p::AbstractUnivariatePolynomial) = Base.Generator(last, pairs(p)) Base.firstindex(p::AbstractUnivariatePolynomial{B, T, X}) where {B,T,X} = XXX() Base.lastindex(p::AbstractUnivariatePolynomial{B, T, X}) where {B,T,X} = XXX() -Base.iterate(p::AbstractUnivariatePolynomial, args...) = Base.iterate(p.coeffs, args...) +#Base.iterate(p::AbstractUnivariatePolynomial, args...) = Base.iterate(values(p), args...) +Base.iterate(p::AbstractUnivariatePolynomial, state = firstindex(p)) = _iterate(p, state) # _iterate in common.jl Base.pairs(p::AbstractUnivariatePolynomial) = XXX() #Base.eltype(::Type{<:AbstractUnivariatePolynomial}) = Float64 @@ -69,27 +82,15 @@ Base.size(p::AbstractUnivariatePolynomial, i::Integer) = i <= 1 ? size(p)[i] : Base.copy(p::AbstractUnivariatePolynomial) = XXX() -# dense collection -Base.iterate(p::AbstractUnivariatePolynomial, state = firstindex(p)) = _iterate(p, state) # _iterate in common.jl + #hasnan(p::AbstractUnivariatePolynomial) = any(hasnan, p) -# norm(q1 - q2) -function normΔ(q1::AbstractUnivariatePolynomial, q2::AbstractUnivariatePolynomial, p::Real = 2) - iszero(q1) && return norm(q2, p) - iszero(q2) && return norm(q1, p) - r = abs(zero(q1[end] + q2[end])) - tot = zero(r) - for i ∈ union(keys(q1), keys(q2)) - @inbounds tot += abs(q1[i] - q2[i])^p - end - return tot^(1/p) -end # map Polynomial terms -> vector terms +# Default degree **assumes** basis element Tᵢ has degree i. degree(p::AbstractUnivariatePolynomial) = iszero(p) ? -1 : lastindex(p) -# order(p::AbstractUnivariatePolynomial) = firstindex(p) XXX conflicts with DataFrames.order # this helps, along with _set, make some storage-generic methods _zeros(p::P, z, N) where {P <: AbstractUnivariatePolynomial} = _zeros(P, z, N) @@ -102,19 +103,19 @@ end #check_same_variable(p::AbstractUnivariatePolynomial, q::AbstractUnivariatePolynomial) = indeterminate(p) == indeterminate(q) -# The zero polynomial. Typically has no coefficients +# The zero polynomial. Typically has no coefficients. in common.jl #Base.zero(p::P,args...) where {P <: AbstractUnivariatePolynomial} = zero(P,args...) #Base.zero(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),indeterminate(P)}) #Base.zero(::Type{P},var::SymbolLike) where {B,P <: AbstractUnivariatePolynomial{B}} = zero(⟒(P){eltype(P),Symbol(var)}) # the polynomial 1 -# one(P) is basis dependent +# one(P) is basis dependent and must be implemented in one(::Type{<:P}) Base.one(p::P,args...) where {P <: AbstractUnivariatePolynomial} = one(P,args...) Base.one(::Type{P}) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){eltype(P),indeterminate(P)}) Base.one(::Type{P}, var::SymbolLike) where {B, P <: AbstractUnivariatePolynomial{B}} = one(⟒(P){eltype(P),Symbol(var)}) -# the variable x +# the variable x is basid dependent and must be implmented in variable(::Type{<:P}) variable(p::P) where {P <: AbstractUnivariatePolynomial} = variable(P) variable(::Type{P}) where {B,P <: AbstractUnivariatePolynomial{B}} = variable(⟒(P){eltype(P),indeterminate(P)}) variable(::Type{P}, var::SymbolLike) where {B,P<:AbstractUnivariatePolynomial{B}} = variable(⟒(P){eltype(P),Var(var)}) @@ -126,24 +127,25 @@ basis(::Type{P}, i::Int) where {B,P <: AbstractUnivariatePolynomial{B}} = basis( copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {B,T, X, S, Y, P <:AbstractUnivariatePolynomial{B,S,Y}} = ⟒(P){T, Symbol(X)}(p.coeffs) -# XXX something feels off here... -# coefficients -# if laurent type trim, return coeffs -# if not laurent, return all -# if laurent type and lowest degree < 0 (after chopping) return p.coeffs (user needs to get offset) -# return Val(::Bool) to indicate if laurent type. This should compile away, unlike the check -laurenttype(P::Type{<:AbstractPolynomial}) = Val(minimumexponent(P) < 0) - -coeffs(p::P) where {P <: AbstractUnivariatePolynomial} = coeffs(laurenttype(P), p) -function coeffs(laurent::Val{true}, p) - p.coeffs -end -function coeffs(laurent::Val{false}, p) + +coeffs(p::P) where {P <: AbstractLaurentUnivariatePolynomial} = p.coeffs +function coeffs(p::P) where {P <: AbstractDenseUnivariatePolynomial} firstindex(p) == 0 && return p.coeffs firstindex(p) > 0 && return [p[i] for i ∈ 0:lastindex(p)] throw(ArgumentError("Polynomial type does not support negative degree terms")) end +gtτ(x, τ) = abs(x) > τ +# return index or nothing of last non "zdero" +function chop_right_index(x; rtol=nothing, atol=nothing) + isempty(x) && return nothing + δ = something(rtol,0) + ϵ = something(atol,0) + τ = max(ϵ, norm(x,2) * δ) + i = findlast(Base.Fix2(gtτ, τ), x) + i +end + # chop chops right side of p # use trunc for left and right # can pass tolerances @@ -154,6 +156,18 @@ chop!(p::AbstractUnivariatePolynomial; kwargs...) = XXX() ## --- #= Comparisons =# + +# norm(q1 - q2) only non-allocating +function normΔ(q1::AbstractUnivariatePolynomial, q2::AbstractUnivariatePolynomial) + iszero(q1) && return norm(q2, 2) + iszero(q2) && return norm(q1, 2) + tot = abs(zero(q1[end] + q2[end])) + for i ∈ unique(Base.Iterators.flatten((keys(q1), keys(q2)))) + @inbounds tot += abs2(q1[i] - q2[i]) + end + return sqrt(tot) +end + # need to promote Number -> Poly function Base.isapprox(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial; kwargs...) isapprox(promote(p1, p2)...; kwargs...) @@ -226,7 +240,7 @@ end ## * polynomial addition: with storage type ## * polynomial multiplication: resolstorage type + basis ## -Base.:-(p::AbstractUnivariatePolynomial) = scalar_mult(-1, p) +Base.:-(p::AbstractUnivariatePolynomial) = map(-, p) #scalar_mult(-1, p) Base.:+(c::Scalar, p::AbstractUnivariatePolynomial) = scalar_add(p, c) Base.:+(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_add(p, c) @@ -260,11 +274,15 @@ Base.:*(p::AbstractUnivariatePolynomial{B, T, X}, q::AbstractUnivariatePolynomial{B, S, Y}) where {B,T,S,X,Y} = _mixed_symbol_op(*, p, q) -Base.:/(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_mult(p, one(eltype(p))/c) +Base.:/(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_div(p, c) Base.:^(p::AbstractUnivariatePolynomial, n::Integer) = Base.power_by_squaring(p, n) -# treat constant polynomials as when symbols mixed +scalar_mult(p::AbstractUnivariatePolynomial{B,T,X}, c) where {B,T,X} = map(Base.Fix2(*,c), p) +scalar_mult(c, p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = map(Base.Fix1(*,c), p) +scalar_div(p::AbstractUnivariatePolynomial{B,T,X}, c) where {B,T,X} = map(Base.Fix2(*,one(T)/c), p) # much better than Fix2(/,c) + +# treat constant polynomials as constants when symbols mixed scalar_op(::typeof(*)) = scalar_mult scalar_op(::typeof(+)) = scalar_add scalar_op(::typeof(/)) = scalar_div @@ -280,7 +298,7 @@ function _mixed_symbol_op(op, end -# only need to define differentiate(p::PolyType) +# only need to define derivative(p::PolyType) function derivative(p::AbstractUnivariatePolynomial, n::Int=1) n < 0 && throw(ArgumentError("n must be non-negative")) iszero(n) && return p @@ -290,6 +308,7 @@ function derivative(p::AbstractUnivariatePolynomial, n::Int=1) end p′ end +## better parallel with integrate, but derivative has been used here. const differentiate = derivative function fit(::Type{P}, @@ -318,7 +337,7 @@ end # * integrate # * divrem # * vander -# * +# * companion # promote, promote_rule, vector specification, untyped specification, handle constants, conversion of Q(p) # poly composition, calling a polynomial diff --git a/src/basis-utils.jl b/src/basis-utils.jl deleted file mode 100644 index 33557717..00000000 --- a/src/basis-utils.jl +++ /dev/null @@ -1,26 +0,0 @@ -## XXX move to contrib.jl -gtτ(x, τ) = abs(x) > τ - -# return index or nothing of last non "zdero" -# chop! then considers cases i==nothing, i=length(x), i < length(x) -function chop_right_index(x; rtol=nothing, atol=nothing) - - isempty(x) && return nothing - δ = something(rtol,0) - ϵ = something(atol,0) - τ = max(ϵ, norm(x,2) * δ) - i = findlast(Base.Fix2(gtτ, τ), x) - i - -end - -function chop_left_index(x; rtol=nothing, atol=nothing) - isempty(x) && return nothing - δ = something(rtol,0) - ϵ = something(atol,0) - τ = max(ϵ, norm(x,2) * δ) - i = findfirst(Base.Fix2(gtτ,τ), x) - i -end - -XXX() = throw(ArgumentError("Method not defined")) diff --git a/src/common.jl b/src/common.jl index 24899cab..4193a1ea 100644 --- a/src/common.jl +++ b/src/common.jl @@ -584,9 +584,14 @@ Base.any(pred, p::AbstractPolynomial{T,X}) where {T, X} = any(pred, values(p)) Transform coefficients of `p` by applying a function (or other callables) `fn` to each of them. -You can implement `real`, etc., to a `Polynomial` by using `map`. +You can implement `real`, etc., to a `Polynomial` by using `map`. The type of `p` may narrow using this function. """ -Base.map(fn, p::P, args...) where {P<:AbstractPolynomial} = _convert(p, map(fn, coeffs(p), args...)) +function Base.map(fn, p::P, args...) where {P<:AbstractPolynomial} + xs = map(fn, p.coeffs, args...) + R = eltype(xs) + X = indeterminate(p) + return ⟒(P){R,X}(xs) +end """ @@ -944,7 +949,7 @@ end arithmetic =# Scalar = Union{Number, Matrix} -Base.:-(p::P) where {P <: AbstractPolynomial} = _convert(p, -coeffs(p)) +Base.:-(p::P) where {P <: AbstractPolynomial} = _convert(p, -coeffs(p)) # map(-, p) Base.:*(p::AbstractPolynomial, c::Scalar) = scalar_mult(p, c) Base.:*(c::Scalar, p::AbstractPolynomial) = scalar_mult(c, p) diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index c1d94ef4..296db22a 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -10,7 +10,7 @@ coefficient tuple as `p`. The `chop` function will trim off trailing zeros, when Immutable is a bit of a misnomer, as using the `@set!` macro from `Setfield.jl` one can modify elements, as in `@set! p[i] = value`. """ -struct ImmutableDensePolynomial{B,T,X,N} <: AbstractUnivariatePolynomial{B,T,X} +struct ImmutableDensePolynomial{B,T,X,N} <: AbstractDenseUnivariatePolynomial{B,T,X} coeffs::NTuple{N,T} function ImmutableDensePolynomial{B,T,X,N}(cs::NTuple{N,S}) where {B,N,T,X,S} new{B,T,Symbol(X),N}(cs) @@ -77,6 +77,12 @@ function Base.convert(::Type{<:ImmutableDensePolynomial{B,T,X,N}}, ImmutableDensePolynomial{B,T,X,N}(ntuple(i -> T(p[i-1]), Val(N))) end +function Base.map(fn, p::P, args...) where {B,T,X, P<:ImmutableDensePolynomial{B,T,X}} + xs = map(fn, p.coeffs, args...) + R = eltype(xs) + return ImmutableDensePolynomial{B,R,X}(xs) +end + Base.copy(p::ImmutableDensePolynomial) = p Base.similar(p::ImmutableDensePolynomial, args...) = p.coeffs @@ -129,15 +135,15 @@ end # isapprox helper -function normΔ(q1::ImmutableDensePolynomial{B}, q2::ImmutableDensePolynomial{B}, p::Real = 2) where {B} +function normΔ(q1::ImmutableDensePolynomial{B}, q2::ImmutableDensePolynomial{B}) where {B} iszero(q1) && return norm(q2, p) iszero(q2) && return norm(q1, p) - r = zero(q1[end] + q2[end]) + r = abs(zero(q1[end] + q2[end])) tot = zero(r) for i ∈ 1:maximum(lastindex, (q1,q2)) - @inbounds tot += (q1[i] - q2[i])^p + @inbounds tot += abs2(q1[i] - q2[i]) end - return tot^(1/p) + return sqrt(tot) end @@ -147,7 +153,7 @@ _zeros(::Type{<:ImmutableDensePolynomial}, z::S, N) where {S} = ntuple(_ -> zero(S), Val(N)) minimumexponent(::Type{<:ImmutableDensePolynomial}) = 0 -laurenttype(::Type{<:ImmutableDensePolynomial}) = Val(false) +#XXXlaurenttype(::Type{<:ImmutableDensePolynomial}) = Val(false) Base.firstindex(p::ImmutableDensePolynomial) = 0 Base.lastindex(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = N - 1 @@ -223,10 +229,7 @@ end ## Vector space operations -# vector ops +, -, c*x -## unary -Base.:-(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = - ImmutableDensePolynomial{B,T,X,N}(map(-, p.coeffs)) +## vector ops +, -, c*x ## binary function Base.:+(p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomial{B,S,X,M}) where{B,X,T,S,N,M} @@ -249,20 +252,9 @@ function _tuple_combine(op, p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDe end -# scalar -scalar_mult(p::ImmutableDensePolynomial{B,T,X,0}, c::S) where {B,T,X,S} = zero(ImmutableDensePolynomial{B,T,X,0}) -function scalar_mult(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B,T,X,S,N} - cs = p.coeffs .* (c,) - R = eltype(cs) - return ImmutableDensePolynomial{B,R,X,N}(cs) -end - -scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,0}) where {B,T,X,S} = zero(ImmutableDensePolynomial{B,T,X,0}) -function scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,S,N} - cs = (c,) .* p.coeffs - R = eltype(cs) - return ImmutableDensePolynomial{B,R,X,N}(cs) -end +# scalar mult faster with 0 default +scalar_mult(p::ImmutableDensePolynomial{B,T,X,0}, c::S) where {B,T,X,S} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) +scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,0}) where {B,T,X,S} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) ## --- diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index 1d1c45cd..0f5ad9ad 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -8,7 +8,7 @@ The typical offset is to have `0` as the order, but, say, to accomodate Laurent This type trims trailing zeros and the leading zeros on construction. """ -struct MutableDensePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T, X} +struct MutableDensePolynomial{B,T,X} <: AbstractLaurentUnivariatePolynomial{B,T, X} coeffs::Vector{T} order::Base.RefValue{Int} # lowest degree, typically 0 function MutableDensePolynomial{B,T,X}(cs::AbstractVector{S}, order::Int=0) where {B,T,X,S} @@ -57,6 +57,13 @@ function Base.convert(::Type{MutableDensePolynomial{B,T,X}}, q::MutableDensePoly MutableDensePolynomial{B,T,X}(Val(false), convert(Vector{T},q.coeffs), q.order[]) end +function Base.map(fn, p::P, args...) where {B,T,X, P<:MutableDensePolynomial{B,T,X}} + xs = map(fn, p.coeffs, args...) + xs = trim_trailing_zeros(xs) + R = eltype(xs) + return ⟒(P){R,X}(Val(false), xs, p.order[]) +end + Base.copy(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs), firstindex(p)) @@ -77,7 +84,7 @@ function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDensePolynom append!(p.coeffs, _zeros(p, z, i - b)) p.coeffs[end] = value elseif i < a - prepend!(p.coeffs, zeros(p, z, a-i)) + prepend!(p.coeffs, _zeros(p, z, a-i)) p.coeffs[1] = value o = i else @@ -108,7 +115,7 @@ function degree(p::MutableDensePolynomial) end -laurenttype(::Type{<:MutableDensePolynomial}) = Val(true) +#XXXlaurenttype(::Type{<:MutableDensePolynomial}) = Val(true) basis(::Type{MutableDensePolynomial{B,T,X}},i::Int) where {B,T,X} = MutableDensePolynomial{B,T,X}([1],i) @@ -130,6 +137,15 @@ end +function chop_left_index(x; rtol=nothing, atol=nothing) + isempty(x) && return nothing + δ = something(rtol,0) + ϵ = something(atol,0) + τ = max(ϵ, norm(x,2) * δ) + i = findfirst(Base.Fix2(gtτ,τ), x) + i +end + Base.chop(p::MutableDensePolynomial{B,T,X}; kwargs...) where {B,T,X} = chop!(copy(p);kwargs...) # trims left **and right** @@ -156,15 +172,15 @@ function chop!(p::MutableDensePolynomial{B,T,X}; p end -function normΔ(q1::MutableDensePolynomial{B}, q2::MutableDensePolynomial{B}, p::Real = 2) where {B} - iszero(q1) && return norm(q2, p) - iszero(q2) && return norm(q1, p) - r = zero(q1[end] + q2[end]) +function normΔ(q1::MutableDensePolynomial{B}, q2::MutableDensePolynomial{B}) where {B} + iszero(q1) && return norm(q2,2) + iszero(q2) && return norm(q1,2) + r = abs(zero(q1[end] + q2[end])) tot = zero(r) for i ∈ minimum(firstindex,(q1,q2)):maximum(lastindex, (q1,q2)) - @inbounds tot += (q1[i] - q2[i])^p + @inbounds tot += abs2(q1[i] - q2[i]) end - return tot^(1/p) + return sqrt(tot) end minimumexponent(::Type{<:MutableDensePolynomial}) = typemin(Int) @@ -172,9 +188,9 @@ minimumexponent(::Type{<:MutableDensePolynomial}) = typemin(Int) # vector ops +, -, c*x -## unary -Base.:-(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = - MutableDensePolynomial{B,T,X}(Val(false), -p.coeffs, p.order[]) +## unary (faster than map(-,p) +#Base.:-(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = +# MutableDensePolynomial{B,T,X}(Val(false), -p.coeffs, p.order[]) ## binary Base.:+(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = @@ -229,15 +245,15 @@ function Base.denominator(p::MutableDensePolynomial{B,T,X}) where {B,T,X} end -# scalar mult -function scalar_mult(p::MutableDensePolynomial{B,T,X}, c::S) where {B,T,X,S} - cs = p.coeffs .* (c,) # works with T[] - return _polynomial(p, cs) -end -function scalar_mult(c::S, p::MutableDensePolynomial{B,T,X}) where {B,T,X,S} - cs = (c,) .* p.coeffs - return _polynomial(p, cs) -end +# scalar mult. +# function scalar_mult(p::MutableDensePolynomial{B,T,X}, c::S) where {B,T,X,S} +# cs = p.coeffs .* (c,) # works with T[] +# return _polynomial(p, cs) +# end +#function scalar_mult(c::S, p::MutableDensePolynomial{B,T,X}) where {B,T,X,S} +# cs = (c,) .* p.coeffs +# return _polynomial(p, cs) +#end ## --- function LinearAlgebra.lmul!(c::Scalar, p::MutableDensePolynomial{B,T,X}) where {B,T,X} diff --git a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl index 6cb90d79..f93e67e9 100644 --- a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl @@ -13,7 +13,7 @@ basis of degree `n` *or less*, using a vector of length This type is useful for reducing copies and allocations in some algorithms. """ -struct MutableDenseViewPolynomial{B,T,X} <: AbstractUnivariatePolynomial{B, T, X} +struct MutableDenseViewPolynomial{B,T,X} <: AbstractDenseUnivariatePolynomial{B, T, X} coeffs::Vector{T} function MutableDenseViewPolynomial{B, T, X}(coeffs::AbstractVector{S}) where {B, T,S, X} new{B,T,Symbol(X)}(convert(Vector{T}, coeffs)) @@ -33,8 +33,16 @@ end @poly_register MutableDenseViewPolynomial constructorof(::Type{<:MutableDenseViewPolynomial{B}}) where {B} = MutableDenseViewPolynomial{B} + +function Base.map(fn, p::P, args...) where {B,T,X, P<:MutableDenseViewPolynomial{B,T,X}} + xs = map(fn, p.coeffs, args...) + R = eltype(xs) + return ⟒(P){R,X}(xs) +end + + minimumexponent(::Type{<:MutableDenseViewPolynomial}) = 0 -laurenttype(::Type{<:MutableDenseViewPolynomial}) = Val(false) +#XXXlaurenttype(::Type{<:MutableDenseViewPolynomial}) = Val(false) _zeros(::Type{<:MutableDenseViewPolynomial}, z, N) = fill(z, N) Base.copy(p::MutableDenseViewPolynomial{B,T,X}) where {B,T,X} = MutableDenseViewPolynomial{B,T,X}(copy(p.coeffs)) # change broadcast semantics @@ -80,12 +88,12 @@ function _vector_combine(op, p::MutableDenseViewPolynomial{B,T,X}, q::MutableDen n,m = length(p.coeffs), length(q.coeffs) R = promote_type(T,S) if n ≥ m - cs = convert(Vector{R}, p.coeffs) + cs = convert(Vector{R}, copy(p.coeffs)) for (i,qᵢ) ∈ enumerate(q.coeffs) cs[i] = op(cs[i], qᵢ) end else - cs = convert(Vector{R}, q.coeffs) + cs = convert(Vector{R}, copy(q.coeffs)) for (i,pᵢ) ∈ enumerate(p.coeffs) cs[i] = op(pᵢ, cs[i]) end diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index 5c61fd6b..13d26ff2 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -4,7 +4,7 @@ This polynomial type uses an `Dict{Int,T}` to store the coefficients of a polyno Explicit `0` coefficients are not stored. This type can be used for Laurent polynomials. """ -struct MutableSparsePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B, T,X} +struct MutableSparsePolynomial{B,T,X} <: AbstractLaurentUnivariatePolynomial{B, T,X} coeffs::Dict{Int, T} function MutableSparsePolynomial{B,T,X}(cs::AbstractDict{Int,S},order::Int=0) where {B,T,S,X} coeffs = convert(Dict{Int,T}, cs) @@ -71,10 +71,17 @@ end constructorof(::Type{<:MutableSparsePolynomial{B}}) where {B} = MutableSparsePolynomial{B} +function Base.map(fn, p::P, args...) where {B,T,X, P<:MutableSparsePolynomial{B,T,X}} + xs = Dict(k => fn(v, args...) for (k,v) ∈ pairs(p.coeffs)) + xs = chop_exact_zeros!(xs) + R = eltype(values(xs)) # narrow_eltype... + return ⟒(P){R,X}(Val(false), xs) +end + ## --- minimumexponent(::Type{<:MutableSparsePolynomial}) = typemin(Int) -laurenttype(::Type{<:MutableSparsePolynomial}) = Val(true) +#XXXlaurenttype(::Type{<:MutableSparsePolynomial}) = Val(true) Base.copy(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(copy(p.coeffs)) @@ -104,6 +111,9 @@ function Base.setindex!(p::MutableSparsePolynomial{B,T,X}, value, i::Int) where p.coeffs[i] = value end +# would like this, but fails a test... (iterate does not guarantee any order) +#Base.iterate(p::MutableSparsePolynomial, args...) = throw(ArgumentError("Use `pairs` to iterate a sparse polynomial")) + # return coeffs as a vector # use p.coeffs to get Dictionary function coeffs(p::MutableSparsePolynomial{B,T}) where {B,T} @@ -158,33 +168,33 @@ function isconstant(p::MutableSparsePolynomial) end -function scalar_mult(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S} +# function scalar_mult(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S<:Scalar} - R = promote_type(T,S) - P = MutableSparsePolynomial{B,R,X} - (iszero(p) || iszero(c)) && return(zero(P)) +# R = promote_type(T,S) +# P = MutableSparsePolynomial{B,R,X} +# (iszero(p) || iszero(c)) && return(zero(P)) - d = convert(Dict{Int, R}, copy(p.coeffs)) - for (k, pₖ) ∈ pairs(d) - @inbounds d[k] = c .* d[k] - end +# d = convert(Dict{Int, R}, copy(p.coeffs)) +# for (k, pₖ) ∈ pairs(d) +# @inbounds d[k] = c .* d[k] +# end - return P(Val(false), d) +# return P(Val(false), d) -end +# end -function scalar_mult(p::MutableSparsePolynomial{B,T,X}, c::S) where {B,T,X,S} - R = promote_type(T,S) - P = MutableSparsePolynomial{B,R,X} - (iszero(p) || iszero(c)) && return(zero(P)) +# function scalar_mult(p::MutableSparsePolynomial{B,T,X}, c::S) where {B,T,X,S<:Scalar} +# R = promote_type(T,S) +# P = MutableSparsePolynomial{B,R,X} +# (iszero(p) || iszero(c)) && return(zero(P)) - d = convert(Dict{Int, R}, copy(p.coeffs)) - for (k, pₖ) ∈ pairs(d) - @inbounds d[k] = d[k] .* c - end +# d = convert(Dict{Int, R}, copy(p.coeffs)) +# for (k, pₖ) ∈ pairs(d) +# @inbounds d[k] = d[k] .* c +# end - return P(Val(false), d) -end +# return P(Val(false), d) +# end Base.:+(p::MutableSparsePolynomial{B,T,X}, q::MutableSparsePolynomial{B,S,X}) where{B,X,T,S} = _dict_combine(+, p, q) diff --git a/src/polynomials/chebyshev.jl b/src/polynomials/chebyshev.jl index 9b95c3b0..f9c861d6 100644 --- a/src/polynomials/chebyshev.jl +++ b/src/polynomials/chebyshev.jl @@ -2,10 +2,10 @@ struct ChebyshevTBasis <: AbstractBasis end # This is the same as ChebyshevT -#const ChebyshevT = MutableDensePolynomial{ChebyshevTBasis} -#export ChebyshevT +const ChebyshevT = MutableDensePolynomial{ChebyshevTBasis} +export ChebyshevT -basis_symbol(::Type{<:AbstractUnivariatePolynomial{ChebyshevTBasis}}) = "T" +basis_symbol(::Type{<:AbstractUnivariatePolynomial{ChebyshevTBasis,T,X}}) where {T,X} = "T" # "T($X)" is better. function Base.convert(P::Type{<:Polynomial}, ch::MutableDensePolynomial{ChebyshevTBasis}) @@ -36,7 +36,15 @@ function Base.convert(C::Type{<:MutableDensePolynomial{ChebyshevTBasis}}, p::Pol end # lowest degree is always 0 -laurenttype(::Type{<:MutableDensePolynomial{ChebyshevTBasis}}) = Val(false) +function coeffs(p::MutableDensePolynomial{ChebyshevTBasis}) + a = firstindex(p) + iszero(a) && return p.coeffs + a > 0 && return [p[i] for i ∈ 0:degree(p)] + a < 0 && throw(ArgumentError("Type does not support Laurent polynomials")) +end + + + minimumexponent(::Type{<:MutableDensePolynomial{ChebyshevTBasis}}) = 0 domain(::Type{<:MutableDensePolynomial{ChebyshevTBasis}}) = Interval(-1, 1) @@ -86,8 +94,8 @@ end # no checking, so can be called directly through any third argument function evalpoly(x::S, ch::MutableDensePolynomial{ChebyshevTBasis,T}, checked) where {T,S} R = promote_type(T, S) - length(ch) == 0 && return zero(R) - length(ch) == 1 && return R(ch[0]) + degree(ch) == -1 && return zero(R) + degree(ch) == 0 && return R(ch[0]) c0 = ch[end - 1] c1 = ch[end] @inbounds for i in lastindex(ch) - 2:-1:0 @@ -99,9 +107,13 @@ end # scalar + function scalar_add(c::S, p::MutableDensePolynomial{B,T,X}) where {B<:ChebyshevTBasis,T,X, S<:Scalar} R = promote_type(T,S) - cs = collect(R, values(p)) + P = MutableDensePolynomial{ChebyshevTBasis,R,X} + cs = convert(Vector{R}, coeffs(p)) + n = length(cs) + iszero(n) && return P([c]) + isone(n) && return P([cs[1] + c]) cs[1] += c - MutableDensePolynomial{ChebyshevTBasis,R,X}(cs) + return P(cs) end # product @@ -114,17 +126,15 @@ function ⊗(p1::MutableDensePolynomial{B,T,X}, p2::MutableDensePolynomial{B,T,X return ret end -function derivative(p::P, order::Integer = 1) where {B<:ChebyshevTBasis,T,X,P<:MutableDensePolynomial{B,T,X}} - order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) +function derivative(p::P) where {B<:ChebyshevTBasis,T,X,P<:MutableDensePolynomial{B,T,X}} R = eltype(one(T)/1) Q = MutableDensePolynomial{ChebyshevTBasis,R,X} - order == 0 && return convert(Q, p) + isconstant(p) && return zero(Q) hasnan(p) && return Q(R[NaN]) - order > length(p) && return zero(Q) - q = convert(P{R,X}, copy(p)) - n = length(p) + q = convert(Q, copy(p)) + n = degree(p) + 1 der = Vector{R}(undef, n) for j in n:-1:3 @@ -136,8 +146,7 @@ function derivative(p::P, order::Integer = 1) where {B<:ChebyshevTBasis,T,X,P<:M end der[1] = q[1] - pp = Q(der) - return order > 1 ? derivative(pp, order - 1) : pp + return Q(der) end @@ -147,7 +156,7 @@ function integrate(p::P) where {B<:ChebyshevTBasis,T,X,P<:MutableDensePolynomial if hasnan(p) return Q([NaN]) end - n = length(p) + n = degree(p) + 1 if n == 1 return Q([zero(R), p[0]]) end @@ -176,7 +185,8 @@ function vander(P::Type{<:MutableDensePolynomial{ChebyshevTBasis}}, x::AbstractV end function companion(p::MutableDensePolynomial{ChebyshevTBasis,T}) where T - d = length(p) - 1 + d = degree(p) + #d = length(p) - 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)) @@ -186,7 +196,7 @@ function companion(p::MutableDensePolynomial{ChebyshevTBasis,T}) where T diag = vcat(√0.5, fill(R(0.5), d - 2)) comp = diagm(1 => diag, -1 => diag) - monics = coeffs(ps) ./ coeffs(p)[end] + monics = coeffs(p) ./ coeffs(p)[end] comp[:, end] .-= monics[1:d] .* scl ./ scl[end] ./ 2 return R.(comp) end @@ -194,8 +204,8 @@ end function Base.divrem(num::MutableDensePolynomial{ChebyshevTBasis}{T,X}, den::MutableDensePolynomial{ChebyshevTBasis}{S,Y}) where {T,X,S,Y} assert_same_variable(num, den) - n = length(num) - 1 - m = length(den) - 1 + n = degree(num) + m = degree(den) R = typeof(one(T) / one(S)) P = MutableDensePolynomial{ChebyshevTBasis}{R,X} diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 5a8ec08b..7a0a1a23 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -1,5 +1,8 @@ struct StandardBasis <: AbstractBasis end -const StandardBasisType = Union{Polynomials.StandardBasisPolynomial{T,X}, Polynomials.AbstractUnivariatePolynomial{<:Polynomials.StandardBasis,T,X}} where {T,X} + +# Allows mix of Polynomial/AbstractUnivariatePolynomial{StandardBasis} +const StandardBasisType = Union{Polynomials.StandardBasisPolynomial{T,X}, + Polynomials.AbstractUnivariatePolynomial{<:Polynomials.StandardBasis,T,X}} where {T,X} basis_symbol(::Type{P}) where {P<:AbstractUnivariatePolynomial{StandardBasis}} = string(indeterminate(P)) function printbasis(io::IO, ::Type{P}, j::Int, m::MIME) where {B<:StandardBasis, P <: AbstractUnivariatePolynomial{B}} @@ -37,11 +40,13 @@ function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B if firstindex(q) >= 0 cs = [q[i] for i ∈ 0:lastindex(q)] o = 0 + return ⟒(P){T′,X′}(cs, o) else cs = [q[i] for i ∈ eachindex(q)] o = firstindex(q) + throw(ArgumentError("Do we need this??? If not, can remove two defs at end")) end - ⟒(P){T′,X′}(cs, o) + #⟒(P){T′,X′}(cs, o) end function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: AbstractUnivariatePolynomial{B}, Q<:StandardBasisPolynomial} @@ -51,8 +56,8 @@ function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: Abstract ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) end -Base.one(p::P) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}([one(p[0])]) -Base.one(::Type{P}) where {B<:StandardBasis,T,P <: AbstractUnivariatePolynomial{B,T}} = ⟒(P){T}(ones(T,1), indeterminate(P)) +#Base.one(p::P) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}([one(p[0])]) +#Base.one(::Type{P}) where {B<:StandardBasis,T,P <: AbstractUnivariatePolynomial{B,T}} = ⟒(P){T}(ones(T,1), indeterminate(P)) Base.one(::Type{P}) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(ones(T,1)) variable(P::Type{<:AbstractUnivariatePolynomial{B,T,X}}) where {B<:StandardBasis,T,X} = basis(P, 1) @@ -70,7 +75,7 @@ mapdomain(::Type{P}, x::AbstractArray) where {B <: StandardBasis, P <: Abstract function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, q::AbstractUnivariatePolynomial{B,S,X}) where {B <: StandardBasis, T,S,X} # simple convolution with order shifted - throw(ArgumentError("Method not defined")) + throw(ArgumentError("Default method not defined")) end # implemented derivative case by case @@ -94,6 +99,7 @@ function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardB P(cs, firstindex(p)) end +# XXX merge in with polynomials/standard-basis.jl function Base.divrem(num::P, den::Q) where {B<:StandardBasis, T, P <: AbstractUnivariatePolynomial{B,T}, S, Q <: AbstractUnivariatePolynomial{B,S}} @@ -147,14 +153,6 @@ function LinearAlgebra.cond(p::P, x) where {B<:StandardBasis, P<:AbstractUnivari p̃(abs(x))/ abs(p(x)) end -# function ngcd(p::P, q::Q, -# args...; -# kwargs...) where {B <: StandardBasis, -# T,X,P<:AbstractUnivariatePolynomial{B,T,X}, -# S,Y,Q<:AbstractUnivariatePolynomial{B,S,Y}} -# ngcd(PnPolynomial(coeffs(p)), PnPolynomial(coeffs(q)), args...; kwargs...) -# end - function fit(::Type{P}, x::AbstractVector{T}, y::AbstractVector{T}, @@ -172,10 +170,7 @@ function fit(::Type{P}, convert(P, fit(Polynomial, x, y, deg; kwargs...)) end -# new constructors taking order in second position +# new constructors taking order in second position for the `convert` method above (to work with LaurentPolynomial) # these are Polynomial{T, X}(coeffs::AbstractVector{S},order::Int) where {T, X, S} = Polynomial{T,X}(coeffs) FactoredPolynomial{T,X}(coeffs::AbstractVector{S}, order::Int) where {T,S,X} = FactoredPolynomial{T,X}(coeffs) -#PnPolynomial{T, X}(coeffs::AbstractVector, order::Int) where {T, X} = PnPolynomial(coeffs) # for generic programming -#PnPolynomial{T}(coeffs::AbstractVector, order::Int,var) where {T} = PnPolynomial(coeffs,var) # for generic programming -#PnPolynomial(coeffs::AbstractVector, order::Int,var) = PnPolynomial(coeffs,var) # for generic programming diff --git a/src/standard-basis/standard-dense-view.jl b/src/standard-basis/standard-dense-view.jl index bdcae1ef..094e97e6 100644 --- a/src/standard-basis/standard-dense-view.jl +++ b/src/standard-basis/standard-dense-view.jl @@ -8,7 +8,7 @@ standard basis of degree `n` *or less*, using a vector of length * Unlike other polynomial types, this type allows trailing zeros in the coefficient vector. Call `chop!` to trim trailing zeros if desired. * Unlike other polynomial types, this does not copy the coefficients on construction * Unlike other polynomial types, this type broadcasts like a vector for in-place vector operations (scalar multiplication, polynomial addition/subtraction of the same size) -* The method inplace `mul!(pq, p, q)` is defined to use precallocated storage for the product of `p` and `q` +* The inplace method `mul!(pq, p, q)` is defined to use precallocated storage for the product of `p` and `q` This type is useful for reducing copies and allocations in some algorithms. @@ -24,9 +24,9 @@ end # scalar add function scalar_add(c::S, p:: PnPolynomial{T,X}) where {S, T, X} R = promote_type(T,S) - P = MutableDenseViewPolynomial{B,R,X} + P = PnPolynomial{R,X} - iszero(p) && return P([c], 0) + iszero(p) && return P([c]) cs = convert(Vector{R}, copy(p.coeffs)) cs[1] += c P(cs) @@ -50,8 +50,8 @@ end function derivative(p::PnPolynomial{T,X}) where {T,X} R = promote_type(T, Int) N = length(p.coeffs) - iszero(N) && return zero(MutableDenseViewPolynomial{B,R,X}) - cs = Vector{R}(undef,N-1) + iszero(N) && return zero(PnPolynomial{R,X}) + cs = Vector{R}(undef, N-1) for (i, pᵢ) ∈ Base.Iterators.drop(pairs(p),1) cs[i] = i * pᵢ end @@ -72,7 +72,7 @@ end # This is for standard basis XXX -function LinearAlgebra.mul!(pq, p::PnPolynomial{T,X}, q) where {T,X} +function LinearAlgebra.mul!(pq::PnPolynomial, p::PnPolynomial, q::PnPolynomial) m,n = length(p)-1, length(q)-1 cs = pq.coeffs cs .= 0 diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 8ed0f28f..4f73393f 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -116,14 +116,14 @@ function scalar_add(c::S, p:: MutableDensePolynomial{B,T,X}) where {B<:StandardB iszero(c) && return convert(P, p) a,b = firstindex(p), lastindex(p) - a′ = min(0,a) - b′ = max(0,b) - cs = _zeros(p, zero(first(p.coeffs)+c), length(a′:b′)) + a′ = min(0, a) + b′ = max(0, b) + cs = _zeros(p, zero(first(p.coeffs) + c), length(a′:b′)) o = offset(p) + a - a′ for (i, cᵢ) ∈ pairs(p) - cs[i+o] = cᵢ + cs[i + o] = cᵢ end - cs[0+o] += c + cs[0 + o] += c iszero(last(cs)) && (cs = trim_trailing_zeros(cs)) P(Val(false), cs, a′) end diff --git a/test/ChebyshevT.jl b/test/ChebyshevT.jl index 752c4ab1..67421d06 100644 --- a/test/ChebyshevT.jl +++ b/test/ChebyshevT.jl @@ -15,7 +15,8 @@ @test length(p) == length(coeff) @test size(p) == size(coeff) @test size(p, 1) == size(coeff, 1) - @test typeof(p).parameters[1] == eltype(coeff) + @test_broken typeof(p).parameters[1] == eltype(coeff) # 2 + @test typeof(p).parameters[2] == eltype(coeff) @test eltype(p) == eltype(coeff) end end @@ -34,7 +35,8 @@ end @test p.coeffs == [30] p = zero(ChebyshevT{Int}) - @test p.coeffs == [0] + @test_broken p.coeffs == [0] + @test p.coeffs == Int[] # XXX p = one(ChebyshevT{Int}) @test p.coeffs == [1] @@ -49,8 +51,8 @@ end as = ones(3:4) bs = parent(as) - @test ChebyshevT(as) == ChebyshevT(bs) - @test ChebyshevT{Float64}(as) == ChebyshevT{Float64}(bs) + @test_broken ChebyshevT(as) == ChebyshevT(bs) + @test_broken ChebyshevT{Float64}(as) == ChebyshevT{Float64}(bs) a = [1,1] b = OffsetVector(a, axes(a)) @@ -58,9 +60,9 @@ end end @testset "Roots $i" for i in 1:5 - roots = cos.(range(-π, stop=0, length = 2i + 1)[2:2:end]) + rts = cos.(range(-π, stop=0, length = 2i + 1)[2:2:end]) target = ChebyshevT(vcat(zeros(i), 1)) - res = fromroots(ChebyshevT, roots) .* 2^(i - 1) + res = fromroots(ChebyshevT, rts) .* 2^(i - 1) @test res == target end @@ -158,7 +160,8 @@ end c2 = ChebyshevT([0, 1, 2, 3]) d, r = divrem(c2, c1) - @test d.coeffs ≈ [0, 2] + @test_broken d.coeffs ≈ [0, 2] + @test coeffs(d) ≈ [0, 2] @test r.coeffs ≈ [-2, -4] # evaluation From eb986abb510835fe7a03808eb8a7cc19f1679b5a Mon Sep 17 00:00:00 2001 From: jverzani Date: Tue, 1 Aug 2023 21:27:42 -0400 Subject: [PATCH 24/31] WIP: cleanup --- src/abstract-polynomial.jl | 29 ------------------ .../immutable-dense-polynomial.jl | 1 - .../mutable-dense-polynomial.jl | 25 ++++------------ .../mutable-dense-view-polynomial.jl | 1 - .../mutable-sparse-polynomial.jl | 30 ------------------- src/standard-basis/standard-dense-view.jl | 2 +- src/standard-basis/standard-dense.jl | 27 +++++++---------- 7 files changed, 18 insertions(+), 97 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 36c020a7..99118566 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -201,35 +201,6 @@ function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, end end -# XXX in common.jl -# function Base.isapprox(p1::AbstractUnivariatePolynomial{B,T,X}, p2::Scalar; kwargs...) where {B,T,X} -# q = p2 * one(⟒(p1){T,X}) -# isapprox(p1, q; kwargs...) -# end -# Base.isapprox(p1::Scalar, p2::AbstractUnivariatePolynomial; kwargs...) = isapprox(p2, p1; kwargs...) - -#Base.isequal(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} = hash(p1) == hash(p2) -# function Base.:(==)(p1::P, p2::P) where {P <: AbstractUnivariatePolynomial} -# iszero(p1) && iszero(p2) && return true -# lastindex(p1) == lastindex(p2) || return false -# # coeffs(p1) == coeffs(p2), but non-allocating -# for i ∈ union(keys(p1), keys(p2)) -# p1[i] == p2[i] || return false -# end -# return true -# end -# function Base.:(==)(p1::AbstractUnivariatePolynomial, p2::AbstractUnivariatePolynomial) -# if isconstant(p1) -# isconstant(p2) && return constantterm(p1) == constantterm(p2) -# return false -# elseif isconstant(p2) -# return false # p1 is not constant -# end -# check_same_variable(p1, p2) || return false -# ==(promote(p1,p2)...) -# end -#Base.:(==)(p::AbstractUnivariatePolynomial, n::Scalar) = isconstant(p) && constantterm(p) == n -#Base.:(==)(n::Scalar, p::AbstractUnivariatePolynomial) = p == n ## --- arithmetic operations --- ## implement diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index 296db22a..e0120cb6 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -153,7 +153,6 @@ _zeros(::Type{<:ImmutableDensePolynomial}, z::S, N) where {S} = ntuple(_ -> zero(S), Val(N)) minimumexponent(::Type{<:ImmutableDensePolynomial}) = 0 -#XXXlaurenttype(::Type{<:ImmutableDensePolynomial}) = Val(false) Base.firstindex(p::ImmutableDensePolynomial) = 0 Base.lastindex(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} = N - 1 diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index 0f5ad9ad..9c7e0922 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -115,7 +115,6 @@ function degree(p::MutableDensePolynomial) end -#XXXlaurenttype(::Type{<:MutableDensePolynomial}) = Val(true) basis(::Type{MutableDensePolynomial{B,T,X}},i::Int) where {B,T,X} = MutableDensePolynomial{B,T,X}([1],i) @@ -188,16 +187,12 @@ minimumexponent(::Type{<:MutableDensePolynomial}) = typemin(Int) # vector ops +, -, c*x -## unary (faster than map(-,p) -#Base.:-(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = -# MutableDensePolynomial{B,T,X}(Val(false), -p.coeffs, p.order[]) - -## binary +## unary - (map is as fast) +## binary + Base.:+(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = offset_vector_combine(+, p, q) Base.:-(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = offset_vector_combine(-, p, q) -# handle +, - # modified from https://github.com/jmichel7/LaurentPolynomials.jl/ function offset_vector_combine(op, p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} R = promote_type(T,S) @@ -231,29 +226,21 @@ function offset_vector_combine(op, p::MutableDensePolynomial{B,T,X}, q::MutableD end - -function Base.numerator(p::MutableDensePolynomial{B,T,X}) where {B,T,X} +function Base.numerator(q::MutableDensePolynomial{B,T,X}) where {B,T,X} + p = chop(q) o = firstindex(p) o ≥ 0 && return p MutableDensePolynomial{B,T,X}(p.coeffs, 0) end -function Base.denominator(p::MutableDensePolynomial{B,T,X}) where {B,T,X} +function Base.denominator(q::MutableDensePolynomial{B,T,X}) where {B,T,X} + p = chop(q) o = firstindex(p) o ≥ 0 && return one(p) basis(MutableDensePolynomial{B,T,X}, -o) end -# scalar mult. -# function scalar_mult(p::MutableDensePolynomial{B,T,X}, c::S) where {B,T,X,S} -# cs = p.coeffs .* (c,) # works with T[] -# return _polynomial(p, cs) -# end -#function scalar_mult(c::S, p::MutableDensePolynomial{B,T,X}) where {B,T,X,S} -# cs = (c,) .* p.coeffs -# return _polynomial(p, cs) -#end ## --- function LinearAlgebra.lmul!(c::Scalar, p::MutableDensePolynomial{B,T,X}) where {B,T,X} diff --git a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl index f93e67e9..f9842599 100644 --- a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl @@ -42,7 +42,6 @@ end minimumexponent(::Type{<:MutableDenseViewPolynomial}) = 0 -#XXXlaurenttype(::Type{<:MutableDenseViewPolynomial}) = Val(false) _zeros(::Type{<:MutableDenseViewPolynomial}, z, N) = fill(z, N) Base.copy(p::MutableDenseViewPolynomial{B,T,X}) where {B,T,X} = MutableDenseViewPolynomial{B,T,X}(copy(p.coeffs)) # change broadcast semantics diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index 13d26ff2..e06b38ce 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -81,7 +81,6 @@ end ## --- minimumexponent(::Type{<:MutableSparsePolynomial}) = typemin(Int) -#XXXlaurenttype(::Type{<:MutableSparsePolynomial}) = Val(true) Base.copy(p::MutableSparsePolynomial{B,T,X}) where {B,T,X} = MutableSparsePolynomial{B,T,X}(copy(p.coeffs)) @@ -167,35 +166,6 @@ function isconstant(p::MutableSparsePolynomial) n == 1 && haskey(p.coeffs, 0) end - -# function scalar_mult(c::S, p::MutableSparsePolynomial{B,T,X}) where {B,T,X,S<:Scalar} - -# R = promote_type(T,S) -# P = MutableSparsePolynomial{B,R,X} -# (iszero(p) || iszero(c)) && return(zero(P)) - -# d = convert(Dict{Int, R}, copy(p.coeffs)) -# for (k, pₖ) ∈ pairs(d) -# @inbounds d[k] = c .* d[k] -# end - -# return P(Val(false), d) - -# end - -# function scalar_mult(p::MutableSparsePolynomial{B,T,X}, c::S) where {B,T,X,S<:Scalar} -# R = promote_type(T,S) -# P = MutableSparsePolynomial{B,R,X} -# (iszero(p) || iszero(c)) && return(zero(P)) - -# d = convert(Dict{Int, R}, copy(p.coeffs)) -# for (k, pₖ) ∈ pairs(d) -# @inbounds d[k] = d[k] .* c -# end - -# return P(Val(false), d) -# end - Base.:+(p::MutableSparsePolynomial{B,T,X}, q::MutableSparsePolynomial{B,S,X}) where{B,X,T,S} = _dict_combine(+, p, q) Base.:-(p::MutableSparsePolynomial{B,T,X}, q::MutableSparsePolynomial{B,S,X}) where{B,X,T,S} = diff --git a/src/standard-basis/standard-dense-view.jl b/src/standard-basis/standard-dense-view.jl index 094e97e6..749d042d 100644 --- a/src/standard-basis/standard-dense-view.jl +++ b/src/standard-basis/standard-dense-view.jl @@ -71,7 +71,7 @@ end -# This is for standard basis XXX +# This is for standard basis function LinearAlgebra.mul!(pq::PnPolynomial, p::PnPolynomial, q::PnPolynomial) m,n = length(p)-1, length(q)-1 cs = pq.coeffs diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index 4f73393f..ccee255a 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -1,9 +1,5 @@ # Dense + StandardBasis -# XXX for now, use older Polynomial type -#const Polynomial = MutableDensePolynomial{StandardBasis} -#export Polynomial - """ LaurentPolynomial{T,X}(coeffs::AbstractVector, [m::Integer = 0], [var = :x]) @@ -268,19 +264,18 @@ end -## XXX ---- - ## ---- ## XXX needs to be incorporated if Polynomial = MutableDensePolynomial{StandardBasis} function roots(p::P; kwargs...) where {T, X, P <: MutableDensePolynomial{StandardBasis,T,X}} - iszero(p) && return float(T)[] - c = p.coeffs - r = degreerange(p) - d = r[end] - min(0, r[1]) + 1 # Length of the coefficient vector, taking into consideration - # the case when the lower degree is strictly positive - # (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{T,X}(z) # The root is then the root of the numerator polynomial. - return roots(a; kwargs...) + return roots(convert(Polynomial, numerator(p)), kwargs...) + # iszero(p) && return float(T)[] + # c = p.coeffs + # r = degreerange(p) + # d = r[end] - min(0, r[1]) + 1 # Length of the coefficient vector, taking into consideration + # # the case when the lower degree is strictly positive + # # (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{T,X}(z) # The root is then the root of the numerator polynomial. + # return roots(a; kwargs...) end From 91bd228bc87a297c62cdecbdf117874c65d8414f Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 2 Aug 2023 11:53:18 -0400 Subject: [PATCH 25/31] WIP: cleanup --- src/Polynomials.jl | 6 +- .../immutable-dense-polynomial.jl | 4 + .../mutable-dense-laurent-polynomial.jl | 243 ++++++++++++++++ .../mutable-dense-polynomial.jl | 145 +++------- .../mutable-dense-view-polynomial.jl | 11 +- .../mutable-sparse-polynomial.jl | 5 +- src/promotions.jl | 6 +- src/standard-basis/standard-basis.jl | 66 ++++- src/standard-basis/standard-dense-laurent.jl | 270 ++++++++++++++++++ src/standard-basis/standard-dense-view.jl | 42 +-- src/standard-basis/standard-dense.jl | 259 +---------------- src/standard-basis/standard-immutable.jl | 61 ++-- 12 files changed, 669 insertions(+), 449 deletions(-) create mode 100644 src/polynomial-basetypes/mutable-dense-laurent-polynomial.jl create mode 100644 src/standard-basis/standard-dense-laurent.jl diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 3f22af99..5ef11870 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -29,15 +29,17 @@ include("polynomials/factored_polynomial.jl") # polynomials with explicit basis include("abstract-polynomial.jl") include("polynomial-basetypes/mutable-dense-polynomial.jl") +include("polynomial-basetypes/mutable-dense-view-polynomial.jl") +include("polynomial-basetypes/mutable-dense-laurent-polynomial.jl") include("polynomial-basetypes/immutable-dense-polynomial.jl") include("polynomial-basetypes/mutable-sparse-polynomial.jl") -include("polynomial-basetypes/mutable-dense-view-polynomial.jl") include("standard-basis/standard-basis.jl") include("standard-basis/standard-dense.jl") +include("standard-basis/standard-dense-view.jl") +include("standard-basis/standard-dense-laurent.jl") include("standard-basis/standard-immutable.jl") include("standard-basis/standard-sparse.jl") -include("standard-basis/standard-dense-view.jl") include("promotions.jl") diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index e0120cb6..e99da74a 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -41,11 +41,15 @@ function ImmutableDensePolynomial{B,T,X,N}(c::S) where {B,T,X,N,S<:Scalar} cs = ntuple(i -> i == 1 ? T(c) : zero(T), Val(N)) return ImmutableDensePolynomial{B,T,X,N}(cs) end +ImmutableDensePolynomial{B,T,X}(::Val{false}, xs::NTuple{N,S}) where {B,T,S,X,N} = ImmutableDensePolynomial{B,T,X,N}(convert(NTuple{N,T}, xs)) ImmutableDensePolynomial{B,T,X}(xs::NTuple{N,S}) where {B,T,S,X,N} = ImmutableDensePolynomial{B,T,X,N}(convert(NTuple{N,T}, xs)) ImmutableDensePolynomial{B,T}(xs::NTuple{N,S}, var::SymbolLike=Var(:x)) where {B,T,S,N} = ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) ImmutableDensePolynomial{B}(xs::NTuple{N,T}, var::SymbolLike=Var(:x)) where {B,T,N} = ImmutableDensePolynomial{B,T,Symbol(var),N}(xs) # abstract vector. Must eat order +ImmutableDensePolynomial{B,T,X}(::Val{false}, xs::AbstractVector{S}, order::Int=0) where {B,T,X,S} = + ImmutableDensePolynomial{B,T,X}(xs, order) + function ImmutableDensePolynomial{B,T,X}(xs::AbstractVector{S}, order::Int=0) where {B,T,X,S} if Base.has_offset_axes(xs) @warn "ignoring the axis offset of the coefficient vector" diff --git a/src/polynomial-basetypes/mutable-dense-laurent-polynomial.jl b/src/polynomial-basetypes/mutable-dense-laurent-polynomial.jl new file mode 100644 index 00000000..5a841574 --- /dev/null +++ b/src/polynomial-basetypes/mutable-dense-laurent-polynomial.jl @@ -0,0 +1,243 @@ +""" + MutableDenseLaurentPolynomial{B,T,X} + +This polynomial type essentially uses an offset vector (`Vector{T}`,`order`) to store the coefficients of a polynomial relative to the basis `B` with indeterminate `X`. + +The typical offset is to have `0` as the order, but, say, to accomodate Laurent polynomials, or more efficient storage of basis elements any order may be specified. + +This type trims trailing zeros and the leading zeros on construction. + +""" +struct MutableDenseLaurentPolynomial{B,T,X} <: AbstractLaurentUnivariatePolynomial{B,T, X} + coeffs::Vector{T} + order::Base.RefValue{Int} # lowest degree, typically 0 + function MutableDenseLaurentPolynomial{B,T,X}(::Val{:false}, cs::AbstractVector, order::Int=0) where {B,T,X} + new{B,T,Symbol(X)}(cs, Ref(order)) + end + function MutableDenseLaurentPolynomial{B,T,X}(::Val{true}, cs::AbstractVector, order::Int=0) where {B,T,X} + if Base.has_offset_axes(cs) + @warn "Using the axis offset of the coefficient vector" + cs, order = cs.parent, firstindex(cs) + end + + i = findlast(!iszero, cs) + if i == nothing + xs = T[] + else + j = findfirst(!iszero, cs) + xs = T[cs[i] for i ∈ j:i] + order = order + j - 1 + end + new{B,T,Symbol(X)}(xs, Ref(order)) + end +end + +function MutableDenseLaurentPolynomial{B,T,X}(cs::AbstractVector{T}, order::Int=0) where {B,T,X} + MutableDenseLaurentPolynomial{B,T,X}(Val(true), cs, order) +end + +function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDenseLaurentPolynomial{B,T,X}, S} + R = eltype(as) + Q = MutableDenseLaurentPolynomial{B, R, X} + as = trim_trailing_zeros(as) + Q(Val(false), as, p.order[]) +end + +@poly_register MutableDenseLaurentPolynomial +constructorof(::Type{<:MutableDenseLaurentPolynomial{B}}) where {B} = MutableDenseLaurentPolynomial{B} + +## --- + +## Generics for polynomials +function Base.convert(::Type{MutableDenseLaurentPolynomial{B,T,X}}, q::MutableDenseLaurentPolynomial{B,T′,X′}) where {B,T,T′,X,X′} + MutableDenseLaurentPolynomial{B,T,X}(Val(false), convert(Vector{T},q.coeffs), q.order[]) +end + +function Base.map(fn, p::P, args...) where {B,T,X, P<:MutableDenseLaurentPolynomial{B,T,X}} + xs = map(fn, p.coeffs, args...) + xs = trim_trailing_zeros(xs) + R = eltype(xs) + return ⟒(P){R,X}(Val(false), xs, p.order[]) +end + +Base.copy(p::MutableDenseLaurentPolynomial{B,T,X}) where {B,T,X} = + MutableDenseLaurentPolynomial{B,T,Var(X)}(copy(p.coeffs), firstindex(p)) + +Base.firstindex(p::MutableDenseLaurentPolynomial) = p.order[] +Base.length(p::MutableDenseLaurentPolynomial) = length(p.coeffs) +Base.lastindex(p::MutableDenseLaurentPolynomial) = firstindex(p) + length(p) - 1 +function Base.getindex(p::MutableDenseLaurentPolynomial{B,T,X}, i::Int) where {B,T,X} + (i < firstindex(p) || i > lastindex(p)) && return zero(T) + p.coeffs[i + offset(p)] +end +# ??? should this call chop! if `iszero(value)`? +function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDenseLaurentPolynomial{B,T,X}} + a,b = firstindex(p), lastindex(p) + o = a + iszero(p) && return P([value], i) + z = zero(first(p.coeffs)) + if i > b + append!(p.coeffs, _zeros(p, z, i - b)) + p.coeffs[end] = value + elseif i < a + prepend!(p.coeffs, _zeros(p, z, a-i)) + p.coeffs[1] = value + o = i + else + p.coeffs[i + offset(p)] = value + end + p.order[] = o + p +end + +offset(p::MutableDenseLaurentPolynomial) = 1 - firstindex(p) +Base.eachindex(p::MutableDenseLaurentPolynomial) = firstindex(p):1:lastindex(p) +Base.iterate(p::MutableDenseLaurentPolynomial, args...) = Base.iterate(p.coeffs, args...) +Base.pairs(p::MutableDenseLaurentPolynomial) = + Base.Generator(=>, firstindex(p):lastindex(p), p.coeffs) + +# return a container of zeros based on basis type +_zeros(::Type{<:MutableDenseLaurentPolynomial}, z, N) = fill(z, N) + +Base.similar(p::MutableDenseLaurentPolynomial, args...) = similar(p.coeffs, args...) + +# iszero +Base.iszero(p::MutableDenseLaurentPolynomial) = iszero(p.coeffs)::Bool + +function degree(p::MutableDenseLaurentPolynomial) + i = findlast(!iszero, p.coeffs) + isnothing(i) && return -1 + firstindex(p) + i - 1 +end + + + +basis(::Type{MutableDenseLaurentPolynomial{B,T,X}},i::Int) where {B,T,X} = MutableDenseLaurentPolynomial{B,T,X}([1],i) + + +function chop_left_index(x; rtol=nothing, atol=nothing) + isempty(x) && return nothing + δ = something(rtol,0) + ϵ = something(atol,0) + τ = max(ϵ, norm(x,2) * δ) + i = findfirst(Base.Fix2(gtτ,τ), x) + i +end + +Base.chop(p::MutableDenseLaurentPolynomial{B,T,X}; kwargs...) where {B,T,X} = chop!(copy(p);kwargs...) + +# trims left **and right** +function chop!(p::MutableDenseLaurentPolynomial{B,T,X}; + atol=nothing, rtol=Base.rtoldefault(float(real(T)))) where {B,T,X} + iᵣ = chop_right_index(p.coeffs; atol=atol, rtol=rtol) # nothing -- nothing to chop + iᵣ === nothing && return zero(p) + iₗ = chop_left_index(p.coeffs; atol=atol, rtol=rtol) + iₗ === nothing && return zero(p) + iₗ == 1 && iᵣ == length(p.coeffs) && return p + + N = length(p.coeffs) + + o = firstindex(p) + for i ∈ 1:(iₗ-1) + popfirst!(p.coeffs) + o += 1 + end + + for i ∈ (iᵣ+1):N + pop!(p.coeffs) + end + p.order[] = o + p +end + +function normΔ(q1::MutableDenseLaurentPolynomial{B}, q2::MutableDenseLaurentPolynomial{B}) where {B} + iszero(q1) && return norm(q2,2) + iszero(q2) && return norm(q1,2) + r = abs(zero(q1[end] + q2[end])) + tot = zero(r) + for i ∈ minimum(firstindex,(q1,q2)):maximum(lastindex, (q1,q2)) + @inbounds tot += abs2(q1[i] - q2[i]) + end + return sqrt(tot) +end + +minimumexponent(::Type{<:MutableDenseLaurentPolynomial}) = typemin(Int) + + + +# vector ops +, -, c*x +## unary - (map is as fast) +## binary + +Base.:+(p::MutableDenseLaurentPolynomial{B,T,X}, q::MutableDenseLaurentPolynomial{B,S,X}) where{B,X,T,S} = + offset_vector_combine(+, p, q) +Base.:-(p::MutableDenseLaurentPolynomial{B,T,X}, q::MutableDenseLaurentPolynomial{B,S,X}) where{B,X,T,S} = + offset_vector_combine(-, p, q) +# modified from https://github.com/jmichel7/LaurentPolynomials.jl/ +function offset_vector_combine(op, p::MutableDenseLaurentPolynomial{B,T,X}, q::MutableDenseLaurentPolynomial{B,S,X}) where{B,X,T,S} + R = promote_type(T,S) + P = MutableDenseLaurentPolynomial{B,R,X} + + iszero(p) && return convert(P, op(q)) + iszero(q) && return convert(P, p) + + a₁, a₂ = firstindex(p), firstindex(q) + b₁, b₂ = lastindex(p), lastindex(q) + a, b = min(a₁, a₂), max(b₁, b₂) + + N = b - a + 1 + z = zero(first(p.coeffs) + first(q.coeffs)) + x = _zeros(p, z, N) + + Δp, Δq = a₁ - a₂, 0 + if a₁ < a₂ + Δq, Δp = -Δp, Δq + end + # zip faster than `pairs` + @inbounds for (i, cᵢ) ∈ zip((1+Δp):(length(p) + Δp), p.coeffs) + x[i] = cᵢ + end + @inbounds for (i, cᵢ) ∈ zip((1+Δq):(length(q) + Δq), q.coeffs) + x[i] = op(x[i], cᵢ) + end + + b₁ == b₂ && (x = trim_trailing_zeros(x)) + P(Val(false), x, a) + +end + +function Base.numerator(q::MutableDenseLaurentPolynomial{B,T,X}) where {B,T,X} + p = chop(q) + o = firstindex(p) + o ≥ 0 && return p + MutableDenseLaurentPolynomial{B,T,X}(p.coeffs, 0) +end + +function Base.denominator(q::MutableDenseLaurentPolynomial{B,T,X}) where {B,T,X} + p = chop(q) + o = firstindex(p) + o ≥ 0 && return one(p) + basis(MutableDenseLaurentPolynomial{B,T,X}, -o) +end + + + +## --- +function LinearAlgebra.lmul!(c::Scalar, p::MutableDenseLaurentPolynomial{B,T,X}) where {B,T,X} + if iszero(c) + empty!(p.coeffs) + p.order[] = 0 + else + lmul!(c, p.coeffs) + end + p +end + +function LinearAlgebra.rmul!(p::MutableDenseLaurentPolynomial{B,T,X}, c::Scalar) where {B,T,X} + if iszero(c) + empty!(p.coeffs) + p.order[] = 0 + else + rmul!(p.coeffs, c) + end + p +end diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index 9c7e0922..0da618df 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -1,44 +1,33 @@ """ MutableDensePolynomial{B,T,X} -This polynomial type essentially uses an offset vector (`Vector{T}`,`order`) to store the coefficients of a polynomial relative to the basis `B` with indeterminate `X`. - -The typical offset is to have `0` as the order, but, say, to accomodate Laurent polynomials, or more efficient storage of basis elements any order may be specified. - -This type trims trailing zeros and the leading zeros on construction. - """ -struct MutableDensePolynomial{B,T,X} <: AbstractLaurentUnivariatePolynomial{B,T, X} +struct MutableDensePolynomial{B,T,X} <: AbstractDenseUnivariatePolynomial{B,T, X} coeffs::Vector{T} - order::Base.RefValue{Int} # lowest degree, typically 0 - function MutableDensePolynomial{B,T,X}(cs::AbstractVector{S}, order::Int=0) where {B,T,X,S} + function MutableDensePolynomial{B,T,X}(::Val{true}, cs::AbstractVector{S}, order::Int=0) where {B,T,X,S} if Base.has_offset_axes(cs) - @warn "Using the axis offset of the coefficient vector" - cs, order = cs.parent, firstindex(cs) + @warn "ignoring the axis offset of the coefficient vector" + cs = parent(cs) end - i = findlast(!iszero, cs) if i == nothing xs = T[] else - j = findfirst(!iszero, cs) - xs = T[cs[i] for i ∈ j:i] - order = order + j - 1 + xs = T[cs[i] for i ∈ 1:i] # make copy end - new{B,T,Symbol(X)}(xs, Ref(order)) - - end - function MutableDensePolynomial{B,T,X}(check::Val{false}, cs::Vector{T}, order::Int=0) where {B,T,X} - if Base.has_offset_axes(cs) - @warn "Using the axis offset of the coefficient vector" - cs, order = cs.parent, first(cs.offsets) + if order > 0 + prepend!(xs, zeros(T, order)) end - new{B,T,Symbol(X)}(cs, Ref(order)) + new{B,T,Symbol(X)}(xs) + end - function MutableDensePolynomial{B,T,X}(check::Val{true}, cs::Vector{T}, order::Int=0) where {B,T,X} - MutableDensePolynomial{B,T,X}(cs, Ref(order)) + function MutableDensePolynomial{B,T,X}(check::Val{false}, cs::AbstractVector{T}, order::Int=0) where {B,T,X} + new{B,T,Symbol(X)}(cs) end end +function MutableDensePolynomial{B,T,X}(cs::AbstractVector{T}, order::Int=0) where {B,T,X} + MutableDensePolynomial{B,T,X}(Val(true), cs, order) +end function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDensePolynomial{B,T,X}, S} R = eltype(as) @@ -54,22 +43,22 @@ constructorof(::Type{<:MutableDensePolynomial{B}}) where {B} = MutableDensePolyn ## Generics for polynomials function Base.convert(::Type{MutableDensePolynomial{B,T,X}}, q::MutableDensePolynomial{B,T′,X′}) where {B,T,T′,X,X′} - MutableDensePolynomial{B,T,X}(Val(false), convert(Vector{T},q.coeffs), q.order[]) + MutableDensePolynomial{B,T,X}(Val(false), convert(Vector{T},q.coeffs)) end function Base.map(fn, p::P, args...) where {B,T,X, P<:MutableDensePolynomial{B,T,X}} xs = map(fn, p.coeffs, args...) xs = trim_trailing_zeros(xs) R = eltype(xs) - return ⟒(P){R,X}(Val(false), xs, p.order[]) + return ⟒(P){R,X}(Val(false), xs) end Base.copy(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = - MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs), firstindex(p)) + MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs)) -Base.firstindex(p::MutableDensePolynomial) = p.order[] +Base.firstindex(p::MutableDensePolynomial) = 0 Base.length(p::MutableDensePolynomial) = length(p.coeffs) -Base.lastindex(p::MutableDensePolynomial) = firstindex(p) + length(p) - 1 +Base.lastindex(p::MutableDensePolynomial) = length(p) - 1 function Base.getindex(p::MutableDensePolynomial{B,T,X}, i::Int) where {B,T,X} (i < firstindex(p) || i > lastindex(p)) && return zero(T) p.coeffs[i + offset(p)] @@ -77,7 +66,6 @@ end # ??? should this call chop! if `iszero(value)`? function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDensePolynomial{B,T,X}} a,b = firstindex(p), lastindex(p) - o = a iszero(p) && return P([value], i) z = zero(first(p.coeffs)) if i > b @@ -86,19 +74,17 @@ function Base.setindex!(p::P, value, i::Int) where {B,T,X,P<:MutableDensePolynom elseif i < a prepend!(p.coeffs, _zeros(p, z, a-i)) p.coeffs[1] = value - o = i else p.coeffs[i + offset(p)] = value end - p.order[] = o p end -offset(p::MutableDensePolynomial) = 1 - firstindex(p) -Base.eachindex(p::MutableDensePolynomial) = firstindex(p):1:lastindex(p) +offset(p::MutableDensePolynomial) = 1 +Base.eachindex(p::MutableDensePolynomial) = 0:1:lastindex(p) Base.iterate(p::MutableDensePolynomial, args...) = Base.iterate(p.coeffs, args...) Base.pairs(p::MutableDensePolynomial) = - Base.Generator(=>, firstindex(p):lastindex(p), p.coeffs) + Base.Generator(=>, 0:lastindex(p), p.coeffs) # return a container of zeros based on basis type _zeros(::Type{<:MutableDensePolynomial}, z, N) = fill(z, N) @@ -109,13 +95,9 @@ Base.similar(p::MutableDensePolynomial, args...) = similar(p.coeffs, args...) Base.iszero(p::MutableDensePolynomial) = iszero(p.coeffs)::Bool function degree(p::MutableDensePolynomial) - i = findlast(!iszero, p.coeffs) - isnothing(i) && return -1 - firstindex(p) + i - 1 + length(p.coeffs) - 1 end - - basis(::Type{MutableDensePolynomial{B,T,X}},i::Int) where {B,T,X} = MutableDensePolynomial{B,T,X}([1],i) function trim_trailing_zeros(cs::Vector{T}) where {T} @@ -135,39 +117,19 @@ function trim_trailing_zeros(cs::Vector{T}) where {T} end - -function chop_left_index(x; rtol=nothing, atol=nothing) - isempty(x) && return nothing - δ = something(rtol,0) - ϵ = something(atol,0) - τ = max(ϵ, norm(x,2) * δ) - i = findfirst(Base.Fix2(gtτ,τ), x) - i -end - Base.chop(p::MutableDensePolynomial{B,T,X}; kwargs...) where {B,T,X} = chop!(copy(p);kwargs...) -# trims left **and right** function chop!(p::MutableDensePolynomial{B,T,X}; atol=nothing, rtol=Base.rtoldefault(float(real(T)))) where {B,T,X} iᵣ = chop_right_index(p.coeffs; atol=atol, rtol=rtol) # nothing -- nothing to chop iᵣ === nothing && return zero(p) - iₗ = chop_left_index(p.coeffs; atol=atol, rtol=rtol) - iₗ === nothing && return zero(p) - iₗ == 1 && iᵣ == length(p.coeffs) && return p + iᵣ == length(p.coeffs) && return p N = length(p.coeffs) - o = firstindex(p) - for i ∈ 1:(iₗ-1) - popfirst!(p.coeffs) - o += 1 - end - for i ∈ (iᵣ+1):N pop!(p.coeffs) end - p.order[] = o p end @@ -176,13 +138,13 @@ function normΔ(q1::MutableDensePolynomial{B}, q2::MutableDensePolynomial{B}) wh iszero(q2) && return norm(q1,2) r = abs(zero(q1[end] + q2[end])) tot = zero(r) - for i ∈ minimum(firstindex,(q1,q2)):maximum(lastindex, (q1,q2)) + for i ∈ o:maximum(lastindex, (q1,q2)) @inbounds tot += abs2(q1[i] - q2[i]) end return sqrt(tot) end -minimumexponent(::Type{<:MutableDensePolynomial}) = typemin(Int) +minimumexponent(::Type{<:MutableDensePolynomial}) = 0 @@ -190,34 +152,28 @@ minimumexponent(::Type{<:MutableDensePolynomial}) = typemin(Int) ## unary - (map is as fast) ## binary + Base.:+(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = - offset_vector_combine(+, p, q) + _vector_combine(+, p, q) Base.:-(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = - offset_vector_combine(-, p, q) -# modified from https://github.com/jmichel7/LaurentPolynomials.jl/ -function offset_vector_combine(op, p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} + _vector_combine(-, p, q) +function _vector_combine(op, p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} R = promote_type(T,S) P = MutableDensePolynomial{B,R,X} iszero(p) && return convert(P, op(q)) iszero(q) && return convert(P, p) - a₁, a₂ = firstindex(p), firstindex(q) b₁, b₂ = lastindex(p), lastindex(q) - a, b = min(a₁, a₂), max(b₁, b₂) + a, b = 0, max(b₁, b₂) N = b - a + 1 z = zero(first(p.coeffs) + first(q.coeffs)) x = _zeros(p, z, N) - Δp, Δq = a₁ - a₂, 0 - if a₁ < a₂ - Δq, Δp = -Δp, Δq - end # zip faster than `pairs` - @inbounds for (i, cᵢ) ∈ zip((1+Δp):(length(p) + Δp), p.coeffs) + @inbounds for (i, cᵢ) ∈ enumerate(p.coeffs) x[i] = cᵢ end - @inbounds for (i, cᵢ) ∈ zip((1+Δq):(length(q) + Δq), q.coeffs) + @inbounds for (i, cᵢ) ∈ enumerate(q.coeffs) x[i] = op(x[i], cᵢ) end @@ -225,40 +181,3 @@ function offset_vector_combine(op, p::MutableDensePolynomial{B,T,X}, q::MutableD P(Val(false), x, a) end - -function Base.numerator(q::MutableDensePolynomial{B,T,X}) where {B,T,X} - p = chop(q) - o = firstindex(p) - o ≥ 0 && return p - MutableDensePolynomial{B,T,X}(p.coeffs, 0) -end - -function Base.denominator(q::MutableDensePolynomial{B,T,X}) where {B,T,X} - p = chop(q) - o = firstindex(p) - o ≥ 0 && return one(p) - basis(MutableDensePolynomial{B,T,X}, -o) -end - - - -## --- -function LinearAlgebra.lmul!(c::Scalar, p::MutableDensePolynomial{B,T,X}) where {B,T,X} - if iszero(c) - empty!(p.coeffs) - p.order[] = 0 - else - lmul!(c, p.coeffs) - end - p -end - -function LinearAlgebra.rmul!(p::MutableDensePolynomial{B,T,X}, c::Scalar) where {B,T,X} - if iszero(c) - empty!(p.coeffs) - p.order[] = 0 - else - rmul!(p.coeffs, c) - end - p -end diff --git a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl index f9842599..3ad0de1b 100644 --- a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl @@ -15,13 +15,14 @@ This type is useful for reducing copies and allocations in some algorithms. """ struct MutableDenseViewPolynomial{B,T,X} <: AbstractDenseUnivariatePolynomial{B, T, X} coeffs::Vector{T} - function MutableDenseViewPolynomial{B, T, X}(coeffs::AbstractVector{S}) where {B, T,S, X} + function MutableDenseViewPolynomial{B, T, X}(::Val{false}, coeffs::AbstractVector{S}) where {B, T,S, X} new{B,T,Symbol(X)}(convert(Vector{T}, coeffs)) end end +MutableDenseViewPolynomial{B,T,X}(check::Val{true}, cs::AbstractVector{S}) where {B,T,S,X} = + throw(ArgumentError("No checking in this polynomialtype")) -MutableDensePolynomial{B,T,X}(check::Val{false}, cs::AbstractVector{S}) where {B,T,S,X} = new{B,T,X}(coeffs) -MutableDensePolynomial{B,T,X}(check::Val{true}, cs::AbstractVector{S}) where {B,T,S,X} = new{B,T,X}(coeffs) +MutableDenseViewPolynomial{B,T,X}(cs::AbstractVector{S}) where {B,T,S,X} = MutableDenseViewPolynomial{B,T,X}(Val(false), cs) function MutableDenseViewPolynomial{B, T, X}(coeffs::AbstractVector{S}, order::Int) where {B, T,S, X} iszero(order) && return MutableDenseViewPolynomial{B,T,X}(coeffs) @@ -30,10 +31,10 @@ function MutableDenseViewPolynomial{B, T, X}(coeffs::AbstractVector{S}, order::I MutableDenseViewPolynomial{B,T,X}(coeffs) end - @poly_register MutableDenseViewPolynomial -constructorof(::Type{<:MutableDenseViewPolynomial{B}}) where {B} = MutableDenseViewPolynomial{B} +constructorof(::Type{<:MutableDenseViewPolynomial{B}}) where {B} = MutableDenseViewPolynomial{B} +offset(p::MutableDenseViewPolynomial) = 1 function Base.map(fn, p::P, args...) where {B,T,X, P<:MutableDenseViewPolynomial{B,T,X}} xs = map(fn, p.coeffs, args...) R = eltype(xs) diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index e06b38ce..48aa16e0 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -5,13 +5,13 @@ Explicit `0` coefficients are not stored. This type can be used for Laurent poly """ struct MutableSparsePolynomial{B,T,X} <: AbstractLaurentUnivariatePolynomial{B, T,X} - coeffs::Dict{Int, T} + coeffs::Dict{Int, T} function MutableSparsePolynomial{B,T,X}(cs::AbstractDict{Int,S},order::Int=0) where {B,T,S,X} coeffs = convert(Dict{Int,T}, cs) chop_exact_zeros!(coeffs) new{B,T,Symbol(X)}(coeffs) end - function MutableSparsePolynomial{B,T,X}(checked::Val{:false}, coeffs::AbstractDict{Int,T},order::Int=0) where {B,T,X} + function MutableSparsePolynomial{B,T,X}(check::Val{:false}, coeffs::AbstractDict{Int,S}) where {B,T,S,X} new{B,T,Symbol(X)}(coeffs) end end @@ -20,7 +20,6 @@ function MutableSparsePolynomial{B,T,X}(checked::Val{:true}, coeffs::AbstractDic MutableSparsePolynomial{B,T,X}(coeffs) end -# Dict function MutableSparsePolynomial{B,T}(coeffs::AbstractDict{Int,S}, var::SymbolLike=Var(:x)) where {B,T,S} MutableSparsePolynomial{B,T,Symbol(var)}(coeffs) end diff --git a/src/promotions.jl b/src/promotions.jl index c449f460..86ae758b 100644 --- a/src/promotions.jl +++ b/src/promotions.jl @@ -1,14 +1,14 @@ Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, P<:AbstractUnivariatePolynomial{B,T,X}, - Q<:AbstractUnivariatePolynomial{B,S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} + Q<:AbstractUnivariatePolynomial{B,S,X}} = MutableDenseLaurentPolynomial{B,promote_type(T,S),X} Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, P<:AbstractPolynomial{T,X}, - Q<:AbstractUnivariatePolynomial{B,S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} + Q<:AbstractUnivariatePolynomial{B,S,X}} = MutableDenseLaurentPolynomial{B,promote_type(T,S),X} Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, P<:AbstractUnivariatePolynomial{B,T,X}, - Q<:AbstractPolynomial{S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} + Q<:AbstractPolynomial{S,X}} = MutableDenseLaurentPolynomial{B,promote_type(T,S),X} ## XXX these are needed for rational-functions Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 7a0a1a23..35353d66 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -70,7 +70,31 @@ domain(::Type{P}) where {B <: StandardBasis, P <: AbstractUnivariatePolynomial{B mapdomain(::Type{P}, x::AbstractArray) where {B <: StandardBasis, P <: AbstractUnivariatePolynomial{B}} = x -## Multiplication + +## Evaluation, Scalar addition, Multiplication, integration, differentiation +function evalpoly(c::S, p::P) where {B<:StandardBasis, S, T, X, P<:AbstractDenseUnivariatePolynomial{B,T,X}} + iszero(p) && return zero(T) * zero(c) + EvalPoly.evalpoly(c, p.coeffs) +end + +function scalar_add(c::S, p::P) where {B<:StandardBasis, S, T, X, P<:AbstractDenseUnivariatePolynomial{B,T,X}} + R = promote_type(T,S) + P′ = ⟒(P){R,X} + + iszero(p) && return P′([c], 0) + iszero(c) && return convert(P′, p) + + a,b = 0, lastindex(p) + cs = _zeros(p, zero(first(p.coeffs) + c), b-a+1) + o = offset(p) + for (i, cᵢ) ∈ pairs(p) + cs = _set(cs, i+o, cᵢ) + end + cs = _set(cs, 0+o, cs[0+o] + c) + iszero(last(cs)) && (cs = trim_trailing_zeros(cs)) + P′(Val(false), cs) +end + # special cases are faster function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, q::AbstractUnivariatePolynomial{B,S,X}) where {B <: StandardBasis, T,S,X} @@ -79,13 +103,47 @@ function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, end # implemented derivative case by case +function derivative(p::P) where {B<:StandardBasis, T,X,P<:AbstractDenseUnivariatePolynomial{B,T,X}} + R = promote_type(T, Int) + N = length(p.coeffs) + P′ = ⟒(P){R,X} + iszero(N) && return zero(P) + z = 0*p[0] + cs = _zeros(p,z,N-1) + o = offset(p) +# cs = Vector{R}(undef, N-1) + for (i, pᵢ) ∈ Base.Iterators.drop(pairs(p),1) + _set(cs, i - 1 + o, i * pᵢ) +# cs[i] = i * pᵢ + end + P′(Val(false), cs) +end +derivative(p::P) where {B<:StandardBasis, T,X,P<:AbstractLaurentUnivariatePolynomial{B,T,X}} = XXX() + +function integrate(p::P) where {B <: StandardBasis,T,X,P<:AbstractDenseUnivariatePolynomial{B,T,X}} + + R = Base.promote_op(/, T, Int) + Q = ⟒(P){R,X} + iszero(p) && return zero(Q) + hasnan(p) && return Q(zero(T)/zero(T)) # NaN{T} + + N = length(p.coeffs) + cs = Vector{R}(undef,N+1) + cs[1] = zero(R) + o = offset(p) + for (i, pᵢ) ∈ pairs(p) + cs[i+1+o] = pᵢ/(i+1) + end + Q(Val(false), cs) +end + -function integrate(p::AbstractUnivariatePolynomial{B,T,X}) where {B <: StandardBasis,T,X} +function integrate(p::AbstractLaurentUnivariatePolynomial{B,T,X}) where {B <: StandardBasis,T,X} iszero(p) && return p/1 N = lastindex(p) - firstindex(p) + 1 - R = typeof(one(T)/1) - z = zero(R) + z = 0*(p[0]/1) + R = eltype(z) P = ⟒(p){R,X} hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} diff --git a/src/standard-basis/standard-dense-laurent.jl b/src/standard-basis/standard-dense-laurent.jl new file mode 100644 index 00000000..a90f53e3 --- /dev/null +++ b/src/standard-basis/standard-dense-laurent.jl @@ -0,0 +1,270 @@ +# Dense + StandardBasis + +""" + 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`. + +The `coeffs` specify `a_{m}, a_{m-1}, ..., a_{n}`. +The argument `m` represents the lowest exponent of the variable in the series, and is taken to be zero by default. + +Laurent polynomials and standard basis polynomials promote to Laurent polynomials. Laurent polynomials may be converted to a standard basis polynomial when `m >= 0` +. + +Integration will fail if there is a `x⁻¹` term in the polynomial. + +!!! note + `LaurentPolynomial` is an alias for `MutableDensePolynomial{StandardBasis}`. + +!!! note + `LaurentPolynomial` is axis-aware, unlike the other polynomial types in this package. + +# Examples: +```jldoctest laurent +julia> using Polynomials + +julia> P = LaurentPolynomial; + +julia> p = P([1,1,1], -1) +LaurentPolynomial(x⁻¹ + 1 + x) + +julia> q = P([1,1,1]) +LaurentPolynomial(1 + x + x²) + +julia> pp = Polynomial([1,1,1]) +Polynomial(1 + x + x^2) + +julia> p + q +LaurentPolynomial(x⁻¹ + 2 + 2*x + x²) + +julia> p * q +LaurentPolynomial(x⁻¹ + 2 + 3*x + 2*x² + x³) + +julia> p * pp +LaurentPolynomial(x⁻¹ + 2 + 3*x + 2*x² + x³) + +julia> pp - q +LaurentPolynomial(0) + +julia> derivative(p) +LaurentPolynomial(-x⁻² + 1) + +julia> integrate(q) +LaurentPolynomial(1.0*x + 0.5*x² + 0.3333333333333333*x³) + +julia> integrate(p) # x⁻¹ term is an issue +ERROR: ArgumentError: Can't integrate Laurent polynomial with `x⁻¹` term + +julia> integrate(P([1,1,1], -5)) +LaurentPolynomial(-0.25*x⁻⁴ - 0.3333333333333333*x⁻³ - 0.5*x⁻²) + +julia> x⁻¹ = inv(variable(LaurentPolynomial)) # `inv` defined on monomials +LaurentPolynomial(1.0*x⁻¹) + +julia> p = Polynomial([1,2,3]) +Polynomial(1 + 2*x + 3*x^2) + +julia> x = variable() +Polynomial(x) + +julia> x^degree(p) * p(x⁻¹) # reverses coefficients +LaurentPolynomial(3.0 + 2.0*x + 1.0*x²) +``` +""" +const LaurentPolynomial = MutableDenseLaurentPolynomial{StandardBasis} +export LaurentPolynomial + +_typealias(::Type{P}) where {P<:LaurentPolynomial} = "LaurentPolynomial" + +# how to show term. Only needed here to get unicode exponents to match old LaurentPolynomial.jl type +function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where {T, P<:MutableDenseLaurentPolynomial{StandardBasis,T}} + if _iszero(pj) return false end + + pj = printsign(io, pj, first, mimetype) + if hasone(T) + if !(_isone(pj) && !(showone(T) || j == 0)) + printcoefficient(io, pj, j, mimetype) + end + else + printcoefficient(io, pj, j, mimetype) + end + + iszero(j) && return true + printproductsign(io, pj, j, mimetype) + print(io, indeterminate(P)) + j == 1 && return true + unicode_exponent(io, j) # print(io, exponent_text(j, mimetype)) + return true +end + + +function evalpoly(c, p::LaurentPolynomial{T,X}) where {T,X} + iszero(p) && return zero(T) * zero(c) + EvalPoly.evalpoly(c, p.coeffs) * c^p.order[] +end + +# scalar add +function scalar_add(c::S, p::LaurentPolynomial{T,X}) where {S, T, X} + R = promote_type(T,S) + P = LaurentPolynomial{R,X} + + iszero(p) && return P([c], 0) + iszero(c) && return convert(P, p) + + a,b = firstindex(p), lastindex(p) + a′ = min(0, a) + b′ = max(0, b) + cs = _zeros(p, zero(first(p.coeffs) + c), length(a′:b′)) + o = offset(p) + a - a′ + for (i, cᵢ) ∈ pairs(p) + cs[i + o] = cᵢ + end + cs[0 + o] += c + iszero(last(cs)) && (cs = trim_trailing_zeros(cs)) + P(Val(false), cs, a′) +end + +function ⊗(p::LaurentPolynomial{T,X}, + q::LaurentPolynomial{S,X}) where {T,S,X} + # simple convolution + # This is ⊗(P,p,q) from polynomial standard-basis + R = promote_type(T,S) + P = LaurentPolynomial{R,X} + + iszero(p) && return zero(P) + iszero(q) && return zero(P) + + a₁, a₂ = firstindex(p), firstindex(q) + b₁, b₂ = lastindex(p), lastindex(q) + a, b = a₁ + a₂, b₁ + b₂ + + z = zero(first(p) * first(q)) + cs = _zeros(p, z, length(a:b)) + + # convolve and shift order + @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) + for (j, qⱼ) ∈ enumerate(q.coeffs) + ind = i + j - 1 + cs[ind] = muladd(pᵢ, qⱼ, cs[ind]) + end + end + if iszero(last(cs)) + cs = trim_trailing_zeros(cs) + end + P(Val(false), cs, a) +end + +function derivative(p::LaurentPolynomial{T,X}) where {T,X} + + N = lastindex(p) - firstindex(p) + 1 + R = promote_type(T, Int) + P = LaurentPolynomial{R,X} + hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} + iszero(p) && return P(0*p[0]) + + ps = p.coeffs + cs = [i*pᵢ for (i,pᵢ) ∈ pairs(p)] + return P(cs, p.order[]-1) +end + +# LaurentPolynomials have `inv` defined for monomials +function Base.inv(p::LaurentPolynomial) + m,n = firstindex(p), lastindex(p) + m != n && throw(ArgumentError("Only monomials can be inverted")) + cs = [1/p for p in p.coeffs] + LaurentPolynomial{eltype(cs), indeterminate(p)}(cs, -m) +end + +""" + paraconj(p) + +[cf.](https://ccrma.stanford.edu/~jos/filters/Paraunitary_FiltersC_3.html) + +Call `p̂ = paraconj(p)` and `p̄` = conj(p)`, then this satisfies +`conj(p(z)) = p̂(1/conj(z))` or `p̂(z) = p̄(1/z) = (conj ∘ p ∘ conj ∘ inf)(z)`. + +Examples: + +```jldoctest laurent +julia> using Polynomials; + +julia> z = variable(LaurentPolynomial, :z) +LaurentPolynomial(z) + +julia> h = LaurentPolynomial([1,1], -1, :z) +LaurentPolynomial(z⁻¹ + 1) + +julia> Polynomials.paraconj(h)(z) ≈ 1 + z ≈ LaurentPolynomial([1,1], 0, :z) +true + +julia> h = LaurentPolynomial([3,2im,1], -2, :z) +LaurentPolynomial(3*z⁻² + 2im*z⁻¹ + 1) + +julia> Polynomials.paraconj(h)(z) ≈ 1 - 2im*z + 3z^2 ≈ LaurentPolynomial([1, -2im, 3], 0, :z) +true + +julia> Polynomials.paraconj(h)(z) ≈ (conj ∘ h ∘ conj ∘ inv)(z) +true +""" +function paraconj(p::LaurentPolynomial{T,X}) where {T,X} + cs = p.coeffs + ds = adjoint.(cs) + n = degree(p) + LaurentPolynomial{T,X}(reverse(ds), -n) +end +paraconj(::AbstractPolynomial) = throw(ArgumentError("`paraconj` not defined for this polynomial type")) + +""" + cconj(p) + +Conjugation of a polynomial with respect to the imaginary axis. + +The `cconj` of a polynomial, `p̃`, conjugates the coefficients and applies `s -> -s`. That is `cconj(p)(s) = conj(p)(-s)`. + +This satisfies for *imaginary* `s`: `conj(p(s)) = p̃(s) = (conj ∘ p)(s) = cconj(p)(s) ` + +[ref](https://github.com/hurak/PolynomialEquations.jl#symmetrix-conjugate-equation-continuous-time-case) + +Examples: +```jldoctest laurent +julia> using Polynomials; + +julia> s = 2im +0 + 2im + +julia> p = LaurentPolynomial([im,-1, -im, 1], 1, :s) +LaurentPolynomial(im*s - s² - im*s³ + s⁴) + +julia> Polynomials.cconj(p)(s) ≈ conj(p(s)) +true + +julia> a = LaurentPolynomial([-0.12, -0.29, 1],:s) +LaurentPolynomial(-0.12 - 0.29*s + 1.0*s²) + +julia> b = LaurentPolynomial([1.86, -0.34, -1.14, -0.21, 1.19, -1.12],:s) +LaurentPolynomial(1.86 - 0.34*s - 1.14*s² - 0.21*s³ + 1.19*s⁴ - 1.12*s⁵) + +julia> x = LaurentPolynomial([-15.5, 50.0096551724139, 1.19], :s) +LaurentPolynomial(-15.5 + 50.0096551724139*s + 1.19*s²) + +julia> Polynomials.cconj(a) * x + a * Polynomials.cconj(x) ≈ b + Polynomials.cconj(b) +true +``` + +""" +function cconj(p::LaurentPolynomial{T,X}) where {T,X} + ps = conj.(coeffs(p)) + m,n = firstindex(p), lastindex(p) + for i in m:n + if isodd(i) + ps[i+1-m] *= -1 + end + end + LaurentPolynomial{T,X}(ps, m) +end +cconj(::AbstractPolynomial) = throw(ArgumentError("`cconj` not defined for this polynomial type")) + + +function roots(p::P; kwargs...) where {T, X, P <:LaurentPolynomial{T,X}} + return roots(convert(Polynomial, numerator(p)), kwargs...) +end diff --git a/src/standard-basis/standard-dense-view.jl b/src/standard-basis/standard-dense-view.jl index 749d042d..8a650c57 100644 --- a/src/standard-basis/standard-dense-view.jl +++ b/src/standard-basis/standard-dense-view.jl @@ -14,23 +14,7 @@ This type is useful for reducing copies and allocations in some algorithms. """ const PnPolynomial = MutableDenseViewPolynomial{StandardBasis} - - -function evalpoly(c, p::PnPolynomial{T,X}) where {T,X} - iszero(p) && return zero(T) * zero(c) - EvalPoly.evalpoly(c, p.coeffs) -end - -# scalar add -function scalar_add(c::S, p:: PnPolynomial{T,X}) where {S, T, X} - R = promote_type(T,S) - P = PnPolynomial{R,X} - - iszero(p) && return P([c]) - cs = convert(Vector{R}, copy(p.coeffs)) - cs[1] += c - P(cs) -end +_typealias(::Type{P}) where {P<:PnPolynomial} = "PnPolynomial" function ⊗(p:: PnPolynomial{T,X}, q:: PnPolynomial{S,X}) where {T,S,X} @@ -47,30 +31,6 @@ function ⊗(p:: PnPolynomial{T,X}, return pq end -function derivative(p::PnPolynomial{T,X}) where {T,X} - R = promote_type(T, Int) - N = length(p.coeffs) - iszero(N) && return zero(PnPolynomial{R,X}) - cs = Vector{R}(undef, N-1) - for (i, pᵢ) ∈ Base.Iterators.drop(pairs(p),1) - cs[i] = i * pᵢ - end - PnPolynomial{R,X}(cs) -end - -function integrate(p::PnPolynomial{T,X}) where {T,X} - R = Base.promote_op(/, T, Int) - N = length(p.coeffs) - cs = Vector{R}(undef,N+1) - cs[1] = zero(R) - for (i, pᵢ) ∈ pairs(p) - cs[i+1+1] = pᵢ/(i+1) - end - PnPolynomial{R,X}(cs) -end - - - # This is for standard basis function LinearAlgebra.mul!(pq::PnPolynomial, p::PnPolynomial, q::PnPolynomial) m,n = length(p)-1, length(q)-1 diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl index ccee255a..ebab47a4 100644 --- a/src/standard-basis/standard-dense.jl +++ b/src/standard-basis/standard-dense.jl @@ -1,145 +1,34 @@ # Dense + StandardBasis """ - 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`. - -The `coeffs` specify `a_{m}, a_{m-1}, ..., a_{n}`. -The argument `m` represents the lowest exponent of the variable in the series, and is taken to be zero by default. - -Laurent polynomials and standard basis polynomials promote to Laurent polynomials. Laurent polynomials may be converted to a standard basis polynomial when `m >= 0` -. - -Integration will fail if there is a `x⁻¹` term in the polynomial. - -!!! note - `LaurentPolynomial` is an alias for `MutableDensePolynomial{StandardBasis}`. - -!!! note - `LaurentPolynomial` is axis-aware, unlike the other polynomial types in this package. + Polynomial{T,X}(coeffs::AbstractVector, [m::Integer = 0], [var = :x]) # Examples: -```jldoctest laurent +```jldoctest polynomial julia> using Polynomials -julia> P = LaurentPolynomial; - -julia> p = P([1,1,1], -1) -LaurentPolynomial(x⁻¹ + 1 + x) - -julia> q = P([1,1,1]) -LaurentPolynomial(1 + x + x²) - -julia> pp = Polynomial([1,1,1]) -Polynomial(1 + x + x^2) - -julia> p + q -LaurentPolynomial(x⁻¹ + 2 + 2*x + x²) - -julia> p * q -LaurentPolynomial(x⁻¹ + 2 + 3*x + 2*x² + x³) - -julia> p * pp -LaurentPolynomial(x⁻¹ + 2 + 3*x + 2*x² + x³) - -julia> pp - q -LaurentPolynomial(0) - -julia> derivative(p) -LaurentPolynomial(-x⁻² + 1) - -julia> integrate(q) -LaurentPolynomial(1.0*x + 0.5*x² + 0.3333333333333333*x³) - -julia> integrate(p) # x⁻¹ term is an issue -ERROR: ArgumentError: Can't integrate Laurent polynomial with `x⁻¹` term - -julia> integrate(P([1,1,1], -5)) -LaurentPolynomial(-0.25*x⁻⁴ - 0.3333333333333333*x⁻³ - 0.5*x⁻²) - -julia> x⁻¹ = inv(variable(LaurentPolynomial)) # `inv` defined on monomials -LaurentPolynomial(1.0*x⁻¹) - -julia> p = Polynomial([1,2,3]) -Polynomial(1 + 2*x + 3*x^2) - -julia> x = variable() -Polynomial(x) - -julia> x^degree(p) * p(x⁻¹) # reverses coefficients -LaurentPolynomial(3.0 + 2.0*x + 1.0*x²) ``` """ -const LaurentPolynomial = MutableDensePolynomial{StandardBasis} -export LaurentPolynomial - -_typealias(::Type{P}) where {P<:LaurentPolynomial} = "LaurentPolynomial" - -# how to show term. Only needed here to get unicode exponents to match old LaurentPolynomial.jl type -function showterm(io::IO, ::Type{P}, pj::T, var, j, first::Bool, mimetype) where {T, P<:MutableDensePolynomial{StandardBasis,T}} - if _iszero(pj) return false end - - pj = printsign(io, pj, first, mimetype) - if hasone(T) - if !(_isone(pj) && !(showone(T) || j == 0)) - printcoefficient(io, pj, j, mimetype) - end - else - printcoefficient(io, pj, j, mimetype) - end - - iszero(j) && return true - printproductsign(io, pj, j, mimetype) - print(io, indeterminate(P)) - j == 1 && return true - unicode_exponent(io, j) # print(io, exponent_text(j, mimetype)) - return true -end - - -function evalpoly(c, p::MutableDensePolynomial{StandardBasis,T,X}) where {T,X} - iszero(p) && return zero(T) * zero(c) - EvalPoly.evalpoly(c, p.coeffs) * c^p.order[] -end - -# scalar add -function scalar_add(c::S, p:: MutableDensePolynomial{B,T,X}) where {B<:StandardBasis, S, T, X} - R = promote_type(T,S) - P = MutableDensePolynomial{B,R,X} +const 𝑃olynomial = MutableDensePolynomial{StandardBasis} +export 𝑃olynomial - iszero(p) && return P([c], 0) - iszero(c) && return convert(P, p) - - a,b = firstindex(p), lastindex(p) - a′ = min(0, a) - b′ = max(0, b) - cs = _zeros(p, zero(first(p.coeffs) + c), length(a′:b′)) - o = offset(p) + a - a′ - for (i, cᵢ) ∈ pairs(p) - cs[i + o] = cᵢ - end - cs[0 + o] += c - iszero(last(cs)) && (cs = trim_trailing_zeros(cs)) - P(Val(false), cs, a′) -end +_typealias(::Type{P}) where {P<:𝑃olynomial} = "Polynomial" -function ⊗(p:: MutableDensePolynomial{StandardBasis,T,X}, - q:: MutableDensePolynomial{StandardBasis,S,X}) where {T,S,X} +function ⊗(p::𝑃olynomial{T,X}, + q::𝑃olynomial{S,X}) where {T,S,X} # simple convolution # This is ⊗(P,p,q) from polynomial standard-basis R = promote_type(T,S) - P = MutableDensePolynomial{StandardBasis,R,X} + P = 𝑃olynomial{R,X} iszero(p) && return zero(P) iszero(q) && return zero(P) - a₁, a₂ = firstindex(p), firstindex(q) b₁, b₂ = lastindex(p), lastindex(q) - a, b = a₁ + a₂, b₁ + b₂ + b = b₁ + b₂ z = zero(first(p) * first(q)) - cs = _zeros(p, z, length(a:b)) + cs = _zeros(p, z, b + 1) # convolve and shift order @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) @@ -151,131 +40,5 @@ function ⊗(p:: MutableDensePolynomial{StandardBasis,T,X}, if iszero(last(cs)) cs = trim_trailing_zeros(cs) end - P(Val(false), cs, a) -end - -function derivative(p::MutableDensePolynomial{B,T,X}) where {B<:StandardBasis,T,X} - - N = lastindex(p) - firstindex(p) + 1 - R = promote_type(T, Int) - P = ⟒(p){R,X} - hasnan(p) && return P(zero(T)/zero(T)) # NaN{T} - iszero(p) && return P(0*p[0]) - - ps = p.coeffs - cs = [i*pᵢ for (i,pᵢ) ∈ pairs(p)] - return P(cs, p.order[]-1) -end - -# LaurentPolynomials have `inv` defined for monomials -function Base.inv(p::MutableDensePolynomial{StandardBasis}) - m,n = firstindex(p), lastindex(p) - m != n && throw(ArgumentError("Only monomials can be inverted")) - cs = [1/p for p in p.coeffs] - LaurentPolynomial{eltype(cs), indeterminate(p)}(cs, -m) -end - -""" - paraconj(p) - -[cf.](https://ccrma.stanford.edu/~jos/filters/Paraunitary_FiltersC_3.html) - -Call `p̂ = paraconj(p)` and `p̄` = conj(p)`, then this satisfies -`conj(p(z)) = p̂(1/conj(z))` or `p̂(z) = p̄(1/z) = (conj ∘ p ∘ conj ∘ inf)(z)`. - -Examples: - -```jldoctest laurent -julia> using Polynomials; - -julia> z = variable(LaurentPolynomial, :z) -LaurentPolynomial(z) - -julia> h = LaurentPolynomial([1,1], -1, :z) -LaurentPolynomial(z⁻¹ + 1) - -julia> Polynomials.paraconj(h)(z) ≈ 1 + z ≈ LaurentPolynomial([1,1], 0, :z) -true - -julia> h = LaurentPolynomial([3,2im,1], -2, :z) -LaurentPolynomial(3*z⁻² + 2im*z⁻¹ + 1) - -julia> Polynomials.paraconj(h)(z) ≈ 1 - 2im*z + 3z^2 ≈ LaurentPolynomial([1, -2im, 3], 0, :z) -true - -julia> Polynomials.paraconj(h)(z) ≈ (conj ∘ h ∘ conj ∘ inv)(z) -true -""" -function paraconj(p::MutableDensePolynomial{B,T,X}) where {B <: StandardBasis, T,X} - cs = p.coeffs - ds = adjoint.(cs) - n = degree(p) - MutableDensePolynomial{B,T,X}(reverse(ds), -n) -end - -""" - cconj(p) - -Conjugation of a polynomial with respect to the imaginary axis. - -The `cconj` of a polynomial, `p̃`, conjugates the coefficients and applies `s -> -s`. That is `cconj(p)(s) = conj(p)(-s)`. - -This satisfies for *imaginary* `s`: `conj(p(s)) = p̃(s) = (conj ∘ p)(s) = cconj(p)(s) ` - -[ref](https://github.com/hurak/PolynomialEquations.jl#symmetrix-conjugate-equation-continuous-time-case) - -Examples: -```jldoctest laurent -julia> using Polynomials; - -julia> s = 2im -0 + 2im - -julia> p = LaurentPolynomial([im,-1, -im, 1], 1, :s) -LaurentPolynomial(im*s - s² - im*s³ + s⁴) - -julia> Polynomials.cconj(p)(s) ≈ conj(p(s)) -true - -julia> a = LaurentPolynomial([-0.12, -0.29, 1],:s) -LaurentPolynomial(-0.12 - 0.29*s + 1.0*s²) - -julia> b = LaurentPolynomial([1.86, -0.34, -1.14, -0.21, 1.19, -1.12],:s) -LaurentPolynomial(1.86 - 0.34*s - 1.14*s² - 0.21*s³ + 1.19*s⁴ - 1.12*s⁵) - -julia> x = LaurentPolynomial([-15.5, 50.0096551724139, 1.19], :s) -LaurentPolynomial(-15.5 + 50.0096551724139*s + 1.19*s²) - -julia> Polynomials.cconj(a) * x + a * Polynomials.cconj(x) ≈ b + Polynomials.cconj(b) -true -``` - -""" -function cconj(p::MutableDensePolynomial{B,T,X}) where {B <: StandardBasis, T,X} - ps = conj.(coeffs(p)) - m,n = firstindex(p), lastindex(p) - for i in m:n - if isodd(i) - ps[i+1-m] *= -1 - end - end - MutableDensePolynomial{B,T,X}(ps, m) -end - - - -## ---- -## XXX needs to be incorporated if Polynomial = MutableDensePolynomial{StandardBasis} -function roots(p::P; kwargs...) where {T, X, P <: MutableDensePolynomial{StandardBasis,T,X}} - return roots(convert(Polynomial, numerator(p)), kwargs...) - # iszero(p) && return float(T)[] - # c = p.coeffs - # r = degreerange(p) - # d = r[end] - min(0, r[1]) + 1 # Length of the coefficient vector, taking into consideration - # # the case when the lower degree is strictly positive - # # (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{T,X}(z) # The root is then the root of the numerator polynomial. - # return roots(a; kwargs...) + P(Val(false), cs) end diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/standard-immutable.jl index f5193b7c..cf19c5d3 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/standard-immutable.jl @@ -58,21 +58,21 @@ export ImmutablePolynomial _typealias(::Type{P}) where {P<:ImmutablePolynomial} = "ImmutablePolynomial" -evalpoly(x, p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = zero(T)*zero(x) -evalpoly(x, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} = EvalPoly.evalpoly(x, p.coeffs) +evalpoly(x, p::ImmutablePolynomial{T,X,0}) where {T,X} = zero(T)*zero(x) +evalpoly(x, p::ImmutablePolynomial{T,X,N}) where {T,X,N} = EvalPoly.evalpoly(x, p.coeffs) -constantterm(p::ImmutableDensePolynomial{B,T,X,0}) where {B <: StandardBasis,T,X} = zero(T) -constantterm(p::ImmutableDensePolynomial{B,T,X,N}) where {B <: StandardBasis,T,X,N} = p.coeffs[1] +constantterm(p::ImmutablePolynomial{T,X,0}) where {T,X} = zero(T) +constantterm(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = p.coeffs[1] -scalar_add(p::ImmutableDensePolynomial{B,T,X,0}, c::S) where {B<:StandardBasis,T,X,S} = - ImmutableDensePolynomial{B,promote_type(T,S),X,1}((c,)) -function scalar_add(p::ImmutableDensePolynomial{B,T,X,1}, c::S) where {B<:StandardBasis,T,X,S} +scalar_add(p::ImmutablePolynomial{T,X,0}, c::S) where {T,X,S} = + ImmutablePolynomial{promote_type(T,S),X,1}((c,)) +function scalar_add(p::ImmutablePolynomial{T,X,1}, c::S) where {T,X,S} R = promote_type(T,S) - ImmutableDensePolynomial{B,R,X}(NTuple{1,R}(p[0] + c)) + ImmutablePolynomial{R,X,1}(NTuple{1,R}(p[0] + c)) end -function scalar_add(p::ImmutableDensePolynomial{B,T,X,N}, c::S) where {B<:StandardBasis,T,X,S,N} +function scalar_add(p::ImmutablePolynomial{T,X,N}, c::S) where {T,X,S,N} R = promote_type(T,S) - P = ImmutableDensePolynomial{B,R,X} + P = ImmutablePolynomial{R,X} iszero(c) && return P{N}(convert(NTuple{N,R}, p.coeffs)) cs = _tuple_combine(+, convert(NTuple{N,R}, p.coeffs), NTuple{1,R}((c,))) @@ -84,28 +84,28 @@ end # return N*M # intercept promotion call -function Base.:*(p::ImmutableDensePolynomial{StandardBasis,T,X,N}, - q::ImmutableDensePolynomial{StandardBasis,S,X,M}) where {T,S,X,N,M} +function Base.:*(p::ImmutablePolynomial{T,X,N}, + q::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} ⊗(p,q) end -⊗(p::ImmutableDensePolynomial{B,T,X,0}, - q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,M} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) -⊗(p::ImmutableDensePolynomial{B,T,X,N}, - q::ImmutableDensePolynomial{B,S,X,0}) where {B<:StandardBasis,T,S,X,N} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) -⊗(p::ImmutableDensePolynomial{B,T,X,0}, - q::ImmutableDensePolynomial{B,S,X,0}) where {B<:StandardBasis,T,S,X} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) -function ⊗(p::ImmutableDensePolynomial{B,T,X,N}, - q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,N,M} +⊗(p::ImmutablePolynomial{T,X,0}, + q::ImmutablePolynomial{S,X,M}) where {T,S,X,M} = zero(ImmutablePolynomial{promote_type(T,S),X,0}) +⊗(p::ImmutablePolynomial{T,X,N}, + q::ImmutablePolynomial{S,X,0}) where {T,S,X,N} = zero(ImmutablePolynomial{promote_type(T,S),X,0}) +⊗(p::ImmutablePolynomial{T,X,0}, + q::ImmutablePolynomial{S,X,0}) where {T,S,X} = zero(ImmutablePolynomial{promote_type(T,S),X,0}) +function ⊗(p::ImmutablePolynomial{T,X,N}, + q::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} cs = fastconv(p.coeffs, q.coeffs) R = eltype(cs) - ImmutableDensePolynomial{B,R,X,N+M-1}(cs) + ImmutablePolynomial{R,X,N+M-1}(cs) end # -function polynomial_composition(p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,N,M} - P = ImmutableDensePolynomial{B, promote_type(T,S), X, N*M} +function polynomial_composition(p::ImmutablePolynomial{T,X,N}, q::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} + P = ImmutablePolynomial{promote_type(T,S), X, N*M} cs = evalpoly(q, p.coeffs) convert(P, cs) end @@ -113,22 +113,23 @@ end # special cases of polynomial composition # ... TBD ... -derivative(p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = p -function derivative(p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} +# special cases are much more performant +derivative(p::ImmutablePolynomial{T,X,0}) where {T,X} = p +function derivative(p::ImmutablePolynomial{T,X,N}) where {T,X,N} N == 0 && return p hasnan(p) && return ⟒(p)(zero(T)/zero(T),X) # NaN{T} cs = ntuple(i -> i*p.coeffs[i+1], Val(N-1)) R = eltype(cs) - ImmutableDensePolynomial{StandardBasis,R,X,N-1}(cs) + ImmutablePolynomial{R,X,N-1}(cs) end -integrate(p::ImmutableDensePolynomial{StandardBasis,T,X,0}) where {T,X} = - ImmutableDensePolynomial{StandardBasis,Base.promote_op(/,T,Int),X,1}((0/1,)) -function integrate(p::ImmutableDensePolynomial{StandardBasis,T,X,N}) where {T,X,N} +integrate(p::ImmutablePolynomial{T,X,0}) where {T,X} = + ImmutablePolynomial{Base.promote_op(/,T,Int),X,1}((0/1,)) +function integrate(p::ImmutablePolynomial{T,X,N}) where {T,X,N} N == 0 && return p # different type hasnan(p) && return ⟒(p)(zero(T)/zero(T), X) # NaN{T} z = zero(first(p.coeffs)) cs = ntuple(i -> i > 1 ? p.coeffs[i-1]/(i-1) : z/1, Val(N+1)) R = eltype(cs) - ImmutableDensePolynomial{StandardBasis,R,X,N+1}(cs) + ImmutablePolynomial{R,X,N+1}(cs) end From ae4ef09eb75c673e19cf8479848a3be82105ae16 Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 2 Aug 2023 16:32:40 -0400 Subject: [PATCH 26/31] WIP: more cleanup --- src/Polynomials.jl | 12 +-- src/abstract-polynomial.jl | 9 ++- .../immutable-dense-polynomial.jl | 9 +-- .../mutable-dense-laurent-polynomial.jl | 6 +- .../mutable-dense-polynomial.jl | 10 +-- .../mutable-dense-view-polynomial.jl | 7 +- .../mutable-sparse-polynomial.jl | 2 +- src/polynomials/chebyshev.jl | 2 +- src/polynomials/multroot.jl | 13 ++-- src/polynomials/ngcd.jl | 17 ++--- ...d-immutable.jl => immutable-polynomial.jl} | 73 +++++++++---------- ...dense-laurent.jl => laurent-polynomial.jl} | 16 ++-- ...tandard-dense-view.jl => pn-polynomial.jl} | 30 ++------ src/standard-basis/polynomial.jl | 41 +++++++++++ ...tandard-sparse.jl => sparse-polynomial.jl} | 4 - src/standard-basis/standard-basis.jl | 47 +++++++++--- src/standard-basis/standard-dense.jl | 44 ----------- test/ChebyshevT.jl | 6 +- test/StandardBasis.jl | 3 +- 19 files changed, 174 insertions(+), 177 deletions(-) rename src/standard-basis/{standard-immutable.jl => immutable-polynomial.jl} (51%) rename src/standard-basis/{standard-dense-laurent.jl => laurent-polynomial.jl} (95%) rename src/standard-basis/{standard-dense-view.jl => pn-polynomial.jl} (60%) create mode 100644 src/standard-basis/polynomial.jl rename src/standard-basis/{standard-sparse.jl => sparse-polynomial.jl} (99%) delete mode 100644 src/standard-basis/standard-dense.jl diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 5ef11870..f810b99c 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -35,11 +35,11 @@ include("polynomial-basetypes/immutable-dense-polynomial.jl") include("polynomial-basetypes/mutable-sparse-polynomial.jl") include("standard-basis/standard-basis.jl") -include("standard-basis/standard-dense.jl") -include("standard-basis/standard-dense-view.jl") -include("standard-basis/standard-dense-laurent.jl") -include("standard-basis/standard-immutable.jl") -include("standard-basis/standard-sparse.jl") +include("standard-basis/polynomial.jl") +include("standard-basis/pn-polynomial.jl") +include("standard-basis/laurent-polynomial.jl") +include("standard-basis/immutable-polynomial.jl") +include("standard-basis/sparse-polynomial.jl") include("promotions.jl") @@ -65,6 +65,6 @@ if !isdefined(Base, :get_extension) include("../ext/PolynomialsMutableArithmeticsExt.jl") end -#include("precompiles.jl") +include("precompiles.jl") end # module diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 99118566..a043ccc2 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -152,6 +152,9 @@ end Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = chop!(copy(p)) chop!(p::AbstractUnivariatePolynomial; kwargs...) = XXX() +chop!!(p::AbstractUnivariatePolynomial; kwargs...) = chop!(p) +truncate!!(p::AbstractUnivariatePolynomial; kwargs...) = truncate!(p) + ## --- @@ -213,9 +216,9 @@ end ## Base.:-(p::AbstractUnivariatePolynomial) = map(-, p) #scalar_mult(-1, p) -Base.:+(c::Scalar, p::AbstractUnivariatePolynomial) = scalar_add(p, c) -Base.:+(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_add(p, c) -scalar_add(p::AbstractUnivariatePolynomial, c) = scalar_add(c,p) # scalar addition is commutative +Base.:+(c::Scalar, p::AbstractUnivariatePolynomial) = scalar_add(c, p) +Base.:+(p::AbstractUnivariatePolynomial, c::Scalar) = scalar_add(c, p) +scalar_add(p::AbstractUnivariatePolynomial, c) = scalar_add(c, p) # scalar addition is commutative Base.:+(p::AbstractUnivariatePolynomial) = p Base.:+(p::AbstractUnivariatePolynomial{B, T, X}, diff --git a/src/polynomial-basetypes/immutable-dense-polynomial.jl b/src/polynomial-basetypes/immutable-dense-polynomial.jl index e99da74a..21f3367a 100644 --- a/src/polynomial-basetypes/immutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/immutable-dense-polynomial.jl @@ -93,7 +93,7 @@ Base.similar(p::ImmutableDensePolynomial, args...) = p.coeffs # not type stable, as N is value dependent -function trim_trailing_zeros(cs::Tuple) +function trim_trailing_zeros!!(cs::Tuple) isempty(cs) && return cs !iszero(last(cs)) && return cs i = findlast(!iszero, cs) @@ -117,10 +117,9 @@ function Base.chop(p::ImmutableDensePolynomial{B,T,X,N}; ImmutableDensePolynomial{B,T,X,N′}(xs) end -# misnamed, should be chop!! -chop!(p::ImmutableDensePolynomial; kwargs...) = chop(p; kwargs...) +chop!(p::ImmutableDensePolynomial; kwargs...) = chop(p; kwargs...) ## misnamed, should be chop!! +chop!!(p::ImmutableDensePolynomial; kwargs...) = chop(p; kwargs...) -# truncate!!; keeps length replacing values with zeros function truncate!(p::ImmutableDensePolynomial{B,T,X,N}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) where {B,T,X,N} @@ -211,7 +210,7 @@ function degree(p::ImmutableDensePolynomial{B,T,X,N}) where {B,T,X,N} end function coeffs(p::P) where {P <: ImmutableDensePolynomial} - trim_trailing_zeros(p.coeffs) + trim_trailing_zeros!!(p.coeffs) end # zero, one diff --git a/src/polynomial-basetypes/mutable-dense-laurent-polynomial.jl b/src/polynomial-basetypes/mutable-dense-laurent-polynomial.jl index 5a841574..8a9983b2 100644 --- a/src/polynomial-basetypes/mutable-dense-laurent-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-laurent-polynomial.jl @@ -39,7 +39,7 @@ end function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDenseLaurentPolynomial{B,T,X}, S} R = eltype(as) Q = MutableDenseLaurentPolynomial{B, R, X} - as = trim_trailing_zeros(as) + as = trim_trailing_zeros!!(as) Q(Val(false), as, p.order[]) end @@ -55,7 +55,7 @@ end function Base.map(fn, p::P, args...) where {B,T,X, P<:MutableDenseLaurentPolynomial{B,T,X}} xs = map(fn, p.coeffs, args...) - xs = trim_trailing_zeros(xs) + xs = trim_trailing_zeros!!(xs) R = eltype(xs) return ⟒(P){R,X}(Val(false), xs, p.order[]) end @@ -200,7 +200,7 @@ function offset_vector_combine(op, p::MutableDenseLaurentPolynomial{B,T,X}, q::M x[i] = op(x[i], cᵢ) end - b₁ == b₂ && (x = trim_trailing_zeros(x)) + b₁ == b₂ && (x = trim_trailing_zeros!!(x)) P(Val(false), x, a) end diff --git a/src/polynomial-basetypes/mutable-dense-polynomial.jl b/src/polynomial-basetypes/mutable-dense-polynomial.jl index 0da618df..3b155c4c 100644 --- a/src/polynomial-basetypes/mutable-dense-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-polynomial.jl @@ -32,7 +32,7 @@ end function _polynomial(p::P, as::Vector{S}) where {B,T, X, P <: MutableDensePolynomial{B,T,X}, S} R = eltype(as) Q = MutableDensePolynomial{B, R, X} - as = trim_trailing_zeros(as) + as = trim_trailing_zeros!!(as) Q(Val(false), as, p.order[]) end @@ -48,7 +48,7 @@ end function Base.map(fn, p::P, args...) where {B,T,X, P<:MutableDensePolynomial{B,T,X}} xs = map(fn, p.coeffs, args...) - xs = trim_trailing_zeros(xs) + xs = trim_trailing_zeros!!(xs) R = eltype(xs) return ⟒(P){R,X}(Val(false), xs) end @@ -100,7 +100,7 @@ end basis(::Type{MutableDensePolynomial{B,T,X}},i::Int) where {B,T,X} = MutableDensePolynomial{B,T,X}([1],i) -function trim_trailing_zeros(cs::Vector{T}) where {T} +function trim_trailing_zeros!!(cs::Vector{T}) where {T} isempty(cs) && return cs !iszero(last(cs)) && return cs i = findlast(!iszero, cs) @@ -138,7 +138,7 @@ function normΔ(q1::MutableDensePolynomial{B}, q2::MutableDensePolynomial{B}) wh iszero(q2) && return norm(q1,2) r = abs(zero(q1[end] + q2[end])) tot = zero(r) - for i ∈ o:maximum(lastindex, (q1,q2)) + for i ∈ 0:maximum(lastindex, (q1,q2)) @inbounds tot += abs2(q1[i] - q2[i]) end return sqrt(tot) @@ -177,7 +177,7 @@ function _vector_combine(op, p::MutableDensePolynomial{B,T,X}, q::MutableDensePo x[i] = op(x[i], cᵢ) end - b₁ == b₂ && (x = trim_trailing_zeros(x)) + b₁ == b₂ && (x = trim_trailing_zeros!!(x)) P(Val(false), x, a) end diff --git a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl index 3ad0de1b..72aa40da 100644 --- a/src/polynomial-basetypes/mutable-dense-view-polynomial.jl +++ b/src/polynomial-basetypes/mutable-dense-view-polynomial.jl @@ -25,10 +25,11 @@ MutableDenseViewPolynomial{B,T,X}(check::Val{true}, cs::AbstractVector{S}) where MutableDenseViewPolynomial{B,T,X}(cs::AbstractVector{S}) where {B,T,S,X} = MutableDenseViewPolynomial{B,T,X}(Val(false), cs) function MutableDenseViewPolynomial{B, T, X}(coeffs::AbstractVector{S}, order::Int) where {B, T,S, X} - iszero(order) && return MutableDenseViewPolynomial{B,T,X}(coeffs) + iszero(order) && return MutableDenseViewPolynomial{B,T,X}(Val(false), coeffs) order < 0 && throw(ArgumentError("Not a Laurent type")) - prepend!(coeffs, zeros(T,order)) - MutableDenseViewPolynomial{B,T,X}(coeffs) + cs = convert(Vector{T}, coeffs) + prepend!(cs, zeros(T,order)) + MutableDenseViewPolynomial{B,T,X}(Val(false), cs) end @poly_register MutableDenseViewPolynomial diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index 48aa16e0..4e29d42a 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -138,7 +138,7 @@ function chop_exact_zeros!(d::Dict) end d end -trim_trailing_zeros(d::Dict) = chop_exact_zeros!(d) # Not properly named, but what is expected in other constructors +trim_trailing_zeros!!(d::Dict) = chop_exact_zeros!(d) # Not properly named, but what is expected in other constructors function chop!(p::MutableSparsePolynomial; atol=nothing, rtol=nothing) isempty(p.coeffs) && return p diff --git a/src/polynomials/chebyshev.jl b/src/polynomials/chebyshev.jl index f9c861d6..bf764093 100644 --- a/src/polynomials/chebyshev.jl +++ b/src/polynomials/chebyshev.jl @@ -108,7 +108,7 @@ end function scalar_add(c::S, p::MutableDensePolynomial{B,T,X}) where {B<:ChebyshevTBasis,T,X, S<:Scalar} R = promote_type(T,S) P = MutableDensePolynomial{ChebyshevTBasis,R,X} - cs = convert(Vector{R}, coeffs(p)) + cs = convert(Vector{R}, copy(coeffs(p))) n = length(cs) iszero(n) && return P([c]) isone(n) && return P([cs[1] + c]) diff --git a/src/polynomials/multroot.jl b/src/polynomials/multroot.jl index e456afac..50dd27ca 100644 --- a/src/polynomials/multroot.jl +++ b/src/polynomials/multroot.jl @@ -7,9 +7,7 @@ using ..Polynomials using LinearAlgebra -PnPolynomial = Polynomials.PnPolynomial -StandardBasisType = Polynomials.StandardBasisType -#PnPolynomial = Polynomials.MutableDenseViewPolynomial{Polynomials.StandardBasis} +import ..Polynomials: PnPolynomial, StandardBasisType """ multroot(p; verbose=false, method=:direct, kwargs...) @@ -103,6 +101,7 @@ is misidentified. function multroot(p::StandardBasisType{T}; verbose=false, kwargs...) where {T} + p = chop(p) # degenerate case, constant degree(p) == 0 && return (values=T[], multiplicities=Int[], κ=NaN, ϵ=NaN) @@ -149,10 +148,8 @@ function pejorative_manifold( S = float(T) u = PnPolynomial{S,X}(S.(coeffs(p))) - nu₂ = norm(u, 2) θ2, ρ2 = θ * nu₂, ρ * nu₂ - u, v, w, ρⱼ, κ = Polynomials.ngcd( u, derivative(u), satol = θ2, srtol = zero(real(T)), @@ -367,7 +364,7 @@ function cond_zl(p, zs::Vector{S}, ls) where {S} 1 / σ end -backward_error(p::StandardBasisType, zs::Vector{S}, ls) where {S} = +backward_error(p::AbstractPolynomial, zs::Vector{S}, ls) where {S} = backward_error(reverse(coeffs(p)), zs, ls) function backward_error(p, z̃s::Vector{S}, ls) where {S} @@ -380,7 +377,7 @@ function backward_error(p, z̃s::Vector{S}, ls) where {S} norm(W*u,2) end -function stats(p, zs, ls) +function stats(p::AbstractPolynomial, zs, ls) cond_zl(p, zs, ls), backward_error(p, zs, ls) end @@ -477,7 +474,7 @@ end # use least-squares rather than Zeng's AGCD refinement strategy function _ngcd(u, k) - @show :_ngcd + # @show :_ngcd n = degree(u) Sy = Polynomials.NGCD.SylvesterMatrix(u, derivative(u), n-k) b = Sy[1:end-1,2*k+1] - n * Sy[1:end-1,k] # X^k*p' - n*X^{k-1}*p diff --git a/src/polynomials/ngcd.jl b/src/polynomials/ngcd.jl index 9eb580fe..c4c50f65 100644 --- a/src/polynomials/ngcd.jl +++ b/src/polynomials/ngcd.jl @@ -45,15 +45,13 @@ function ngcd(p::P, q::Q, end ## call ngcd - #P′ = PnPolynomial - P′ = MutableDenseViewPolynomial{StandardBasis} + P′ = PnPolynomial p′ = P′{R,X}(ps[nz:end]) q′ = P′{R,X}(qs[nz:end]) out = NGCD.ngcd(p′, q′, args...; kwargs...) - ## convert to original polynomial type + ## convert to original polynomial type 𝑷 = Polynomials.constructorof(promote_type(P,Q)){R,X} - 𝑷 = MutableDenseViewPolynomial{StandardBasis,R,X} u,v,w = convert.(𝑷, (out.u,out.v,out.w)) if nz > 1 u *= variable(u)^(nz-1) @@ -94,10 +92,7 @@ end module NGCD using Polynomials, LinearAlgebra -import Polynomials: constructorof, MutableDenseViewPolynomial, StandardBasis - -#PnPolynomial = Polynomials.PnPolynomial -PnPolynomial = MutableDenseViewPolynomial{StandardBasis} +import Polynomials: constructorof, PnPolynomial """ @@ -298,6 +293,7 @@ function ngcd(p::PnPolynomial{T,X}, u, v, w = initial_uvw(Val(:ispossible), j, p, q, xx) end ϵₖ, κ = refine_uvw!(u, v, w, p, q, uv, uw) + # we have limsup Θᵏ / ‖(p,q) - (p̃,q̃)‖ = κ, so # ‖Θᵏ‖ ≤ κ ⋅ ‖(p,q)‖ ⋅ ϵ seems a reasonable heuristic. # Too tight a tolerance and the right degree will be missed; too @@ -307,7 +303,6 @@ function ngcd(p::PnPolynomial{T,X}, ϵ = max(atol, npq₂ * κ * rtol) #@show ϵₖ, ϵ, κ if ϵₖ ≤ ϵ - #@show :success, σ₋₁, ϵₖ return (u=u, v=v, w=w, Θ=ϵₖ, κ=κ) end #@show :failure, j @@ -454,9 +449,10 @@ end ## Find u₀,v₀,w₀ from right singular vector function initial_uvw(::Val{:ispossible}, j, p::P, q::Q, x) where {T,X, P<:PnPolynomial{T,X}, - Q<:PnPolynomial{T,X}} + Q<:PnPolynomial{T,X}} # Sk*[w;-v] = 0, so pick out v,w after applying permutation m, n = length(p)-1, length(q)-1 + vᵢ = vcat(2:m-n+2, m-n+4:2:length(x)) wᵢ = m-n+3 > length(x) ? [1] : vcat(1, (m-n+3):2:length(x)) @@ -533,7 +529,6 @@ end function refine_uvw!(u::P, v::P, w::P, p, q, uv, uw) where {T,X, P<:PnPolynomial{T,X}} - mul!(uv, u, v) mul!(uw, u, w) ρ₁ = residual_error(p, q, uv, uw) diff --git a/src/standard-basis/standard-immutable.jl b/src/standard-basis/immutable-polynomial.jl similarity index 51% rename from src/standard-basis/standard-immutable.jl rename to src/standard-basis/immutable-polynomial.jl index cf19c5d3..c9c6d89c 100644 --- a/src/standard-basis/standard-immutable.jl +++ b/src/standard-basis/immutable-polynomial.jl @@ -33,8 +33,8 @@ are precluded from use in rational functions. # Examples -```jldoctest -julia> using Polynomials +```jldoctest immutable_polynomials +julia> using Polynomials julia> ImmutablePolynomial((1, 0, 3, 4)) ImmutablePolynomial(1 + 3*x^2 + 4*x^3) @@ -49,30 +49,27 @@ ImmutablePolynomial(1.0) !!! note This was modeled after [StaticUnivariatePolynomials](https://github.com/tkoolen/StaticUnivariatePolynomials.jl) by `@tkoolen`. -!!! note - `ImmutablePolynomial` is an alias for `ImmutableDensePolynomial{StandardBasis}`. - """ ImmutablePolynomial = ImmutableDensePolynomial{StandardBasis} export ImmutablePolynomial _typealias(::Type{P}) where {P<:ImmutablePolynomial} = "ImmutablePolynomial" -evalpoly(x, p::ImmutablePolynomial{T,X,0}) where {T,X} = zero(T)*zero(x) -evalpoly(x, p::ImmutablePolynomial{T,X,N}) where {T,X,N} = EvalPoly.evalpoly(x, p.coeffs) +evalpoly(x, p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = zero(T)*zero(x) +evalpoly(x, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} = EvalPoly.evalpoly(x, p.coeffs) -constantterm(p::ImmutablePolynomial{T,X,0}) where {T,X} = zero(T) -constantterm(p::ImmutablePolynomial{T,X,N}) where {T,X,N} = p.coeffs[1] +constantterm(p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = zero(T) +constantterm(p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} = p.coeffs[1] -scalar_add(p::ImmutablePolynomial{T,X,0}, c::S) where {T,X,S} = - ImmutablePolynomial{promote_type(T,S),X,1}((c,)) -function scalar_add(p::ImmutablePolynomial{T,X,1}, c::S) where {T,X,S} +scalar_add(c::S, p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X,S} = + ImmutableDensePolynomial{B,promote_type(T,S),X,1}((c,)) +function scalar_add(c::S, p::ImmutableDensePolynomial{B,T,X,1}) where {B<:StandardBasis,T,X,S} R = promote_type(T,S) - ImmutablePolynomial{R,X,1}(NTuple{1,R}(p[0] + c)) + ImmutableDensePolynomial{B,R,X,1}(NTuple{1,R}(p[0] + c)) end -function scalar_add(p::ImmutablePolynomial{T,X,N}, c::S) where {T,X,S,N} +function scalar_add(c::S, p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,S,N} R = promote_type(T,S) - P = ImmutablePolynomial{R,X} + P = ImmutableDensePolynomial{B,R,X} iszero(c) && return P{N}(convert(NTuple{N,R}, p.coeffs)) cs = _tuple_combine(+, convert(NTuple{N,R}, p.coeffs), NTuple{1,R}((c,))) @@ -84,28 +81,28 @@ end # return N*M # intercept promotion call -function Base.:*(p::ImmutablePolynomial{T,X,N}, - q::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} +function Base.:*(p::ImmutableDensePolynomial{B,T,X,N}, + q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,N,M} ⊗(p,q) end -⊗(p::ImmutablePolynomial{T,X,0}, - q::ImmutablePolynomial{S,X,M}) where {T,S,X,M} = zero(ImmutablePolynomial{promote_type(T,S),X,0}) -⊗(p::ImmutablePolynomial{T,X,N}, - q::ImmutablePolynomial{S,X,0}) where {T,S,X,N} = zero(ImmutablePolynomial{promote_type(T,S),X,0}) -⊗(p::ImmutablePolynomial{T,X,0}, - q::ImmutablePolynomial{S,X,0}) where {T,S,X} = zero(ImmutablePolynomial{promote_type(T,S),X,0}) -function ⊗(p::ImmutablePolynomial{T,X,N}, - q::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} +⊗(p::ImmutableDensePolynomial{B,T,X,0}, + q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,M} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) +⊗(p::ImmutableDensePolynomial{B,T,X,N}, + q::ImmutableDensePolynomial{B,S,X,0}) where {B<:StandardBasis,T,S,X,N} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) +⊗(p::ImmutableDensePolynomial{B,T,X,0}, + q::ImmutableDensePolynomial{B,S,X,0}) where {B<:StandardBasis,T,S,X} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) +function ⊗(p::ImmutableDensePolynomial{B,T,X,N}, + q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,N,M} cs = fastconv(p.coeffs, q.coeffs) R = eltype(cs) - ImmutablePolynomial{R,X,N+M-1}(cs) + ImmutableDensePolynomial{B,R,X,N+M-1}(cs) end # -function polynomial_composition(p::ImmutablePolynomial{T,X,N}, q::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} - P = ImmutablePolynomial{promote_type(T,S), X, N*M} +function polynomial_composition(p::ImmutableDensePolynomial{B,T,X,N}, q::ImmutableDensePolynomial{B,S,X,M}) where {B<:StandardBasis,T,S,X,N,M} + P = ImmutableDensePolynomial{B,promote_type(T,S), X, N*M} cs = evalpoly(q, p.coeffs) convert(P, cs) end @@ -113,23 +110,25 @@ end # special cases of polynomial composition # ... TBD ... + # special cases are much more performant -derivative(p::ImmutablePolynomial{T,X,0}) where {T,X} = p -function derivative(p::ImmutablePolynomial{T,X,N}) where {T,X,N} +derivative(p::ImmutableDensePolynomial{B,T,X,0}) where {B<:StandardBasis,T,X} = p +function derivative(p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} N == 0 && return p - hasnan(p) && return ⟒(p)(zero(T)/zero(T),X) # NaN{T} + hasnan(p) && return ImmutableDensePolynomial{B,T,X,1}(zero(T)/zero(T)) # NaN{T} cs = ntuple(i -> i*p.coeffs[i+1], Val(N-1)) R = eltype(cs) - ImmutablePolynomial{R,X,N-1}(cs) + ImmutableDensePolynomial{B,R,X,N-1}(cs) end -integrate(p::ImmutablePolynomial{T,X,0}) where {T,X} = - ImmutablePolynomial{Base.promote_op(/,T,Int),X,1}((0/1,)) -function integrate(p::ImmutablePolynomial{T,X,N}) where {T,X,N} +integrate(p::ImmutableDensePolynomial{B, T,X,0}) where {B<:StandardBasis,T,X} = + ImmutableDensePolynomial{B,Base.promote_op(/,T,Int),X,1}((0/1,)) +function integrate(p::ImmutableDensePolynomial{B,T,X,N}) where {B<:StandardBasis,T,X,N} N == 0 && return p # different type - hasnan(p) && return ⟒(p)(zero(T)/zero(T), X) # NaN{T} + R′ = Base.promote_op(/,T,Int) + hasnan(p) && return ImmutableDensePolynomial{B,R′,X,1}(zero(T)/zero(T)) # NaN{T} z = zero(first(p.coeffs)) cs = ntuple(i -> i > 1 ? p.coeffs[i-1]/(i-1) : z/1, Val(N+1)) R = eltype(cs) - ImmutablePolynomial{R,X,N+1}(cs) + ImmutableDensePolynomial{B,R,X,N+1}(cs) end diff --git a/src/standard-basis/standard-dense-laurent.jl b/src/standard-basis/laurent-polynomial.jl similarity index 95% rename from src/standard-basis/standard-dense-laurent.jl rename to src/standard-basis/laurent-polynomial.jl index a90f53e3..11e72f50 100644 --- a/src/standard-basis/standard-dense-laurent.jl +++ b/src/standard-basis/laurent-polynomial.jl @@ -8,14 +8,10 @@ A [Laurent](https://en.wikipedia.org/wiki/Laurent_polynomial) polynomial is of t The `coeffs` specify `a_{m}, a_{m-1}, ..., a_{n}`. The argument `m` represents the lowest exponent of the variable in the series, and is taken to be zero by default. -Laurent polynomials and standard basis polynomials promote to Laurent polynomials. Laurent polynomials may be converted to a standard basis polynomial when `m >= 0` -. +Laurent polynomials and standard basis polynomials promote to Laurent polynomials. Laurent polynomials may be converted to a standard basis polynomial when `m >= 0`, Integration will fail if there is a `x⁻¹` term in the polynomial. -!!! note - `LaurentPolynomial` is an alias for `MutableDensePolynomial{StandardBasis}`. - !!! note `LaurentPolynomial` is axis-aware, unlike the other polynomial types in this package. @@ -120,7 +116,7 @@ function scalar_add(c::S, p::LaurentPolynomial{T,X}) where {S, T, X} cs[i + o] = cᵢ end cs[0 + o] += c - iszero(last(cs)) && (cs = trim_trailing_zeros(cs)) + iszero(last(cs)) && (cs = trim_trailing_zeros!!(cs)) P(Val(false), cs, a′) end @@ -149,7 +145,7 @@ function ⊗(p::LaurentPolynomial{T,X}, end end if iszero(last(cs)) - cs = trim_trailing_zeros(cs) + cs = trim_trailing_zeros!!(cs) end P(Val(false), cs, a) end @@ -168,11 +164,11 @@ function derivative(p::LaurentPolynomial{T,X}) where {T,X} end # LaurentPolynomials have `inv` defined for monomials -function Base.inv(p::LaurentPolynomial) +function Base.inv(p::LaurentPolynomial{T,X}) where {T,X} m,n = firstindex(p), lastindex(p) m != n && throw(ArgumentError("Only monomials can be inverted")) - cs = [1/p for p in p.coeffs] - LaurentPolynomial{eltype(cs), indeterminate(p)}(cs, -m) + c = 1/p[n] + return LaurentPolynomial(c, -m, X) end """ diff --git a/src/standard-basis/standard-dense-view.jl b/src/standard-basis/pn-polynomial.jl similarity index 60% rename from src/standard-basis/standard-dense-view.jl rename to src/standard-basis/pn-polynomial.jl index 8a650c57..cb028163 100644 --- a/src/standard-basis/standard-dense-view.jl +++ b/src/standard-basis/pn-polynomial.jl @@ -16,30 +16,14 @@ This type is useful for reducing copies and allocations in some algorithms. const PnPolynomial = MutableDenseViewPolynomial{StandardBasis} _typealias(::Type{P}) where {P<:PnPolynomial} = "PnPolynomial" -function ⊗(p:: PnPolynomial{T,X}, - q:: PnPolynomial{S,X}) where {T,S,X} - R = promote_type(T,S) - P = PnPolynomial{R,X} - - N,M = length(p), length(q) - iszero(N) && return zero(P) - iszero(M) && return zero(P) - - cs = Vector{R}(undef, N+M-1) - pq = P(cs) - mul!(pq, p, q) - return pq -end - -# This is for standard basis -function LinearAlgebra.mul!(pq::PnPolynomial, p::PnPolynomial, q::PnPolynomial) +# used by multroot +function LinearAlgebra.mul!(pq::PnPolynomial, p::PnPolynomial{T,X}, q::PnPolynomial) where {T,X} m,n = length(p)-1, length(q)-1 - cs = pq.coeffs - cs .= 0 - @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) - for (j, qⱼ) ∈ enumerate(q.coeffs) - ind = i + j - 1 - cs[ind] = muladd(pᵢ, qⱼ, cs[ind]) + pq.coeffs .= zero(T) + for i ∈ 0:m + for j ∈ 0:n + k = i + j + @inbounds pq.coeffs[1+k] = muladd(p.coeffs[1+i], q.coeffs[1+j], pq.coeffs[1+k]) end end nothing diff --git a/src/standard-basis/polynomial.jl b/src/standard-basis/polynomial.jl new file mode 100644 index 00000000..d556baaa --- /dev/null +++ b/src/standard-basis/polynomial.jl @@ -0,0 +1,41 @@ +# """ +# Polynomial{T, 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. + +# If ``p = a_n x^n + \\ldots + a_2 x^2 + a_1 x + a_0``, we construct this through +# `Polynomial([a_0, a_1, ..., a_n])`. + +# The usual arithmetic operators are overloaded to work with polynomials as well as +# with combinations of polynomials and scalars. However, operations involving two +# polynomials of different variables causes an error except those involving a constant polynomial. + +# !!! note +# `Polynomial` is not axis-aware, and it treats `coeffs` simply as a list of coefficients with the first +# index always corresponding to the constant term. In order to use the axis of `coeffs` as exponents, +# consider using a [`LaurentPolynomial`](@ref) or possibly a [`SparsePolynomial`](@ref). + +# # Examples +# ```jldoctest +# julia> using Polynomials + +# julia> Polynomial([1, 0, 3, 4]) +# Polynomial(1 + 3*x^2 + 4*x^3) + +# julia> Polynomial([1, 2, 3], :s) +# Polynomial(1 + 2*s + 3*s^2) + +# julia> one(Polynomial) +# Polynomial(1.0) +# ``` +# """ +""" + 𝑃olynomial{T,X} + +A proposed type for `Polynomial`. Worried this may be breaking +""" +const 𝑃olynomial = MutableDensePolynomial{StandardBasis} +# export Polynomial + +_typealias(::Type{P}) where {P<:𝑃olynomial} = "𝑃olynomial" diff --git a/src/standard-basis/standard-sparse.jl b/src/standard-basis/sparse-polynomial.jl similarity index 99% rename from src/standard-basis/standard-sparse.jl rename to src/standard-basis/sparse-polynomial.jl index 2a20ca13..72ec3fde 100644 --- a/src/standard-basis/standard-sparse.jl +++ b/src/standard-basis/sparse-polynomial.jl @@ -46,7 +46,6 @@ export SparsePolynomial _typealias(::Type{P}) where {P<:SparsePolynomial} = "SparsePolynomial" function evalpoly(x, p::MutableSparsePolynomial) - tot = zero(p[0]*x) for (i, cᵢ) ∈ p.coeffs tot = muladd(cᵢ, x^i, tot) @@ -54,7 +53,6 @@ function evalpoly(x, p::MutableSparsePolynomial) return tot end -# much faster than default function scalar_add(c::S, p::MutableSparsePolynomial{B,T,X}) where {B<:StandardBasis,T,X,S} c₀ = c + p[0] R = eltype(c₀) @@ -68,7 +66,6 @@ function scalar_add(c::S, p::MutableSparsePolynomial{B,T,X}) where {B<:StandardB return P(Val(false), D) end - function ⊗(p::MutableSparsePolynomial{StandardBasis,T,X}, q::MutableSparsePolynomial{StandardBasis,S,X}) where {T,S,X} @@ -90,7 +87,6 @@ function ⊗(p::MutableSparsePolynomial{StandardBasis,T,X}, P(Val(false), cs) end - # sparse function derivative(p:: MutableSparsePolynomial{B,T,X}) where {B<:StandardBasis,T,X} N = lastindex(p) - firstindex(p) + 1 diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index 35353d66..da345c9f 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -44,9 +44,9 @@ function Base.convert(P::Type{PP}, q::Q) where {PP <: StandardBasisPolynomial, B else cs = [q[i] for i ∈ eachindex(q)] o = firstindex(q) - throw(ArgumentError("Do we need this??? If not, can remove two defs at end")) + @warn "This can be removed, right?" end - #⟒(P){T′,X′}(cs, o) + ⟒(P){T′,X′}(cs, o) end function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: AbstractUnivariatePolynomial{B}, Q<:StandardBasisPolynomial} @@ -72,10 +72,12 @@ mapdomain(::Type{P}, x::AbstractArray) where {B <: StandardBasis, P <: Abstract ## Evaluation, Scalar addition, Multiplication, integration, differentiation +## may be no good fallback function evalpoly(c::S, p::P) where {B<:StandardBasis, S, T, X, P<:AbstractDenseUnivariatePolynomial{B,T,X}} iszero(p) && return zero(T) * zero(c) EvalPoly.evalpoly(c, p.coeffs) end +evalpoly(c::S, p::AbstractLaurentUnivariatePolynomial{StandardBasis}) where {S} = throw(ArgumentError("Default method not defined")) function scalar_add(c::S, p::P) where {B<:StandardBasis, S, T, X, P<:AbstractDenseUnivariatePolynomial{B,T,X}} R = promote_type(T,S) @@ -91,9 +93,10 @@ function scalar_add(c::S, p::P) where {B<:StandardBasis, S, T, X, P<:AbstractDen cs = _set(cs, i+o, cᵢ) end cs = _set(cs, 0+o, cs[0+o] + c) - iszero(last(cs)) && (cs = trim_trailing_zeros(cs)) + iszero(last(cs)) && (cs = trim_trailing_zeros!!(cs)) P′(Val(false), cs) end +scalar_add(c::S, p::AbstractLaurentUnivariatePolynomial{StandardBasis}) where {S} = throw(ArgumentError("Default method not defined")) # special cases are faster function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, @@ -102,23 +105,51 @@ function ⊗(p::AbstractUnivariatePolynomial{B,T,X}, throw(ArgumentError("Default method not defined")) end +# for dense 0-based polys with p.coeffs +function ⊗(p::P, q::P) where {B <: StandardBasis,T,X, P<:AbstractDenseUnivariatePolynomial{B,T,X}} + + P′ = ⟒(P){T,X} + + iszero(p) && return zero(P′) + iszero(q) && return zero(P′) + + n,m = length(p), length(q) + cs = _zeros(p, zero(p[0] + q[0]), n + m - 1) + mul!(cs, p, q) + P′(Val(false), cs) +end + +# +function LinearAlgebra.mul!(cs, p::AbstractDenseUnivariatePolynomial, q::AbstractDenseUnivariatePolynomial) + m,n = length(p)-1, length(q)-1 + cs .= 0 + @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) + for (j, qⱼ) ∈ enumerate(q.coeffs) + ind = i + j - 1 + cs[ind] = muladd(pᵢ, qⱼ, cs[ind]) + end + end + nothing +end + + # implemented derivative case by case function derivative(p::P) where {B<:StandardBasis, T,X,P<:AbstractDenseUnivariatePolynomial{B,T,X}} - R = promote_type(T, Int) + R = Base.promote_type(T, Int) N = length(p.coeffs) P′ = ⟒(P){R,X} iszero(N) && return zero(P) + hasnan(p) && return P′(NaN) z = 0*p[0] cs = _zeros(p,z,N-1) o = offset(p) -# cs = Vector{R}(undef, N-1) for (i, pᵢ) ∈ Base.Iterators.drop(pairs(p),1) _set(cs, i - 1 + o, i * pᵢ) -# cs[i] = i * pᵢ end P′(Val(false), cs) end -derivative(p::P) where {B<:StandardBasis, T,X,P<:AbstractLaurentUnivariatePolynomial{B,T,X}} = XXX() +derivative(p::P) where {B<:StandardBasis, T,X,P<:AbstractLaurentUnivariatePolynomial{B,T,X}} = + throw(ArgumentError("Default method not defined")) function integrate(p::P) where {B <: StandardBasis,T,X,P<:AbstractDenseUnivariatePolynomial{B,T,X}} @@ -200,7 +231,6 @@ function Base.divrem(num::P, den::Q) where {B<:StandardBasis, end -## XXX This needs resolving! ## XXX copy or pass along to other system for now where things are defined for StandardBasisPolynomial function vander(p::Type{<:P}, x::AbstractVector{T}, degs) where {B<:StandardBasis, P<:AbstractUnivariatePolynomial{B}, T <: Number} vander(StandardBasisPolynomial, x, degs) @@ -229,6 +259,5 @@ function fit(::Type{P}, end # new constructors taking order in second position for the `convert` method above (to work with LaurentPolynomial) -# these are Polynomial{T, X}(coeffs::AbstractVector{S},order::Int) where {T, X, S} = Polynomial{T,X}(coeffs) FactoredPolynomial{T,X}(coeffs::AbstractVector{S}, order::Int) where {T,S,X} = FactoredPolynomial{T,X}(coeffs) diff --git a/src/standard-basis/standard-dense.jl b/src/standard-basis/standard-dense.jl deleted file mode 100644 index ebab47a4..00000000 --- a/src/standard-basis/standard-dense.jl +++ /dev/null @@ -1,44 +0,0 @@ -# Dense + StandardBasis - -""" - Polynomial{T,X}(coeffs::AbstractVector, [m::Integer = 0], [var = :x]) - -# Examples: -```jldoctest polynomial -julia> using Polynomials - -``` -""" -const 𝑃olynomial = MutableDensePolynomial{StandardBasis} -export 𝑃olynomial - -_typealias(::Type{P}) where {P<:𝑃olynomial} = "Polynomial" - -function ⊗(p::𝑃olynomial{T,X}, - q::𝑃olynomial{S,X}) where {T,S,X} - # simple convolution - # This is ⊗(P,p,q) from polynomial standard-basis - R = promote_type(T,S) - P = 𝑃olynomial{R,X} - - iszero(p) && return zero(P) - iszero(q) && return zero(P) - - b₁, b₂ = lastindex(p), lastindex(q) - b = b₁ + b₂ - - z = zero(first(p) * first(q)) - cs = _zeros(p, z, b + 1) - - # convolve and shift order - @inbounds for (i, pᵢ) ∈ enumerate(p.coeffs) - for (j, qⱼ) ∈ enumerate(q.coeffs) - ind = i + j - 1 - cs[ind] = muladd(pᵢ, qⱼ, cs[ind]) - end - end - if iszero(last(cs)) - cs = trim_trailing_zeros(cs) - end - P(Val(false), cs) -end diff --git a/test/ChebyshevT.jl b/test/ChebyshevT.jl index 67421d06..f3be822b 100644 --- a/test/ChebyshevT.jl +++ b/test/ChebyshevT.jl @@ -51,8 +51,8 @@ end as = ones(3:4) bs = parent(as) - @test_broken ChebyshevT(as) == ChebyshevT(bs) - @test_broken ChebyshevT{Float64}(as) == ChebyshevT{Float64}(bs) + @test ChebyshevT(as) == ChebyshevT(bs) + @test ChebyshevT{Float64}(as) == ChebyshevT{Float64}(bs) a = [1,1] b = OffsetVector(a, axes(a)) @@ -160,7 +160,7 @@ end c2 = ChebyshevT([0, 1, 2, 3]) d, r = divrem(c2, c1) - @test_broken d.coeffs ≈ [0, 2] + @test d.coeffs ≈ [0, 2] @test coeffs(d) ≈ [0, 2] @test r.coeffs ≈ [-2, -4] diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index 88a34f41..5f4b75e4 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -28,7 +28,8 @@ _isimmutable(::Type{P}) where {P <: Union{ImmutablePolynomial, FactoredPolynomia _isimmutable(P) = false -Ps = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, FactoredPolynomial) +Ps = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, FactoredPolynomial, + Polynomials.𝑃olynomial) @testset "Construction" begin @testset for coeff in Any[ From f844a17daf6b71fa77c4b7ba179f5df8ad7784cd Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 2 Aug 2023 17:31:47 -0400 Subject: [PATCH 27/31] WIP: run doctests, adjust Chebyshev printing --- .gitignore | 1 + docs/src/index.md | 1 + src/Polynomials.jl | 8 - .../mutable-sparse-polynomial.jl | 3 +- src/polynomials/ChebyshevT.jl | 291 -------- src/polynomials/ImmutablePolynomial.jl | 271 -------- src/polynomials/LaurentPolynomial.jl | 621 ------------------ src/polynomials/SparsePolynomial.jl | 278 -------- src/polynomials/chebyshev.jl | 48 +- src/polynomials/multroot.jl | 2 +- src/polynomials/pi_n_polynomial.jl | 63 -- 11 files changed, 36 insertions(+), 1551 deletions(-) delete mode 100644 src/polynomials/ChebyshevT.jl delete mode 100644 src/polynomials/ImmutablePolynomial.jl delete mode 100644 src/polynomials/LaurentPolynomial.jl delete mode 100644 src/polynomials/SparsePolynomial.jl delete mode 100644 src/polynomials/pi_n_polynomial.jl diff --git a/.gitignore b/.gitignore index 3a9dadfc..7ae77490 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ docs/site/ *.jl.mem Manifest.toml +archive/ \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index f6f2121e..d5dfa591 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -790,6 +790,7 @@ julia> for (λ, rs) ∈ r # reconstruct p/q from output of `residues` end end + julia> d ((x - 4.0) * (x - 1.0000000000000002)) // ((x - 5.0) * (x - 2.0)) ``` diff --git a/src/Polynomials.jl b/src/Polynomials.jl index f810b99c..d5944099 100644 --- a/src/Polynomials.jl +++ b/src/Polynomials.jl @@ -18,14 +18,6 @@ include("polynomials/standard-basis.jl") include("polynomials/Polynomial.jl") include("polynomials/factored_polynomial.jl") -#include("polynomials/ImmutablePolynomial.jl") -#include("polynomials/SparsePolynomial.jl") -#include("polynomials/LaurentPolynomial.jl") -#include("polynomials/pi_n_polynomial.jl") -#include("polynomials/ngcd.jl") -#include("polynomials/multroot.jl") -#include("polynomials/ChebyshevT.jl") - # polynomials with explicit basis include("abstract-polynomial.jl") include("polynomial-basetypes/mutable-dense-polynomial.jl") diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index 4e29d42a..4051d8ab 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -140,7 +140,8 @@ function chop_exact_zeros!(d::Dict) end trim_trailing_zeros!!(d::Dict) = chop_exact_zeros!(d) # Not properly named, but what is expected in other constructors -function chop!(p::MutableSparsePolynomial; atol=nothing, rtol=nothing) +chop!(p::MutableSparsePolynomial; kwargs...) = chop!(p.coeffs; kwargs...) +function chop!(p::Dict; atol=nothing, rtol=nothing) isempty(p.coeffs) && return p δ = something(rtol,0) ϵ = something(atol,0) diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/ChebyshevT.jl deleted file mode 100644 index 0fa28147..00000000 --- a/src/polynomials/ChebyshevT.jl +++ /dev/null @@ -1,291 +0,0 @@ -export ChebyshevT - -""" - ChebyshevT{T, X}(coeffs::AbstractVector) - -Chebyshev polynomial of the first kind. - -Construct a polynomial from its coefficients `coeffs`, lowest order first, optionally in -terms of the given variable `var`, which can be a character, symbol, or string. - -!!! note - `ChebyshevT` is not axis-aware, and it treats `coeffs` simply as a list of coefficients with the first - index always corresponding to the coefficient of `T_0(x)`. - -# Examples - -```jldoctest ChebyshevT -julia> using Polynomials - -julia> p = ChebyshevT([1, 0, 3, 4]) -ChebyshevT(1⋅T_0(x) + 3⋅T_2(x) + 4⋅T_3(x)) - -julia> ChebyshevT([1, 2, 3, 0], :s) -ChebyshevT(1⋅T_0(s) + 2⋅T_1(s) + 3⋅T_2(s)) - -julia> one(ChebyshevT) -ChebyshevT(1.0⋅T_0(x)) - -julia> p(0.5) --4.5 - -julia> Polynomials.evalpoly(5.0, p, false) # bypasses the domain check done in p(5.0) -2088.0 -``` - -The latter shows how to evaluate a `ChebyshevT` polynomial outside of its domain, which is `[-1,1]`. (For newer versions of `Julia`, `evalpoly` is an exported function from Base with methods extended in this package, so the module qualification is unnecessary. - -!!! note - The Chebyshev polynomials are also implemented in `ApproxFun`, `ClassicalOrthogonalPolynomials.jl`, `FastTransforms.jl`, and `SpecialPolynomials.jl`. - -""" -struct ChebyshevT{T, X} <: AbstractPolynomial{T, X} - coeffs::Vector{T} - function ChebyshevT{T, X}(coeffs::AbstractVector{S}) where {T, X, S} - - if Base.has_offset_axes(coeffs) - @warn "ignoring the axis offset of the coefficient vector" - end - - 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 - -@register ChebyshevT - -function Base.convert(P::Type{<:Polynomial}, ch::ChebyshevT) - - T = _eltype(P,ch) - X = indeterminate(P,ch) - Q = ⟒(P){T,X} - - if length(ch) < 3 - return Q(coeffs(ch)) - end - - c0 = Q(ch[end - 1]) - c1 = Q(ch[end]) - x = variable(Q) - @inbounds for i in degree(ch):-1:2 - tmp = c0 - c0 = Q(ch[i - 2]) - c1 - c1 = tmp + c1 * x * 2 - end - return c0 + c1 * x -end - -function Base.convert(C::Type{<:ChebyshevT}, p::Polynomial) - x = variable(C) - isconstant(p) || assert_same_variable(indeterminate(x),indeterminate(p)) - p(x) -end - -#= XXX -Base.promote_rule(::Type{P},::Type{Q}) where { - T, X, P <: LaurentPolynomial{T,X}, - S, Q <: ChebyshevT{S, X}} = - LaurentPolynomial{promote_type(T, S), X} -=# - -domain(::Type{<:ChebyshevT}) = Interval(-1, 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) - -Evaluate the Chebyshev polynomial at `x`. If `x` is outside of the domain of [-1, 1], an error will be thrown. The evaluation uses Clenshaw Recursion. - -# Examples -```jldoctest ChebyshevT -julia> using Polynomials - -julia> c = ChebyshevT([2.5, 1.5, 1.0]) -ChebyshevT(2.5⋅T_0(x) + 1.5⋅T_1(x) + 1.0⋅T_2(x)) - -julia> c(0) -1.5 - -julia> c.(-1:0.5:1) -5-element Vector{Float64}: - 2.0 - 1.25 - 1.5 - 2.75 - 5.0 -``` -""" -function evalpoly(x::S, ch::ChebyshevT{T}) where {T,S} - x ∉ domain(ch) && throw(ArgumentError("$x outside of domain")) - evalpoly(x, ch, false) -end - -function evalpoly(x::AbstractPolynomial, ch::ChebyshevT) - evalpoly(x, ch, false) -end - -# no checking, so can be called directly through any third argument -function evalpoly(x::S, ch::ChebyshevT{T}, checked) where {T,S} - R = promote_type(T, S) - length(ch) == 0 && return zero(R) - length(ch) == 1 && return R(ch[0]) - c0 = ch[end - 1] - c1 = ch[end] - @inbounds for i in lastindex(ch) - 2:-1:0 - c0, c1 = ch[i] - c1, c0 + c1 * 2x - end - return R(c0 + c1 * x) -end - - -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) - if n > 0 - A[:, 2] .= x - @inbounds for i in 3:n + 1 - A[:, i] .= A[:, i - 1] .* 2x .- A[:, i - 2] - end - end - return A -end - -function integrate(p::ChebyshevT{T,X}) where {T,X} - R = eltype(one(T) / 1) - Q = ChebyshevT{R,X} - if hasnan(p) - return Q([NaN]) - end - n = length(p) - if n == 1 - return Q([zero(R), p[0]]) - end - a2 = Vector{R}(undef, n + 1) - a2[1] = zero(R) - a2[2] = p[0] - a2[3] = p[1] / 4 - @inbounds for i in 2:n - 1 - a2[i + 2] = p[i] / (2 * (i + 1)) - a2[i] -= p[i] / (2 * (i - 1)) - end - - return Q(a2) -end - - -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], indeterminate(p)) - order > length(p) && return zero(ChebyshevT{R}) - - - q = convert(ChebyshevT{R}, copy(p)) - n = length(p) - der = Vector{R}(undef, n) - - for j in n:-1:3 - der[j] = 2j * q[j] - q[j - 2] += j * q[j] / (j - 2) - end - if n > 1 - der[2] = 4q[2] - end - der[1] = q[1] - - pp = ChebyshevT(der, indeterminate(p)) - return order > 1 ? derivative(pp, order - 1) : pp - -end - -function companion(p::ChebyshevT{T}) where T - d = length(p) - 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)) - - scl = vcat(1.0, fill(R(√0.5), d - 1)) - - diag = vcat(√0.5, fill(R(0.5), d - 2)) - comp = diagm(1 => diag, - -1 => diag) - ps = coeffs(p) - monics = ps ./ ps[end] - comp[:, end] .-= monics[1:d] .* scl ./ scl[end] ./ 2 - return R.(comp) -end - -# 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{R,X}(cs) -end - -function Base.:+(p::P, c::T) where {T <: Number,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)) - c = T[p1[i] + p2[i] for i = 0:n] - return ChebyshevT{T,X}(c) -end - - -function Base.:*(p1::ChebyshevT{T,X}, p2::ChebyshevT{T,X}) where {T,X} - z1 = _c_to_z(coeffs(p1)) - z2 = _c_to_z(coeffs(p2)) - prod = fastconv(z1, z2) - cs = _z_to_c(prod) - ret = ChebyshevT(cs,X) - return truncate!(ret) -end - -function Base.divrem(num::ChebyshevT{T,X}, den::ChebyshevT{S,Y}) where {T,X,S,Y} - assert_same_variable(num, den) - n = length(num) - 1 - m = length(den) - 1 - - R = typeof(one(T) / one(S)) - P = ChebyshevT{R,X} - - if n < m - return zero(P), convert(P, num) - elseif m == 0 - den[0] ≈ 0 && throw(DivideError()) - return num ./ den[end], zero(P) - end - - znum = _c_to_z(coeffs(num)) - zden = _c_to_z(coeffs(den)) - quo, rem = _z_division(znum, zden) - q_coeff = _z_to_c(quo) - r_coeff = _z_to_c(rem) - return P(q_coeff), P(r_coeff) -end - -function showterm(io::IO, ::Type{ChebyshevT{T,X}}, pj::T, var, j, first::Bool, mimetype) where {T,X} - iszero(pj) && return false - !first && print(io, " ") - if hasneg(T) - print(io, isneg(pj) ? "- " : (!first ? "+ " : "")) - print(io, "$(abs(pj))⋅T_$j($var)") - else - print(io, "+ ", "$(pj)⋅T_$j($var)") - end - return true -end diff --git a/src/polynomials/ImmutablePolynomial.jl b/src/polynomials/ImmutablePolynomial.jl deleted file mode 100644 index 778b71ee..00000000 --- a/src/polynomials/ImmutablePolynomial.jl +++ /dev/null @@ -1,271 +0,0 @@ -export ImmutablePolynomial - -""" - ImmutablePolynomial{T, X, N}(coeffs::AbstractVector{T}) - -Construct an immutable (static) polynomial from its coefficients -`a₀, a₁, …, aₙ`, -lowest order first, optionally in terms of the given variable `x` -where `x` can be a character, symbol, or string. - -If ``p = a_n x^n + \\ldots + a_2 x^2 + a_1 x + a_0``, we construct -this through `ImmutablePolynomial((a_0, a_1, ..., a_n))` (assuming -`a_n ≠ 0`). As well, a vector or number can be used for construction. - - -The usual arithmetic operators are overloaded to work with polynomials -as well as with combinations of polynomials and scalars. However, -operations involving two non-constant polynomials of different variables causes an -error. Unlike other polynomials, `setindex!` is not defined for `ImmutablePolynomials`. - -As the degree of the polynomial (`+1`) is a compile-time constant, -several performance improvements are possible. For example, immutable -polynomials can take advantage of faster polynomial evaluation -provided by `evalpoly` from Julia 1.4; similar methods are also used -for addition and multiplication. - -However, as the degree is included in the type, promotion between -immutable polynomials can not promote to a common type. As such, they -are precluded from use in rational functions. - -!!! note - `ImmutablePolynomial` is not axis-aware, and it treats `coeffs` simply as a list of coefficients with the first - index always corresponding to the constant term. - -# Examples - -```jldoctest -julia> using Polynomials - -julia> ImmutablePolynomial((1, 0, 3, 4)) -ImmutablePolynomial(1 + 3*x^2 + 4*x^3) - -julia> ImmutablePolynomial((1, 2, 3), :s) -ImmutablePolynomial(1 + 2*s + 3*s^2) - -julia> one(ImmutablePolynomial) -ImmutablePolynomial(1.0) -``` - -!!! note - This was modeled after [StaticUnivariatePolynomials](https://github.com/tkoolen/StaticUnivariatePolynomials.jl) by `@tkoolen`. - -""" -struct ImmutablePolynomial{T, X, N} <: StandardBasisPolynomial{T, X} - coeffs::NTuple{N, T} - function ImmutablePolynomial{T,X,N}(coeffs::NTuple{N,T}) where {T, X, N} - N == 0 && return new{T,X, 0}(coeffs) - iszero(coeffs[end]) && throw(ArgumentError("Leading term must be non-zero")) - new{T,X,N}(coeffs) - end -end - -@register ImmutablePolynomial - -## Various interfaces -## Abstract Vector coefficients -function ImmutablePolynomial{T, X, N}(coeffs::AbstractVector{T}) where {T,X, N} - cs = NTuple{N,T}(coeffs[i] for i ∈ firstindex(coeffs):N) - ImmutablePolynomial{T, X, N}(cs) - -end - -function ImmutablePolynomial{T,X, N}(coeffs::AbstractVector{S}) where {T,X, N, S} - cs = NTuple{N,T}(coeffs[i] for i ∈ firstindex(coeffs):N) - ImmutablePolynomial{T, X, N}(cs) - -end - -function ImmutablePolynomial{T,X}(coeffs::AbstractVector{S}) where {T,X,S} - R = promote_type(T,S) - - if Base.has_offset_axes(coeffs) - @warn "ignoring the axis offset of the coefficient vector" - end - N = findlast(!iszero, coeffs) - isnothing(N) && return ImmutablePolynomial{R,X,0}(()) - N′ = N + 1 - firstindex(coeffs) - ImmutablePolynomial{T, X, N′}([coeffs[i] for i ∈ firstindex(coeffs):N]) -end - -## -- Tuple arguments -function ImmutablePolynomial{T,X}(coeffs::Tuple) where {T,X} - N = findlast(!iszero, coeffs) - isnothing(N) && return zero(ImmutablePolynomial{T,X}) - ImmutablePolynomial{T,X,N}(NTuple{N,T}(coeffs[i] for i in 1:N)) -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...)) - T = eltype(cs) - ImmutablePolynomial{T, Symbol(var)}(cs) -end - -## -## ---- -## -# overrides from common.jl due to coeffs being non mutable, N in type parameters - -Base.copy(p::P) where {P <: ImmutablePolynomial} = P(coeffs(p)) -copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {T, X, S, Y, N, P <:ImmutablePolynomial{S,Y,N}} = - ⟒(P){T, Symbol(X),N}(map(T, p.coeffs)) -Base.similar(p::ImmutablePolynomial, args...) = similar(collect(coeffs(p)), args...) # ??? -# 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 - -Base.setindex!(p::ImmutablePolynomial, val, idx::Int) = throw(ArgumentError("ImmutablePolynomials are immutable")) - -for op in [:isequal, :(==)] - @eval function Base.$op(p1::ImmutablePolynomial{T,N}, p2::ImmutablePolynomial{S,M}) where {T,N,S,M} - check_same_variable(p1,p2) || return false - p1s, p2s = coeffs(p1), coeffs(p2) - (N == M && $op(p1s,p2s)) && return true - n1 = findlast(!iszero, p1s) # now trim out zeros - n2 = findlast(!iszero, p2s) - (isnothing(n1) && isnothing(n2)) && return true - (isnothing(n1) || isnothing(n2)) && return false - $op(p1s[1:n1],p2s[1:n2]) && return true - false - end -end - -# in common.jl these call chop! and truncate! -function Base.chop(p::ImmutablePolynomial{T,X}; - rtol::Real = Base.rtoldefault(real(T)), - 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}; - 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 - - -## -## -------------------- -## - -## 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 P′(R.(cs)) - elseif N < M - cs = ⊕(P, p2.coeffs, p1.coeffs) - return P′{M}(R.(cs)) - else - cs = ⊕(P, p1.coeffs, p2.coeffs) - return P′{N}(R.(cs)) - end - -end - -## multiplication -function scalar_mult(p::ImmutablePolynomial{T,X,N}, c::S) where {T, X,N, S <: Number} - iszero(N) && return zero(ImmutablePolynomial{promote_type(T,S),X}) - iszero(c) && ImmutablePolynomial([p[0] .* c], X) - return _polynomial(p, p.coeffs .* (c,)) -end - -function scalar_mult(c::S, p::ImmutablePolynomial{T,X,N}) where {T, X,N, S <: Number} - iszero(N) && return zero(ImmutablePolynomial{promote_type(T,S),X}) - iszero(c) && ImmutablePolynomial([c .* p[0]], X) - return _polynomial(p, (c,) .* p.coeffs) -end - -function _polynomial(p::ImmutablePolynomial{T,X,N}, cs) where {T, X, N} - R = eltype(cs) - P = ImmutablePolynomial{R,X} - iszero(cs[end]) ? P(cs) : P{N}(cs) -end - -function Base.:*(p1::ImmutablePolynomial{T,X,N}, p2::ImmutablePolynomial{S,X,M}) where {T,S,X,N,M} - (iszero(N) || iszero(M)) && return zero(ImmutablePolynomial{promote_type(T,S),X}) - - cs = ⊗(ImmutablePolynomial, p1.coeffs, p2.coeffs) #(p1.coeffs) ⊗ (p2.coeffs) - R = eltype(cs) - P = ImmutablePolynomial{R,X} - iszero(cs[end]) ? P(cs) : P{N+M-1}(cs) # more performant to specify when N is known - -end - -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,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) - -@generated function _norm(a::NTuple{N,T}) where {T, N} - - expr = :(abs2(a[1])) - for j = 2:N - expr = :($expr + abs2(a[$j])) - end - - return quote - $(Expr(:meta, :inline)) # 1.8 deprecation - #Base.@inline - @inbounds return sqrt($expr) - end - -end - -_norm_p0(x) = iszero(x) ? zero(x) : one(x) -@generated function _norm(a::NTuple{N,T}, p::Real) where {T, N} - expr = :(abs(a[1])^p) - for j = 2:N - expr = :($expr + abs(a[$j])^p) - end - - expr_p1 = :(abs(a[1])) - for j = 2:N - expr_p1 = :($expr_p1 + abs(a[$j])) - end - - return quote - $(Expr(:meta, :inline)) # 1.8 deprecation - #Base.@inline - if p == Inf - return mapreduce(abs, max, a) - elseif p == 1 - @inbounds return $expr_p1 - elseif p == 2 - return norm(a) - elseif p == 0 - return mapreduce(_norm_p0, +, a) - else - @inbounds return ($expr)^(inv(p)) - end - end - -end diff --git a/src/polynomials/LaurentPolynomial.jl b/src/polynomials/LaurentPolynomial.jl deleted file mode 100644 index 0918aae0..00000000 --- a/src/polynomials/LaurentPolynomial.jl +++ /dev/null @@ -1,621 +0,0 @@ -export LaurentPolynomial - - -""" - 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`. - -The `coeffs` specify `a_{m}, a_{m-1}, ..., a_{n}`. -The argument `m` represents the lowest exponent of the variable in the series, and is taken to be zero by default. - -Laurent polynomials and standard basis polynomials promote to Laurent polynomials. Laurent polynomials may be converted to a standard basis polynomial when `m >= 0` -. - -Integration will fail if there is a `x⁻¹` term in the polynomial. - -!!! note - `LaurentPolynomial` is axis-aware, unlike the other polynomial types in this package. - -# Examples: -```jldoctest laurent -julia> using Polynomials - -julia> P = LaurentPolynomial -LaurentPolynomial - -julia> p = P([1,1,1], -1) -LaurentPolynomial(x⁻¹ + 1 + x) - -julia> q = P([1,1,1]) -LaurentPolynomial(1 + x + x²) - -julia> pp = Polynomial([1,1,1]) -Polynomial(1 + x + x^2) - -julia> p + q -LaurentPolynomial(x⁻¹ + 2 + 2*x + x²) - -julia> p * q -LaurentPolynomial(x⁻¹ + 2 + 3*x + 2*x² + x³) - -julia> p * pp -LaurentPolynomial(x⁻¹ + 2 + 3*x + 2*x² + x³) - -julia> pp - q -LaurentPolynomial(0) - -julia> derivative(p) -LaurentPolynomial(-x⁻² + 1) - -julia> integrate(q) -LaurentPolynomial(1.0*x + 0.5*x² + 0.3333333333333333*x³) - -julia> integrate(p) # x⁻¹ term is an issue -ERROR: ArgumentError: Can't integrate Laurent polynomial with `x⁻¹` term - -julia> integrate(P([1,1,1], -5)) -LaurentPolynomial(-0.25*x⁻⁴ - 0.3333333333333333*x⁻³ - 0.5*x⁻²) - -julia> x⁻¹ = inv(variable(LaurentPolynomial)) # `inv` defined on monomials -LaurentPolynomial(1.0*x⁻¹) - -julia> p = Polynomial([1,2,3]) -Polynomial(1 + 2*x + 3*x^2) - -julia> x = variable() -Polynomial(x) - -julia> x^degree(p) * p(x⁻¹) # reverses coefficients -LaurentPolynomial(3.0 + 2.0*x + 1.0*x²) -``` -""" -struct LaurentPolynomial{T, X} <: LaurentBasisPolynomial{T, X} - coeffs::Vector{T} - m::Base.RefValue{Int} - n::Base.RefValue{Int} - function LaurentPolynomial{T,X}(coeffs::AbstractVector{S}, - m::Union{Int, Nothing}=nothing) where {T, X, S} - - fnz = findfirst(!iszero, coeffs) - isnothing(fnz) && 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 = convert(Vector{T}, coeffs[fnz:lnz]) - return new{T,X}(cs, Ref(fnz), Ref(lnz)) - else - - c = convert(Vector{T}, coeffs[fnz:lnz]) - - m′ = fnz - 1 + (isnothing(m) ? 0 : m) - n = m′ + (lnz-fnz) - - (n - m′ + 1 == length(c)) || throw(ArgumentError("Lengths do not match")) - new{T,X}(c, Ref(m′), Ref(n)) - end - end - # non copying version assumes trimmed coeffs - function LaurentPolynomial{T,X}(::Val{false}, coeffs::AbstractVector{T}, - m::Integer=0) where {T, X} - new{T,X}(coeffs, Ref(m), Ref(m + length(coeffs) - 1)) - end -end - -@register LaurentPolynomial - -## constructors -function LaurentPolynomial{T}(coeffs::AbstractVector{S}, m::Int, var::SymbolLike=Var(:x)) where { - T, S <: Number} - LaurentPolynomial{T,Symbol(var)}(T.(coeffs), m) -end - -function LaurentPolynomial{T}(coeffs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {T} - LaurentPolynomial{T, Symbol(var)}(coeffs, 0) -end - -function LaurentPolynomial(coeffs::AbstractVector{T}, m::Int, var::SymbolLike=Var(:x)) where {T} - LaurentPolynomial{T, Symbol(var)}(coeffs, m) -end - - - - -## -## conversion -## - -# LaurentPolynomial is a wider collection than other standard basis polynomials. -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, X, P <: LaurentPolynomial{T,X}, S, Q <: StandardBasisPolynomial{S,X}} = - LaurentPolynomial{promote_type(T, S),X} - -# need to add p.m[], so abstract.jl method isn't sufficient -# XXX unlike abstract.jl, this uses Y variable in conversion; no error -# Used in DSP.jl -function Base.convert(::Type{LaurentPolynomial{S,Y}}, p::LaurentPolynomial{T,X}) where {T,X,S,Y} - LaurentPolynomial{S,Y}(p.coeffs, p.m[]) -end - -# work around for non-applicable convert(::Type{<:P}, p::P{T,X}) in abstract.jl -struct OffsetCoeffs{V} - coeffs::V - m::Int -end - -_coeffs(p::LaurentPolynomial) = OffsetCoeffs(p.coeffs, p.m[]) -function LaurentPolynomial{T,X}(p::OffsetCoeffs) where {T, X} - LaurentPolynomial{T,X}(p.coeffs, p.m) -end - - -function Base.convert(::Type{P}, q::AbstractPolynomial{T,X}) where {T,X,P <:LaurentPolynomial} - convert(P, convert(Polynomial, q)) -end - -function Base.convert(::Type{P}, q::StandardBasisPolynomial{T′,X′}) where {P <:LaurentPolynomial,T′,X′} - T = _eltype(P, q) - X = indeterminate(P, q) - ⟒(P){T,X}([q[i] for i in eachindex(q)], firstindex(q)) -end - -# resolve ambiguity -function Base.convert(::Type{P}, q::Polynomial{T, X}) where {T, X, P<:LaurentPolynomial} - p = convert(Polynomial, q) - LaurentPolynomial{eltype(p), indeterminate(p)}(p.coeffs, 0) -end - - -Base.convert(::Type{LaurentPolynomial{T, X}}, p::LaurentPolynomial{T, X}) where {T, X} = p - -# Ambiguity, issue #435 -function Base.convert(𝑷::Type{P}, p::ArnoldiFit{T, M, X}) where {P<:LaurentPolynomial, T, M, X} - convert(𝑷, convert(Polynomial, p)) -end - -## -## generic functions -## - -function Base.inv(p::LaurentPolynomial{T, X}) where {T, X} - m,n = (extrema∘degreerange)(p) - m != n && throw(ArgumentError("Only monomials can be inverted")) - cs = [1/p for p in p.coeffs] - LaurentPolynomial{eltype(cs), X}(cs, -m) -end - -Base.numerator(p::LaurentPolynomial) = numerator(convert(RationalFunction, p)) -Base.denominator(p::LaurentPolynomial) = denominator(convert(RationalFunction, p)) - -## -## changes to common.jl mostly as the range in the type is different -## -Base.:(==)(p1::LaurentPolynomial, p2::LaurentPolynomial) = - 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,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} - 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} - - m,n = (extrema ∘ degreerange)(p) - if idx > n - append!(p.coeffs, zeros(T, idx-n)) - n = idx - p.n[] = n - elseif idx < m - prepend!(p.coeffs, zeros(T, m-idx)) - m = idx - p.m[] = m - end - - i = idx - m + 1 - p.coeffs[i] = value - - return p - -end - -minimumexponent(::Type{<:LaurentPolynomial}) = typemin(Int) -minimumexponent(p::LaurentPolynomial) = p.m[] -Base.firstindex(p::LaurentPolynomial) = minimumexponent(p) -degree(p::LaurentPolynomial) = p.n[] - - -_convert(p::P, as) where {T,X,P <: LaurentPolynomial{T,X}} = ⟒(P)(as, firstindex(p), Var(X)) - -## chop! -# trim from *both* ends -function chop!(p::LaurentPolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} - - m0,n0 = m,n = (extrema ∘ degreerange)(p) - for k in n:-1:m - if isapprox(p[k], zero(T); rtol = rtol, atol = atol) - n -= 1 - else - break - end - end - for k in m:n-1 - if isapprox(p[k], zero(T); rtol = rtol, atol = atol) - m += 1 - else - break - end - end - - cs = copy(coeffs(p)) - rng = m-m0+1:n-m0+1 - resize!(p.coeffs, length(rng)) - p.coeffs[:] = cs[rng] - isempty(p.coeffs) && push!(p.coeffs,zero(T)) - p.m[], p.n[] = m, max(m,n) - - 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 - pj = printsign(io, pj, first, mimetype) - if !(hasone(T) && pj == one(T) && !(showone(T) || j == 0)) - printcoefficient(io, pj, j, mimetype) - end - printproductsign(io, pj, j, mimetype) - iszero(j) && return true - print(io, var) - j ==1 && return true - unicode_exponent(io, j) - return true -end - - -## -## ---- Conjugation has different definitions -## - -""" - conj(p) - -This satisfies `conj(p(x)) = conj(p)(conj(x)) = p̄(conj(x))` or `p̄(x) = (conj ∘ p ∘ conj)(x)` - -Examples - -```jldoctest laurent -julia> using Polynomials; - -julia> z = variable(LaurentPolynomial, :z) -LaurentPolynomial(1.0*z) - -julia> p = LaurentPolynomial([im, 1+im, 2 + im], -1, :z) -LaurentPolynomial(im*z⁻¹ + (1 + im) + (2 + im)z) - -julia> conj(p)(conj(z)) ≈ conj(p(z)) -true - -julia> conj(p)(z) ≈ (conj ∘ p ∘ conj)(z) -true -``` -""" -LinearAlgebra.conj(p::P) where {P <: LaurentPolynomial} = map(conj, p) - - -""" - paraconj(p) - -[cf.](https://ccrma.stanford.edu/~jos/filters/Paraunitary_FiltersC_3.html) - -Call `p̂ = paraconj(p)` and `p̄` = conj(p)`, then this satisfies -`conj(p(z)) = p̂(1/conj(z))` or `p̂(z) = p̄(1/z) = (conj ∘ p ∘ conj ∘ inf)(z)`. - -Examples: - -```jldoctest laurent -julia> using Polynomials; - -julia> z = variable(LaurentPolynomial, :z) -LaurentPolynomial(z) - -julia> h = LaurentPolynomial([1,1], -1, :z) -LaurentPolynomial(z⁻¹ + 1) - -julia> Polynomials.paraconj(h)(z) ≈ 1 + z ≈ LaurentPolynomial([1,1], 0, :z) -true - -julia> h = LaurentPolynomial([3,2im,1], -2, :z) -LaurentPolynomial(3*z⁻² + 2im*z⁻¹ + 1) - -julia> Polynomials.paraconj(h)(z) ≈ 1 - 2im*z + 3z^2 ≈ LaurentPolynomial([1, -2im, 3], 0, :z) -true - -julia> Polynomials.paraconj(h)(z) ≈ (conj ∘ h ∘ conj ∘ inv)(z) -true -""" -function paraconj(p::LaurentPolynomial) - cs = p.coeffs - ds = adjoint.(cs) - n = degree(p) - LaurentPolynomial(reverse(ds), -n, indeterminate(p)) -end - -""" - cconj(p) - -Conjugation of a polynomial with respect to the imaginary axis. - -The `cconj` of a polynomial, `p̃`, conjugates the coefficients and applies `s -> -s`. That is `cconj(p)(s) = conj(p)(-s)`. - -This satisfies for *imaginary* `s`: `conj(p(s)) = p̃(s) = (conj ∘ p)(s) = cconj(p)(s) ` - -[ref](https://github.com/hurak/PolynomialEquations.jl#symmetrix-conjugate-equation-continuous-time-case) - -Examples: -```jldoctest laurent -julia> using Polynomials; - -julia> s = 2im -0 + 2im - -julia> p = LaurentPolynomial([im,-1, -im, 1], 1, :s) -LaurentPolynomial(im*s - s² - im*s³ + s⁴) - -julia> Polynomials.cconj(p)(s) ≈ conj(p(s)) -true - -julia> a = LaurentPolynomial([-0.12, -0.29, 1],:s) -LaurentPolynomial(-0.12 - 0.29*s + 1.0*s²) - -julia> b = LaurentPolynomial([1.86, -0.34, -1.14, -0.21, 1.19, -1.12],:s) -LaurentPolynomial(1.86 - 0.34*s - 1.14*s² - 0.21*s³ + 1.19*s⁴ - 1.12*s⁵) - -julia> x = LaurentPolynomial([-15.5, 50.0096551724139, 1.19], :s) -LaurentPolynomial(-15.5 + 50.0096551724139*s + 1.19*s²) - -julia> Polynomials.cconj(a) * x + a * Polynomials.cconj(x) ≈ b + Polynomials.cconj(b) -true -``` - -""" -function cconj(p::LaurentPolynomial) - ps = conj.(coeffs(p)) - m,n = (extrema ∘ degreerange)(p) - for i in m:n - if isodd(i) - ps[i+1-m] *= -1 - end - end - LaurentPolynomial(ps, m, indeterminate(p)) -end - - - -## -## ---- -## - - -# evaluation uses `evalpoly` -function evalpoly(x::S, p::LaurentPolynomial{T}) where {T,S} - xᵐ = firstindex(p) < 0 ? inv(x)^(abs(firstindex(p))) : (x/1)^firstindex(p) # make type stable - return EvalPoly.evalpoly(x, p.coeffs) * xᵐ -end - - - -# 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) - q = LaurentPolynomial{R,X}(p.coeffs, firstindex(p)) - q[0] += c - q -end - -## -## Poly +, - and * -## uses some ideas from https://github.com/jmichel7/LaurentPolynomials.jl/blob/main/src/LaurentPolynomials.jl for speedups -Base.:+(p1::LaurentPolynomial, p2::LaurentPolynomial) = add_sub(+, p1, p2) -Base.:-(p1::LaurentPolynomial, p2::LaurentPolynomial) = add_sub(-, p1, p2) - -function add_sub(op, p1::P, p2::Q) where {T, X, P <: LaurentPolynomial{T,X}, - S, Y, Q <: LaurentPolynomial{S,Y}} - - isconstant(p1) && return op(constantterm(p1), p2) - isconstant(p2) && return op(p1, constantterm(p2)) - assert_same_variable(X, Y) - - m1,n1 = (extrema ∘ degreerange)(p1) - m2,n2 = (extrema ∘ degreerange)(p2) - m, n = min(m1,m2), max(n1, n2) - - R = typeof(p1.coeffs[1] + p2.coeffs[1]) # non-empty - as = zeros(R, length(m:n)) - - d = m1 - m2 - d1, d2 = m1 > m2 ? (d,0) : (0, -d) - - for (i, pᵢ) ∈ pairs(p1.coeffs) - @inbounds as[d1 + i] = pᵢ - end - for (i, pᵢ) ∈ pairs(p2.coeffs) - @inbounds as[d2 + i] = op(as[d2+i], pᵢ) - end - - m = _laurent_chop!(as, m) - isempty(as) && return zero(LaurentPolynomial{R,X}) - q = LaurentPolynomial{R,X}(Val(false), as, m) - return q - -end - -function Base.:*(p1::P, p2::Q) where {T,X,P<:LaurentPolynomial{T,X}, - S,Y,Q<:LaurentPolynomial{S,Y}} - - isconstant(p1) && return constantterm(p1) * p2 - isconstant(p2) && return p1 * constantterm(p2) - assert_same_variable(X, Y) - - m1,n1 = (extrema ∘ degreerange)(p1) - m2,n2 = (extrema ∘ degreerange)(p2) - m,n = m1 + m2, n1+n2 - - R = promote_type(T,S) - as = zeros(R, length(m:n)) - - for (i, p₁ᵢ) ∈ pairs(p1.coeffs) - for (j, p₂ⱼ) ∈ pairs(p2.coeffs) - @inbounds as[i+j-1] += p₁ᵢ * p₂ⱼ - end - end - - m = _laurent_chop!(as, m) - - isempty(as) && return zero(LaurentPolynomial{R,X}) - p = LaurentPolynomial{R,X}(Val(false), as, m) - - return p -end - -function _laurent_chop!(as, m) - while !isempty(as) - if iszero(first(as)) - m += 1 - popfirst!(as) - else - break - end - end - while !isempty(as) - if iszero(last(as)) - pop!(as) - else - break - end - end - m -end - -function scalar_mult(p::LaurentPolynomial{T,X}, c::Number) where {T,X} - LaurentPolynomial(p.coeffs .* c, p.m[], Var(X)) -end -function scalar_mult(c::Number, p::LaurentPolynomial{T,X}) where {T,X} - LaurentPolynomial(c .* p.coeffs, p.m[], Var(X)) -end - - -function integrate(p::P) where {T, X, P <: LaurentBasisPolynomial{T, X}} - - R = typeof(constantterm(p) / 1) - Q = ⟒(P){R,X} - - hasnan(p) && return Q([NaN]) - iszero(p) && return zero(Q) - - ∫p = zero(Q) - for (k, pₖ) ∈ pairs(p) - iszero(pₖ) && continue - k == -1 && throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term")) - ∫p[k+1] = pₖ/(k+1) - end - ∫p -end - -## -## roots -## -""" - roots(p) - -Compute the roots of the Laurent polynomial `p`. - -The roots of a function (Laurent polynomial in this case) `a(z)` are the values of `z` for which the function vanishes. A Laurent polynomial ``a(z) = a_m z^m + a_{m+1} z^{m+1} + ... + a_{-1} z^{-1} + a_0 + a_1 z + ... + a_{n-1} z^{n-1} + a_n z^n`` can equivalently be viewed as a rational function with a multiple singularity (pole) at the origin. The roots are then the roots of the numerator polynomial. For example, ``a(z) = 1/z + 2 + z`` can be written as ``a(z) = (1+2z+z^2) / z`` and the roots of `a` are the roots of ``1+2z+z^2``. - -# Example - -```julia -julia> using Polynomials; - -julia> p = LaurentPolynomial([24,10,-15,0,1],-2,:z) -LaurentPolynomial(24*z⁻² + 10*z⁻¹ - 15 + z²) - -julia> roots(p) -4-element Vector{Float64}: - -3.999999999999999 - -0.9999999999999994 - 1.9999999999999998 - 2.9999999999999982 -``` -""" -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 - # the case when the lower degree is strictly positive - # (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{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, X, P<:LaurentPolynomial{T,X}} - - order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) - order == 0 && return p - - hasnan(p) && return ⟒(P)(T[NaN], 0, X) - - m,n = (extrema ∘ degreerange)(p) - m = m - order - n = n - order - as = zeros(T, length(m:n)) - - for (k, pₖ) in pairs(p) - iszero(pₖ) && continue - idx = 1 + k - order - m - if 0 ≤ k ≤ order - 1 - as[idx] = zero(T) - else - as[idx] = reduce(*, (k - order + 1):k, init = pₖ) - end - end - - chop!(LaurentPolynomial{T,X}(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) - if mp < 0 || mq < 0 - throw(ArgumentError("GCD is not defined when there are `x⁻ⁿ` terms")) - end - - degree(p) == 0 && return iszero(p) ? q : one(q) - degree(q) == 0 && return iszero(q) ? p : one(p) - assert_same_variable(p,q) - - pp, qq = convert(Polynomial, p), convert(Polynomial, q) - u = gcd(pp, qq, args..., kwargs...) - return LaurentPolynomial(coeffs(u), X) -end diff --git a/src/polynomials/SparsePolynomial.jl b/src/polynomials/SparsePolynomial.jl deleted file mode 100644 index 0fd204c7..00000000 --- a/src/polynomials/SparsePolynomial.jl +++ /dev/null @@ -1,278 +0,0 @@ -export SparsePolynomial - -""" - 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 -advantageous. - -# Examples: - -```jldoctest -julia> using Polynomials - -julia> P = SparsePolynomial -SparsePolynomial - -julia> p, q = P([1,2,3]), P([4,3,2,1]) -(SparsePolynomial(1 + 2*x + 3*x^2), SparsePolynomial(4 + 3*x + 2*x^2 + x^3)) - -julia> p + q -SparsePolynomial(5 + 5*x + 5*x^2 + x^3) - -julia> p * q -SparsePolynomial(4 + 11*x + 20*x^2 + 14*x^3 + 8*x^4 + 3*x^5) - -julia> p + 1 -SparsePolynomial(2 + 2*x + 3*x^2) - -julia> q * 2 -SparsePolynomial(8 + 6*x + 4*x^2 + 2*x^3) - -julia> p = Polynomials.basis(P, 10^9) - Polynomials.basis(P,0) # also P(Dict(0=>-1, 10^9=>1)) -SparsePolynomial(-1.0 + 1.0*x^1000000000) - -julia> p(1) -0.0 -``` - -""" -struct SparsePolynomial{T, X} <: LaurentBasisPolynomial{T, X} - coeffs::Dict{Int, T} - function SparsePolynomial{T, X}(coeffs::AbstractDict{Int, S}) where {T, X, S} - c = Dict{Int, T}(coeffs) - for (k,v) in coeffs - iszero(v) && pop!(c, k) - end - new{T, X}(c) - end - function SparsePolynomial{T,X}(checked::Val{false}, coeffs::AbstractDict{Int, T}) where {T, X} - new{T,X}(coeffs) - end -end - -@register SparsePolynomial - -function SparsePolynomial{T}(coeffs::AbstractDict{Int, S}, var::SymbolLike=Var(:x)) where {T, S} - SparsePolynomial{T, Symbol(var)}(convert(Dict{Int,T}, coeffs)) -end - -function SparsePolynomial(coeffs::AbstractDict{Int, T}, var::SymbolLike=Var(:x)) where {T} - SparsePolynomial{T, Symbol(var)}(coeffs) -end - -function SparsePolynomial{T,X}(coeffs::AbstractVector{S}) where {T, X, S} - - if Base.has_offset_axes(coeffs) - @warn "ignoring the axis offset of the coefficient vector" - end - - offset = firstindex(coeffs) - p = Dict{Int,T}(k - offset => v for (k,v) ∈ pairs(coeffs)) - return SparsePolynomial{T,X}(p) -end - -minimumexponent(::Type{<:SparsePolynomial}) = typemin(Int) -minimumexponent(p::SparsePolynomial) = isempty(p.coeffs) ? 0 : min(0, minimum(keys(p.coeffs))) -Base.firstindex(p::SparsePolynomial) = minimumexponent(p) - -## changes to common -degree(p::SparsePolynomial) = isempty(p.coeffs) ? -1 : maximum(keys(p.coeffs)) -function isconstant(p::SparsePolynomial) - n = length(keys(p.coeffs)) - (n > 1 || (n==1 && iszero(p[0]))) && return false - return true -end - -Base.convert(::Type{T}, p::StandardBasisPolynomial) where {T<:SparsePolynomial} = T(Dict(pairs(p))) - -function basis(P::Type{<:SparsePolynomial}, n::Int) - T,X = eltype(P), indeterminate(P) - SparsePolynomial{T,X}(Dict(n=>one(T))) -end - -# return coeffs as a vector -# use p.coeffs to get Dictionary -function coeffs(p::SparsePolynomial{T}) where {T} - - n = degree(p) - cs = zeros(T, length(p)) - keymin = firstindex(p) - for k in sort(collect(keys(p.coeffs))) - v = p.coeffs[k] - cs[k - keymin + 1] = v - end - cs - -end - -# get/set index -function Base.getindex(p::SparsePolynomial{T}, idx::Int) where {T} - get(p.coeffs, idx, zero(T)) -end - -function Base.setindex!(p::SparsePolynomial, value::Number, idx::Int) - if iszero(value) - haskey(p.coeffs, idx) && pop!(p.coeffs, idx) - else - p.coeffs[idx] = value - end - return p -end - -# 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...) - isnothing(y) && 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...) - isnothing(y) && return nothing - return (y[1][2], y[2]) -end - -Base.length(S::SparsePolynomial) = isempty(S.coeffs) ? 0 : begin - minkey, maxkey = extrema(keys(S.coeffs)) - maxkey - min(0, minkey) + 1 -end - -## -## ---- -## - -function evalpoly(x::S, p::SparsePolynomial{T}) where {T,S} - - tot = zero(x*p[0]) - for (k,v) in p.coeffs - tot = EvalPoly._muladd(x^k, v, tot) - end - - return tot - -end - -# map: over values -- not keys -function Base.map(fn, p::P, args...) where {P <: SparsePolynomial} - ks, vs = keys(p.coeffs), values(p.coeffs) - vs′ = map(fn, vs, args...) - _convert(p, Dict(Pair.(ks, vs′))) -end - - -## Addition -function Base.:+(p::SparsePolynomial{T,X}, c::S) where {T, X, S <: Number} - - c₀ = p[0] + c - R = eltype(c₀) - - D = convert(Dict{Int, R}, copy(p.coeffs)) - !iszero(c₀) && (@inbounds D[0] = c₀) - - P = SparsePolynomial{R,X} - length(keys(D)) > 0 ? P(Val(false), D) : zero(P) -end - -# much faster than default -function Base.:+(p1::P1, p2::P2) where {T, X, P1<:SparsePolynomial{T,X}, - S, P2<:SparsePolynomial{S,X}} - - R = promote_type(T,S) - D = convert(Dict{Int,R}, copy(p1.coeffs)) - for (i, pᵢ) ∈ pairs(p2.coeffs) - qᵢ = get(D, i, zero(R)) - pqᵢ = pᵢ + qᵢ - if iszero(pqᵢ) - pop!(D,i) # will be zero - else - D[i] = pᵢ + qᵢ - end - end - - P = SparsePolynomial{R,X} - isempty(keys(D)) ? zero(P) : P(Val(false), D) - -end - -Base.:-(a::SparsePolynomial) = typeof(a)(Dict(k=>-v for (k,v) in a.coeffs)) - -## Multiplication -function scalar_mult(p::P, c::S) where {T, X, P <: SparsePolynomial{T,X}, S<:Number} - - R = promote_type(T,S) - iszero(c) && return(zero(SparsePolynomial{R,X})) - - d = convert(Dict{Int, R}, copy(p.coeffs)) - for (k, pₖ) ∈ pairs(d) - @inbounds d[k] = d[k] .* c - end - return SparsePolynomial{R,X}(Val(false), d) - - - R1 = promote_type(T,S) - R = typeof(zero(c)*zero(T)) - Q = ⟒(P){R,X} - q = zero(Q) - for (k,pₖ) ∈ pairs(p) - q[k] = pₖ * c - end - - return q -end - -function scalar_mult(c::S, p::P) where {T, X, P <: SparsePolynomial{T,X}, S<:Number} - - R = promote_type(T,S) - iszero(c) && return(zero(SparsePolynomial{R,X})) - - d = convert(Dict{Int, R}, copy(p.coeffs)) - for (k, pₖ) ∈ pairs(d) - @inbounds d[k] = c .* d[k] - end - return SparsePolynomial{R,X}(Val(false), d) - - vs = (c,) .* values(p) - d = Dict(k=>v for (k,v) ∈ zip(keys(p), vs)) - return SparsePolynomial{eltype(vs), X}(d) -end - - - -function derivative(p::SparsePolynomial{T,X}, order::Integer = 1) where {T,X} - - order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) - order == 0 && return p - - R = eltype(p[0]*1) - P = SparsePolynomial - hasnan(p) && return P{R,X}(Dict(0 => R(NaN))) - - n = degree(p) - - dpn = zero(P{R,X}) - @inbounds for (k,v) in pairs(p) - dpn[k-order] = reduce(*, (k - order + 1):k, init = v) - end - - return dpn - -end - -function integrate(p:: SparsePolynomial{T,X}) where {T,X} - - R = Base.promote_op(/, T, Int) - P = SparsePolynomial{R,X} - hasnan(p) && return ⟒(P)(NaN) - iszero(p) && return zero(p)/1 - - d = Dict{Int, R}() - for (i, pᵢ) ∈ pairs(p.coeffs) - i == -1 && throw(ArgumentError("Can't integrate Laurent polynomial with `x⁻¹` term")) - cᵢ₊₁ = pᵢ/(i+1) - !iszero(cᵢ₊₁) && (d[i+1] = cᵢ₊₁) - end - return P(d) -end diff --git a/src/polynomials/chebyshev.jl b/src/polynomials/chebyshev.jl index bf764093..38cb7083 100644 --- a/src/polynomials/chebyshev.jl +++ b/src/polynomials/chebyshev.jl @@ -4,8 +4,22 @@ struct ChebyshevTBasis <: AbstractBasis end # This is the same as ChebyshevT const ChebyshevT = MutableDensePolynomial{ChebyshevTBasis} export ChebyshevT - -basis_symbol(::Type{<:AbstractUnivariatePolynomial{ChebyshevTBasis,T,X}}) where {T,X} = "T" # "T($X)" is better. +_typealias(::Type{P}) where {P<:ChebyshevT} = "ChebyshevT" + +basis_symbol(::Type{<:AbstractUnivariatePolynomial{ChebyshevTBasis,T,X}}) where {T,X} = "T" + +# match old style +function showterm(io::IO, ::Type{ChebyshevT{T,X}}, pj::T, var, j, first::Bool, mimetype) where {T,X} + iszero(pj) && return false + !first && print(io, " ") + if hasneg(T) + print(io, isneg(pj) ? "- " : (!first ? "+ " : "")) + print(io, "$(abs(pj))⋅T_$j($var)") + else + print(io, "+ ", "$(pj)⋅T_$j($var)") + end + return true +end function Base.convert(P::Type{<:Polynomial}, ch::MutableDensePolynomial{ChebyshevTBasis}) @@ -29,14 +43,14 @@ function Base.convert(P::Type{<:Polynomial}, ch::MutableDensePolynomial{Chebyshe return c0 + c1 * x end -function Base.convert(C::Type{<:MutableDensePolynomial{ChebyshevTBasis}}, p::Polynomial) +function Base.convert(C::Type{<:ChebyshevT}, p::Polynomial) x = variable(C) isconstant(p) || assert_same_variable(indeterminate(x),indeterminate(p)) p(x) end # lowest degree is always 0 -function coeffs(p::MutableDensePolynomial{ChebyshevTBasis}) +function coeffs(p::ChebyshevT) a = firstindex(p) iszero(a) && return p.coeffs a > 0 && return [p[i] for i ∈ 0:degree(p)] @@ -45,21 +59,21 @@ end -minimumexponent(::Type{<:MutableDensePolynomial{ChebyshevTBasis}}) = 0 -domain(::Type{<:MutableDensePolynomial{ChebyshevTBasis}}) = Interval(-1, 1) +minimumexponent(::Type{<:ChebyshevT}) = 0 +domain(::Type{<:ChebyshevT}) = Interval(-1, 1) -constantterm(p::MutableDensePolynomial{ChebyshevTBasis}) = p(0) -function Base.one(::Type{P}) where {P<:MutableDensePolynomial{ChebyshevTBasis}} +constantterm(p::ChebyshevT) = p(0) +function Base.one(::Type{P}) where {P<:ChebyshevT} T,X = eltype(P), indeterminate(P) ⟒(P){T,X}(ones(T,1)) end -function variable(::Type{P}) where {P<:MutableDensePolynomial{ChebyshevTBasis}} +function variable(::Type{P}) where {P<:ChebyshevT} T,X = eltype(P), indeterminate(P) ⟒(P){T,X}([zero(T), one(T)]) end """ - (::MutableDensePolynomial{ChebyshevTBasis})(x) + (::ChebyshevT)(x) Evaluate the Chebyshev polynomial at `x`. If `x` is outside of the domain of [-1, 1], an error will be thrown. The evaluation uses Clenshaw Recursion. @@ -82,12 +96,12 @@ julia> c.(-1:0.5:1) 5.0 ``` """ -function evalpoly(x::S, ch::MutableDensePolynomial{ChebyshevTBasis}) where {S} +function evalpoly(x::S, ch::ChebyshevT) where {S} x ∉ domain(ch) && throw(ArgumentError("$x outside of domain")) evalpoly(x, ch, false) end -function evalpoly(x::AbstractPolynomial, ch::MutableDensePolynomial{ChebyshevTBasis}) +function evalpoly(x::AbstractPolynomial, ch::ChebyshevT) evalpoly(x, ch, false) end @@ -122,7 +136,7 @@ function ⊗(p1::MutableDensePolynomial{B,T,X}, p2::MutableDensePolynomial{B,T,X z2 = _c_to_z(coeffs(p2)) prod = fastconv(z1, z2) cs = _z_to_c(prod) - ret = MutableDensePolynomial{ChebyshevTBasis}(cs,X) + ret = ChebyshevT(cs,X) return ret end @@ -172,7 +186,7 @@ function integrate(p::P) where {B<:ChebyshevTBasis,T,X,P<:MutableDensePolynomial return Q(a2) end -function vander(P::Type{<:MutableDensePolynomial{ChebyshevTBasis}}, x::AbstractVector{T}, n::Integer) where {T <: Number} +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) if n > 0 @@ -201,14 +215,14 @@ function companion(p::MutableDensePolynomial{ChebyshevTBasis,T}) where T return R.(comp) end -function Base.divrem(num::MutableDensePolynomial{ChebyshevTBasis}{T,X}, - den::MutableDensePolynomial{ChebyshevTBasis}{S,Y}) where {T,X,S,Y} +function Base.divrem(num::ChebyshevT{T,X}, + den::ChebyshevT{S,Y}) where {T,X,S,Y} assert_same_variable(num, den) n = degree(num) m = degree(den) R = typeof(one(T) / one(S)) - P = MutableDensePolynomial{ChebyshevTBasis}{R,X} + P = ChebyshevT{R,X} if n < m return zero(P), convert(P, num) diff --git a/src/polynomials/multroot.jl b/src/polynomials/multroot.jl index 50dd27ca..98410f3a 100644 --- a/src/polynomials/multroot.jl +++ b/src/polynomials/multroot.jl @@ -101,7 +101,6 @@ is misidentified. function multroot(p::StandardBasisType{T}; verbose=false, kwargs...) where {T} - p = chop(p) # degenerate case, constant degree(p) == 0 && return (values=T[], multiplicities=Int[], κ=NaN, ϵ=NaN) @@ -148,6 +147,7 @@ function pejorative_manifold( S = float(T) u = PnPolynomial{S,X}(S.(coeffs(p))) + chop!(u) nu₂ = norm(u, 2) θ2, ρ2 = θ * nu₂, ρ * nu₂ u, v, w, ρⱼ, κ = Polynomials.ngcd( diff --git a/src/polynomials/pi_n_polynomial.jl b/src/polynomials/pi_n_polynomial.jl deleted file mode 100644 index 2f2a526a..00000000 --- a/src/polynomials/pi_n_polynomial.jl +++ /dev/null @@ -1,63 +0,0 @@ -""" - PnPolynomial{T,X}(coeffs::Vector{T}) - -Construct a polynomial in `P_n` (or `Πₙ`), the collection of polynomials in the -standard basis of degree `n` *or less*, using a vector of length -`N+1`. - -* Unlike other polynomial types, this type allows trailing zeros in the coefficient vector. Call `chop!` to trim trailing zeros if desired. -* Unlike other polynomial types, this does not copy the coefficients on construction -* Unlike other polynomial types, this type broadcasts like a vector for in-place vector operations (scalar multiplication, polynomial addition/subtraction of the same size) -* The method inplace `mul!(pq, p, q)` is defined to use precallocated storage for the product of `p` and `q` - -This type is useful for reducing copies and allocations in some algorithms. - -""" -struct PnPolynomial{T,X} <: StandardBasisPolynomial{T, X} - coeffs::Vector{T} - function PnPolynomial{T, X}(coeffs::AbstractVector{T}) where {T, X} - N = length(coeffs) - 1 - new{T,X}(coeffs) # NO CHECK on trailing zeros - end -end - -PnPolynomial{T, X}(coeffs::Tuple) where {T, X} = - PnPolynomial{T,X}(T[pᵢ for pᵢ ∈ coeffs]) - -@register PnPolynomial - -# change broadcast semantics -Base.broadcastable(p::PnPolynomial) = p.coeffs; -Base.ndims(::Type{<:PnPolynomial}) = 1 -Base.copyto!(p::PnPolynomial{T, X}, x::S) where -{T, X, - S<:Union{AbstractVector, Base.AbstractBroadcasted, Tuple} # to avoid an invalidation. Might need to be more general? - } = copyto!(p.coeffs, x) - -function degree(p::PnPolynomial) - i = findlast(!iszero, coeffs(p)) - isnothing(i) && return -1 - i - 1 -end - -# pre-allocated multiplication -function LinearAlgebra.lmul!(c::Number, p::PnPolynomial{T,X}) where {T,X} - p.coeffs[:] = (c,) .* p.coeffs - p -end -function LinearAlgebra.rmul!(p::PnPolynomial{T,X}, c::Number) where {T,X} - p.coeffs[:] = p.coeffs .* (c,) - p -end - -function LinearAlgebra.mul!(pq, p::PnPolynomial{T,X}, q) where {T,X} - m,n = length(p)-1, length(q)-1 - pq.coeffs .= zero(T) - for i ∈ 0:m - for j ∈ 0:n - k = i + j - @inbounds pq.coeffs[1+k] = muladd(p.coeffs[1+i], q.coeffs[1+j], pq.coeffs[1+k]) - end - end - nothing -end From 0c13cdadd6e2d1a4b1a9ae0bdfc0bed0b6e63caf Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 2 Aug 2023 17:59:05 -0400 Subject: [PATCH 28/31] WIP: convert --- src/#common.jl# | 1232 ++++++++++++++++++++++++++ src/.#common.jl | 1 + src/polynomials/multroot.jl | 3 +- src/standard-basis/standard-basis.jl | 2 +- 4 files changed, 1235 insertions(+), 3 deletions(-) create mode 100644 src/#common.jl# create mode 120000 src/.#common.jl diff --git a/src/#common.jl# b/src/#common.jl# new file mode 100644 index 00000000..4193a1ea --- /dev/null +++ b/src/#common.jl# @@ -0,0 +1,1232 @@ +using LinearAlgebra + +export fromroots, + truncate!, + chop!, + coeffs, + degree, + mapdomain, + hasnan, + roots, + companion, + vander, + fit, + integrate, + derivative, + variable, + @variable, # deprecated!! + isintegral, + ismonic + +""" + fromroots(::AbstractVector{<:Number}; var=:x) + fromroots(::Type{<:AbstractPolynomial}, ::AbstractVector{<:Number}; var=:x) + +Construct a polynomial of the given type given the roots. If no type is given, defaults to `Polynomial`. + +# Examples +```jldoctest common +julia> using Polynomials + +julia> r = [3, 2]; # (x - 3)(x - 2) + +julia> fromroots(r) +Polynomial(6 - 5*x + x^2) +``` +""" +function fromroots(P::Type{<:AbstractPolynomial}, rs::AbstractVector; var::SymbolLike = :x) + x = variable(P, var) + p = prod(x-r for r ∈ rs; init=one(x)) + return truncate!(p) +end +fromroots(r::AbstractVector{<:Number}; var::SymbolLike = :x) = + fromroots(Polynomial, r, var = var) + +""" + fromroots(::AbstractMatrix{<:Number}; var=:x) + fromroots(::Type{<:AbstractPolynomial}, ::AbstractMatrix{<:Number}; var=:x) + +Construct a polynomial of the given type using the eigenvalues of the given matrix as the roots. If no type is given, defaults to `Polynomial`. + +# Examples +```jldoctest common +julia> using Polynomials + +julia> A = [1 2; 3 4]; # (x - 5.37228)(x + 0.37228) + +julia> fromroots(A) +Polynomial(-1.9999999999999998 - 5.0*x + 1.0*x^2) +``` +""" +fromroots(P::Type{<:AbstractPolynomial}, + A::AbstractMatrix{T}; + var::SymbolLike = :x,) where {T <: Number} = fromroots(P, eigvals(A), var = var) +fromroots(A::AbstractMatrix{T}; var::SymbolLike = :x) where {T <: Number} = + fromroots(Polynomial, eigvals(A), var = var) + +""" + fit(x, y, deg=length(x) - 1; [weights], var=:x) + 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 `||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). + + +## 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 diagonal +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. + + +""" +function fit(P::Type{<:AbstractPolynomial}, + x::AbstractVector{T}, + y::AbstractVector{T}, + deg::Integer = length(x) - 1; + weights = nothing, + var = :x,) where {T} + _fit(P, x, y, deg; weights=weights, var=var) +end + +fit(P::Type{<:AbstractPolynomial}, + x, + y, + deg::Integer = length(x) - 1; + weights = nothing, + 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(ArgumentError("x and y do not produce abstract vectors")) +fit′(P::Type{<:AbstractPolynomial}, + x::AbstractVector{T}, + y::AbstractVector{T}, + args...; kwargs...) where {T} = fit(P,x,y,args...; kwargs...) + + +fit(x::AbstractVector, + y::AbstractVector, + deg::Integer = length(x) - 1; + weights = nothing, + var = :x,) = fit(Polynomial, x, y, deg; weights = weights, var = var) + +function _fit(P::Type{<:AbstractPolynomial}, + x::AbstractVector{T}, + y::AbstractVector{T}, + deg = length(x) - 1; + weights = nothing, + var = :x,) where {T} + x = mapdomain(P, x) + vand = vander(P, x, deg) + if !isnothing(weights) + coeffs = _wlstsq(vand, y, weights) + else + coeffs = vand \ y + end + R = float(T) + if isa(deg, Integer) + return P(R.(coeffs), var) + else + cs = zeros(T, 1 + maximum(deg)) + for (i,aᵢ) ∈ zip(deg, coeffs) + cs[1 + i] = aᵢ + end + return P(cs, var) + end + + +end + + +# Weighted linear least squares +_wlstsq(vand, y, W::Number) = _wlstsq(vand, y, fill!(similar(y), W)) +function _wlstsq(vand, y, w::AbstractVector) + W = Diagonal(sqrt.(w)) + (W * vand) \ (W * y) +end +_wlstsq(vand, y, W::AbstractMatrix) = (vand' * W * vand) \ (vand' * W * y) + +""" + roots(::AbstractPolynomial; kwargs...) + +Returns the roots, or zeros, of the given polynomial. + +For non-factored, standard basis polynomials the roots are calculated via the eigenvalues of the companion matrix. The `kwargs` are passed to the `LinearAlgebra.eigvals` call. + +!!! note + The default `roots` implementation is for polynomials in the + standard basis. The companion matrix approach is reasonably fast + and accurate for modest-size polynomials. However, other packages + in the `Julia` ecosystem may be of interest and are mentioned in the documentation. + + +""" +function roots(q::AbstractPolynomial{T}; kwargs...) where {T} + + p = convert(Polynomial{T}, q) + roots(p; kwargs...) + +end + +""" + companion(::AbstractPolynomial) + +Return the companion matrix for the given polynomial. + +# References +[Companion Matrix](https://en.wikipedia.org/wiki/Companion_matrix) +""" +companion(::AbstractPolynomial) + +""" + vander(::Type{AbstractPolynomial}, x::AbstractVector, deg::Integer) + +Calculate the pseudo-Vandermonde matrix of the given polynomial type with the given degree. + +# References +[Vandermonde Matrix](https://en.wikipedia.org/wiki/Vandermonde_matrix) +""" +vander(::Type{<:AbstractPolynomial}, x::AbstractVector, deg::Integer) + + +""" + critical_points(p::AbstractPolynomial{<:Real}, I=domain(p); endpoints::Bool=true) + +Return the critical points of `p` (real zeros of the derivative) within `I` in sorted order. + +* `p`: a polynomial + +* `I`: a specification of a closed or infinite domain, defaulting to `Polynomials.domain(p)`. When specified, the values of `extrema(I)` are used with closed endpoints when finite. + +* `endpoints::Bool`: if `true`, return the endpoints of `I` along with the critical points + + +Can be used in conjunction with `findmax`, `findmin`, `argmax`, `argmin`, `extrema`, etc. + +## Example +``` +x = variable() +p = x^2 - 2 +cps = Polynomials.critical_points(p) +findmin(p, cps) # (-2.0, 2.0) +argmin(p, cps) # 0.0 +extrema(p, cps) # (-2.0, Inf) +cps = Polynomials.critical_points(p, (0, 2)) +extrema(p, cps) # (-2.0, 2.0) +``` + +!!! note + There is a *big* difference between `minimum(p)` and `minimum(p, cps)`. The former takes the viewpoint that a polynomial `p` is a certain type of vector of its coefficients; returning the smallest coefficient. The latter uses `p` as a callable object, returning the smallest of the values `p.(cps)`. +""" +function critical_points(p::AbstractPolynomial{T}, I = domain(p); + endpoints::Bool=true) where {T <: Real} + + I′ = Interval(I) + l, r = extrema(I′) + + q = Polynomials.ngcd(derivative(p), derivative(p,2)).v + pts = sort(real.(filter(isreal, roots(q)))) + pts = filter(in(I′), pts) + + !endpoints && return pts + + l !== first(pts) && pushfirst!(pts, l) + r != last(pts) && push!(pts, r) + pts +end + + + + + + +""" + integrate(p::AbstractPolynomial) + +Return an antiderivative for `p` +""" +integrate(P::AbstractPolynomial) = throw(ArgumentError("`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 + (C - constantterm(∫p)) +end + +""" + integrate(::AbstractPolynomial, a, b) + +Compute the definite integral of the given polynomial from `a` to `b`. Will throw an error if either `a` or `b` are out of the polynomial's domain. +""" +function integrate(p::AbstractPolynomial, a, b) + P = integrate(p) + return P(b) - P(a) +end + +""" + derivative(::AbstractPolynomial, order::Int = 1) + +Returns a polynomial that is the `order`th derivative of the given polynomial. `order` must be non-negative. +""" +derivative(::AbstractPolynomial, ::Int) + +""" + truncate!(::AbstractPolynomial{T}; + rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) + +In-place version of [`truncate`](@ref) +""" +function truncate!(p::AbstractPolynomial{T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {T} + truncate!(p.coeffs, rtol=rtol, atol=atol) + 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} + 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} + + isempty(ps) && return nothing + 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.")) +# function truncate!(ps::NTuple{N,T}; +# rtol::Real = Base.rtoldefault(real(T)), +# atol::Real = 0,) where {N,T} +# #throw(ArgumentError("`truncate!` not defined.")) +# thresh = norm(ps, Inf) * rtol + atol +# for (i, pᵢ) ∈ enumerate(ps) +# if abs(pᵢ) ≤ thresh +# ps = _set(ps, i, zero(pᵢ)) +# end +# end +# ps +# end + +_truncate(ps::NTuple{0}; kwargs...) = ps +function _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}; + 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. +""" +function Base.truncate(p::AbstractPolynomial{T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {T} + truncate!(deepcopy(p), rtol = rtol, atol = atol) +end + +""" + 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} + 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} + isempty(ps) && return ps + tol = norm(ps) * rtol + atol + for i = lastindex(ps):-1:1 + val = ps[i] + if abs(val) > tol + resize!(ps, i); + return nothing + end + end + 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(ps::NTuple{0}; kwargs...) = ps +function _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 + + + +""" + chop(::AbstractPolynomial{T}; + rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0)) + +Removes any leading coefficients that are approximately 0 (using `rtol` and `atol` with `norm(p)`). Returns a polynomial whose degree will guaranteed to be equal to or less than the given polynomial's. +""" +function Base.chop(p::AbstractPolynomial{T}; + rtol::Real = Base.rtoldefault(real(T)), + atol::Real = 0,) where {T} + chop!(deepcopy(p), rtol = rtol, atol = atol) +end + + + + +""" + check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) + +Check if either `p` or `q` is constant or if `p` and `q` share the same variable +""" +check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) = + (isconstant(p) || isconstant(q)) || indeterminate(p) == indeterminate(q) + +function assert_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) + check_same_variable(p,q) || throw(ArgumentError("Non-constant polynomials have different indeterminates")) +end + +function assert_same_variable(X::Symbol, Y::Symbol) + X == Y || throw(ArgumentError("Polynomials have different indeterminates")) +end + +#= +Linear Algebra =# +""" + norm(::AbstractPolynomial, p=2) + +Calculates the p-norm of the polynomial's coefficients +""" +function LinearAlgebra.norm(q::AbstractPolynomial{T,X}, p::Real = 2) where {T,X} + iszero(q) && return zero(real(T))^(1/p) + return norm(values(q), p) +end + +""" + conj(::AbstractPolynomial) + +Returns the complex conjugate of the polynomial +""" +LinearAlgebra.conj(p::P) where {P <: AbstractPolynomial} = map(conj, p) +LinearAlgebra.adjoint(p::P) where {P <: AbstractPolynomial} = map(adjoint, p) +LinearAlgebra.adjoint(A::VecOrMat{<:AbstractPolynomial}) = adjoint.(permutedims(A)) # default has indeterminate indeterminate +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) +function Base.convert(P::Type{<:AbstractPolynomial}, q::AbstractPolynomial) + X = indeterminate(P,q) + x = variable(P, X) + q(x) +end +function Base.convert(::Type{T}, p::AbstractPolynomial{T,X}) where {T <: Number,X} + isconstant(p) && return T(constantterm(p)) + throw(ArgumentError("Can't convert a nonconstant polynomial to type $T")) +end + + + +#= +Inspection =# +""" + length(::AbstractPolynomial) + +The length of the polynomial. +""" +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) = (length(p),) +Base.size(p::AbstractPolynomial, i::Integer) = i <= 1 ? size(p)[i] : 1 +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 +_eltype(::Type{<:AbstractPolynomial}) = nothing +_eltype(::Type{<:AbstractPolynomial{T}}) where {T} = T +function _eltype(P::Type{<:AbstractPolynomial}, p::AbstractPolynomial) + T′ = _eltype(P) + T = isnothing(T′) ? eltype(p) : T′ + T +end + +""" + copy_with_eltype(::Type{T}, [::Val{X}], p::AbstractPolynomial) + +Copy polynomial `p` changing the underlying element type and optionally the symbol. +""" +copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {T, X, S, Y, P <:AbstractPolynomial{S,Y}} = + ⟒(P){T, Symbol(X)}(p.coeffs) +copy_with_eltype(::Type{T}, p::P) where {T, S, Y, P <:AbstractPolynomial{S,Y}} = + copy_with_eltype(T, Val(Y), p) +# easier to type if performance isn't an issue, but could be dropped +#copy_with_eltype(::Type{T}, X, p::P) where {T, S, Y, P<:AbstractPolynomial{S, Y}} = +# copy_with_eltype(Val(T), Val(X), p) +#copy_with_eltype(::Type{T}, p::P) where {T, S, X, P<:AbstractPolynomial{S,X}} = +# copy_with_eltype(Val(T), Val(X), p) + +Base.iszero(p::AbstractPolynomial) = all(iszero, values(p)) + + +# See discussions in https://github.com/JuliaMath/Polynomials.jl/issues/258 +""" + all(pred, poly::AbstractPolynomial) + +Test whether all coefficients of an `AbstractPolynomial` satisfy predicate `pred`. + +You can implement `isreal`, etc., to a `Polynomial` by using `all`. +""" +Base.all(pred, p::AbstractPolynomial{T, X}) where {T,X} = all(pred, values(p)) +""" + any(pred, poly::AbstractPolynomial) + +Test whether any coefficient of an `AbstractPolynomial` satisfies predicate `pred`. +""" +Base.any(pred, p::AbstractPolynomial{T,X}) where {T, X} = any(pred, values(p)) + + + + +""" + map(fn, p::AbstractPolynomial, args...) + +Transform coefficients of `p` by applying a function (or other callables) `fn` to each of them. + +You can implement `real`, etc., to a `Polynomial` by using `map`. The type of `p` may narrow using this function. +""" +function Base.map(fn, p::P, args...) where {P<:AbstractPolynomial} + xs = map(fn, p.coeffs, args...) + R = eltype(xs) + X = indeterminate(p) + return ⟒(P){R,X}(xs) +end + + +""" + isreal(p::AbstractPolynomial) + +Determine whether a polynomial is a real polynomial, i.e., having only real numbers as coefficients. + +See also: [`real`](@ref) +""" +Base.isreal(p::AbstractPolynomial) = all(isreal, p) +""" + real(p::AbstractPolynomial) + +Construct a real polynomial from the real parts of the coefficients of `p`. + +See also: [`isreal`](@ref) + +!!! note + This could cause losing terms in `p`. This method is usually called on polynomials like `p = Polynomial([1, 2 + 0im, 3.0, 4.0 + 0.0im])` where you want to chop the imaginary parts of the coefficients of `p`. +""" +Base.real(p::AbstractPolynomial) = map(real, p) + +""" + isintegral(p::AbstractPolynomial) + +Determine whether a polynomial is an integer polynomial, i.e., having only integers as coefficients. +""" +isintegral(p::AbstractPolynomial) = all(isinteger, p) + +""" + ismonic(p::AbstractPolynomial) + +Determine whether a polynomial is a monic polynomial, i.e., its leading coefficient is one. +""" +ismonic(p::AbstractPolynomial) = isone(convert(Polynomial, p)[end]) + +"`hasnan(p::AbstractPolynomial)` are any coefficients `NaN`" +hasnan(p::AbstractPolynomial) = any(hasnan, p) +hasnan(p::AbstractArray) = any(hasnan.(p)) +hasnan(x) = isnan(x) + +""" + isconstant(::AbstractPolynomial) + +Is the polynomial `p` a constant. +""" +isconstant(p::AbstractPolynomial) = degree(p) <= 0 && firstindex(p) == 0 + +""" + coeffs(::AbstractPolynomial) + +Return the coefficient vector. For a standard basis polynomial these are `[a_0, a_1, ..., a_n]`. +""" +coeffs(p::AbstractPolynomial) = p.coeffs + +# hook in for offset coefficients of Laurent Polynomials +_coeffs(p::AbstractPolynomial) = coeffs(p) + + +# 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)) + +""" + degree(::AbstractPolynomial) + +Return the degree of the polynomial, i.e. the highest exponent in the polynomial that +has a nonzero coefficient. The degree of the zero polynomial is defined to be -1. The default method assumes the basis polynomial, `βₖ` has degree `k`. +""" +degree(p::AbstractPolynomial) = iszero(coeffs(p)) ? -1 : length(coeffs(p)) - 1 + min(0, minimumexponent(p)) + +@deprecate order degree false + + +""" + Polynomials.domain(::Type{<:AbstractPolynomial}) + +Returns the domain of the polynomial. +""" +domain(::Type{<:AbstractPolynomial}) +domain(::P) where {P <: AbstractPolynomial} = domain(P) + +""" + mapdomain(::Type{<:AbstractPolynomial}, x::AbstractArray) + mapdomain(::AbstractPolynomial, x::AbstractArray) + +Given values of x that are assumed to be unbounded (-∞, ∞), return values rescaled to the domain of the given polynomial. + +# Examples +```jldoctest common +julia> using Polynomials + +julia> x = -10:10 +-10:10 + +julia> extrema(mapdomain(ChebyshevT, x)) +(-1.0, 1.0) + +``` +""" +function mapdomain(P::Type{<:AbstractPolynomial}, x::AbstractArray) + d = domain(P) + x = collect(x) + x_zerod = x .- minimum(x) + x_scaled = x_zerod .* (last(d) - first(d)) ./ maximum(x_zerod) + x_scaled .+= first(d) + return x_scaled +end +mapdomain(::P, x::AbstractArray) where {P <: AbstractPolynomial} = mapdomain(P, x) + +#= +indexing =# +# minimumexponent(p) returns min(0, minimum(keys(p))) +# For most polynomial types, this is statically known to be zero +# For polynomials that support negative indices, minimumexponent(typeof(p)) +# should return typemin(Int) +minimumexponent(p::AbstractPolynomial) = minimumexponent(typeof(p)) +minimumexponent(::Type{<:AbstractPolynomial}) = 0 +Base.firstindex(p::AbstractPolynomial) = 0 +Base.lastindex(p::AbstractPolynomial) = length(p) - 1 + firstindex(p) +Base.eachindex(p::AbstractPolynomial) = firstindex(p):lastindex(p) +Base.broadcastable(p::AbstractPolynomial) = Ref(p) +degreerange(p::AbstractPolynomial) = firstindex(p):lastindex(p) + +# getindex +function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T} + m,M = firstindex(p), lastindex(p) + m > M && return zero(T) + idx < m && throw(BoundsError(p, idx)) + idx > M && return zero(T) + p.coeffs[idx - m + 1] +end +#XXXBase.getindex(p::AbstractPolynomial, idx::Number) = getindex(p, convert(Int, idx)) +Base.getindex(p::AbstractPolynomial, indices) = [p[i] for i in indices] +Base.getindex(p::AbstractPolynomial, ::Colon) = coeffs(p) + +# setindex +function Base.setindex!(p::AbstractPolynomial, value, idx::Int) + n = length(coeffs(p)) + if n ≤ idx + resize!(p.coeffs, idx + 1) + p.coeffs[n + 1:idx] .= 0 + end + p.coeffs[idx + 1] = value + return p +end + +Base.setindex!(p::AbstractPolynomial, value, idx::Number) = + setindex!(p, value, convert(Int, idx)) +Base.setindex!(p::AbstractPolynomial, values, indices) = + [setindex!(p, v, i) for (v, i) in tuple.(values, indices)] +Base.setindex!(p::AbstractPolynomial, values, ::Colon) = + [setindex!(p, v, i) for (v, i) in tuple.(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 _iterate(p, state) + firstindex(p) <= state <= lastindex(p) || return nothing + return p[state], state+1 +end +Base.iterate(p::AbstractPolynomial, state = firstindex(p)) = _iterate(p, state) + +# pairs map i -> aᵢ *possibly* skipping over ai == 0 +# cf. abstractdict.jl +struct PolynomialKeys{P} <: AbstractSet{Int} + p::P +end +struct PolynomialValues{P, T} + p::P + + PolynomialValues{P}(p::P) where {P} = new{P, eltype(p)}(p) + PolynomialValues(p::P) where {P} = new{P, eltype(p)}(p) +end +Base.keys(p::AbstractPolynomial) = PolynomialKeys(p) +Base.values(p::AbstractPolynomial) = PolynomialValues(p) +Base.length(p::PolynomialValues) = length(p.p.coeffs) +Base.eltype(p::PolynomialValues{<:Any,T}) where {T} = T +Base.length(p::PolynomialKeys) = length(p.p.coeffs) +Base.size(p::Union{PolynomialValues, PolynomialKeys}) = (length(p),) +function Base.iterate(v::PolynomialKeys, state = firstindex(v.p)) + firstindex(v.p) <= state <= lastindex(v.p) || return nothing + return state, state+1 +end + +Base.iterate(v::PolynomialValues, state = firstindex(v.p)) = _iterate(v.p, state) + + +# 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...) + isnothing(y) && 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(p.coeffs)) +Base.hash(p::AbstractPolynomial{T,X}, h::UInt) where {T,X} = hash(indeterminate(p), hash(p.coeffs, hash(X,h))) + +# get symbol of polynomial. (e.g. `:x` from 1x^2 + 2x^3... +_indeterminate(::Type{P}) where {P <: AbstractPolynomial} = nothing +_indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T,X}} = X +indeterminate(::Type{P}) where {P <: AbstractPolynomial} = something(_indeterminate(P), :x) +indeterminate(p::P) where {P <: AbstractPolynomial} = _indeterminate(P) + +function indeterminate(PP::Type{P}, p::AbstractPolynomial{T,Y}) where {P <: AbstractPolynomial, T,Y} + X = _indeterminate(PP) + isnothing(X) && return Y + isconstant(p) && return X + assert_same_variable(X,Y) + return X + #X = isnothing(_indeterminate(PP)) ? indeterminate(p) : _indeterminate(PP) +end +indeterminate(PP::Type{P}, x::Symbol) where {P <: AbstractPolynomial} = something(_indeterminate(PP), x) + +#= +zero, one, variable, basis =# + + + +""" + zero(::Type{<:AbstractPolynomial}) + zero(::AbstractPolynomial) + +Returns a representation of 0 as the given polynomial. +""" +function Base.zero(::Type{P}) where {P<:AbstractPolynomial} + T,X = eltype(P), indeterminate(P) + ⟒(P){T,X}(T[]) +end +Base.zero(::Type{P}, var::SymbolLike) where {P <: AbstractPolynomial} = zero(⟒(P){eltype(P),Symbol(var)}) #default 0⋅b₀ +Base.zero(p::P, var=indeterminate(p)) where {P <: AbstractPolynomial} = zero(P, var) +""" + one(::Type{<:AbstractPolynomial}) + one(::AbstractPolynomial) + +Returns a representation of 1 as the given polynomial. +""" +Base.one(::Type{P}) where {P<:AbstractPolynomial} = throw(ArgumentError("No default method defined")) # no default method +Base.one(::Type{P}, var::SymbolLike) where {P <: AbstractPolynomial} = one(⟒(P){eltype(P), Symbol(isnothing(var) ? :x : var)}) +Base.one(p::P, var=indeterminate(p)) where {P <: AbstractPolynomial} = one(P, var) + +Base.oneunit(::Type{P}, args...) where {P <: AbstractPolynomial} = one(P, args...) +Base.oneunit(p::P, args...) where {P <: AbstractPolynomial} = one(p, args...) + + +""" + variable(var=:x) + variable(::Type{<:AbstractPolynomial}, var=:x) + 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)`. + +# Examples +```jldoctest common +julia> using Polynomials + +julia> x = variable() +Polynomial(x) + +julia> p = 100 + 24x - 3x^2 +Polynomial(100 + 24*x - 3*x^2) + +julia> roots((x - 3) * (x + 2)) +2-element Vector{Float64}: + -2.0 + 3.0 + +``` +""" +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 = indeterminate(p)) = variable(typeof(p), var) +variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) + +# Exported in #470. Exporting was a mistake! +#@variable x +#@variable x::Polynomial +#@variable x::Polynomial{t] +macro variable(x) + Base.depwarn("Export of macro `@variable` is deprecated due to naming conflicts", :variable) + q = Expr(:block) + if isa(x, Expr) && x.head == :(::) + x, P = x.args + push!(q.args, Expr(:(=), esc(x), + Expr(:call, :variable, P, Expr(:quote, x)))) + else + push!(q.args, Expr(:(=), esc(x), + Expr(:call, :variable, Expr(:quote, x)))) + end + push!(q.args, esc(x)) + + q +end + + + +# 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 {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; var=_var) where {P <: AbstractPolynomial} + T,X = eltype(P), Symbol(var) + basis(⟒(P){T,X}, k) +end +basis(p::P, k::Int, _var=indeterminate(p); var=_var) where {P<:AbstractPolynomial} = basis(P, k, var) + +#= +composition +cf. https://github.com/JuliaMath/Polynomials.jl/issues/511 for a paper with implentations + +=# +""" + polynomial_composition(p, q) + +Evaluate `p(q)`, possibly exploiting a faster evaluation scheme, defaulting to `evalpoly`. +""" +function polynomial_composition(p::AbstractPolynomial, q::AbstractPolynomial) + evalpoly(q, p) +end + +#= +arithmetic =# +Scalar = Union{Number, Matrix} + +Base.:-(p::P) where {P <: AbstractPolynomial} = _convert(p, -coeffs(p)) # map(-, p) + +Base.:*(p::AbstractPolynomial, c::Scalar) = scalar_mult(p, c) +Base.:*(c::Scalar, p::AbstractPolynomial) = scalar_mult(c, p) +Base.:*(c::T, p::P) where {T, X, P <: AbstractPolynomial{T,X}} = scalar_mult(c, p) +Base.:*(p::P, c::T) where {T, X, P <: AbstractPolynomial{T,X}} = scalar_mult(p, c) + +# implicitly identify c::Scalar with a constant polynomials +Base.:+(c::Scalar, p::AbstractPolynomial) = +(p, c) +Base.:-(p::AbstractPolynomial, c::Scalar) = +(p, -c) +Base.:-(c::Scalar, p::AbstractPolynomial) = +(-p, c) + +# scalar operations +# no generic p+c, as polynomial addition falls back to scalar ops + + +Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) + + +## addition +## Fall back addition is possible as vector addition with padding by 0s +## Subtypes will likely want to implement both: +## +(p::P,c::Scalar) 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 + +Base.:+(p::AbstractPolynomial) = p + +# polynomial + scalar; implicit identification of c with c*one(p) +Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = scalar_add(p, c) +scalar_add(p::AbstractPolynomial, c) = 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 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 + +# 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}} + cs = degree(p) >= degree(q) ? ⊕(P, p.coeffs, q.coeffs) : ⊕(P, q.coeffs, p.coeffs) + return P(cs) +end + +# 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,p1) + for i in 1:n2 + cs[i] += p2[i] + end + + return cs +end + +# 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] + 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 + #Base.@inline + tuple($(exprs...)) + 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) + p = Dict{Int, R}() + + + # this allocates in the union +# for i in union(eachindex(p1), eachindex(p2)) +# p[i] = p1[i] + p2[i] +# end + + for (i,pi) ∈ pairs(p1) + @inbounds p[i] = pi + get(p2, i, zero(R)) + end + for (i,pi) ∈ pairs(p2) + if iszero(get(p,i,zero(R))) + @inbounds p[i] = get(p1, i, zero(R)) + pi + end + end + + return p + +end + +## -- multiplication + +# Scalar multiplication; no assumption of commutivity +function scalar_mult(p::P, c::S) where {S, T, X, P<:AbstractPolynomial{T,X}} + result = coeffs(p) .* (c,) + ⟒(P){eltype(result), X}(result) +end + +function scalar_mult(c::S, p::P) where {S, T, X, P<:AbstractPolynomial{T, X}} + result = (c,) .* coeffs(p) + ⟒(P){eltype(result), X}(result) +end + +scalar_mult(p1::AbstractPolynomial, p2::AbstractPolynomial) = error("scalar_mult(::$(typeof(p1)), ::$(typeof(p2))) is not defined.") # avoid ambiguity, issue #435 + +# scalar div +Base.:/(p::AbstractPolynomial, c) = scalar_div(p, c) +function scalar_div(p::P, c::S) where {S, T, X, P<:AbstractPolynomial{T, X}} + iszero(p) && return zero(⟒(P){Base.promote_op(/,T,S), X}) + _convert(p, coeffs(p) ./ Ref(c)) +end + +## Polynomial p*q +## Polynomial multiplication formula depend on the particular basis used. The subtype must implement +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) + 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} + n, d = promote(num, den) + return divrem(n, d) +end + +""" + gcd(a::AbstractPolynomial, b::AbstractPolynomial; atol::Real=0, rtol::Real=Base.rtoldefault) + +Find the greatest common denominator of two polynomials recursively using +[Euclid's algorithm](http://en.wikipedia.org/wiki/Polynomial_greatest_common_divisor#Euclid.27s_algorithm). + +# Examples + +```jldoctest common +julia> using Polynomials + +julia> gcd(fromroots([1, 1, 2]), fromroots([1, 2, 3])) +Polynomial(4.0 - 6.0*x + 2.0*x^2) + +``` +""" +function Base.gcd(p1::AbstractPolynomial{T}, p2::AbstractPolynomial{S}; kwargs...) where {T,S} + gcd(promote(p1, p2)...; kwargs...) +end + +function Base.gcd(p1::AbstractPolynomial{T}, p2::AbstractPolynomial{T}; + atol::Real=zero(real(T)), + rtol::Real=Base.rtoldefault(real(T)) + ) where {T} + + + r₀, r₁ = p1, p2 + iter = 1 + itermax = length(r₁) + + while !iszero(r₁) && iter ≤ itermax + _, rtemp = divrem(r₀, r₁) + r₀ = r₁ + r₁ = truncate(rtemp; atol=atol, rtol=rtol) + iter += 1 + end + return r₀ +end + +""" + uvw(p,q; kwargs...) + +return `u` the gcd of `p` and `q`, and `v` and `w`, where `u*v = p` and `u*w = q`. +""" +uvw(p::AbstractPolynomial, q::AbstractPolynomial; kwargs...) = uvw(promote(p,q)...; kwargs...) +#uvw(p1::P, p2::P; kwargs...) where {P <:AbstractPolynomial} = throw(ArgumentError("uvw not defined")) + +""" + div(::AbstractPolynomial, ::AbstractPolynomial) +""" +Base.div(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[1] + +""" + rem(::AbstractPolynomial, ::AbstractPolynomial) +""" +Base.rem(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[2] + +#= +Comparisons =# +Base.isequal(p1::P, p2::P) where {P <: AbstractPolynomial} = hash(p1) == hash(p2) +function Base.:(==)(p1::P, p2::P) where {P <: AbstractPolynomial} + iszero(p1) && iszero(p2) && return true + eachindex(p1) == eachindex(p2) || return false + # coeffs(p1) == coeffs(p2), but non-allocating + p1val = (p1[i] for i in eachindex(p1)) + p2val = (p2[i] for i in eachindex(p2)) + all(((a,b),) -> a == b, zip(p1val, p2val)) +end +function Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial) + if isconstant(p1) + isconstant(p2) && return constantterm(p1) == constantterm(p2) + return false + elseif isconstant(p2) + return false # p1 is not constant + end + check_same_variable(p1, p2) || return false + ==(promote(p1,p2)...) +end +Base.:(==)(p::AbstractPolynomial, n::Scalar) = isconstant(p) && constantterm(p) == n +Base.:(==)(n::Scalar, p::AbstractPolynomial) = p == n + +function Base.isapprox(p1::AbstractPolynomial, p2::AbstractPolynomial; kwargs...) + if isconstant(p1) + isconstant(p2) && return constantterm(p1) == constantterm(p2) + return false + elseif isconstant(p2) + return false + end + assert_same_variable(p1, p2) || return false + isapprox(promote(p1, p2)...; kwargs...) +end + +function Base.isapprox(p1::AbstractPolynomial{T,X}, + p2::AbstractPolynomial{S,X}; + rtol::Real = (Base.rtoldefault(T,S,0)), + atol::Real = 0,) where {T,S,X} + (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons + # copy over from abstractarray.jl + Δ = norm(p1-p2) + if isfinite(Δ) + return Δ <= max(atol, rtol*max(norm(p1), norm(p2))) + else + for i in 0:max(degree(p1), degree(p2)) + isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false + end + return true + end +end + +function Base.isapprox(p1::AbstractPolynomial{T}, + n::S; + rtol::Real = (Base.rtoldefault(T, S, 0)), + atol::Real = 0,) where {T,S} + return isapprox(p1, _convert(p1, [n])) +end + +Base.isapprox(::AbstractPolynomial{T}, ::Missing, args...; kwargs...) where T = + missing +Base.isapprox(::Missing, ::AbstractPolynomial{T}, args...; kwargs...) where T = + missing + +Base.isapprox(n::S, + p1::AbstractPolynomial{T}; + rtol::Real = (Base.rtoldefault(T, S, 0)), + atol::Real = 0,) where {T,S} = isapprox(p1, n, rtol = rtol, atol = atol) diff --git a/src/.#common.jl b/src/.#common.jl new file mode 120000 index 00000000..645a64a0 --- /dev/null +++ b/src/.#common.jl @@ -0,0 +1 @@ +jverzani@john-verzanis-macbook-pro.local.3609 \ No newline at end of file diff --git a/src/polynomials/multroot.jl b/src/polynomials/multroot.jl index 98410f3a..4ee7171a 100644 --- a/src/polynomials/multroot.jl +++ b/src/polynomials/multroot.jl @@ -146,8 +146,7 @@ function pejorative_manifold( ) where {T,X} S = float(T) - u = PnPolynomial{S,X}(S.(coeffs(p))) - chop!(u) + u = convert(PnPolynomial{S,X}, p) nu₂ = norm(u, 2) θ2, ρ2 = θ * nu₂, ρ * nu₂ u, v, w, ρⱼ, κ = Polynomials.ngcd( diff --git a/src/standard-basis/standard-basis.jl b/src/standard-basis/standard-basis.jl index da345c9f..0873426a 100644 --- a/src/standard-basis/standard-basis.jl +++ b/src/standard-basis/standard-basis.jl @@ -26,7 +26,7 @@ function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: Abstract if i₀ < 0 return ⟒(P){T,X}([q[i] for i in eachindex(q)], i₀) else - return ⟒(P){T,X}([q[i] for i in 0:lastindex(q)]) # full poly + return ⟒(P){T,X}([q[i] for i in 0:max(0,degree(q))]) # should trim trailing 0s end end From 04017cd63fe574d3c6471ae49b65e84328481b20 Mon Sep 17 00:00:00 2001 From: jverzani Date: Wed, 2 Aug 2023 20:10:58 -0400 Subject: [PATCH 29/31] WIP: more edits --- docs/src/extending.md | 4 +- src/#common.jl# | 1232 ----------------- src/.#common.jl | 1 - src/abstract-polynomial.jl | 3 +- src/common.jl | 5 +- .../mutable-sparse-polynomial.jl | 2 +- test/ChebyshevT.jl | 4 +- 7 files changed, 11 insertions(+), 1240 deletions(-) delete mode 100644 src/#common.jl# delete mode 120000 src/.#common.jl diff --git a/docs/src/extending.md b/docs/src/extending.md index 14af95e6..41a12f7e 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -154,7 +154,7 @@ The unexported `Polynomials.PnPolynomial` type implements much of this. An `AbstractUnivariatePolynomial` polynomial consists of a basis and a storage type. The storage type can be mutable dense, mutable sparse, or immutable dense. -A basis inherits from `Polynomials.AbstractBasis`, in the example our basis type has a parameter. +A basis inherits from `Polynomials.AbstractBasis`, in the example our basis type has a parameter. The `ChebyshevT` type, gives a related example of how this task can be implemented. ### The generalized Laguerre polynomials @@ -257,6 +257,8 @@ julia> Polynomials.evalpoly(x, p::P) where {P<:AbstractUnivariatePolynomial{<:La clenshaw_eval(p, x) ``` +We test it out by passing in the variable `x` in the standard basis: + ```jldoctest abstract_univariate_polynomial julia> p = P([0,0,1]) MutableDensePolynomial(L^0_2) diff --git a/src/#common.jl# b/src/#common.jl# deleted file mode 100644 index 4193a1ea..00000000 --- a/src/#common.jl# +++ /dev/null @@ -1,1232 +0,0 @@ -using LinearAlgebra - -export fromroots, - truncate!, - chop!, - coeffs, - degree, - mapdomain, - hasnan, - roots, - companion, - vander, - fit, - integrate, - derivative, - variable, - @variable, # deprecated!! - isintegral, - ismonic - -""" - fromroots(::AbstractVector{<:Number}; var=:x) - fromroots(::Type{<:AbstractPolynomial}, ::AbstractVector{<:Number}; var=:x) - -Construct a polynomial of the given type given the roots. If no type is given, defaults to `Polynomial`. - -# Examples -```jldoctest common -julia> using Polynomials - -julia> r = [3, 2]; # (x - 3)(x - 2) - -julia> fromroots(r) -Polynomial(6 - 5*x + x^2) -``` -""" -function fromroots(P::Type{<:AbstractPolynomial}, rs::AbstractVector; var::SymbolLike = :x) - x = variable(P, var) - p = prod(x-r for r ∈ rs; init=one(x)) - return truncate!(p) -end -fromroots(r::AbstractVector{<:Number}; var::SymbolLike = :x) = - fromroots(Polynomial, r, var = var) - -""" - fromroots(::AbstractMatrix{<:Number}; var=:x) - fromroots(::Type{<:AbstractPolynomial}, ::AbstractMatrix{<:Number}; var=:x) - -Construct a polynomial of the given type using the eigenvalues of the given matrix as the roots. If no type is given, defaults to `Polynomial`. - -# Examples -```jldoctest common -julia> using Polynomials - -julia> A = [1 2; 3 4]; # (x - 5.37228)(x + 0.37228) - -julia> fromroots(A) -Polynomial(-1.9999999999999998 - 5.0*x + 1.0*x^2) -``` -""" -fromroots(P::Type{<:AbstractPolynomial}, - A::AbstractMatrix{T}; - var::SymbolLike = :x,) where {T <: Number} = fromroots(P, eigvals(A), var = var) -fromroots(A::AbstractMatrix{T}; var::SymbolLike = :x) where {T <: Number} = - fromroots(Polynomial, eigvals(A), var = var) - -""" - fit(x, y, deg=length(x) - 1; [weights], var=:x) - 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 `||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). - - -## 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 diagonal -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. - - -""" -function fit(P::Type{<:AbstractPolynomial}, - x::AbstractVector{T}, - y::AbstractVector{T}, - deg::Integer = length(x) - 1; - weights = nothing, - var = :x,) where {T} - _fit(P, x, y, deg; weights=weights, var=var) -end - -fit(P::Type{<:AbstractPolynomial}, - x, - y, - deg::Integer = length(x) - 1; - weights = nothing, - 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(ArgumentError("x and y do not produce abstract vectors")) -fit′(P::Type{<:AbstractPolynomial}, - x::AbstractVector{T}, - y::AbstractVector{T}, - args...; kwargs...) where {T} = fit(P,x,y,args...; kwargs...) - - -fit(x::AbstractVector, - y::AbstractVector, - deg::Integer = length(x) - 1; - weights = nothing, - var = :x,) = fit(Polynomial, x, y, deg; weights = weights, var = var) - -function _fit(P::Type{<:AbstractPolynomial}, - x::AbstractVector{T}, - y::AbstractVector{T}, - deg = length(x) - 1; - weights = nothing, - var = :x,) where {T} - x = mapdomain(P, x) - vand = vander(P, x, deg) - if !isnothing(weights) - coeffs = _wlstsq(vand, y, weights) - else - coeffs = vand \ y - end - R = float(T) - if isa(deg, Integer) - return P(R.(coeffs), var) - else - cs = zeros(T, 1 + maximum(deg)) - for (i,aᵢ) ∈ zip(deg, coeffs) - cs[1 + i] = aᵢ - end - return P(cs, var) - end - - -end - - -# Weighted linear least squares -_wlstsq(vand, y, W::Number) = _wlstsq(vand, y, fill!(similar(y), W)) -function _wlstsq(vand, y, w::AbstractVector) - W = Diagonal(sqrt.(w)) - (W * vand) \ (W * y) -end -_wlstsq(vand, y, W::AbstractMatrix) = (vand' * W * vand) \ (vand' * W * y) - -""" - roots(::AbstractPolynomial; kwargs...) - -Returns the roots, or zeros, of the given polynomial. - -For non-factored, standard basis polynomials the roots are calculated via the eigenvalues of the companion matrix. The `kwargs` are passed to the `LinearAlgebra.eigvals` call. - -!!! note - The default `roots` implementation is for polynomials in the - standard basis. The companion matrix approach is reasonably fast - and accurate for modest-size polynomials. However, other packages - in the `Julia` ecosystem may be of interest and are mentioned in the documentation. - - -""" -function roots(q::AbstractPolynomial{T}; kwargs...) where {T} - - p = convert(Polynomial{T}, q) - roots(p; kwargs...) - -end - -""" - companion(::AbstractPolynomial) - -Return the companion matrix for the given polynomial. - -# References -[Companion Matrix](https://en.wikipedia.org/wiki/Companion_matrix) -""" -companion(::AbstractPolynomial) - -""" - vander(::Type{AbstractPolynomial}, x::AbstractVector, deg::Integer) - -Calculate the pseudo-Vandermonde matrix of the given polynomial type with the given degree. - -# References -[Vandermonde Matrix](https://en.wikipedia.org/wiki/Vandermonde_matrix) -""" -vander(::Type{<:AbstractPolynomial}, x::AbstractVector, deg::Integer) - - -""" - critical_points(p::AbstractPolynomial{<:Real}, I=domain(p); endpoints::Bool=true) - -Return the critical points of `p` (real zeros of the derivative) within `I` in sorted order. - -* `p`: a polynomial - -* `I`: a specification of a closed or infinite domain, defaulting to `Polynomials.domain(p)`. When specified, the values of `extrema(I)` are used with closed endpoints when finite. - -* `endpoints::Bool`: if `true`, return the endpoints of `I` along with the critical points - - -Can be used in conjunction with `findmax`, `findmin`, `argmax`, `argmin`, `extrema`, etc. - -## Example -``` -x = variable() -p = x^2 - 2 -cps = Polynomials.critical_points(p) -findmin(p, cps) # (-2.0, 2.0) -argmin(p, cps) # 0.0 -extrema(p, cps) # (-2.0, Inf) -cps = Polynomials.critical_points(p, (0, 2)) -extrema(p, cps) # (-2.0, 2.0) -``` - -!!! note - There is a *big* difference between `minimum(p)` and `minimum(p, cps)`. The former takes the viewpoint that a polynomial `p` is a certain type of vector of its coefficients; returning the smallest coefficient. The latter uses `p` as a callable object, returning the smallest of the values `p.(cps)`. -""" -function critical_points(p::AbstractPolynomial{T}, I = domain(p); - endpoints::Bool=true) where {T <: Real} - - I′ = Interval(I) - l, r = extrema(I′) - - q = Polynomials.ngcd(derivative(p), derivative(p,2)).v - pts = sort(real.(filter(isreal, roots(q)))) - pts = filter(in(I′), pts) - - !endpoints && return pts - - l !== first(pts) && pushfirst!(pts, l) - r != last(pts) && push!(pts, r) - pts -end - - - - - - -""" - integrate(p::AbstractPolynomial) - -Return an antiderivative for `p` -""" -integrate(P::AbstractPolynomial) = throw(ArgumentError("`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 + (C - constantterm(∫p)) -end - -""" - integrate(::AbstractPolynomial, a, b) - -Compute the definite integral of the given polynomial from `a` to `b`. Will throw an error if either `a` or `b` are out of the polynomial's domain. -""" -function integrate(p::AbstractPolynomial, a, b) - P = integrate(p) - return P(b) - P(a) -end - -""" - derivative(::AbstractPolynomial, order::Int = 1) - -Returns a polynomial that is the `order`th derivative of the given polynomial. `order` must be non-negative. -""" -derivative(::AbstractPolynomial, ::Int) - -""" - truncate!(::AbstractPolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0) - -In-place version of [`truncate`](@ref) -""" -function truncate!(p::AbstractPolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} - truncate!(p.coeffs, rtol=rtol, atol=atol) - 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} - 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} - - isempty(ps) && return nothing - 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.")) -# function truncate!(ps::NTuple{N,T}; -# rtol::Real = Base.rtoldefault(real(T)), -# atol::Real = 0,) where {N,T} -# #throw(ArgumentError("`truncate!` not defined.")) -# thresh = norm(ps, Inf) * rtol + atol -# for (i, pᵢ) ∈ enumerate(ps) -# if abs(pᵢ) ≤ thresh -# ps = _set(ps, i, zero(pᵢ)) -# end -# end -# ps -# end - -_truncate(ps::NTuple{0}; kwargs...) = ps -function _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}; - 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. -""" -function Base.truncate(p::AbstractPolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} - truncate!(deepcopy(p), rtol = rtol, atol = atol) -end - -""" - 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} - 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} - isempty(ps) && return ps - tol = norm(ps) * rtol + atol - for i = lastindex(ps):-1:1 - val = ps[i] - if abs(val) > tol - resize!(ps, i); - return nothing - end - end - 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(ps::NTuple{0}; kwargs...) = ps -function _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 - - - -""" - chop(::AbstractPolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0)) - -Removes any leading coefficients that are approximately 0 (using `rtol` and `atol` with `norm(p)`). Returns a polynomial whose degree will guaranteed to be equal to or less than the given polynomial's. -""" -function Base.chop(p::AbstractPolynomial{T}; - rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} - chop!(deepcopy(p), rtol = rtol, atol = atol) -end - - - - -""" - check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) - -Check if either `p` or `q` is constant or if `p` and `q` share the same variable -""" -check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) = - (isconstant(p) || isconstant(q)) || indeterminate(p) == indeterminate(q) - -function assert_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) - check_same_variable(p,q) || throw(ArgumentError("Non-constant polynomials have different indeterminates")) -end - -function assert_same_variable(X::Symbol, Y::Symbol) - X == Y || throw(ArgumentError("Polynomials have different indeterminates")) -end - -#= -Linear Algebra =# -""" - norm(::AbstractPolynomial, p=2) - -Calculates the p-norm of the polynomial's coefficients -""" -function LinearAlgebra.norm(q::AbstractPolynomial{T,X}, p::Real = 2) where {T,X} - iszero(q) && return zero(real(T))^(1/p) - return norm(values(q), p) -end - -""" - conj(::AbstractPolynomial) - -Returns the complex conjugate of the polynomial -""" -LinearAlgebra.conj(p::P) where {P <: AbstractPolynomial} = map(conj, p) -LinearAlgebra.adjoint(p::P) where {P <: AbstractPolynomial} = map(adjoint, p) -LinearAlgebra.adjoint(A::VecOrMat{<:AbstractPolynomial}) = adjoint.(permutedims(A)) # default has indeterminate indeterminate -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) -function Base.convert(P::Type{<:AbstractPolynomial}, q::AbstractPolynomial) - X = indeterminate(P,q) - x = variable(P, X) - q(x) -end -function Base.convert(::Type{T}, p::AbstractPolynomial{T,X}) where {T <: Number,X} - isconstant(p) && return T(constantterm(p)) - throw(ArgumentError("Can't convert a nonconstant polynomial to type $T")) -end - - - -#= -Inspection =# -""" - length(::AbstractPolynomial) - -The length of the polynomial. -""" -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) = (length(p),) -Base.size(p::AbstractPolynomial, i::Integer) = i <= 1 ? size(p)[i] : 1 -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 -_eltype(::Type{<:AbstractPolynomial}) = nothing -_eltype(::Type{<:AbstractPolynomial{T}}) where {T} = T -function _eltype(P::Type{<:AbstractPolynomial}, p::AbstractPolynomial) - T′ = _eltype(P) - T = isnothing(T′) ? eltype(p) : T′ - T -end - -""" - copy_with_eltype(::Type{T}, [::Val{X}], p::AbstractPolynomial) - -Copy polynomial `p` changing the underlying element type and optionally the symbol. -""" -copy_with_eltype(::Type{T}, ::Val{X}, p::P) where {T, X, S, Y, P <:AbstractPolynomial{S,Y}} = - ⟒(P){T, Symbol(X)}(p.coeffs) -copy_with_eltype(::Type{T}, p::P) where {T, S, Y, P <:AbstractPolynomial{S,Y}} = - copy_with_eltype(T, Val(Y), p) -# easier to type if performance isn't an issue, but could be dropped -#copy_with_eltype(::Type{T}, X, p::P) where {T, S, Y, P<:AbstractPolynomial{S, Y}} = -# copy_with_eltype(Val(T), Val(X), p) -#copy_with_eltype(::Type{T}, p::P) where {T, S, X, P<:AbstractPolynomial{S,X}} = -# copy_with_eltype(Val(T), Val(X), p) - -Base.iszero(p::AbstractPolynomial) = all(iszero, values(p)) - - -# See discussions in https://github.com/JuliaMath/Polynomials.jl/issues/258 -""" - all(pred, poly::AbstractPolynomial) - -Test whether all coefficients of an `AbstractPolynomial` satisfy predicate `pred`. - -You can implement `isreal`, etc., to a `Polynomial` by using `all`. -""" -Base.all(pred, p::AbstractPolynomial{T, X}) where {T,X} = all(pred, values(p)) -""" - any(pred, poly::AbstractPolynomial) - -Test whether any coefficient of an `AbstractPolynomial` satisfies predicate `pred`. -""" -Base.any(pred, p::AbstractPolynomial{T,X}) where {T, X} = any(pred, values(p)) - - - - -""" - map(fn, p::AbstractPolynomial, args...) - -Transform coefficients of `p` by applying a function (or other callables) `fn` to each of them. - -You can implement `real`, etc., to a `Polynomial` by using `map`. The type of `p` may narrow using this function. -""" -function Base.map(fn, p::P, args...) where {P<:AbstractPolynomial} - xs = map(fn, p.coeffs, args...) - R = eltype(xs) - X = indeterminate(p) - return ⟒(P){R,X}(xs) -end - - -""" - isreal(p::AbstractPolynomial) - -Determine whether a polynomial is a real polynomial, i.e., having only real numbers as coefficients. - -See also: [`real`](@ref) -""" -Base.isreal(p::AbstractPolynomial) = all(isreal, p) -""" - real(p::AbstractPolynomial) - -Construct a real polynomial from the real parts of the coefficients of `p`. - -See also: [`isreal`](@ref) - -!!! note - This could cause losing terms in `p`. This method is usually called on polynomials like `p = Polynomial([1, 2 + 0im, 3.0, 4.0 + 0.0im])` where you want to chop the imaginary parts of the coefficients of `p`. -""" -Base.real(p::AbstractPolynomial) = map(real, p) - -""" - isintegral(p::AbstractPolynomial) - -Determine whether a polynomial is an integer polynomial, i.e., having only integers as coefficients. -""" -isintegral(p::AbstractPolynomial) = all(isinteger, p) - -""" - ismonic(p::AbstractPolynomial) - -Determine whether a polynomial is a monic polynomial, i.e., its leading coefficient is one. -""" -ismonic(p::AbstractPolynomial) = isone(convert(Polynomial, p)[end]) - -"`hasnan(p::AbstractPolynomial)` are any coefficients `NaN`" -hasnan(p::AbstractPolynomial) = any(hasnan, p) -hasnan(p::AbstractArray) = any(hasnan.(p)) -hasnan(x) = isnan(x) - -""" - isconstant(::AbstractPolynomial) - -Is the polynomial `p` a constant. -""" -isconstant(p::AbstractPolynomial) = degree(p) <= 0 && firstindex(p) == 0 - -""" - coeffs(::AbstractPolynomial) - -Return the coefficient vector. For a standard basis polynomial these are `[a_0, a_1, ..., a_n]`. -""" -coeffs(p::AbstractPolynomial) = p.coeffs - -# hook in for offset coefficients of Laurent Polynomials -_coeffs(p::AbstractPolynomial) = coeffs(p) - - -# 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)) - -""" - degree(::AbstractPolynomial) - -Return the degree of the polynomial, i.e. the highest exponent in the polynomial that -has a nonzero coefficient. The degree of the zero polynomial is defined to be -1. The default method assumes the basis polynomial, `βₖ` has degree `k`. -""" -degree(p::AbstractPolynomial) = iszero(coeffs(p)) ? -1 : length(coeffs(p)) - 1 + min(0, minimumexponent(p)) - -@deprecate order degree false - - -""" - Polynomials.domain(::Type{<:AbstractPolynomial}) - -Returns the domain of the polynomial. -""" -domain(::Type{<:AbstractPolynomial}) -domain(::P) where {P <: AbstractPolynomial} = domain(P) - -""" - mapdomain(::Type{<:AbstractPolynomial}, x::AbstractArray) - mapdomain(::AbstractPolynomial, x::AbstractArray) - -Given values of x that are assumed to be unbounded (-∞, ∞), return values rescaled to the domain of the given polynomial. - -# Examples -```jldoctest common -julia> using Polynomials - -julia> x = -10:10 --10:10 - -julia> extrema(mapdomain(ChebyshevT, x)) -(-1.0, 1.0) - -``` -""" -function mapdomain(P::Type{<:AbstractPolynomial}, x::AbstractArray) - d = domain(P) - x = collect(x) - x_zerod = x .- minimum(x) - x_scaled = x_zerod .* (last(d) - first(d)) ./ maximum(x_zerod) - x_scaled .+= first(d) - return x_scaled -end -mapdomain(::P, x::AbstractArray) where {P <: AbstractPolynomial} = mapdomain(P, x) - -#= -indexing =# -# minimumexponent(p) returns min(0, minimum(keys(p))) -# For most polynomial types, this is statically known to be zero -# For polynomials that support negative indices, minimumexponent(typeof(p)) -# should return typemin(Int) -minimumexponent(p::AbstractPolynomial) = minimumexponent(typeof(p)) -minimumexponent(::Type{<:AbstractPolynomial}) = 0 -Base.firstindex(p::AbstractPolynomial) = 0 -Base.lastindex(p::AbstractPolynomial) = length(p) - 1 + firstindex(p) -Base.eachindex(p::AbstractPolynomial) = firstindex(p):lastindex(p) -Base.broadcastable(p::AbstractPolynomial) = Ref(p) -degreerange(p::AbstractPolynomial) = firstindex(p):lastindex(p) - -# getindex -function Base.getindex(p::AbstractPolynomial{T}, idx::Int) where {T} - m,M = firstindex(p), lastindex(p) - m > M && return zero(T) - idx < m && throw(BoundsError(p, idx)) - idx > M && return zero(T) - p.coeffs[idx - m + 1] -end -#XXXBase.getindex(p::AbstractPolynomial, idx::Number) = getindex(p, convert(Int, idx)) -Base.getindex(p::AbstractPolynomial, indices) = [p[i] for i in indices] -Base.getindex(p::AbstractPolynomial, ::Colon) = coeffs(p) - -# setindex -function Base.setindex!(p::AbstractPolynomial, value, idx::Int) - n = length(coeffs(p)) - if n ≤ idx - resize!(p.coeffs, idx + 1) - p.coeffs[n + 1:idx] .= 0 - end - p.coeffs[idx + 1] = value - return p -end - -Base.setindex!(p::AbstractPolynomial, value, idx::Number) = - setindex!(p, value, convert(Int, idx)) -Base.setindex!(p::AbstractPolynomial, values, indices) = - [setindex!(p, v, i) for (v, i) in tuple.(values, indices)] -Base.setindex!(p::AbstractPolynomial, values, ::Colon) = - [setindex!(p, v, i) for (v, i) in tuple.(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 _iterate(p, state) - firstindex(p) <= state <= lastindex(p) || return nothing - return p[state], state+1 -end -Base.iterate(p::AbstractPolynomial, state = firstindex(p)) = _iterate(p, state) - -# pairs map i -> aᵢ *possibly* skipping over ai == 0 -# cf. abstractdict.jl -struct PolynomialKeys{P} <: AbstractSet{Int} - p::P -end -struct PolynomialValues{P, T} - p::P - - PolynomialValues{P}(p::P) where {P} = new{P, eltype(p)}(p) - PolynomialValues(p::P) where {P} = new{P, eltype(p)}(p) -end -Base.keys(p::AbstractPolynomial) = PolynomialKeys(p) -Base.values(p::AbstractPolynomial) = PolynomialValues(p) -Base.length(p::PolynomialValues) = length(p.p.coeffs) -Base.eltype(p::PolynomialValues{<:Any,T}) where {T} = T -Base.length(p::PolynomialKeys) = length(p.p.coeffs) -Base.size(p::Union{PolynomialValues, PolynomialKeys}) = (length(p),) -function Base.iterate(v::PolynomialKeys, state = firstindex(v.p)) - firstindex(v.p) <= state <= lastindex(v.p) || return nothing - return state, state+1 -end - -Base.iterate(v::PolynomialValues, state = firstindex(v.p)) = _iterate(v.p, state) - - -# 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...) - isnothing(y) && 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(p.coeffs)) -Base.hash(p::AbstractPolynomial{T,X}, h::UInt) where {T,X} = hash(indeterminate(p), hash(p.coeffs, hash(X,h))) - -# get symbol of polynomial. (e.g. `:x` from 1x^2 + 2x^3... -_indeterminate(::Type{P}) where {P <: AbstractPolynomial} = nothing -_indeterminate(::Type{P}) where {T, X, P <: AbstractPolynomial{T,X}} = X -indeterminate(::Type{P}) where {P <: AbstractPolynomial} = something(_indeterminate(P), :x) -indeterminate(p::P) where {P <: AbstractPolynomial} = _indeterminate(P) - -function indeterminate(PP::Type{P}, p::AbstractPolynomial{T,Y}) where {P <: AbstractPolynomial, T,Y} - X = _indeterminate(PP) - isnothing(X) && return Y - isconstant(p) && return X - assert_same_variable(X,Y) - return X - #X = isnothing(_indeterminate(PP)) ? indeterminate(p) : _indeterminate(PP) -end -indeterminate(PP::Type{P}, x::Symbol) where {P <: AbstractPolynomial} = something(_indeterminate(PP), x) - -#= -zero, one, variable, basis =# - - - -""" - zero(::Type{<:AbstractPolynomial}) - zero(::AbstractPolynomial) - -Returns a representation of 0 as the given polynomial. -""" -function Base.zero(::Type{P}) where {P<:AbstractPolynomial} - T,X = eltype(P), indeterminate(P) - ⟒(P){T,X}(T[]) -end -Base.zero(::Type{P}, var::SymbolLike) where {P <: AbstractPolynomial} = zero(⟒(P){eltype(P),Symbol(var)}) #default 0⋅b₀ -Base.zero(p::P, var=indeterminate(p)) where {P <: AbstractPolynomial} = zero(P, var) -""" - one(::Type{<:AbstractPolynomial}) - one(::AbstractPolynomial) - -Returns a representation of 1 as the given polynomial. -""" -Base.one(::Type{P}) where {P<:AbstractPolynomial} = throw(ArgumentError("No default method defined")) # no default method -Base.one(::Type{P}, var::SymbolLike) where {P <: AbstractPolynomial} = one(⟒(P){eltype(P), Symbol(isnothing(var) ? :x : var)}) -Base.one(p::P, var=indeterminate(p)) where {P <: AbstractPolynomial} = one(P, var) - -Base.oneunit(::Type{P}, args...) where {P <: AbstractPolynomial} = one(P, args...) -Base.oneunit(p::P, args...) where {P <: AbstractPolynomial} = one(p, args...) - - -""" - variable(var=:x) - variable(::Type{<:AbstractPolynomial}, var=:x) - 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)`. - -# Examples -```jldoctest common -julia> using Polynomials - -julia> x = variable() -Polynomial(x) - -julia> p = 100 + 24x - 3x^2 -Polynomial(100 + 24*x - 3*x^2) - -julia> roots((x - 3) * (x + 2)) -2-element Vector{Float64}: - -2.0 - 3.0 - -``` -""" -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 = indeterminate(p)) = variable(typeof(p), var) -variable(var::SymbolLike = :x) = variable(Polynomial{Int}, var) - -# Exported in #470. Exporting was a mistake! -#@variable x -#@variable x::Polynomial -#@variable x::Polynomial{t] -macro variable(x) - Base.depwarn("Export of macro `@variable` is deprecated due to naming conflicts", :variable) - q = Expr(:block) - if isa(x, Expr) && x.head == :(::) - x, P = x.args - push!(q.args, Expr(:(=), esc(x), - Expr(:call, :variable, P, Expr(:quote, x)))) - else - push!(q.args, Expr(:(=), esc(x), - Expr(:call, :variable, Expr(:quote, x)))) - end - push!(q.args, esc(x)) - - q -end - - - -# 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 {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; var=_var) where {P <: AbstractPolynomial} - T,X = eltype(P), Symbol(var) - basis(⟒(P){T,X}, k) -end -basis(p::P, k::Int, _var=indeterminate(p); var=_var) where {P<:AbstractPolynomial} = basis(P, k, var) - -#= -composition -cf. https://github.com/JuliaMath/Polynomials.jl/issues/511 for a paper with implentations - -=# -""" - polynomial_composition(p, q) - -Evaluate `p(q)`, possibly exploiting a faster evaluation scheme, defaulting to `evalpoly`. -""" -function polynomial_composition(p::AbstractPolynomial, q::AbstractPolynomial) - evalpoly(q, p) -end - -#= -arithmetic =# -Scalar = Union{Number, Matrix} - -Base.:-(p::P) where {P <: AbstractPolynomial} = _convert(p, -coeffs(p)) # map(-, p) - -Base.:*(p::AbstractPolynomial, c::Scalar) = scalar_mult(p, c) -Base.:*(c::Scalar, p::AbstractPolynomial) = scalar_mult(c, p) -Base.:*(c::T, p::P) where {T, X, P <: AbstractPolynomial{T,X}} = scalar_mult(c, p) -Base.:*(p::P, c::T) where {T, X, P <: AbstractPolynomial{T,X}} = scalar_mult(p, c) - -# implicitly identify c::Scalar with a constant polynomials -Base.:+(c::Scalar, p::AbstractPolynomial) = +(p, c) -Base.:-(p::AbstractPolynomial, c::Scalar) = +(p, -c) -Base.:-(c::Scalar, p::AbstractPolynomial) = +(-p, c) - -# scalar operations -# no generic p+c, as polynomial addition falls back to scalar ops - - -Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) - - -## addition -## Fall back addition is possible as vector addition with padding by 0s -## Subtypes will likely want to implement both: -## +(p::P,c::Scalar) 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 - -Base.:+(p::AbstractPolynomial) = p - -# polynomial + scalar; implicit identification of c with c*one(p) -Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = scalar_add(p, c) -scalar_add(p::AbstractPolynomial, c) = 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 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 - -# 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}} - cs = degree(p) >= degree(q) ? ⊕(P, p.coeffs, q.coeffs) : ⊕(P, q.coeffs, p.coeffs) - return P(cs) -end - -# 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,p1) - for i in 1:n2 - cs[i] += p2[i] - end - - return cs -end - -# 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] - 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 - #Base.@inline - tuple($(exprs...)) - 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) - p = Dict{Int, R}() - - - # this allocates in the union -# for i in union(eachindex(p1), eachindex(p2)) -# p[i] = p1[i] + p2[i] -# end - - for (i,pi) ∈ pairs(p1) - @inbounds p[i] = pi + get(p2, i, zero(R)) - end - for (i,pi) ∈ pairs(p2) - if iszero(get(p,i,zero(R))) - @inbounds p[i] = get(p1, i, zero(R)) + pi - end - end - - return p - -end - -## -- multiplication - -# Scalar multiplication; no assumption of commutivity -function scalar_mult(p::P, c::S) where {S, T, X, P<:AbstractPolynomial{T,X}} - result = coeffs(p) .* (c,) - ⟒(P){eltype(result), X}(result) -end - -function scalar_mult(c::S, p::P) where {S, T, X, P<:AbstractPolynomial{T, X}} - result = (c,) .* coeffs(p) - ⟒(P){eltype(result), X}(result) -end - -scalar_mult(p1::AbstractPolynomial, p2::AbstractPolynomial) = error("scalar_mult(::$(typeof(p1)), ::$(typeof(p2))) is not defined.") # avoid ambiguity, issue #435 - -# scalar div -Base.:/(p::AbstractPolynomial, c) = scalar_div(p, c) -function scalar_div(p::P, c::S) where {S, T, X, P<:AbstractPolynomial{T, X}} - iszero(p) && return zero(⟒(P){Base.promote_op(/,T,S), X}) - _convert(p, coeffs(p) ./ Ref(c)) -end - -## Polynomial p*q -## Polynomial multiplication formula depend on the particular basis used. The subtype must implement -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) - 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} - n, d = promote(num, den) - return divrem(n, d) -end - -""" - gcd(a::AbstractPolynomial, b::AbstractPolynomial; atol::Real=0, rtol::Real=Base.rtoldefault) - -Find the greatest common denominator of two polynomials recursively using -[Euclid's algorithm](http://en.wikipedia.org/wiki/Polynomial_greatest_common_divisor#Euclid.27s_algorithm). - -# Examples - -```jldoctest common -julia> using Polynomials - -julia> gcd(fromroots([1, 1, 2]), fromroots([1, 2, 3])) -Polynomial(4.0 - 6.0*x + 2.0*x^2) - -``` -""" -function Base.gcd(p1::AbstractPolynomial{T}, p2::AbstractPolynomial{S}; kwargs...) where {T,S} - gcd(promote(p1, p2)...; kwargs...) -end - -function Base.gcd(p1::AbstractPolynomial{T}, p2::AbstractPolynomial{T}; - atol::Real=zero(real(T)), - rtol::Real=Base.rtoldefault(real(T)) - ) where {T} - - - r₀, r₁ = p1, p2 - iter = 1 - itermax = length(r₁) - - while !iszero(r₁) && iter ≤ itermax - _, rtemp = divrem(r₀, r₁) - r₀ = r₁ - r₁ = truncate(rtemp; atol=atol, rtol=rtol) - iter += 1 - end - return r₀ -end - -""" - uvw(p,q; kwargs...) - -return `u` the gcd of `p` and `q`, and `v` and `w`, where `u*v = p` and `u*w = q`. -""" -uvw(p::AbstractPolynomial, q::AbstractPolynomial; kwargs...) = uvw(promote(p,q)...; kwargs...) -#uvw(p1::P, p2::P; kwargs...) where {P <:AbstractPolynomial} = throw(ArgumentError("uvw not defined")) - -""" - div(::AbstractPolynomial, ::AbstractPolynomial) -""" -Base.div(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[1] - -""" - rem(::AbstractPolynomial, ::AbstractPolynomial) -""" -Base.rem(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[2] - -#= -Comparisons =# -Base.isequal(p1::P, p2::P) where {P <: AbstractPolynomial} = hash(p1) == hash(p2) -function Base.:(==)(p1::P, p2::P) where {P <: AbstractPolynomial} - iszero(p1) && iszero(p2) && return true - eachindex(p1) == eachindex(p2) || return false - # coeffs(p1) == coeffs(p2), but non-allocating - p1val = (p1[i] for i in eachindex(p1)) - p2val = (p2[i] for i in eachindex(p2)) - all(((a,b),) -> a == b, zip(p1val, p2val)) -end -function Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial) - if isconstant(p1) - isconstant(p2) && return constantterm(p1) == constantterm(p2) - return false - elseif isconstant(p2) - return false # p1 is not constant - end - check_same_variable(p1, p2) || return false - ==(promote(p1,p2)...) -end -Base.:(==)(p::AbstractPolynomial, n::Scalar) = isconstant(p) && constantterm(p) == n -Base.:(==)(n::Scalar, p::AbstractPolynomial) = p == n - -function Base.isapprox(p1::AbstractPolynomial, p2::AbstractPolynomial; kwargs...) - if isconstant(p1) - isconstant(p2) && return constantterm(p1) == constantterm(p2) - return false - elseif isconstant(p2) - return false - end - assert_same_variable(p1, p2) || return false - isapprox(promote(p1, p2)...; kwargs...) -end - -function Base.isapprox(p1::AbstractPolynomial{T,X}, - p2::AbstractPolynomial{S,X}; - rtol::Real = (Base.rtoldefault(T,S,0)), - atol::Real = 0,) where {T,S,X} - (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons - # copy over from abstractarray.jl - Δ = norm(p1-p2) - if isfinite(Δ) - return Δ <= max(atol, rtol*max(norm(p1), norm(p2))) - else - for i in 0:max(degree(p1), degree(p2)) - isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false - end - return true - end -end - -function Base.isapprox(p1::AbstractPolynomial{T}, - n::S; - rtol::Real = (Base.rtoldefault(T, S, 0)), - atol::Real = 0,) where {T,S} - return isapprox(p1, _convert(p1, [n])) -end - -Base.isapprox(::AbstractPolynomial{T}, ::Missing, args...; kwargs...) where T = - missing -Base.isapprox(::Missing, ::AbstractPolynomial{T}, args...; kwargs...) where T = - missing - -Base.isapprox(n::S, - p1::AbstractPolynomial{T}; - rtol::Real = (Base.rtoldefault(T, S, 0)), - atol::Real = 0,) where {T,S} = isapprox(p1, n, rtol = rtol, atol = atol) diff --git a/src/.#common.jl b/src/.#common.jl deleted file mode 120000 index 645a64a0..00000000 --- a/src/.#common.jl +++ /dev/null @@ -1 +0,0 @@ -jverzani@john-verzanis-macbook-pro.local.3609 \ No newline at end of file diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index a043ccc2..4bda60d9 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -152,7 +152,8 @@ end Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = chop!(copy(p)) chop!(p::AbstractUnivariatePolynomial; kwargs...) = XXX() -chop!!(p::AbstractUnivariatePolynomial; kwargs...) = chop!(p) +# for generic usage, as immutable types are not mutable +chop!!(p::AbstractUnivariatePolynomial; kwargs...) = (p = chop!(p); p) truncate!!(p::AbstractUnivariatePolynomial; kwargs...) = truncate!(p) diff --git a/src/common.jl b/src/common.jl index 4193a1ea..18bfe613 100644 --- a/src/common.jl +++ b/src/common.jl @@ -37,7 +37,8 @@ Polynomial(6 - 5*x + x^2) function fromroots(P::Type{<:AbstractPolynomial}, rs::AbstractVector; var::SymbolLike = :x) x = variable(P, var) p = prod(x-r for r ∈ rs; init=one(x)) - return truncate!(p) + p = truncate!!(p) + p end fromroots(r::AbstractVector{<:Number}; var::SymbolLike = :x) = fromroots(Polynomial, r, var = var) @@ -314,7 +315,7 @@ function truncate!(p::AbstractPolynomial{T}; rtol::Real = Base.rtoldefault(real(T)), atol::Real = 0,) where {T} truncate!(p.coeffs, rtol=rtol, atol=atol) - return chop!(p, rtol = rtol, atol = atol) + chop!(p, rtol = rtol, atol = atol) end ## truncate! underlying storage type diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index 4051d8ab..dbfdcedc 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -140,7 +140,7 @@ function chop_exact_zeros!(d::Dict) end trim_trailing_zeros!!(d::Dict) = chop_exact_zeros!(d) # Not properly named, but what is expected in other constructors -chop!(p::MutableSparsePolynomial; kwargs...) = chop!(p.coeffs; kwargs...) +chop!(p::MutableSparsePolynomial; kwargs...) = (chop!(p.coeffs; kwargs...); p) function chop!(p::Dict; atol=nothing, rtol=nothing) isempty(p.coeffs) && return p δ = something(rtol,0) diff --git a/test/ChebyshevT.jl b/test/ChebyshevT.jl index f3be822b..7d1fdeb8 100644 --- a/test/ChebyshevT.jl +++ b/test/ChebyshevT.jl @@ -15,7 +15,7 @@ @test length(p) == length(coeff) @test size(p) == size(coeff) @test size(p, 1) == size(coeff, 1) - @test_broken typeof(p).parameters[1] == eltype(coeff) # 2 + @test_broken typeof(p).parameters[1] == eltype(coeff) # XXX is 2 now @test typeof(p).parameters[2] == eltype(coeff) @test eltype(p) == eltype(coeff) end @@ -36,7 +36,7 @@ end p = zero(ChebyshevT{Int}) @test_broken p.coeffs == [0] - @test p.coeffs == Int[] # XXX + @test p.coeffs == Int[] p = one(ChebyshevT{Int}) @test p.coeffs == [1] From 0022fec1fd3acf5194fa1e756eea839f05a4d37a Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 3 Aug 2023 10:53:54 -0400 Subject: [PATCH 30/31] WIP: reshuffle --- src/abstract-polynomial.jl | 3 --- src/common.jl | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl index 4bda60d9..f2faad3e 100644 --- a/src/abstract-polynomial.jl +++ b/src/abstract-polynomial.jl @@ -152,9 +152,6 @@ end Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = chop!(copy(p)) chop!(p::AbstractUnivariatePolynomial; kwargs...) = XXX() -# for generic usage, as immutable types are not mutable -chop!!(p::AbstractUnivariatePolynomial; kwargs...) = (p = chop!(p); p) -truncate!!(p::AbstractUnivariatePolynomial; kwargs...) = truncate!(p) ## --- diff --git a/src/common.jl b/src/common.jl index 18bfe613..82ac3d4a 100644 --- a/src/common.jl +++ b/src/common.jl @@ -459,6 +459,9 @@ function Base.chop(p::AbstractPolynomial{T}; end +# for generic usage, as immutable types are not mutable +chop!!(p::AbstractPolynomial; kwargs...) = (p = chop!(p); p) +truncate!!(p::AbstractPolynomial; kwargs...) = truncate!(p) """ From 7b3fe93fd9aa88e07139ad22db4ff7c58a1cc626 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 3 Aug 2023 11:38:54 -0400 Subject: [PATCH 31/31] fix truncate --- src/common.jl | 4 ++-- .../mutable-sparse-polynomial.jl | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/common.jl b/src/common.jl index 82ac3d4a..87ea60a5 100644 --- a/src/common.jl +++ b/src/common.jl @@ -332,9 +332,9 @@ function truncate!(ps::Vector{T}; nothing end -function truncate!(ps::Dict{Int,T}; +function truncate!(ps::Dict{S,T}; rtol::Real = Base.rtoldefault(real(T)), - atol::Real = 0,) where {T} + atol::Real = 0,) where {S,T} isempty(ps) && return nothing max_coeff = norm(values(ps), Inf) diff --git a/src/polynomial-basetypes/mutable-sparse-polynomial.jl b/src/polynomial-basetypes/mutable-sparse-polynomial.jl index dbfdcedc..fc5af259 100644 --- a/src/polynomial-basetypes/mutable-sparse-polynomial.jl +++ b/src/polynomial-basetypes/mutable-sparse-polynomial.jl @@ -141,15 +141,15 @@ end trim_trailing_zeros!!(d::Dict) = chop_exact_zeros!(d) # Not properly named, but what is expected in other constructors chop!(p::MutableSparsePolynomial; kwargs...) = (chop!(p.coeffs; kwargs...); p) -function chop!(p::Dict; atol=nothing, rtol=nothing) - isempty(p.coeffs) && return p +function chop!(d::Dict; atol=nothing, rtol=nothing) + isempty(d) && return d δ = something(rtol,0) ϵ = something(atol,0) - τ = max(ϵ, norm(values(p.coeffs),2) * δ) - for (i,pᵢ) ∈ pairs(p) - abs(pᵢ) ≤ τ && delete!(p.coeffs, i) + τ = max(ϵ, norm(values(d),2) * δ) + for (i,pᵢ) ∈ pairs(d) + abs(pᵢ) ≤ τ && delete!(d, i) end - p + d end ## ---