From 27d548fb9dfa2ec6ff93ad83f19403b7608bff50 Mon Sep 17 00:00:00 2001 From: john verzani Date: Tue, 15 Aug 2023 13:28:01 -0400 Subject: [PATCH] decouple basis from storage type (#517) * adding in explicit basis code * add polynomial_composition code path * fix bug in scalar_add * WIP: coeffs * WIP: doc adjustments * WIP: fiddle with promotion * WIP: identify DescriptorSystems issue * WIP: More promotions to match old behaviours * WIP: run doctests, adjust Chebyshev printing * WIP: convert * WIP: change Polynomial type; migrate standard-basis.jl * WIP: work to shift to new base type for most polys * doc fix=true * WIP: fixes for downstream * relax error type for nightly * add skip, not broken for a test * aqua test * WIP, update docs, reduce redundancies * WIP: more cleanup * don't test Aqua on 1.6 * clean up * test that copy is not alias * clean up identified invalidations * remove tests of deprecated (now removed) functions * reorg docs * fix sparse * * reorg docs * fix sparse * * SP test * WIP: more cleanup * version bump; signal breaking changes --- .github/workflows/downstream.yml | 11 +- .gitignore | 1 + Project.toml | 11 +- README.md | 322 +-------- docs/src/extending.md | 418 +++++++++--- docs/src/extensions.md | 103 +++ docs/src/index.md | 106 +-- docs/src/polynomials/chebyshev.md | 2 +- docs/src/reference.md | 68 +- src/Polynomials.jl | 39 +- src/abstract-polynomial.jl | 318 +++++++++ src/abstract.jl | 56 +- src/common.jl | 475 ++++++++------ src/compat.jl | 17 - src/contrib.jl | 12 +- src/{polynomials => legacy}/Poly.jl | 105 ++- src/legacy/misc.jl | 33 + src/{ => legacy}/pade.jl | 0 .../immutable-dense-polynomial.jl | 303 +++++++++ .../mutable-dense-laurent-polynomial.jl | 242 +++++++ .../mutable-dense-polynomial.jl | 189 ++++++ .../mutable-dense-view-polynomial.jl | 118 ++++ .../mutable-sparse-polynomial.jl | 210 ++++++ src/polynomials/ImmutablePolynomial.jl | 271 -------- src/polynomials/LaurentPolynomial.jl | 612 ------------------ src/polynomials/SparsePolynomial.jl | 261 -------- .../{ChebyshevT.jl => chebyshev.jl} | 252 ++++---- src/polynomials/factored_polynomial.jl | 30 +- src/polynomials/multroot.jl | 56 +- src/polynomials/ngcd.jl | 24 +- src/polynomials/pi_n_polynomial.jl | 63 -- .../standard-basis/immutable-polynomial.jl | 134 ++++ .../standard-basis/laurent-polynomial.jl | 266 ++++++++ .../standard-basis/pn-polynomial.jl | 30 + src/polynomials/standard-basis/polynomial.jl | 36 ++ .../standard-basis/sparse-polynomial.jl | 120 ++++ .../{ => standard-basis}/standard-basis.jl | 393 +++++------ src/precompiles.jl | 2 +- src/promotions.jl | 26 + src/rational-functions/common.jl | 10 +- src/rational-functions/rational-function.jl | 9 +- src/show.jl | 19 +- test/ChebyshevT.jl | 11 +- test/Poly.jl | 2 +- test/Project.toml | 1 + test/StandardBasis.jl | 313 +++++---- test/aqua.jl | 6 + test/rational-functions.jl | 40 +- test/runtests.jl | 1 + 49 files changed, 3676 insertions(+), 2471 deletions(-) create mode 100644 docs/src/extensions.md create mode 100644 src/abstract-polynomial.jl delete mode 100644 src/compat.jl rename src/{polynomials => legacy}/Poly.jl (63%) create mode 100644 src/legacy/misc.jl rename src/{ => legacy}/pade.jl (100%) create mode 100644 src/polynomial-container-types/immutable-dense-polynomial.jl create mode 100644 src/polynomial-container-types/mutable-dense-laurent-polynomial.jl create mode 100644 src/polynomial-container-types/mutable-dense-polynomial.jl create mode 100644 src/polynomial-container-types/mutable-dense-view-polynomial.jl create mode 100644 src/polynomial-container-types/mutable-sparse-polynomial.jl delete mode 100644 src/polynomials/ImmutablePolynomial.jl delete mode 100644 src/polynomials/LaurentPolynomial.jl delete mode 100644 src/polynomials/SparsePolynomial.jl rename src/polynomials/{ChebyshevT.jl => chebyshev.jl} (67%) delete mode 100644 src/polynomials/pi_n_polynomial.jl create mode 100644 src/polynomials/standard-basis/immutable-polynomial.jl create mode 100644 src/polynomials/standard-basis/laurent-polynomial.jl create mode 100644 src/polynomials/standard-basis/pn-polynomial.jl create mode 100644 src/polynomials/standard-basis/polynomial.jl create mode 100644 src/polynomials/standard-basis/sparse-polynomial.jl rename src/polynomials/{ => standard-basis}/standard-basis.jl (69%) create mode 100644 src/promotions.jl create mode 100644 test/aqua.jl diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index d3157ab9..a0c2ab6b 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -14,12 +14,17 @@ jobs: strategy: fail-fast: false matrix: - julia-version: [1,1.6] + julia-version: [1] 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: andreasvarga, repo: MatrixPencils.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} + - {user: JuliaGNI, repo: RungeKutta.jl, group: All} steps: - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 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/Project.toml b/Project.toml index 77992a5c..c7cc3058 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "Polynomials" uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" license = "MIT" author = "JuliaMath" -version = "3.2.15" +version = "4.0.0" [deps] ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" @@ -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,19 +27,21 @@ ChainRulesCore = "1" MakieCore = "0.6" MutableArithmetics = "1" RecipesBase = "0.7, 0.8, 1" +Setfield = "1" julia = "1.6" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" 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" [targets] -test = ["ChainRulesCore", "DualNumbers", "LinearAlgebra", "SparseArrays", "OffsetArrays", "SpecialFunctions", "Test"] +test = ["Aqua", "ChainRulesCore", "DualNumbers", "LinearAlgebra", "SparseArrays", "OffsetArrays", "SpecialFunctions", "Test"] diff --git a/README.md b/README.md index 76284118..30d6840a 100644 --- a/README.md +++ b/README.md @@ -1,326 +1,8 @@ # Polynomials.jl -Basic arithmetic, integration, differentiation, evaluation, and root finding over dense univariate [polynomials](https://en.wikipedia.org/wiki/Polynomial). +Basic arithmetic, integration, differentiation, evaluation, root finding, and fitting for +univariate [polynomials](https://en.wikipedia.org/wiki/Polynomial) in [Julia](https://julialang.org/). [![](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaMath.github.io/Polynomials.jl/stable) [![CI](https://github.com/JuliaMath/Polynomials.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/JuliaMath/Polynomials.jl/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/JuliaMath/Polynomials.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaMath/Polynomials.jl) - - -## Installation - -```julia -(v1.6) pkg> add Polynomials -``` - -This package supports Julia v1.6 and later. - -## Available Types of Polynomials - -* `Polynomial` –⁠ standard basis polynomials, $a(x) = a_0 + a_1 x + a_2 x^2 + … + a_n x^n$ for $n ≥ 0$. -* `ImmutablePolynomial` –⁠ standard basis polynomials backed by a [Tuple type](https://docs.julialang.org/en/v1/manual/functions/#Tuples-1) for faster evaluation of values -* `SparsePolynomial` –⁠ standard basis polynomial backed by a [dictionary](https://docs.julialang.org/en/v1/base/collections/#Dictionaries-1) to hold sparse high-degree polynomials -* `LaurentPolynomial` –⁠ [Laurent polynomials](https://docs.julialang.org/en/v1/base/collections/#Dictionaries-1), $a(x) = a_m x^m + … + a_n x^n$ for $m ≤ n$ and $m,n ∈ ℤ$. This is backed by an [offset array](https://github.com/JuliaArrays/OffsetArrays.jl); for example, if $m<0$ and $n>0$, we obtain $a(x) = a_m x^m + … + a_{-1} x^{-1} + a_0 + a_1 x + … + a_n x^n$ -* `FactoredPolynomial` –⁠ standard basis polynomials, storing the roots, with multiplicity, and leading coefficient of a polynomial -* `ChebyshevT` –⁠ [Chebyshev polynomials](https://en.wikipedia.org/wiki/Chebyshev_polynomials) of the first kind -* `RationalFunction` - a type for ratios of polynomials. - -## Usage - -```julia -julia> using Polynomials -``` - -### Construction and Evaluation - -Construct a polynomial from an array (a vector) of its coefficients, lowest order first. - -```julia -julia> Polynomial([1,0,3,4]) -Polynomial(1 + 3*x^2 + 4*x^3) -``` - -Optionally, the variable of the polynomial can be specified. - -```julia -julia> Polynomial([1,2,3], :s) -Polynomial(1 + 2*s + 3*s^2) -``` - -Construct a polynomial from its roots. - -```julia -julia> fromroots([1,2,3]) # (x-1)*(x-2)*(x-3) -Polynomial(-6 + 11*x - 6*x^2 + x^3) -``` - -Evaluate the polynomial `p` at `x`. - -```julia -julia> p = Polynomial([1, 0, -1]); -julia> p(0.1) -0.99 -``` - -### Arithmetic - -Methods are added to the usual arithmetic operators so that they work on polynomials, and combinations of polynomials and scalars. - -```julia -julia> p = Polynomial([1,2]) -Polynomial(1 + 2*x) - -julia> q = Polynomial([1, 0, -1]) -Polynomial(1 - x^2) - -julia> p - q -Polynomial(2*x + x^2) - -julia> p = Polynomial([1,2]) -Polynomial(1 + 2*x) - -julia> q = Polynomial([1, 0, -1]) -Polynomial(1 - x^2) - -julia> 2p -Polynomial(2 + 4*x) - -julia> 2+p -Polynomial(3 + 2*x) - -julia> p - q -Polynomial(2*x + x^2) - -julia> p * q -Polynomial(1 + 2*x - x^2 - 2*x^3) - -julia> q / 2 -Polynomial(0.5 - 0.5*x^2) - -julia> q ÷ p # `div`, also `rem` and `divrem` -Polynomial(0.25 - 0.5*x) -``` - -Most operations involving polynomials with different variables will error. - -```julia -julia> p = Polynomial([1, 2, 3], :x); -julia> q = Polynomial([1, 2, 3], :s); -julia> p + q -ERROR: ArgumentError: Polynomials have different indeterminates -``` - -#### Construction and Evaluation - -While polynomials of type `Polynomial` are mutable objects, operations such as -`+`, `-`, `*`, always create new polynomials without modifying its arguments. -The time needed for these allocations and copies of the polynomial coefficients -may be noticeable in some use cases. This is amplified when the coefficients -are for instance `BigInt` or `BigFloat` which are mutable themselves. -This can be avoided by modifying existing polynomials to contain the result -of the operation using the [MutableArithmetics (MA) API](https://github.com/jump-dev/MutableArithmetics.jl). - -Consider for instance the following arrays of polynomials -```julia -using Polynomials -d, m, n = 30, 20, 20 -p(d) = Polynomial(big.(1:d)) -A = [p(d) for i in 1:m, j in 1:n] -b = [p(d) for i in 1:n] -``` - -In this case, the arrays are mutable objects for which the elements are mutable -polynomials which have mutable coefficients (`BigInt`s). -These three nested levels of mutable objects communicate with the MA -API in order to reduce allocation. -Calling `A * b` requires approximately 40 MiB due to 2 M allocations -as it does not exploit any mutability. - -Using - -```julia -using PolynomialsMutableArithmetics -``` - -to register `Polynomials` with `MutableArithmetics`, then multiplying with: - -```julia -using MutableArithmetics -const MA = MutableArithmetics -MA.operate(*, A, b) -``` - -exploits the mutability and hence only allocates approximately 70 KiB due to 4 k -allocations. - -If the resulting vector is already allocated, e.g., - -```julia -z(d) = Polynomial([zero(BigInt) for i in 1:d]) -c = [z(2d - 1) for i in 1:m] -``` - -then we can exploit its mutability with - -```julia -MA.operate!(MA.add_mul, c, A, b) -``` - -to reduce the allocation down to 48 bytes due to 3 allocations. - -These remaining allocations are due to the `BigInt` buffer used to -store the result of intermediate multiplications. This buffer can be -preallocated with: - -```julia -buffer = MA.buffer_for(MA.add_mul, typeof(c), typeof(A), typeof(b)) -MA.buffered_operate!(buffer, MA.add_mul, c, A, b) -``` - -then the second line is allocation-free. - -The `MA.@rewrite` macro rewrite an expression into an equivalent code that -exploit the mutability of the intermediate results. -For instance -```julia -MA.@rewrite(A1 * b1 + A2 * b2) -``` -is rewritten into -```julia -c = MA.operate!(MA.add_mul, MA.Zero(), A1, b1) -MA.operate!(MA.add_mul, c, A2, b2) -``` -which is equivalent to -```julia -c = MA.operate(*, A1, b1) -MA.mutable_operate!(MA.add_mul, c, A2, b2) -``` - -*Note that currently, only the `Polynomial` type implements the API and it only -implements part of it.* - -### Integrals and Derivatives - -Integrate the polynomial `p` term by term, optionally adding a constant -term `k`. The degree of the resulting polynomial is one higher than the -degree of `p` (for a nonzero polynomial). - -```julia -julia> integrate(Polynomial([1, 0, -1])) -Polynomial(1.0*x - 0.3333333333333333*x^3) - -julia> integrate(Polynomial([1, 0, -1]), 2) -Polynomial(2.0 + 1.0*x - 0.3333333333333333*x^3) -``` - -Differentiate the polynomial `p` term by term. For non-zero -polynomials the degree of the resulting polynomial is one lower than -the degree of `p`. - -```julia -julia> derivative(Polynomial([1, 3, -1])) -Polynomial(3 - 2*x) -``` - -### Root-finding - - -Return the roots (zeros) of `p`, with multiplicity. The number of -roots returned is equal to the degree of `p`. By design, this is not type-stable, the returned roots may be real or complex. - -```julia -julia> roots(Polynomial([1, 0, -1])) -2-element Vector{Float64}: - -1.0 - 1.0 - -julia> roots(Polynomial([1, 0, 1])) -2-element Vector{ComplexF64}: - 0.0 - 1.0im - 0.0 + 1.0im - -julia> roots(Polynomial([0, 0, 1])) -2-element Vector{Float64}: - 0.0 - 0.0 -``` - -### Fitting arbitrary data - -Fit a polynomial (of degree `deg` or less) to `x` and `y` using a least-squares approximation. - -```julia -julia> xs = 0:4; ys = @. exp(-xs) + sin(xs); - -julia> fit(xs, ys) |> p -> round.(coeffs(p), digits=4) |> Polynomial -Polynomial(1.0 + 0.0593*x + 0.3959*x^2 - 0.2846*x^3 + 0.0387*x^4) - -julia> fit(ChebyshevT, xs, ys, 2) |> p -> round.(coeffs(p), digits=4) |> ChebyshevT -ChebyshevT(0.5413⋅T_0(x) - 0.8991⋅T_1(x) - 0.4238⋅T_2(x)) -``` - -Visual example: - -![fit example](https://user-images.githubusercontent.com/14099459/70382587-9e055500-1902-11ea-8952-3f03ae08b7dc.png) - -### Other methods - -Polynomial objects also have other methods: - -* For standard basis polynomials, 0-based indexing is used to extract - the coefficients of `[a0, a1, a2, ...]`; for mutable polynomials, - coefficients may be changed using indexing notation. - -* `coeffs`: returns the coefficients - -* `degree`: returns the polynomial degree, `length` is number of stored coefficients - -* `variable`: returns the polynomial symbol as a polynomial in the underlying type - -* `LinearAlgebra.norm`: find the `p`-norm of a polynomial - -* `conj`: finds the conjugate of a polynomial over a complex field - -* `truncate`: set to 0 all small terms in a polynomial; - -* `chop` chops off any small leading values that may arise due to floating point operations. - -* `gcd`: greatest common divisor of two polynomials. - -* `Pade`: Return the - [Padé approximant](https://en.wikipedia.org/wiki/Pad%C3%A9_approximant) of order `m/n` for a polynomial as a `Pade` object. - - -## Related Packages - -* [StaticUnivariatePolynomials.jl](https://github.com/tkoolen/StaticUnivariatePolynomials.jl) Fixed-size univariate polynomials backed by a Tuple - -* [MultiPoly.jl](https://github.com/daviddelaat/MultiPoly.jl) for sparse multivariate polynomials - -* [DynamicPolynomials.jl](https://github.com/JuliaAlgebra/DynamicPolynomials.jl) Multivariate polynomials implementation of commutative and non-commutative variables - -* [MultivariatePolynomials.jl](https://github.com/JuliaAlgebra/MultivariatePolynomials.jl) for multivariate polynomials and moments of commutative or non-commutative variables - -* [PolynomialRings.jl](https://github.com/tkluck/PolynomialRings.jl) A library for arithmetic and algebra with multi-variable polynomials. - -* [AbstractAlgebra.jl](https://github.com/wbhart/AbstractAlgebra.jl), [Nemo.jl](https://github.com/wbhart/Nemo.jl) for generic polynomial rings, matrix spaces, fraction fields, residue rings, power series, [Hecke.jl](https://github.com/thofma/Hecke.jl) for algebraic number theory. - -* [LaurentPolynomials.jl](https://github.com/jmichel7/LaurentPolynomials.jl) A package for Laurent polynomials. - -* [CommutativeAlgebra.jl](https://github.com/KlausC/CommutativeRings.jl) the start of a computer algebra system specialized to discrete calculations with support for polynomials. - -* [PolynomialRoots.jl](https://github.com/giordano/PolynomialRoots.jl) for a fast complex polynomial root finder. For larger degree problems, also [FastPolynomialRoots](https://github.com/andreasnoack/FastPolynomialRoots.jl) and [AMRVW](https://github.com/jverzani/AMRVW.jl). For real roots only [RealPolynomialRoots](https://github.com/jverzani/RealPolynomialRoots.jl). - - -* [SpecialPolynomials.jl](https://github.com/jverzani/SpecialPolynomials.jl) A package providing various polynomial types beyond the standard basis polynomials in `Polynomials.jl`. Includes interpolating polynomials, Bernstein polynomials, and classical orthogonal polynomials. - -* [ClassicalOrthogonalPolynomials.jl](https://github.com/JuliaApproximation/ClassicalOrthogonalPolynomials.jl) A Julia package for classical orthogonal polynomials and expansions. Includes `chebyshevt`, `chebyshevu`, `legendrep`, `jacobip`, `ultrasphericalc`, `hermiteh`, and `laguerrel`. The same repository includes `FastGaussQuadrature.jl`, `FastTransforms.jl`, and the `ApproxFun` packages. - - -## Legacy code - -As of v0.7, the internals of this package were greatly generalized and new types and method names were introduced. For compatibility purposes, legacy code can be run after issuing `using Polynomials.PolyCompat`. - -## Contributing - -If you are interested in contributing, feel free to open an issue or pull request to get started. diff --git a/docs/src/extending.md b/docs/src/extending.md index 9a815044..ebecb27b 100644 --- a/docs/src/extending.md +++ b/docs/src/extending.md @@ -1,72 +1,331 @@ # Extending Polynomials -The [`AbstractPolynomial`](@ref) type was made to be extended via a rich interface. +The [`AbstractUnivaeriatePolynomial`](@ref) type was made to be extended. -```@docs -AbstractPolynomial -``` +A polynomial's coefficients are relative to some *basis*. The `Polynomial` type relates coefficients `[a0, a1, ..., an]`, say, to the polynomial ``a_0 + a_1\cdot x + a_2\cdot x^2 + \cdots + a_n\cdot x^n``, through the standard basis ``1, x, x^2, ..., x^n``. New polynomial types typically represent the polynomial through a different basis. For example, `CheyshevT` uses a basis ``T_0=1, T_1=x, T_2=2x^2-1, \cdots, T_n = 2xT_{n-1} - T_{n-2}``. For this type the coefficients `[a0,a1,...,an]` are associated with the polynomial ``a0\cdot T_0 + a_1 \cdot T_1 + \cdots + a_n\cdot T_n`. -A polynomial's coefficients are relative to some *basis*. The `Polynomial` type relates coefficients `[a0, a1, ..., an]`, say, to the polynomial `a0 + a1*x + a2*x^ + ... + an*x^n`, through the standard basis `1, x, x^2, ..., x^n`. New polynomial types typically represent the polynomial through a different basis. For example, `CheyshevT` uses a basis `T_0=1, T_1=x, T_2=2x^2-1, ..., T_n = 2xT_{n-1} - T_{n-2}`. For this type the coefficients `[a0,a1,...,an]` are associated with the polynomial `a0*T0 + a1*T_1 + ... + an*T_n`. +A polynomial type consists of a container type (with parent type `AbstractUnivariatePolynomial`) and a basis type (with parent type `AbstractBasis`). There a several different storage types implemented. -To implement a new polynomial type, `P`, the following methods should -be implemented. +To implement a new polynomial type, `P`, the following methods should be implemented: -!!! note - Promotion rules will always coerce towards the [`Polynomial`](@ref) type, so not all methods have to be implemented if you provide a conversion function. - -As always, if the default implementation does not work or there are more efficient ways of implementing, feel free to overwrite functions from `common.jl` for your type. | Function | Required | Notes | |----------|:--------:|:------------| -| Constructor | x | | -| Type function (`(::P)(x)`) | x | | -| `convert(::Polynomial, ...)` | | Not required, but the library is built off the [`Polynomial`](@ref) type, so all operations are guaranteed to work with it. Also consider writing the inverse conversion method. | -| `Polynomials.evalpoly(x, p::P)` | to evaluate the polynomial at `x` (`Base.evalpoly` okay post `v"1.4.0"`) | -| `Polynomials.domain` | x | Should return a `Polynomials.Interval` instance| -| `vander` | | Required for [`fit`](@ref) | -| `companion` | | Required for [`roots`](@ref) | -| `*(::P, ::P)` | | Multiplication of polynomials | -| `divrem` | | Required for [`gcd`](@ref)| -| `one`| | Convenience to find constant in new basis | -| `variable`| | Convenience to find monomial `x` in new basis| - -Check out both the [`Polynomial`](@ref) and [`ChebyshevT`](@ref) for examples of this interface being extended. - -## Example - -The following shows a minimal example where the polynomial aliases the vector defining the coefficients. -The constructor ensures that there are no trailing zeros. The `@register` call ensures a common interface. This example subtypes `StandardBasisPolynomial`, not `AbstractPolynomial`, and consequently inherits the methods above that otherwise would have been required. For other bases, more methods may be necessary to define (again, refer to [`ChebyshevT`](@ref) for an example). - -```jldoctest AliasPolynomial +| A container type | x | Usually selected from an available one. | +| A basis type | x | | +| `variable` | | Convenience to find the monomial `x` in the new basis.| +| `Base.evalpoly(x, p::P)` | x | To evaluate the polynomial at `x` | +| `*(::P, ::P)` | | Multiplication of polynomials | +| `convert(::P, p::Polynomial)` | Defaults to polynomial evaluation. Can be used to define `*` by round trip through `Polynomial` type| +| `convert(::Polynomial, p)` | | Defaults to polynomial evaluation, which uses `evalpoly`, `variable`, `*`| +| `scalar_add(c::S, ::P)` | | Scalar addition. Default requires `one` to be defined. | +| `one` | x | Convenience to find the constant $1$ in the new basis. | +| `map(f, p)` | x | Used to define scalar multiplication | +| `divrem` | | Required for [`gcd`](@ref)| +| `vander` | | Required for [`fit`](@ref) | +| `companion` | | Required for [`roots`](@ref) | +| `Polynomials.domain` | | Should return a `Polynomials.Interval` instance| + +As always, if the default implementation does not work or there are more efficient ways of implementing, feel free to overwrite functions from `common.jl` for your type. + +The general idea is the container type should provide the vector operations of polynomial addition, subtraction, and scalar multiplication. +The latter is generically implemented through a `map(f,p)` method. The second example illustrates, though it isn't expected that container types will need being defined by users of this package. + +The basis type directs dispatch for other operations and allows definitions for `one` and `variable`. An `evalpoly` method may be defined for a given basis type, though specializations based on the container may be desirable. + +Methods like `*` will typically need to consider both the underlying container type and the basis, though if `convert` methods are defined, the defaults can be utilized as converting to the `Polynomial` type, performing the operation, then converting back is possible, though likely not as efficient. + + +!!! note + Most promotion rules will coerce towards the [`Polynomial`](@ref) type, so not all methods have to be implemented if you provide a conversion function. + + + +## A new basis type + +The generalized Laguerre polynomials 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 show how to define a new basis type, `LaguerreBasis`, leveraging one of the existing container types. +In this example our basis type has a parameter. The `ChebyshevT` type, gives a related example of how this task can be implemented. + + +First we load the package and import a few non-exported functions: + +```jldoctest abstract_univariate_polynomial +julia> using Polynomials; + +julia> import Polynomials: AbstractUnivariatePolynomial, AbstractBasis, MutableDensePolynomial; +``` + +We define the basis with: + +```jldoctest abstract_univariate_polynomial +julia> struct LaguerreBasis{alpha} <: AbstractBasis end + +julia> Polynomials.basis_symbol(::Type{<:AbstractUnivariatePolynomial{LaguerreBasis{α},T,X}}) where {α,T,X} = + "L^$(α)" +``` + +We added a method to `basis_symbol` to show this basis. The display of the basis symbol has a poor default. The method above requires the full type, as the indeterminate, `X`, may be part of the desired output. More generally, `Polynomials.printbasis` can have methods added to adjust for different display types. + +Polynomial types can be initiated through specifying a storage type and a basis type, say: + +```jldoctest abstract_univariate_polynomial +julia> P = MutableDensePolynomial{LaguerreBasis{0}} +MutableDensePolynomial{LaguerreBasis{0}} +``` + +Instances can now be created: + +```jldoctest abstract_univariate_polynomial +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 polynomial types have vector addition and scalar multiplication defined, as these are basis independent: + +```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) + +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 and polynomial multiplication; and no defaults for `one` (used by default for scalar addition), and `variable` (used by default in conversion). + +For the Laguerre Polynomials, Clenshaw recursion can be used for evaluation. + +```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) +``` +Internally, `evalpoly` is called so we forward that method. + +```jldoctest abstract_univariate_polynomial +julia> Polynomials.evalpoly(x, p::P) where {P<:AbstractUnivariatePolynomial{<:LaguerreBasis}} = + clenshaw_eval(p, x) +``` + +We test this 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) + +julia> x = variable(Polynomial) +Polynomial(1.0*x) + +julia> p(x) +Polynomial(1.0 - 2.0*x + 0.5*x^2) +``` + +This shows evaluation works and also that conversion to the `Polynomial` type is available through polynomial evaluation. This is used by default by `convert`, so we immediately have other `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) +``` + +The values of `one` and `variable` are straightforward to define, as ``L_0=1`` and ``L_1=1 - x`` or ``x = L_0 - 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 +``` + +Scalar addition defaults to a call to `one(p)`, so this is now defined: + +```jldoctest abstract_univariate_polynomial +julia> 2 + p +MutableDensePolynomial(2L^0_0 + L^0_2) +``` + +Often it is more performant to implement a specific method for `scalar_add`. Here 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) +``` + +Multiplication defaults to a code path where the two polynomials are promoted to a common type and then multiplied. +Here 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, but that isn't so illustrative for this example. See the `SpecialPolynomials` package for an implementation. + +```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, as we have defined a `variable` method. + +## A new container type + +This example shows how to make a new container type, though this should be unnecessary, given the current variety, there may be gains to be had (e.g. an immutable, sparse type?) +In this case, we offer a minimal example where the polynomial type aliases the vector defining the coefficients is created. For other bases, more methods may be necessary to define (again, refer to ChebyshevT for an example). + + +We have two constructor methods. The first is the typical code path. It makes a copy of the coefficients and then wraps those within the polynomial container type. For performance reasons, generically it is helpful to pass in a flag to indicate no copying or checking of the input is needed (`Val{false}`). This is used by some inherited methods when we specialize to the `StandardBasis` type. Generically, a container type *may* accept an offset, though this type won't; a `0`-based vector is implicit. + +```jldoctest new_container_type julia> using Polynomials -julia> struct AliasPolynomial{T <: Number, X} <: Polynomials.StandardBasisPolynomial{T, X} - coeffs::Vector{T} - function AliasPolynomial{T, X}(coeffs::Vector{S}) where {T, X, S} - p = new{T,X}(coeffs) - chop!(p) - end +julia> struct AliasPolynomialType{B,T,X} <: Polynomials.AbstractDenseUnivariatePolynomial{B, T, X} + coeffs::Vector{T} + function AliasPolynomialType{B, T, X}(coeffs::AbstractVector{S}, o::Int=0) where {B, T, S, X} + new{B,T,Symbol(X)}(convert(Vector{T}, copy(coeffs))) + end + function AliasPolynomialType{B, T, X}(::Val{false}, coeffs::AbstractVector{S}, o::Int=0) where {B, T, S, X} + new{B,T,Symbol(X)}(convert(Vector{T}, coeffs)) + end + end + +julia> Polynomials.@poly_register AliasPolynomialType +``` + +The call to `@poly_register` adds many different means to construct polynomials of this type along with some other default methods. + +A few methods need defining to get indexing to work: + +```jldoctest new_container_type +julia> Base.firstindex(p::AliasPolynomialType) = 0 + +julia> Base.lastindex(p::AliasPolynomialType) = length(p.coeffs) - 1 + +``` + +```jldoctest new_container_type +julia> Polynomials.constructorof(::Type{<:AliasPolynomialType{B}}) where {B} = AliasPolynomialType{B} + +``` + +We need to add in the vector-space operations: + +```jldoctest new_container_type +julia> function Base.:+(p::AliasPolynomialType{B,T,X}, q::AliasPolynomialType{B,S,X}) where {B,S,T,X} + R = promote_type(T,S) + n = maximum(degree, (p,q)) + cs = [p[i] + q[i] for i in 0:n] + AliasPolynomialType{B,R,X}(Val(false), cs) # save a copy + end + +julia> function Base.:-(p::AliasPolynomialType{B,T,X}, q::AliasPolynomialType{B,S,X}) where {B,S,T,X} + R = promote_type(T,S) + n = maximum(degree, (p,q)) + cs = [p[i] - q[i] for i in 0:n] + AliasPolynomialType{B,R,X}(Val(false), cs) + end + +julia> function Base.map(fn, p::P) where {B,T,X,P<:AliasPolynomialType{B,T,X}} + cs = map(fn, p.coeffs) + R = eltype(cs) + AliasPolynomialType{B,R,X}(Val(false), cs) end -julia> Polynomials.@register AliasPolynomial +``` + + +A type and a basis defines a polynomial type. +This example uses the `StandardBasis` basis type and consequently inherits the methods mentioned above that otherwise would need implementing. + +```jldoctest new_container_type +julia> AliasPolynomial = AliasPolynomialType{Polynomials.StandardBasis}; + ``` To see this new polynomial type in action, we have: -```jldoctest AliasPolynomial +```jldoctest new_container_type julia> xs = [1,2,3,4]; julia> p = AliasPolynomial(xs) -AliasPolynomial(1 + 2*x + 3*x^2 + 4*x^3) +AliasPolynomialType(1 + 2*x + 3*x^2 + 4*x^3) julia> q = AliasPolynomial(1.0, :y) -AliasPolynomial(1.0) - -julia> p + q -AliasPolynomial(2.0 + 2.0*x + 3.0*x^2 + 4.0*x^3) +AliasPolynomialType(1.0) -julia> p * p -AliasPolynomial(1 + 4*x + 10*x^2 + 20*x^3 + 25*x^4 + 24*x^5 + 16*x^6) +julia> 2p - q +AliasPolynomialType(3.0 + 4.0*x + 6.0*x^2 + 8.0*x^3) julia> (derivative ∘ integrate)(p) == p true @@ -75,76 +334,69 @@ julia> p(3) 142 ``` -For the `Polynomial` type, the default on operations is to copy the array. For this type, it might seem reasonable -- to avoid allocations -- to update the coefficients in place for scalar addition and scalar multiplication. +The default for polynomial multiplication is to call `*` for two instances of the type with the same variable, and possibly different element types. For standard basis types, we can add this method: -Scalar addition, `p+c`, defaults to `p + c*one(p)`, or polynomial addition, which is not inplace without addition work. As such, we create a new method and an infix operator +```jldoctest new_container_type +julia> Base.:*(p::AliasPolynomialType{T,X}, q::AliasPolynomialType{S,X}) where {T,S,X} = Polynomials._standard_basis_multiplication(p,q) -```jldoctest AliasPolynomial -julia> function scalar_add!(p::AliasPolynomial{T}, c::T) where {T} - p.coeffs[1] += c - p - end; +julia> p * p +AliasPolynomialType(1 + 4*x + 10*x^2 + 20*x^3 + 25*x^4 + 24*x^5 + 16*x^6) +``` -julia> p::AliasPolynomial ⊕ c::Number = scalar_add!(p,c); -``` +For the Polynomial type, the default on operations is to copy the array. For this type, it might seem reasonable -- to avoid allocations -- to update the coefficients in place for scalar addition and scalar multiplication. -Then we have: +Scalar addition, `p+c`, defaults to `p + c*one(p)`, or polynomial addition, which is not inplace without additional work. As such, we create a new method and an infix operator -```jldoctest AliasPolynomial -julia> p -AliasPolynomial(1 + 2*x + 3*x^2 + 4*x^3) +```jldoctest new_container_type +julia> function scalar_add!(c::T, p::AliasPolynomial{T}) where {T} + p.coeffs[1] += c + p + end; -julia> p ⊕ 2 -AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) +julia> p::AliasPolynomial +ₛ c::Number = scalar_add!(c, p); -julia> p -AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) +julia> c::Number +ₛ p::AliasPolynomial = scalar_add!(c, p); ``` -The viewpoint that a polynomial represents a vector of coefficients leads to an expectation that vector operations should match when possible. Scalar multiplication is a vector operation, so it seems reasonable to override the broadcast machinery to implement an in place operation (e.g. `p .*= 2`). By default, the polynomial types are not broadcastable over their coefficients. We would need to make a change there and modify the `copyto!` function: +The viewpoint that a polynomial represents a vector of coefficients leads to an expectation that vector operations should match when possible. Scalar multiplication is a vector operation, so it seems reasonable to override the broadcast machinery to implement an in place operation (e.g. `p .*= 2`). By default, the polynomial types are not broadcastable over their coefficients. We would need to make a change there and modify the `copyto!` function: -```jldoctest AliasPolynomial +```jldoctest new_container_type julia> Base.broadcastable(p::AliasPolynomial) = p.coeffs; - julia> Base.ndims(::Type{<:AliasPolynomial}) = 1 - julia> Base.copyto!(p::AliasPolynomial, x) = (copyto!(p.coeffs, x); chop!(p)); -``` - -The last `chop!` call would ensure that there are no trailing zeros in the coefficient vector after multiplication, as multiplication by `0` is possible. - -Then we might have: - -```jldoctest AliasPolynomial julia> p -AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) +AliasPolynomialType(1 + 2*x + 3*x^2 + 4*x^3) julia> p .*= 2 -AliasPolynomial(6 + 4*x + 6*x^2 + 8*x^3) - -julia> p -AliasPolynomial(6 + 4*x + 6*x^2 + 8*x^3) +AliasPolynomialType(2 + 4*x + 6*x^2 + 8*x^3) julia> p ./= 2 -AliasPolynomial(3 + 2*x + 3*x^2 + 4*x^3) +AliasPolynomialType(1 + 2*x + 3*x^2 + 4*x^3) ``` Trying to divide again would throw an error, as the result would not fit with the integer type of `p`. Now `p` is treated as the vector `p.coeffs`, as regards broadcasting, so some things may be surprising, for example this expression returns a vector, not a polynomial: -```jldoctest AliasPolynomial +```jldoctest new_container_type julia> p .+ 2 4-element Vector{Int64}: - 5 + 3 4 5 6 ``` -The unexported `Polynomials.PnPolynomial` type implements much of this. +The unexported `Polynomials.PnPolynomial` polynomial type implements much of the above. + +---- + +```@docs +Polynomials.AbstractUnivariatePolynomial +Polynomials.AbstractBasis +``` diff --git a/docs/src/extensions.md b/docs/src/extensions.md new file mode 100644 index 00000000..03f673c8 --- /dev/null +++ b/docs/src/extensions.md @@ -0,0 +1,103 @@ +# Extensions + +As of `v1.9` of `Julia`, packages can provide extension code which is loaded when external packages are loaded. + +## Makie + +When `Makie` is loaded, a plot recipe is provided. + +## ChainRulesCore + +When `ChainRulesCore` is loaded, a `frule` and `rrule` is defined for to integrate with different autodifferentiation packages. + +## MutableArithmetics + +When the `MutableArithmetics` package is loaded, an extension provides its functionality for a few polynomial types, described in the following. Prior to `v1.9` the external package `PolynomialsMutableArithmetics` provided the same functionality. + + +While polynomials of type `Polynomial` are mutable objects, operations such as +`+`, `-`, `*`, always create new polynomials without modifying its arguments. +The time needed for these allocations and copies of the polynomial coefficients +may be noticeable in some use cases. This is amplified when the coefficients +are for instance `BigInt` or `BigFloat` which are mutable themselves. +This can be avoided by modifying existing polynomials to contain the result +of the operation using the [MutableArithmetics (MA) API](https://github.com/jump-dev/MutableArithmetics.jl). + +Consider for instance the following arrays of polynomials + +```julia +using Polynomials +d, m, n = 30, 20, 20 +p(d) = Polynomial(big.(1:d)) +A = [p(d) for i in 1:m, j in 1:n] +b = [p(d) for i in 1:n] +``` + +In this case, the arrays are mutable objects for which the elements are mutable +polynomials which have mutable coefficients (`BigInt`s). +These three nested levels of mutable objects communicate with the MA +API in order to reduce allocation. +Calling `A * b` requires approximately 40 MiB due to 2 M allocations +as it does not exploit any mutability. + +```julia +using MutableArithmetics # or `using PolynomialsMutableArithmetics` to register `Polynomials` with `MutableArithmetics` + +const MA = MutableArithmetics +MA.operate(*, A, b) +``` + +exploits the mutability and hence only allocates approximately 70 KiB due to 4 k +allocations. + +If the resulting vector is already allocated, e.g., + +```julia +z(d) = Polynomial([zero(BigInt) for i in 1:d]) +c = [z(2d - 1) for i in 1:m] +``` + +then we can exploit its mutability with + +```julia +MA.operate!(MA.add_mul, c, A, b) +``` + +to reduce the allocation down to 48 bytes due to 3 allocations. + +These remaining allocations are due to the `BigInt` buffer used to +store the result of intermediate multiplications. This buffer can be +preallocated with: + +```julia +buffer = MA.buffer_for(MA.add_mul, typeof(c), typeof(A), typeof(b)) +MA.buffered_operate!(buffer, MA.add_mul, c, A, b) +``` + +then the second line is allocation-free. + +The `MA.@rewrite` macro rewrite an expression into an equivalent code that +exploit the mutability of the intermediate results. +For instance +```julia +MA.@rewrite(A1 * b1 + A2 * b2) +``` +is rewritten into +```julia +c = MA.operate!(MA.add_mul, MA.Zero(), A1, b1) +MA.operate!(MA.add_mul, c, A2, b2) +``` +which is equivalent to +```julia +c = MA.operate(*, A1, b1) +MA.mutable_operate!(MA.add_mul, c, A2, b2) +``` + +!!! note + Note that currently, only the `Polynomial` and `Polynomials.PnPolynomial` types implement the API and only +part of it is implemented + +## PolyCompat + +While not an extension, the older `Poly` type that this package used prior to `v0.7` is implemented as an alternate basis +and provided on an opt-in bases by executing `using Polynomials.PolyCompat`. This is to provide support for older code bases. diff --git a/docs/src/index.md b/docs/src/index.md index f6f2121e..a221f9f6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -2,17 +2,12 @@ [Polynomials.jl](https://github.com/JuliaMath/Polynomials.jl) is a Julia package that provides basic arithmetic, integration, -differentiation, evaluation, and root finding for univariate polynomials. - -To install the package, run - -```julia -(v1.6) pkg> add Polynomials -``` +differentiation, evaluation, root finding, and data fitting for univariate polynomials. +The `Polynomials` package is hosted on GitHub and installed as other `Julia` packages. As of version `v3.0.0` Julia version `1.6` or higher is required. -The package can then be loaded into the current session through +The package can be loaded into the current session through ```julia using Polynomials @@ -24,9 +19,7 @@ DocTestSetup = quote end ``` -## Quick Start - -### Construction and Evaluation +## Construction and Evaluation Construct a polynomial from its coefficients, lowest order first. @@ -49,7 +42,7 @@ julia> fromroots([1,2,3]) # (x-1)*(x-2)*(x-3) Polynomial(-6 + 11*x - 6*x^2 + x^3) ``` -Evaluate the polynomial `p` at `x`. +Evaluate the polynomial `p` at `1` using call notation: ```jldoctest julia> p = Polynomial([1, 0, -1]) @@ -62,7 +55,7 @@ julia> p(1) The `Polynomial` constructor stores all coefficients using the standard basis with a vector. Other types (e.g. `ImmutablePolynomial`, `SparsePolynomial`, or `FactoredPolynomial`) use different back-end containers which may have advantage for some uses. -### Arithmetic +## Arithmetic The usual arithmetic operators are overloaded to work on polynomials, and combinations of polynomials and scalars. @@ -115,14 +108,34 @@ Polynomial(1 + 2*x + 3*x^2) julia> q = Polynomial(1, :y) Polynomial(1) -julia> p+q +julia> p + q Polynomial(2 + 2*x + 3*x^2) ``` -### Integrals and Derivatives + +### Mixing polynomial types + +Arithmetic of different polynomial types is supported through promotion to a common type, which is typically the `Polynomial` type, but may be the `LaurentPolynomial` type when negative powers of the indeterminate are possible: + +```jldoctext +julia> p, q = ImmutablePolynomial([1,2,3]), Polynomial([3,2,1]) +(ImmutablePolynomial(1 + 2*x + 3*x^2), Polynomial(3 + 2*x + x^2)) + +julia> p + q +Polynomial(4 + 4*x + 4*x^2) + +julia> p, q = ImmutablePolynomial([1,2,3]), SparsePolynomial(Dict(0=>1, 2=>3, 10=>1)) +(ImmutablePolynomial(1 + 2*x + 3*x^2), SparsePolynomial(1 + 3*x^2 + x^10)) + +julia> p + q +LaurentPolynomial(2 + 2*x + 6*x² + x¹⁰) +``` + + +## Integrals and Derivatives Integrate the polynomial `p` term by term, optionally adding constant -term `C`. The degree of the resulting polynomial is one higher than the +term `C`. For non-zero polynomials, the degree of the resulting polynomial is one higher than the degree of `p`. ```jldoctest @@ -133,16 +146,15 @@ julia> integrate(Polynomial([1, 0, -1]), 2) Polynomial(2.0 + 1.0*x - 0.3333333333333333*x^3) ``` -Differentiate the polynomial `p` term by term. The degree of the -resulting polynomial is one lower than the degree of `p`, unless `p` -is a zero polynomial. +Differentiate the polynomial `p` term by term. For non-zero polynomials, the degree of the +resulting polynomial is one lower than the degree of `p`. ```jldoctest julia> derivative(Polynomial([1, 3, -1])) Polynomial(3 - 2*x) ``` -### Root-finding +## Root-finding Return the `d` roots (or zeros) of the degree `d` polynomial `p`. @@ -163,7 +175,7 @@ julia> roots(Polynomial([0, 0, 1])) 0.0 ``` -By design, this is not type-stable; the returned roots may be real or complex. +By design, this is not type-stable; the return type may be real or complex. The default `roots` function uses the eigenvalues of the [companion](https://en.wikipedia.org/wiki/Companion_matrix) matrix for @@ -189,7 +201,7 @@ julia> roots(p) 2.999999999999999999999999999999999999999999999999999999999999999999999999999793 + 0.0im ``` -#### Comments on root finding +### Comments on root finding * The [PolynomialRoots.jl](https://github.com/giordano/PolynomialRoots.jl) @@ -223,7 +235,7 @@ The roots are always returned as complex numbers. number types. ``` -julia> import AMRVW # import as `roots` conflicts +julia> using AMRVW julia> AMRVW.roots(float.(coeffs(p))) 3-element Vector{ComplexF64}: @@ -238,7 +250,7 @@ Both `PolynomialRoots` and `AMRVW` are generic and work with `BigFloat` coefficients, for example. The `AMRVW` package works with much larger polynomials than either -`roots` or `Polynomial.roots`. For example, the roots of this 1000 +`roots` or `PolynomialRoots.roots`. For example, the roots of this 1000 degree random polynomial are quickly and accurately solved for: ``` @@ -427,28 +439,45 @@ There were 3 isolating intervals found: [-0.50…, 1.5…]₂₅₆ ``` -### Fitting arbitrary data +## Fitting a polynomial to arbitrary data + +The `fit` function will fit a polynomial (of degree `deg`) to data `x` and `y` using polynomial interpolation +or a (weighted) least-squares approximation. + +Fit a polynomial (of degree `deg` or less) to `x` and `y` using a least-squares approximation. + +```jldoctest +julia> xs = 0:4; ys = @. exp(-xs) + sin(xs); + +julia> p = fit(xs, ys); map(x -> round(x, digits=4), p) +Polynomial(1.0 + 0.0593*x + 0.3959*x^2 - 0.2846*x^3 + 0.0387*x^4) + +julia> p = fit(ChebyshevT, xs, ys, 2); map(x -> round(x, digits=4), p) +ChebyshevT(0.5413⋅T_0(x) - 0.8991⋅T_1(x) - 0.4238⋅T_2(x)) +``` -Fit a polynomial (of degree `deg`) to `x` and `y` using polynomial interpolation or a (weighted) least-squares approximation. +This provides a visual example: ```@example using Plots, Polynomials + xs = range(0, 10, length=10) ys = @. exp(-xs) -f = fit(xs, ys) # degree = length(xs) - 1 + +f = fit(xs, ys) # degree = length(xs) - 1 f2 = fit(xs, ys, 2) # degree = 2 scatter(xs, ys, markerstrokewidth=0, label="Data") -plot!(f, extrema(xs)..., label="Fit") +plot!(f, extrema(xs)..., label="Interpolation") plot!(f2, extrema(xs)..., label="Quadratic Fit") savefig("polyfit.svg"); nothing # hide ``` ![](polyfit.svg) -### Other bases +## Other bases -A polynomial, e.g. `a_0 + a_1 x + a_2 x^2 + ... + a_n x^n`, can be seen as a collection of coefficients, `[a_0, a_1, ..., a_n]`, relative to some polynomial basis. The most familiar basis being the standard one: `1`, `x`, `x^2`, ... Alternative bases are possible. The `ChebyshevT` polynomials are implemented, as an example. Instead of `Polynomial` or `Polynomial{T}`, `ChebyshevT` or `ChebyshevT{T}` constructors are used: +A polynomial, e.g. `a_0 + a_1 x + a_2 x^2 + ... + a_n x^n`, can be seen as a collection of coefficients, `[a_0, a_1, ..., a_n]`, relative to some polynomial basis. The most familiar basis being the standard one: `1`, `x`, `x^2`, ... Alternative bases are possible. The `ChebyshevT` polynomials are implemented, as an example. The constructor is `ChebyshevT`, an exposed alias for `MutableDensePolynomial{ChebyshevTBasis}`. ```jldoctest julia> p1 = ChebyshevT([1.0, 2.0, 3.0]) @@ -476,10 +505,7 @@ julia> convert(ChebyshevT, Polynomial([1.0, 2, 3])) ChebyshevT(2.5⋅T_0(x) + 2.0⋅T_1(x) + 1.5⋅T_2(x)) ``` -!!! warning - The older `Poly` type that this package used prior to `v0.7` is implemented as an alternate basis to provide support for older code bases. As of `v1.0`, this type will be only available by executing `using Polynomials.PolyCompat`. - -### Iteration +## Iteration If its basis is implicit, then a polynomial may be seen as just a vector of coefficients. Vectors are 1-based, but, for convenience, most polynomial types are naturally 0-based, for purposes of indexing (e.g. `getindex`, `setindex!`, `eachindex`). Iteration over a polynomial steps through the underlying coefficients. @@ -560,8 +586,9 @@ julia> map(x -> round(x, digits=10), q) FactoredPolynomial((x - 4.0) * (x - 2.0) * (x - 3.0) * (x - 1.0)) ``` +## The element type -## Relationship between the `T` and `P{T,X}` +### Relationship between the `T` and `P{T,X}` The addition of a polynomial and a scalar, such as @@ -639,7 +666,7 @@ Using `one(q)` for a constant polynomial with indeterminate `:y` we have: ```jldoctest natural_inclusion julia> P = typeof(p) -Polynomial{Int64, :x} +Polynomial{Int64, :x} (alias for Polynomials.MutableDensePolynomial{Polynomials.StandardBasis, Int64, :x}) julia> P[one(p) one(q)] 1×2 Matrix{Polynomial{Int64, :x}}: @@ -676,7 +703,7 @@ Though were a non-constant polynomial with indeterminate `y` replacing `2one(q)` above, that addition would throw an error. -## Non-number types for `T` +### Non-number types for `T` The coefficients of the polynomial may be non-number types, such as matrices or other polynomials, albeit not every operation is fully supported. @@ -726,7 +753,7 @@ julia> p(b) 18 3 ``` -But if the type `T` lacks support of some generic functions, such as `zero(T)` and `one(T)`, then there may be issues. For example, when `T <: AbstractMatrix` the output of `p-p` is an error, as the implementation assumes `zero(T)` is defined. For static arrays, this isn't an issue, as there is support for `zero(T)`. Other polynomial types, such as `SparsePolynomial` have less support, as some specialized methods assume more of the generic interface be implemented. +But if the type `T` lacks support of some generic functions, such as `zero(T)` and `one(T)`, then there may be issues. For example, when `T <: AbstractMatrix` the output of `p[degree(p)+1]` is an error, as the implementation assumes `zero(T)` is defined. For static arrays, this isn't an issue, as there is support for `zero(T)`. Other polynomial types, such as `SparsePolynomial` have less support, as some specialized methods assume more of the generic interface be implemented. Similarly, using polynomials for `T` is a possibility: @@ -757,7 +784,7 @@ julia> p(b) Polynomial(1 + y^2 + y^4) ``` -But much doesn't. For example, implicit promotion can fail. For example, the scalar multiplication `p * b` will fail, as the methods assume this is the fallback polynomial multiplication and not the intended scalar multiplication. +But much doesn't. For example, implicit promotion can fail. For example, the scalar multiplication `p * b` will fail, as the methods assume this is the fallback polynomial multiplication and not the intended scalar multiplication. ## Rational functions @@ -790,6 +817,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/docs/src/polynomials/chebyshev.md b/docs/src/polynomials/chebyshev.md index caa2b8df..3ef0187c 100644 --- a/docs/src/polynomials/chebyshev.md +++ b/docs/src/polynomials/chebyshev.md @@ -52,7 +52,7 @@ ChebyshevT(1⋅T_0(x) + 3⋅T_2(x) + 4⋅T_3(x)) julia> p = convert(Polynomial, c) -Polynomial(-2 - 12*x + 6*x^2 + 16*x^3) +Polynomial(-2.0 - 12.0*x + 6.0*x^2 + 16.0*x^3) julia> convert(ChebyshevT, p) ChebyshevT(1.0⋅T_0(x) + 3.0⋅T_2(x) + 4.0⋅T_3(x)) diff --git a/docs/src/reference.md b/docs/src/reference.md index 56bc1ecd..f7c7a2f3 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -12,24 +12,6 @@ DocTestSetup = quote end ``` -## Inspection - -```@docs -coeffs -degree -length -size -Polynomials.domain -mapdomain -chop -chop! -truncate -truncate! -isreal -real -isintegral -ismonic -``` ## Arithmetic @@ -58,8 +40,50 @@ julia> q / 2 Polynomial(0.5 - 0.5*x^2) ``` +## Inspection + ```@docs -gcd +degree +length +size +Polynomials.domain +mapdomain +chop +chop! +truncate +truncate! +iszero +Polynomials.isconstant +Polynomials.constantterm +isreal +real +isintegral +ismonic +Polynomials.hasnan +``` + +## Iteration + +For the `Polynomial` type, a natural mapping between the polynomial ``a_0 + a_1 x + a_2 x^2 + \cdots + a_n x^n`` with the coefficients ``(a_0, a_1, \dots, a_n))`` leads to the view point of a polynomial being a ``0``-based vector. Similarly, when the basis terms are not the standard basis. The `coeffs` method returns these coefficients in an iterable (a vector or tuple). For Laurent type polynomials, the coefficients between `firstindex(p)` and `lastindex(p)` are returned. + +More generally, `pairs(p)` returns values `i => aᵢ` where the polynomial has terms ``a_i T_i`` for the basis ``T_i``. (For sparse polynomials these need not be in order and only terms where ``a_i \ne 0`` are given.) The `keys` and `values` methods iterate over `i` and `aᵢ`. + +The `firstindex` method refers to the lowest stored basis index, which due to offsets need not be `0`. It will be no smaller than `Polynomials.minimumexponent`, which is the smalled allowed index for the polynomial type. The `lastindex` method refers to the last basis index. If the type allows trailing zeros (like `ImmutablePolynomial`) this will differ from the value returned by `degree`. + +The `getindex(p,i)` method returns `p_i` or zero when out of bounds (if the element type of the polynomial has `zero(T)` defined). +For mutable polynomials, the `setindex!(p, val, i)` method sets `p[i]` to `val`. This may extend the underlying storage container for some polynomial types. For `ImmutablePolynomial` the `@set!` macro from `Setfield` can be used with the typical `setindex!` notation. + +The `map(fn, p)` method maps `fn` over the coefficients and returns a polynomial with the same polynomial type as `p`. + +```@docs +coeffs +pairs +values +keys +firstindex +lastindex +eachindex +map ``` ## Mathematical Functions @@ -68,12 +92,14 @@ gcd zero one variable +Polynomials.basis fromroots -roots +gcd derivative integrate -fit +roots companion +fit vander ``` diff --git a/src/Polynomials.jl b/src/Polynomials.jl index 6d0a1f7b..d7008de9 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") @@ -12,17 +13,33 @@ include("contrib.jl") # Interface for all AbstractPolynomials 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/pi_n_polynomial.jl") -include("polynomials/factored_polynomial.jl") +# polynomials with explicit basis +include("abstract-polynomial.jl") +include("polynomial-container-types/mutable-dense-polynomial.jl") +include("polynomial-container-types/mutable-dense-view-polynomial.jl") +include("polynomial-container-types/mutable-dense-laurent-polynomial.jl") +include("polynomial-container-types/immutable-dense-polynomial.jl") +include("polynomial-container-types/mutable-sparse-polynomial.jl") +const PolynomialContainerTypes = (:MutableDensePolynomial, :MutableDenseViewPolynomial, :ImmutableDensePolynomial, + :MutableDenseLaurentPolynomial, :MutableSparsePolynomial) # useful for some purposes +const ZeroBasedDensePolynomialContainerTypes = (:MutableDensePolynomial, :MutableDenseViewPolynomial, :ImmutableDensePolynomial) + +include("polynomials/standard-basis/standard-basis.jl") +include("polynomials/standard-basis/polynomial.jl") +include("polynomials/standard-basis/pn-polynomial.jl") +include("polynomials/standard-basis/laurent-polynomial.jl") +include("polynomials/standard-basis/immutable-polynomial.jl") +include("polynomials/standard-basis/sparse-polynomial.jl") + include("polynomials/ngcd.jl") include("polynomials/multroot.jl") -include("polynomials/ChebyshevT.jl") + +include("polynomials/factored_polynomial.jl") +include("polynomials/chebyshev.jl") + +include("promotions.jl") + + # Rational functions include("rational-functions/common.jl") @@ -31,9 +48,9 @@ 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") +include("legacy/misc.jl") +include("legacy/Poly.jl") if !isdefined(Base, :get_extension) include("../ext/PolynomialsChainRulesCoreExt.jl") diff --git a/src/abstract-polynomial.jl b/src/abstract-polynomial.jl new file mode 100644 index 00000000..65f32bb5 --- /dev/null +++ b/src/abstract-polynomial.jl @@ -0,0 +1,318 @@ +""" + AbstractUnivariatePolynomial{B,T,X} <: AbstractPolynomial{T,X} + AbstractDenseUnivariatePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T,X} + AbstractLaurentUnivariatePolynomial{B,T,X} <: AbstractUnivariatePolynomial{B,T,X} + +Abstract container types for polynomials with an explicit basis, `B`. +`AbstractDenseUnivariatePolynomial` is for `0`-based polynomials; +`AbstractLaurentUnivariatePolynomial` is for polynomials with possibly negative powers of the indeterminate. + +""" +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 powers +abstract type AbstractLaurentUnivariatePolynomial{B, T, X} <: AbstractUnivariatePolynomial{B,T,X} end + +""" + AbstractBasis + +Abstract type for specifying a polynomial basis. +""" +abstract type AbstractBasis end +export AbstractUnivariatePolynomial + +#XXX() = throw(ArgumentError("No default method defined")) + +## -------------------------------------------------- + +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) + #printbasis(io, P, j, mimetype) + printbasis(io, ⟒(P){T,Symbol(var)}, j, mimetype) + return true +end + +# 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 + +basis_symbol(::Type{AbstractUnivariatePolynomial{B,T,X}}) where {B,T,X} = "Χ($(X))" + + +## 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 + +# type of basis +basistype(p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = B +basistype(::Type{<:AbstractUnivariatePolynomial{B}}) where {B} = B # some default + +# eltype of polynomial +Base.eltype(p::AbstractUnivariatePolynomial{B,T,X}) where {B,T,X} = T + +# indeterminate of polynomial +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 (returns polynomial type from instance or type) +⟒(P::Type{<:AbstractUnivariatePolynomial}) = constructorof(P) # constructorof defined with different container-types +⟒(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.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(values(p), args...) +Base.iterate(p::AbstractUnivariatePolynomial, state = firstindex(p)) = _iterate(p, state) # _iterate in common.jl + +Base.keys(p::AbstractUnivariatePolynomial) = eachindex(p) +Base.values(p::AbstractUnivariatePolynomial) = values(p.coeffs) # Dict based containers must specialize +Base.pairs(p::AbstractUnivariatePolynomial) = Base.Generator(=>, keys(p), values(p)) + +function coeffs(p::AbstractDenseUnivariatePolynomial) + firstindex(p) < 0 && throw(ArgumentError("Polynomial has negative index terms. Use `pairs` instead.")) + firstindex(p) == 0 && return p.coeffs # need sparse override + firstindex(p) > 0 && return [p[i] for i ∈ 0:lastindex(p)] +end + +function coeffs(p::AbstractLaurentUnivariatePolynomial) + [p[i] for i ∈ firstindex(p):lastindex(p)] +end + +# a₀, …, aₙ or error even for Laurent +function coeffs0(p::AbstractUnivariatePolynomial) + a,b = firstindex(p), lastindex(p) + a < 0 && throw(ArgumentError("Polynomial has negative index terms. Use `pairs` instead.")) + [p[i] for i ∈ 0:b] +end +function coeffs0(p::AbstractDenseUnivariatePolynomial) + a,b = firstindex(p), lastindex(p) + iszero(a) && return p.coeffs + [p[i] for i ∈ 0:b] +end + + +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 + +# map Polynomial terms -> vector terms +# Default degree **assumes** basis element Tᵢ has degree i. +degree(p::AbstractDenseUnivariatePolynomial) = iszero(p) ? -1 : lastindex(p) # (no trailing zero assumed) +degree(p::AbstractLaurentUnivariatePolynomial) = lastindex(p) + +# this helps, along with _set, make some storage-generic methods +_zeros(p::P, z, N) where {P <: AbstractUnivariatePolynomial} = _zeros(P, z, N) +_zeros(::Type{<:AbstractDenseUnivariatePolynomial}, z::T, N) where {T} = fill(z,(N,)) # default +_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 + +# for indexing for Tᵢ i + offset should be array index +# this default is for 1-based backends (vector, tuple) +offset(p::AbstractDenseUnivariatePolynomial) = 1 + +# i -> basis polynomial. Uses `order` argument, which may save some space +basis(::Type{P}, i::Int) where {B,P <: AbstractUnivariatePolynomial{B}} = basis(⟒(P){eltype(P),indeterminate(P)}, i) + +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) + + +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 +Base.chop(p::AbstractUnivariatePolynomial; kwargs...) = chop!(copy(p)) +chop!(p::AbstractDenseUnivariatePolynomial; kwargs...) = (chop!(p.coeffs); p) # default for mutable vector backed; tuple backed need other + + + +## --- + +#= Comparisons =# +# iterable over keys of both +function keys_union(p::AbstractUnivariatePolynomial, q::AbstractUnivariatePolynomial) + minimum(firstindex, (p,q)):maximum(lastindex, (p,q)) +end + + +# norm(q1 - q2) only non-allocating (save for unique) +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 ∈ keys_union(q1, 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...) +end + +# compare X != Y +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,S,X}; + rtol = nothing, + atol = nothing) where {B,T,S, X} + + (hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons + R = float(real(promote_type(T,S))) + rtol = something(rtol, Base.rtoldefault(R,R,0)) + atol = something(atol, 0) + + + # copy over from abstractarray.jl + Δ = normΔ(p1,p2) + if isfinite(Δ) + return Δ <= max(atol, rtol * max(norm(p1), norm(p2))) + else + for i in keys_union(p1, p2) + isapprox(p1[i], p2[i]; rtol=rtol, atol=atol) || return false + end + return true + end +end + + +## --- arithmetic operations --- +## all in common.jl + +# 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 + p′ = differentiate(p) + for i ∈ 2:n + p′ = differentiate(p′) + end + p′ +end +## better parallel with integrate, but derivative has been used in this package for too long to change +const differentiate = derivative + +function fit(::Type{P}, + x::AbstractVector{T}, + y::AbstractVector{T}, + deg, + cs::Dict; + kwargs...) where {T, P<:AbstractUnivariatePolynomial} + convert(P, fit(Polynomial, x, y, deg, cs; kwargs...)) +end + + +## 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,T,X,P<:AbstractUnivariatePolynomial{B,T,X}} = 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 +# * companion + +# 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 + #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<: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}) + $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 <: 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)) + + # 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 +end diff --git a/src/abstract.jl b/src/abstract.jl index 2227cd8c..c7c5b417 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -1,9 +1,13 @@ export AbstractPolynomial - # *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} @@ -13,7 +17,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. @@ -55,11 +59,7 @@ abstract type AbstractPolynomial{T,X} end # We want ⟒(P{α…,T,X}) = P{α…}; this default # works for most cases ⟒(P::Type{<:AbstractPolynomial}) = constructorof(P) - -# 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)) - +⟒(p::P) where {P <: AbstractPolynomial} = ⟒(P) """ Polynomials.@register(name) @@ -84,19 +84,21 @@ macro register(name) Base.convert(::Type{P}, p::P) where {P<:$poly} = p function Base.convert(P::Type{<:$poly}, p::$poly{T,X}) where {T,X} isconstant(p) && return constructorof(P){eltype(P),indeterminate(P)}(constantterm(p)) - constructorof(P){eltype(P), indeterminate(P,p)}(_coeffs(p)) + constructorof(P){eltype(P), indeterminate(P,p)}(coeffs(p)) end Base.promote(p::P, q::Q) where {X, T, P <:$poly{T,X}, Q <: $poly{T,X}} = p,q Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{<:$poly{S,X}}) where {T,S,X} = $poly{promote_type(T, S),X} - Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{S}) where {T,S<:Number,X} = + Base.promote_rule(::Type{P}, ::Type{S}) where {T,S<:Number,X,P<:$poly{T,X}} = $poly{promote_type(T, S),X} +# Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{S}) where {T,S<:Number,X} = +# $poly{promote_type(T, S),X} $poly(coeffs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {T} = $poly{T, Symbol(var)}(coeffs) $poly{T}(x::AbstractVector{S}, var::SymbolLike=Var(:x)) where {T,S} = $poly{T,Symbol(var)}(T.(x)) - function $poly{T}(coeffs::G, var::SymbolLike=Var(x)) where {T,G} + function $poly{T}(coeffs::G, var::SymbolLike=Var(:x)) where {T,G} !Base.isiterable(G) && throw(ArgumentError("coeffs is not iterable")) cs = collect(T, coeffs) $poly{T, Symbol(var)}(cs) @@ -120,39 +122,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) = evalpoly(x, p) - end -end - - -macro registerN(name, params...) - poly = esc(name) - αs = tuple(esc.(params)...) - quote - Base.convert(::Type{P}, q::Q) where {$(αs...),T, P<:$poly{$(αs...),T}, Q <: $poly{$(αs...),T}} = q - Base.convert(::Type{$poly{$(αs...)}}, q::Q) where {$(αs...),T, Q <: $poly{$(αs...),T}} = q - Base.promote(p::P, q::Q) where {$(αs...),T, X, P <:$poly{$(αs...),T,X}, Q <: $poly{$(αs...),T,X}} = p,q - Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{<:$poly{$(αs...),S,X}}) where {$(αs...),T,S,X} = - $poly{$(αs...),promote_type(T, S),X} - Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{S}) where {$(αs...),T,X,S<:Number} = - $poly{$(αs...),promote_type(T,S),X} - - function $poly{$(αs...),T}(x::AbstractVector{S}, var::SymbolLike = Var(:x)) where {$(αs...),T,S} - $poly{$(αs...),T,Symbol(var)}(T.(x)) - end - $poly{$(αs...)}(coeffs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {$(αs...),T} = - $poly{$(αs...),T,Symbol(var)}(coeffs) - $poly{$(αs...),T,X}(c::AbstractPolynomial{S,Y}) where {$(αs...),T,X,S,Y} = convert($poly{$(αs...),T,X}, c) - $poly{$(αs...),T}(c::AbstractPolynomial{S,Y}) where {$(αs...),T,S,Y} = convert($poly{$(αs...),T}, c) - $poly{$(αs...),}(c::AbstractPolynomial{S,Y}) where {$(αs...),S,Y} = convert($poly{$(αs...),}, c) - - $poly{$(αs...),T,X}(n::Number) where {$(αs...),T,X} = T(n)*one($poly{$(αs...),T,X}) - $poly{$(αs...),T}(n::Number, var::SymbolLike = Var(:x)) where {$(αs...),T} = T(n)*one($poly{$(αs...),T,Symbol(var)}) - $poly{$(αs...)}(n::S, var::SymbolLike = Var(:x)) where {$(αs...), S<:Number} = - n*one($poly{$(αs...),S,Symbol(var)}) - $poly{$(αs...),T}(var::SymbolLike=Var(:x)) where {$(αs...), T} = - variable($poly{$(αs...),T,Symbol(var)}) - $poly{$(αs...)}(var::SymbolLike=Var(:x)) where {$(αs...)} = variable($poly{$(αs...)},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 676df0dc..fdd020a8 100644 --- a/src/common.jl +++ b/src/common.jl @@ -14,7 +14,6 @@ export fromroots, integrate, derivative, variable, - @variable, # deprecated!! isintegral, ismonic @@ -34,10 +33,11 @@ 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; var::SymbolLike = :x) x = variable(P, var) - p = prod(x - r for r in roots) - return truncate!(p) + p = prod(x-r for r ∈ rs; init=one(x)) + p = truncate!!(p) + p end fromroots(r::AbstractVector{<:Number}; var::SymbolLike = :x) = fromroots(Polynomial, r, var = var) @@ -126,7 +126,7 @@ fit(P::Type{<:AbstractPolynomial}, var = :x,) = fit′(P, promote(collect(x), collect(y))..., deg; weights = weights, var = var) # avoid issue 214 -fit′(P::Type{<:AbstractPolynomial}, x, y, args...;kwargs...) = throw(ArgumentError("x and y do not produce abstract vectors")) +fit′(P::Type{<:AbstractPolynomial}, x, y, args...;kwargs...) = throw(ArgumentError("x and y do not produce abstract vectors")) fit′(P::Type{<:AbstractPolynomial}, x::AbstractVector{T}, y::AbstractVector{T}, @@ -218,6 +218,44 @@ Calculate the pseudo-Vandermonde matrix of the given polynomial type with the gi vander(::Type{<:AbstractPolynomial}, x::AbstractVector, deg::Integer) + + +""" + 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) + + """ critical_points(p::AbstractPolynomial{<:Real}, I=domain(p); endpoints::Bool=true) @@ -265,44 +303,7 @@ function critical_points(p::AbstractPolynomial{T}, I = domain(p); 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}; @@ -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 @@ -331,10 +332,11 @@ 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) thresh = max_coeff * rtol + atol @@ -348,13 +350,15 @@ end truncate!(ps::NTuple; kwargs...) = throw(ArgumentError("`truncate!` not defined.")) -_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(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 """ @@ -418,9 +422,14 @@ 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} +function _chop(ps::NTuple{N}; + rtol = nothing, + atol = nothing) where {N} + + T = real(eltype(ps)) + rtol = something(rtol, Base.rtoldefault(T)) + atol = something(atol, zero(T)) + thresh = norm(ps, Inf) * rtol + atol for i in N:-1:1 if abs(ps[i]) > thresh @@ -445,7 +454,11 @@ 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) +## -------------------------------------------------- """ check_same_variable(p::AbstractPolynomial, q::AbstractPolynomial) @@ -456,7 +469,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) @@ -470,9 +483,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 """ @@ -490,18 +503,16 @@ 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 -# 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}} = - Polynomial{promote_type(T, S),X} -Base.promote_rule(::Type{P},::Type{Q}) where {T,X, P<:AbstractPolynomial{T,X}, - S,Y, Q<:AbstractPolynomial{S,Y}} = - assert_same_variable(X,Y) #= @@ -546,7 +557,15 @@ 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, values(p)) +""" + iszero(p::AbstractPolynomial) + +Is this a ``0`` polynomial. + +For most types, the ``0`` polynomial is one with no coefficients (coefficient vector `T[]`), +though some types have the possibility of trailing zeros. The degree of a zero polynomial is conventionally ``-1``, though this is not the convention for Laurent polynomials. +""" +Base.iszero(p::AbstractPolynomial) = all(iszero, values(p))::Bool # See discussions in https://github.com/JuliaMath/Polynomials.jl/issues/258 @@ -573,9 +592,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 """ @@ -613,7 +637,7 @@ Determine whether a polynomial is a monic polynomial, i.e., its leading coeffici ismonic(p::AbstractPolynomial) = isone(convert(Polynomial, p)[end]) "`hasnan(p::AbstractPolynomial)` are any coefficients `NaN`" -hasnan(p::AbstractPolynomial) = any(hasnan, p) +hasnan(p::AbstractPolynomial) = any(hasnan, p)::Bool hasnan(p::AbstractArray) = any(hasnan.(p)) hasnan(x) = isnan(x) @@ -622,17 +646,30 @@ hasnan(x) = isnan(x) Is the polynomial `p` a constant. """ -isconstant(p::AbstractPolynomial) = degree(p) <= 0 && iszero(firstindex(p)) +isconstant(p::AbstractPolynomial) = degree(p) <= 0 && firstindex(p) == 0 +# XXX docstring isn't quite right! +# coeffs returns +# * p.coeffs for Laurent types (caller needs to be aware that offset is possible) +# * a container with (a₀, a₁, …, aₙ) which for some types may have trailing zeros (ImmutableDense) """ coeffs(::AbstractPolynomial) + coeffs(::AbstractDenseUnivariatePolynomial) + coeffs(::AbstractLaurentUnivariatePolynomial) + +For a dense, univariate polynomial return the coefficients ``(a_0, a_1, \\dots, a_n)`` +as an interable. This may be a vector or tuple, and may alias the +polynomials coefficients. -Return the coefficient vector. For a standard basis polynomial these are `[a_0, a_1, ..., a_n]`. +For a Laurent type polynomial (e.g. `LaurentPolynomial`, `SparsePolynomial`) return the coefficients ``(a_i, a_{i+1}, \\dots, a_j)`` where +``i`` is found from `firstindex(p)` and ``j`` from `lastindex(p)`. + +For `LaurentPolynomial` and `SparsePolynomial`, the `pairs` iterator is more generically useful, as it iterates over ``(i, p_i)`` possibly skipping the terms where ``p_i = 0``. + +Defaults to `p.coeffs`. """ 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 @@ -647,11 +684,16 @@ 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`. +has a nonzero coefficient. + +For standard basis polynomials the degree of the zero polynomial is defined to be ``-1``. +For Laurent type polynomials, this is `0`, or `lastindex(p)`. The `firstindex` method gives the smallest power +of the indeterminate for the polynomial. +The default method assumes the basis polynomials, `βₖ`, have degree `k`. + """ degree(p::AbstractPolynomial) = iszero(coeffs(p)) ? -1 : length(coeffs(p)) - 1 + min(0, minimumexponent(p)) -@deprecate order degree false """ @@ -698,8 +740,26 @@ indexing =# # 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) +# firstindex, lastindex correspond to the range of which basis vectors are being represented by the coefficients +""" + firstindex(p::AbstractPolynomial) + +The index of the smallest basis element, ``\beta_i``, represented by the coefficients. This is ``0`` for +a zero polynomial. +""" +Base.firstindex(p::AbstractPolynomial) = 0 # XXX() is a better default +""" + lastindex(p::AbstractPolynomial) + +The index of the largest basis element, ``\beta_i``, represented by the coefficients. +May be ``-1`` or ``0`` for the zero polynomial, depending on the storage type. +""" +Base.lastindex(p::AbstractPolynomial) = length(p) - 1 + firstindex(p) # not degree, which accounts for any trailing zeros +""" + eachindex(p::AbstractPolynomial) + +Iterator over all indices of the represented basis elements +""" Base.eachindex(p::AbstractPolynomial) = firstindex(p):lastindex(p) Base.broadcastable(p::AbstractPolynomial) = Ref(p) degreerange(p::AbstractPolynomial) = firstindex(p):lastindex(p) @@ -712,7 +772,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) @@ -764,8 +824,24 @@ struct PolynomialValues{P, T} PolynomialValues{P}(p::P) where {P} = new{P, eltype(p)}(p) PolynomialValues(p::P) where {P} = new{P, eltype(p)}(p) end +""" + keys(p::AbstractPolynomial) + +Iterator over ``i``s for each basis element, ``\\beta_i``, represented by the coefficients. +""" Base.keys(p::AbstractPolynomial) = PolynomialKeys(p) +""" + values(p::AbstractPolynomial) + +Iterator over ``p_i``s for each basis element, ``\\beta_i``, represented by the coefficients. +""" Base.values(p::AbstractPolynomial) = PolynomialValues(p) +""" + pairs(p::AbstractPolynomial) + +Iterator over ``(i, p_i)`` for each basis element, ``\\beta_i``, represented by the coefficients. +""" +Base.pairs(p::AbstractPolynomial) 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) @@ -799,27 +875,24 @@ 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} = map(identity, p) +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 -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 + isconstant(p) && return X assert_same_variable(X,Y) 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 =# @@ -844,9 +917,11 @@ Base.zero(p::P, var=indeterminate(p)) where {P <: AbstractPolynomial} = zero(P, 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(::Type{P}) where {P <: AbstractPolynomial} = one(⟒(P){eltype(P), indeterminate(P)}) +Base.one(::Type{P}, var::SymbolLike) where {P <: AbstractPolynomial} = one(⟒(P){eltype(P), Symbol(var)}) Base.one(p::P, var=indeterminate(p)) where {P <: AbstractPolynomial} = one(P, var) +# each polynomial type implements: +# Base.one(::Type{P}) where {T,X,P<:AbstractPolynomial{T,X}} = throw(ArgumentError("No default method defined")) # no default method Base.oneunit(::Type{P}, args...) where {P <: AbstractPolynomial} = one(P, args...) Base.oneunit(p::P, args...) where {P <: AbstractPolynomial} = one(p, args...) @@ -876,17 +951,19 @@ julia> roots((x - 3) * (x + 2)) ``` """ -variable(::Type{P}) where {P <: AbstractPolynomial} = throw(ArgumentError("No default method defined")) # no default +variable(::Type{P}) where {P <: AbstractPolynomial} = variable(⟒(P){eltype(P), indeterminate(P)}) 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) +variable(p::AbstractPolynomial, var = indeterminate(p)) = variable(⟒(p){eltype(p), Symbol(var)}) +variable(var::SymbolLike = :x) = variable(Polynomial{Int,Symbol(var)}) +# Each polynomial type implements: +# variable(::Type{P}) where {T,X,P <: AbstractPolynomial{T,X}} = throw(ArgumentError("No default method defined")) # no default # Exported in #470. Exporting was a mistake! +# Now must be used through qualification: `Polynomials.@variable ...` #@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 @@ -906,6 +983,12 @@ 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} +""" + basis(p::P, i::Int) + basis(::Type{<:AbstractPolynomial}, i::Int, var=:x) + +Return ith basis element for a given polynomial type, optionally with a specified variable. +""" function basis(::Type{P}, k::Int) where {P<:AbstractPolynomial} T,X = eltype(P), indeterminate(P) zs = zeros(T, k+1) @@ -919,66 +1002,134 @@ end basis(p::P, k::Int, _var=indeterminate(p); var=_var) where {P<:AbstractPolynomial} = basis(P, k, var) #= -arithmetic =# -Base.:-(p::P) where {P <: AbstractPolynomial} = _convert(p, -coeffs(p)) +composition +cf. https://github.com/JuliaMath/Polynomials.jl/issues/511 for a paper with implentations -Base.:*(p::AbstractPolynomial, c::Number) = scalar_mult(p, c) -Base.:*(c::Number, 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) +=# +""" + 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 -# 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) +#= +arithmetic =# +Scalar = Union{Number, Matrix} # scalar operations -# no generic p+c, as polynomial addition falls back to scalar ops +# scalar_add utilized polynomial addition. May be more performant to provide new method +scalar_add(c::S, p::AbstractPolynomial) where {S} = p + c * one(p) + +# Scalar multiplication; no assumption of commutivity +scalar_mult(p::P, c::S) where {S, T, X, P<:AbstractPolynomial{T,X}} = map(Base.Fix2(*,c), p) +scalar_mult(c::S, p::P) where {S, T, X, P<:AbstractPolynomial{T,X}} = map(Base.Fix1(*,c), p) +scalar_mult(p1::AbstractPolynomial, p2::AbstractPolynomial) = + throw(ArgumentError("scalar_mult(::$(typeof(p1)), ::$(typeof(p2))) is not defined.")) # avoid ambiguity, issue #435 + +# scalar div (faster than Base.Fix2(/,c) ) +scalar_div(p::P, c::S) where {S, T, X, P<:AbstractPolynomial{T, X}} = map(Base.Fix2(*, one(T)/c), p) + + +Base.:-(p::P) where {P <: AbstractPolynomial} = map(-, p) -Base.:-(p1::AbstractPolynomial, p2::AbstractPolynomial) = +(p1, -p2) +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) = scalar_add(c, p) +Base.:-(p::AbstractPolynomial, c::Scalar) = scalar_add(-c, p) +Base.:-(c::Scalar, p::AbstractPolynomial) = scalar_add(c, -p) # extra copy! eww ## 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) +# what are these here for? +Base.:+(p::P, c::T) where {T,X, P<:AbstractPolynomial{T,X}} = scalar_add(c, p) +Base.:+(p::P, c::S) where {T,X, P<:AbstractPolynomial{T,X}, S} = scalar_add(c,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) +## polynomial + polynomial when different types +## - each polynomial container type implents PB{B,T,X} + PB{B,S,X} +## - this handles case X ≠ Y unless constant +## - when PB₁ ≠ PB₂ we promote both polynomials +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) # should error end +function Base.:+(p::P, q::Q) where {T,X,P <: AbstractPolynomial{T,X}, S,Q <: AbstractPolynomial{S,X}} + sum(promote(p,q)) +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}} + +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)) + assert_same_variable(X,Y) # should error +end + +function Base.:-(p::P, q::Q) where {T,X,P <: AbstractPolynomial{T,X}, S,Q <: AbstractPolynomial{S,X}} + -(promote(p,q)...) +end + +# the case p::P{B,T,X} - q::P{B,S,X} should be done in container types +Base.:-(p::P, q::P) where {T,X,P <: AbstractPolynomial{T,X}} = p + (-1*q) + + +## -- multiplication +## Polynomial p*q +## Polynomial multiplication formula depend on the particular basis used. +## The subtype must implement *(::PT{T,X,[N]}, ::PT{S,X,[M]}) +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) # should error +end +function Base.:*(p1::P, p2::Q) where {T,X,P <: AbstractPolynomial{T,X}, + S, Q <: AbstractPolynomial{S,X}} + prod(promote(p1, p2)) end + +# scalar div +Base.:/(p::AbstractPolynomial, c) = scalar_div(p, c) + +Base.:^(p::AbstractPolynomial, n::Integer) = Base.power_by_squaring(p, n) + + +## ----- + +# <<< could be moved to SpecialPolynomials # 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 +# 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 + + +# Th ⊕ methd below is used in Special Polynomials, but not here, as it was removed for +# similar methods in the polynomial-basetypes # 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) @@ -991,8 +1142,7 @@ function ⊕(P::Type{<:AbstractPolynomial}, p1::Vector{T}, p2::Vector{S}) where 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} - +@generated function ⊕(P::Type{<:AbstractPolynomial}, p1::NTuple{N}, p2::NTuple{M}) where {N,M} exprs = Any[nothing for i = 1:N] for i in 1:M exprs[i] = :(p1[$i] + p2[$i]) @@ -1011,7 +1161,6 @@ 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}() @@ -1033,41 +1182,9 @@ function ⊕(P::Type{<:AbstractPolynomial}, p1::Dict{Int,T}, p2::Dict{Int,S}) wh return p end +## >>> moved to SpecialPolynomials (or rewrite that to use new container types) -## -- 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) @@ -1119,7 +1236,7 @@ end 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")) +#uvw(p1::P, p2::P; kwargs...) where {P <:AbstractPolynomial} = throw(ArgumentError("uvw not defined")) """ div(::AbstractPolynomial, ::AbstractPolynomial) @@ -1132,7 +1249,9 @@ Base.div(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[1] Base.rem(n::AbstractPolynomial, d::AbstractPolynomial) = divrem(n, d)[2] #= -Comparisons =# +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 @@ -1152,8 +1271,9 @@ function Base.:(==)(p1::AbstractPolynomial, p2::AbstractPolynomial) check_same_variable(p1, p2) || return false ==(promote(p1,p2)...) end -Base.:(==)(p::AbstractPolynomial, n::Number) = isconstant(p) && constantterm(p) == n -Base.:(==)(n::Number, p::AbstractPolynomial) = 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...) if isconstant(p1) @@ -1167,35 +1287,24 @@ 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) if isfinite(Δ) return Δ <= max(atol, rtol*max(norm(p1), norm(p2))) else - for i in 0:max(degree(p1), degree(p2)) + for i in minimum(firstindex, (p1,p2)):maximum(degree, (p1,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(p1::AbstractPolynomial{T}, n::S;kwargs...) where {S,T} = isapprox(p1, n*one(p1)) +Base.isapprox(n::S,p1::AbstractPolynomial{T}; kwargs...) where {S,T} = isapprox(p1, n; kwargs...) -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) +Base.isapprox(::AbstractPolynomial{T}, ::Missing, args...; kwargs...) where T = missing +Base.isapprox(::Missing, ::AbstractPolynomial{T}, args...; kwargs...) where T = missing diff --git a/src/compat.jl b/src/compat.jl deleted file mode 100644 index 2e2a1167..00000000 --- a/src/compat.jl +++ /dev/null @@ -1,17 +0,0 @@ -## We have renamed the MATLAB/numpy type names to more Julian names -## How to keep the old names during a transition is the question. -## The plan: keep these to ensure underlying changes are not disruptive -## For now we ensure compatibility by defining these for `Poly` objects such -## that they do not signal a deprecation (save polyfit)), -## but do for other `AbstractPolynomial` types. -## At v1.0, these will be opt-in via `using Polynomials.PolyCompat` - - -include("polynomials/Poly.jl") -using .PolyCompat -export Poly -export poly, polyval, polyint, polyder - -## Pade -## Pade will likely be moved into a separate package, for now we will put into PolyCompat -export Pade, padeval diff --git a/src/contrib.jl b/src/contrib.jl index e1ca2cac..1c30c1e8 100644 --- a/src/contrib.jl +++ b/src/contrib.jl @@ -35,10 +35,10 @@ end ## e.g. 1 => I module EvalPoly using LinearAlgebra -function evalpoly(x::S, p::Tuple) where {S} +function evalpoly(x, p::Tuple) if @generated - N = length(p.parameters) - ex = :(p[end]*_one(S)) + N = length(p.parameters::Core.SimpleVector) + ex = :(p[end]*_one(x)) for i in N-1:-1:1 ex = :(_muladd($ex, x, p[$i])) end @@ -52,15 +52,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 diff --git a/src/polynomials/Poly.jl b/src/legacy/Poly.jl similarity index 63% rename from src/polynomials/Poly.jl rename to src/legacy/Poly.jl index eb5b778c..999b4213 100644 --- a/src/polynomials/Poly.jl +++ b/src/legacy/Poly.jl @@ -20,7 +20,7 @@ This type provides support for `poly`, `polyval`, `polyder`, and base. Call `using Polynomials.PolyCompat` to enable this module. """ -struct Poly{T <: Number,X} <: Polynomials.StandardBasisPolynomial{T,X} +struct Poly{T <: Number,X} <: AbstractPolynomial{T,X} #Polynomials.StandardBasisPolynomial{T,X} coeffs::Vector{T} function Poly(a::AbstractVector{T}, var::Polynomials.SymbolLike = :x) where {T <: Number} # if a == [] we replace it with a = [0] @@ -38,6 +38,26 @@ end Polynomials.@register Poly +function Polynomials.showterm(io::IO, ::Type{<:Poly}, pj::T, var, j, first::Bool, mimetype) where {T} + + if Polynomials._iszero(pj) return false end + + pj = Polynomials.printsign(io, pj, first, mimetype) + + if Polynomials.hasone(T) + if !(Polynomials._isone(pj) && !(Polynomials.showone(T) || j == 0)) + Polynomials.printcoefficient(io, pj, j, mimetype) + end + else + Polynomials.printcoefficient(io, pj, j, mimetype) + end + + Polynomials.printproductsign(io, pj, j, mimetype) + Polynomials.printexponent(io, var, j, mimetype) + return true +end + + Poly{T,X}(coeffs::AbstractVector{S}) where {T,X,S} = Poly(convert(Vector{T},coeffs), X) Base.convert(P::Type{<:Polynomial}, p::Poly{T}) where {T} = Polynomial{eltype(P),indeterminate(P,p)}(p.coeffs) @@ -53,6 +73,10 @@ function Base.iterate(p::Poly, state = firstindex(p)) end Base.collect(p::Poly) = [pᵢ for pᵢ ∈ p] +Polynomials.domain(::Type{<:Poly}) = Polynomials.Interval{Polynomials.Open,Polynomials.Open}(-Inf, Inf) +Polynomials.mapdomain(::Type{<:Poly}, x::AbstractArray) = x +Polynomials.coeffs(p::Poly) = p.coeffs + # need two here as `eltype(P)` is `_eltype(P)`. Base.zero(::Type{P}) where {P <: Poly} = Poly{_eltype(P), Polynomials.indeterminate(P)}([0]) Base.zero(::Type{P},var::Polynomials.SymbolLike) where {P <: Poly} = Poly(zeros(_eltype(P),1), var) @@ -66,6 +90,17 @@ function Polynomials.basis(P::Type{<:Poly}, k::Int, _var::Polynomials.SymbolLike Polynomials.constructorof(P){_eltype(P), Symbol(var)}(zs) end +function Base.evalpoly(x::S, p::Poly{T}) where {T,S} + oS = one(x) + length(p) == 0 && return zero(T) * oS + b = p[end] * oS + @inbounds for i in (lastindex(p) - 1):-1:0 + b = p[i]*oS .+ x * b + end + return b +end + + function (p::Poly{T})(x::S) where {T,S} oS = one(x) length(p) == 0 && return zero(T) * oS @@ -103,7 +138,7 @@ end poly(r, var = :x) = fromroots(Poly, r; var = var) -polyval(p::Poly, x::Number) = p(x) +polyval(p::Poly, x::Number) = evalpoly(x, p) #p(x) polyval(p::Poly, x) = p.(x) polyval(p::AbstractPolynomial, x) = error("`polyval` is a legacy name for use with `Poly` objects only. Use `p(x)`.") @@ -114,7 +149,56 @@ function Base.getproperty(p::Poly, nm::Symbol) return getfield(p, nm) end -function Polynomials.integrate(p::P, k::S) where {T, X, P <: Poly{T, X}, S<:Number} +function Base.divrem(num::P, den::Q) where {T, X, P <: Poly{T,X}, S, Q <: Poly{S,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 Poly(q_coeff,X), Poly(r_coeff,X) + +end + +function derivative(p::P, order::Integer = 1) where {T, X, P <: Poly{T,X}} + + order < 0 && throw(ArgumentError("Order of derivative must be non-negative")) + order == 0 && return p + d = degree(p) + order > d && return 0*p + hasnan(p) && return Poly(zero(T)/zero(T), X) # NaN{T} + + n = d + 1 + dp = [reduce(*, (i - order + 1):i, init = p[i]) for i ∈ order:d] + return Poly(dp, X) + +end + + +integrate(p::P, a, b) where {T,X,P <: Poly{T,X}} = (∫ = integrate(p); ∫(b) - ∫(a)) +function integrate(p::P, k::S=0) where {T, X, P <: Poly{T, X}, S<:Number} R = eltype(one(T)/1 + one(S)) Q = Poly{R,X} @@ -140,10 +224,23 @@ polyder(p::AbstractPolynomial, args...) = error("`polyder` is a legacy name for polyfit(x, y, n = length(x) - 1, sym=:x) = fit(Poly, x, y, n; var = sym) polyfit(x, y, sym::Symbol) = fit(Poly, x, y, var = sym) +function Polynomials.vander(P::Type{<:Poly}, x::AbstractVector{T}, d::Int) where {T <: Number} + A = Matrix{T}(undef, length(x), d+1) + Aᵢ = ones(T, length(x)) + + i′ = 1 + for i ∈ 1:(d+1) + A[:, i] = Aᵢ + Aᵢ .*= x + end + A +end + + export Poly, poly, polyval, polyint, polyder, polyfit ## Pade -include("../pade.jl") +include("pade.jl") using .PadeApproximation export Pade export padeval diff --git a/src/legacy/misc.jl b/src/legacy/misc.jl new file mode 100644 index 00000000..f9509c40 --- /dev/null +++ b/src/legacy/misc.jl @@ -0,0 +1,33 @@ + + +macro registerN(name, params...) + poly = esc(name) + αs = tuple(esc.(params)...) + quote + Base.convert(::Type{P}, q::Q) where {$(αs...),T, P<:$poly{$(αs...),T}, Q <: $poly{$(αs...),T}} = q + Base.convert(::Type{$poly{$(αs...)}}, q::Q) where {$(αs...),T, Q <: $poly{$(αs...),T}} = q + Base.promote(p::P, q::Q) where {$(αs...),T, X, P <:$poly{$(αs...),T,X}, Q <: $poly{$(αs...),T,X}} = p,q + Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{<:$poly{$(αs...),S,X}}) where {$(αs...),T,S,X} = + $poly{$(αs...),promote_type(T, S),X} + Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{S}) where {$(αs...),T,X,S<:Number} = + $poly{$(αs...),promote_type(T,S),X} + + function $poly{$(αs...),T}(x::AbstractVector{S}, var::SymbolLike = Var(:x)) where {$(αs...),T,S} + $poly{$(αs...),T,Symbol(var)}(T.(x)) + end + $poly{$(αs...)}(coeffs::AbstractVector{T}, var::SymbolLike=Var(:x)) where {$(αs...),T} = + $poly{$(αs...),T,Symbol(var)}(coeffs) + $poly{$(αs...),T,X}(c::AbstractPolynomial{S,Y}) where {$(αs...),T,X,S,Y} = convert($poly{$(αs...),T,X}, c) + $poly{$(αs...),T}(c::AbstractPolynomial{S,Y}) where {$(αs...),T,S,Y} = convert($poly{$(αs...),T}, c) + $poly{$(αs...),}(c::AbstractPolynomial{S,Y}) where {$(αs...),S,Y} = convert($poly{$(αs...),}, c) + + $poly{$(αs...),T,X}(n::Number) where {$(αs...),T,X} = T(n)*one($poly{$(αs...),T,X}) + $poly{$(αs...),T}(n::Number, var::SymbolLike = Var(:x)) where {$(αs...),T} = T(n)*one($poly{$(αs...),T,Symbol(var)}) + $poly{$(αs...)}(n::S, var::SymbolLike = Var(:x)) where {$(αs...), S<:Number} = + n*one($poly{$(αs...),S,Symbol(var)}) + $poly{$(αs...),T}(var::SymbolLike=Var(:x)) where {$(αs...), T} = + variable($poly{$(αs...),T,Symbol(var)}) + $poly{$(αs...)}(var::SymbolLike=Var(:x)) where {$(αs...)} = variable($poly{$(αs...)},Symbol(var)) + (p::$poly)(x) = evalpoly(x, p) + end +end diff --git a/src/pade.jl b/src/legacy/pade.jl similarity index 100% rename from src/pade.jl rename to src/legacy/pade.jl diff --git a/src/polynomial-container-types/immutable-dense-polynomial.jl b/src/polynomial-container-types/immutable-dense-polynomial.jl new file mode 100644 index 00000000..87f0964d --- /dev/null +++ b/src/polynomial-container-types/immutable-dense-polynomial.jl @@ -0,0 +1,303 @@ +# 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} <: AbstractDenseUnivariatePolynomial{B,T,X} + coeffs::NTuple{N,T} + function ImmutableDensePolynomial{B,T,X,N}(cs::NTuple{N}) where {B,N,T,X} + new{B,T,Symbol(X),N}(cs) + end +end + +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}(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 + +# 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}(::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}) where {B,T,X,N} = ImmutableDensePolynomial{B,T,X,N}(convert(NTuple{N,T}, xs)) +ImmutableDensePolynomial{B,T}(xs::NTuple{N}, var::SymbolLike=Var(:x)) where {B,T,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, order::Int=0) where {B,T,X} = + ImmutableDensePolynomial{B,T,X}(xs, order) + +function ImmutableDensePolynomial{B,T,X}(xs::AbstractVector, order::Int=0) where {B,T,X} + 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(T∘getindex,xs), Val(N)) + ImmutableDensePolynomial{B,T,X,N}(cs) +end + +constructorof(::Type{<:ImmutableDensePolynomial{B}}) where {B <: AbstractBasis} = ImmutableDensePolynomial{B} +@poly_register ImmutableDensePolynomial + +## ---- + +# 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{P}, ::Type{<:S}) where {S<:Number,B,T,X,N,P<:ImmutableDensePolynomial{B,T,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′′ = findlast(!iszero, p) + isnothing(N′′) && return zero(ImmutableDensePolynomial{B,T,X,N}) + N < N′′ && throw(ArgumentError("Wrong size")) + 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 + + +# 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} + i = chop_right_index(p.coeffs; rtol=rtol, atol=atol) + if i == nothing + xs = () + N′ = 0 + else + N′ = i + xs = ntuple(Base.Fix1(getindex, p.coeffs), Val(N′)) + end + ImmutableDensePolynomial{B,T,X,N′}(xs) +end + +chop!(p::ImmutableDensePolynomial; kwargs...) = chop(p; kwargs...) ## misnamed, should be chop!! +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 + + +# isapprox helper +function normΔ(q1::ImmutableDensePolynomial{B}, q2::ImmutableDensePolynomial{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 ∈ 0:maximum(lastindex, (q1,q2)) + @inbounds tot += abs2(q1[i] - q2[i]) + end + return sqrt(tot) +end + + +## --- + +_zeros(::Type{<:ImmutableDensePolynomial}, z::S, N) where {S} = + ntuple(_ -> zero(S), Val(N)) + +minimumexponent(::Type{<:ImmutableDensePolynomial}) = 0 + +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 + +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 + +# 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("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} + iszero(p1) && iszero(p2) && return true + 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 + + + +## --- +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} + i = findlast(!iszero, p.coeffs) + isnothing(i) && return -1 + 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}(()) + +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}}, i::Int) where {B} + xs = _zeros(P, zero(eltype(P)), i + 1) + @set! xs[end] = 1 + ImmutableDensePolynomial{B,eltype(P),indeterminate(P)}(xs) +end + + + +## Vector space operations +## 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} + 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 +, -; 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 + xs = _tuple_combine(op, p.coeffs, q.coeffs) + R = eltype(xs) + ImmutableDensePolynomial{B,R,X,N}(xs) +end + + +# scalar mult faster with 0 default +scalar_mult(p::ImmutableDensePolynomial{B,T,X,0}, c::S) where {B,T,X,S<:Scalar} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) +scalar_mult(c::S, p::ImmutableDensePolynomial{B,T,X,0}) where {B,T,X,S<:Scalar} = zero(ImmutableDensePolynomial{B,promote_type(T,S),X,0}) + + +## --- +# Padded vector combination of two homogeneous tuples assuming N ≥ M +@generated function _tuple_combine(op, p1::NTuple{N}, p2::NTuple{M}) where {N,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 + +## 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}, p2::NTuple{M}) where {N,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 diff --git a/src/polynomial-container-types/mutable-dense-laurent-polynomial.jl b/src/polynomial-container-types/mutable-dense-laurent-polynomial.jl new file mode 100644 index 00000000..c26c2f25 --- /dev/null +++ b/src/polynomial-container-types/mutable-dense-laurent-polynomial.jl @@ -0,0 +1,242 @@ +""" + 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 + +constructorof(::Type{<:MutableDenseLaurentPolynomial{B}}) where {B <: AbstractBasis} = MutableDenseLaurentPolynomial{B} +@poly_register MutableDenseLaurentPolynomial + +## --- + +## 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 + + +function Base.map!(fn, q::Q, p::P, args...) where {B,T,X, P<:MutableDenseLaurentPolynomial{B,T,X},S,Q<:MutableDenseLaurentPolynomial{B,S,X}} + map!(fn, q.coeffs, p.coeffs, args...) + nothing +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-container-types/mutable-dense-polynomial.jl b/src/polynomial-container-types/mutable-dense-polynomial.jl new file mode 100644 index 00000000..f3311ad1 --- /dev/null +++ b/src/polynomial-container-types/mutable-dense-polynomial.jl @@ -0,0 +1,189 @@ +""" + MutableDensePolynomial{B,T,X} + +""" +struct MutableDensePolynomial{B,T,X} <: AbstractDenseUnivariatePolynomial{B,T, X} + coeffs::Vector{T} + 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 "ignoring the axis offset of the coefficient vector" + cs = parent(cs) + end + i = findlast(!iszero, cs) + if i == nothing + xs = T[] + else + xs = T[cs[i] for i ∈ 1:i] # make copy + end + if order > 0 + prepend!(xs, zeros(T, order)) + end + new{B,T,Symbol(X)}(xs) + + end + 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 + +constructorof(::Type{<:MutableDensePolynomial{B}}) where {B <: AbstractBasis} = MutableDensePolynomial{B} +@poly_register MutableDensePolynomial + +## --- + +## Generics for polynomials +function Base.convert(::Type{MutableDensePolynomial{B,T,X}}, q::MutableDensePolynomial{B,T′,X′}) where {B,T,T′,X,X′} + !isconstant(q) && assert_same_variable(X,X′) + 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...) # faster than q=copy(p); map!(fn,q,p, args...); q + xs = trim_trailing_zeros!!(xs) + R = eltype(xs) + return ⟒(P){R,X}(Val(false), xs) +end + +function Base.map!(fn, q::Q, p::P, args...) where {B,T,X, P<:MutableDensePolynomial{B,T,X},S,Q<:MutableDensePolynomial{B,S,X}} + xs = map!(fn, q.coeffs, p.coeffs, args...) + xs = trim_trailing_zeros!!(q.coeffs) + nothing +end + +Base.copy(p::MutableDensePolynomial{B,T,X}) where {B,T,X} = + MutableDensePolynomial{B,T,Var(X)}(copy(p.coeffs)) + +Base.firstindex(p::MutableDensePolynomial) = 0 +Base.length(p::MutableDensePolynomial) = length(p.coeffs) +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)] +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) + 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 + else + p.coeffs[i + offset(p)] = value + end + p +end + +Base.eachindex(p::MutableDensePolynomial) = 0:1:lastindex(p) +Base.iterate(p::MutableDensePolynomial, args...) = Base.iterate(p.coeffs, args...) +Base.pairs(p::MutableDensePolynomial) = + Base.Generator(=>, 0:lastindex(p), p.coeffs) + +function coeffs(p::MutableDensePolynomial) + firstindex(p) < 0 && throw(ArgumentError("Polynomial has negative index terms. Use `pairs` instead`")) + iszero(firstindex(p)) && return p.coeffs + return [p[i] for i ∈ 0:lastindex(p)] +end + + +# 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 +Base.iszero(p::MutableDensePolynomial) = iszero(p.coeffs)::Bool + +function degree(p::MutableDensePolynomial) + 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} + 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) + n -= 1 + 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ᵣ == length(p.coeffs) && return p + + N = length(p.coeffs) + + for i ∈ (iᵣ+1):N + pop!(p.coeffs) + end + p +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 ∈ 0:maximum(lastindex, (q1,q2)) + @inbounds tot += abs2(q1[i] - q2[i]) + end + return sqrt(tot) +end + +minimumexponent(::Type{<:MutableDensePolynomial}) = 0 + + + +# vector ops +, -, c*x +## unary - (map is as fast) +## binary + +Base.:+(p::MutableDensePolynomial{B,T,X}, q::MutableDensePolynomial{B,S,X}) where{B,X,T,S} = + _vector_combine(+, p, q) +Base.:-(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) + + b₁, b₂ = lastindex(p), lastindex(q) + a, b = 0, max(b₁, b₂) + + N = b - a + 1 + z = zero(first(p.coeffs) + first(q.coeffs)) + x = _zeros(p, z, N) + + # zip faster than `pairs` + @inbounds for (i, cᵢ) ∈ enumerate(p.coeffs) + x[i] = cᵢ + end + @inbounds for (i, cᵢ) ∈ enumerate(q.coeffs) + x[i] = op(x[i], cᵢ) + end + + b₁ == b₂ && (x = trim_trailing_zeros!!(x)) + P(Val(false), x, a) + +end diff --git a/src/polynomial-container-types/mutable-dense-view-polynomial.jl b/src/polynomial-container-types/mutable-dense-view-polynomial.jl new file mode 100644 index 00000000..1f29a504 --- /dev/null +++ b/src/polynomial-container-types/mutable-dense-view-polynomial.jl @@ -0,0 +1,118 @@ +""" + 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} <: AbstractDenseUnivariatePolynomial{B, T, X} + coeffs::Vector{T} + 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")) + +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}(Val(false), coeffs) + order < 0 && throw(ArgumentError("Not a Laurent type")) + cs = convert(Vector{T}, coeffs) + prepend!(cs, zeros(T,order)) + MutableDenseViewPolynomial{B,T,X}(Val(false), cs) +end + +constructorof(::Type{<:MutableDenseViewPolynomial{B}}) where {B <: AbstractBasis} = MutableDenseViewPolynomial{B} +@poly_register MutableDenseViewPolynomial + +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 + +function Base.map!(fn, q::Q, p::P, args...) where {B,T,X, P<:MutableDenseViewPolynomial{B,T,X},S,Q<:MutableDenseViewPolynomial{B,S,X}} + map!(fn, q.coeffs, p.coeffs, args...) + nothing +end + + +minimumexponent(::Type{<:MutableDenseViewPolynomial}) = 0 +_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}, copy(p.coeffs)) + for (i,qᵢ) ∈ enumerate(q.coeffs) + cs[i] = op(cs[i], qᵢ) + end + else + cs = convert(Vector{R}, copy(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-container-types/mutable-sparse-polynomial.jl b/src/polynomial-container-types/mutable-sparse-polynomial.jl new file mode 100644 index 00000000..50e7ba1d --- /dev/null +++ b/src/polynomial-container-types/mutable-sparse-polynomial.jl @@ -0,0 +1,210 @@ +""" + +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} <: 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) + chop_exact_zeros!(coeffs) + new{B,T,Symbol(X)}(coeffs) + end + 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 + +function MutableSparsePolynomial{B,T,X}(checked::Val{:true}, coeffs::AbstractDict{Int,T}) where {B,T,X<:Symbol} + MutableSparsePolynomial{B,T,X}(coeffs) +end + +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=Var(: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} + 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) + iszero(n) && zero(P) + xs = convert(Vector{T}, coeffs) + d = Dict{Int, T}(Base.Generator(=>, order:(order+n-1), xs)) + P(d) +end + + +# 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) + 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 b ∈ c + T = promote_type(T, typeof(b)) + end + ks = 0:length(cs)-1 + vs = cs + d = Dict{Int,T}(Base.Generator(=>, ks, vs)) + return MutableSparsePolynomial{B,T,X}(d) + end +end + +constructorof(::Type{<:MutableSparsePolynomial{B}}) where {B <: AbstractBasis} = MutableSparsePolynomial{B} +@poly_register MutableSparsePolynomial + +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 + +function Base.map!(fn, q::Q, p::P, args...) where {B,T,X, P<:MutableSparsePolynomial{B,T,X},S,Q<:MutableSparsePolynomial{B,S,X}} + for (k,v) ∈ pairs(p.coeffs) + val = fn(val, args...) + iszero(val) ? deleteat!(q,k) : (q[k] = val) + end + nothing +end + +## --- + +minimumexponent(::Type{<:MutableSparsePolynomial}) = typemin(Int) + +# This is *annoying*. It should just be `lastindex(p)`, as this is a LaurentType, but for +# past compatibility, this is kept as is. +degree(p::MutableSparsePolynomial) = iszero(p) ? -1 : lastindex(p) + +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 + +Base.keys(p::MutableSparsePolynomial) = keys(p.coeffs) +Base.values(p::MutableSparsePolynomial) = values(p.coeffs) + +# 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} + 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] + cs[k - a + 1] = v + end + cs +end + + +hasnan(p::MutableSparsePolynomial) = any(hasnan, values(p.coeffs))::Bool +Base.pairs(p::MutableSparsePolynomial) = pairs(p.coeffs) + +offset(p::MutableSparsePolynomial) = 0 +function keys_union(p::MutableSparsePolynomial, q::MutableSparsePolynomial) + # IterTools.distinct(Base.Iterators.flatten((keys(p), keys(q)))) may allocate less + unique(Base.Iterators.flatten((keys(p), keys(q)))) +end + + + +## --- + +function chop_exact_zeros!(d::Dict) + for (k,v) ∈ pairs(d) + 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 + +chop!(p::MutableSparsePolynomial; kwargs...) = (chop!(p.coeffs; kwargs...); p) +function chop!(d::Dict; atol=nothing, rtol=nothing) + isempty(d) && return d + δ = something(rtol,0) + ϵ = something(atol,0) + τ = max(ϵ, norm(values(d),2) * δ) + for (i,pᵢ) ∈ pairs(d) + abs(pᵢ) ≤ τ && delete!(d, i) + end + d +end + +## --- + +_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 isconstant(p::MutableSparsePolynomial) + n = length(p.coeffs) + n == 0 && return true + n == 1 && haskey(p.coeffs, 0) +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/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 6780174a..00000000 --- a/src/polynomials/LaurentPolynomial.jl +++ /dev/null @@ -1,612 +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::StandardBasisPolynomial{S}) where {P <:LaurentPolynomial,S} - - 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 - -# 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 f024b360..00000000 --- a/src/polynomials/SparsePolynomial.jl +++ /dev/null @@ -1,261 +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,v) in p.coeffs - 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 diff --git a/src/polynomials/ChebyshevT.jl b/src/polynomials/chebyshev.jl similarity index 67% rename from src/polynomials/ChebyshevT.jl rename to src/polynomials/chebyshev.jl index f66837f5..bbad037b 100644 --- a/src/polynomials/ChebyshevT.jl +++ b/src/polynomials/chebyshev.jl @@ -1,4 +1,5 @@ -export ChebyshevT +# Example of using mutable dense container with a different basis +struct ChebyshevTBasis <: AbstractBasis end """ ChebyshevT{T, X}(coeffs::AbstractVector) @@ -39,43 +40,46 @@ The latter shows how to evaluate a `ChebyshevT` polynomial outside of its domain 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} +const ChebyshevT = MutableDensePolynomial{ChebyshevTBasis} +export ChebyshevT +_typealias(::Type{P}) where {P<:ChebyshevT} = "ChebyshevT" - if Base.has_offset_axes(coeffs) - @warn "ignoring the axis offset of the coefficient vector" - end +basis_symbol(::Type{<:AbstractUnivariatePolynomial{ChebyshevTBasis,T,X}}) where {T,X} = "T" - 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) +# 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 -@register ChebyshevT - -function Base.convert(P::Type{<:Polynomial}, ch::ChebyshevT) +# function Base.convert(P::Type{<:Polynomial}, ch::MutableDensePolynomial{ChebyshevTBasis}) - T = _eltype(P,ch) - X = indeterminate(P,ch) - Q = ⟒(P){T,X} +# T = _eltype(P,ch) +# X = indeterminate(P,ch) +# Q = ⟒(P){T,X} - if length(ch) < 3 - return Q(ch.coeffs) - end +# 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 degree(ch):-1:2 - tmp = c0 - c0 = Q(ch[i - 2]) - c1 - c1 = tmp + c1 * x * 2 - end - return c0 + c1 * x -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{<:ChebyshevT}, p::Polynomial) x = variable(C) @@ -83,21 +87,29 @@ function Base.convert(C::Type{<:ChebyshevT}, p::Polynomial) p(x) end -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} +# lowest degree is always 0 +function coeffs(p::ChebyshevT) + 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{<:ChebyshevT}) = 0 domain(::Type{<:ChebyshevT}) = Interval(-1, 1) + +constantterm(p::ChebyshevT) = p(0) function Base.one(::Type{P}) where {P<:ChebyshevT} T,X = eltype(P), indeterminate(P) - ChebyshevT{T,X}(ones(T,1)) + ⟒(P){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)]) + ⟒(P){T,X}([zero(T), one(T)]) end -constantterm(p::ChebyshevT) = p(0) + """ (::ChebyshevT)(x) @@ -122,16 +134,20 @@ julia> c.(-1:0.5:1) 5.0 ``` """ -function evalpoly(x::S, ch::ChebyshevT{T}) where {T,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::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} +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 @@ -140,26 +156,59 @@ function evalpoly(x::S, ch::ChebyshevT{T}, checked) where {T,S} 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) + P = MutableDensePolynomial{ChebyshevTBasis,R,X} + cs = convert(Vector{R}, copy(coeffs(p))) + n = length(cs) + iszero(n) && return P([c]) + isone(n) && return P([cs[1] + c]) + cs[1] += c + return P(cs) +end + +# product +function Base.:*(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 = ChebyshevT(cs,X) + return ret +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 +function derivative(p::P) where {B<:ChebyshevTBasis,T,X,P<:MutableDensePolynomial{B,T,X}} + R = eltype(one(T)/1) + Q = MutableDensePolynomial{ChebyshevTBasis,R,X} + isconstant(p) && return zero(Q) + hasnan(p) && return Q(R[NaN]) + + + q = convert(Q, copy(p)) + n = degree(p) + 1 + 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 - return A + if n > 1 + der[2] = 4q[2] + end + der[1] = q[1] + + return Q(der) + end -function integrate(p::ChebyshevT{T,X}) where {T,X} +function integrate(p::P) where {B<:ChebyshevTBasis,T,X,P<:MutableDensePolynomial{B,T,X}} R = eltype(one(T) / 1) - Q = ChebyshevT{R,X} + Q = MutableDensePolynomial{B,R,X} if hasnan(p) return Q([NaN]) end - n = length(p) + n = degree(p) + 1 if n == 1 return Q([zero(R), p[0]]) end @@ -175,35 +224,21 @@ function integrate(p::ChebyshevT{T,X}) where {T,X} 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] +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 - der[1] = q[1] - - pp = ChebyshevT(der, indeterminate(p)) - return order > 1 ? derivative(pp, order - 1) : pp - + return A end -function companion(p::ChebyshevT{T}) where T - d = length(p) - 1 +function companion(p::MutableDensePolynomial{ChebyshevTBasis,T}) where T + 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)) @@ -213,45 +248,16 @@ 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] + monics = coeffs(p) ./ coeffs(p)[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(p1.coeffs) - z2 = _c_to_z(p2.coeffs) - 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} +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 + n = degree(num) + m = degree(den) R = typeof(one(T) / one(S)) P = ChebyshevT{R,X} @@ -263,29 +269,17 @@ 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) 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 - - #= -zseries =# +zseries -- for ChebyshevT example +=# function _c_to_z(cs::AbstractVector{T}) where {T} n = length(cs) diff --git a/src/polynomials/factored_polynomial.jl b/src/polynomials/factored_polynomial.jl index 2b24bd41..d9401bc7 100644 --- a/src/polynomials/factored_polynomial.jl +++ b/src/polynomials/factored_polynomial.jl @@ -37,7 +37,7 @@ julia> map(x->round(x, digits=12), q) # map works over factors and leading coeff FactoredPolynomial((x - 4.0) * (x - 2.0) * (x - 3.0) * (x - 1.0)) ``` """ -struct FactoredPolynomial{T <: Number, X} <: StandardBasisPolynomial{T, X} +struct FactoredPolynomial{T <: Number, X} <: AbstractPolynomial{T, X} coeffs::Dict{T,Int} c::T function FactoredPolynomial{T, X}(checked::Val{false}, cs::Dict{T,Int}, c::T) where {T, X} @@ -86,6 +86,8 @@ function FactoredPolynomial(coeffs::AbstractVector{T}, var::SymbolLike=:x) where FactoredPolynomial{T,X}(coeffs) end + + ## ---- # abstract.jl The use of @register FactoredPolynomial didn't quite work, so # that is replicated here and modified. @@ -104,7 +106,7 @@ Base.promote_rule(::Type{<:FactoredPolynomial{T,X}}, ::Type{<:FactoredPolynomial Base.promote_rule(::Type{<:FactoredPolynomial{T,X}}, ::Type{S}) where {T,S<:Number,X} = FactoredPolynomial{promote_type(T,S), X} FactoredPolynomial{T,X}(n::S) where {T,X,S<:Number} = T(n) * one(FactoredPolynomial{T,X}) -FactoredPolynomial{T}(n::S, var::SymbolLike=:x) where {T,S<:Number} = T(n) * one(FactoredPolynomial{T,X}) +FactoredPolynomial{T}(n::S, var::SymbolLike=:x) where {T,S<:Number} = T(n) * one(FactoredPolynomial{T,Symbol(var)}) FactoredPolynomial(n::S, var::SymbolLike=:x) where {S<:Number} = n * one(FactoredPolynomial{S,Symbol(var)}) FactoredPolynomial(var::SymbolLike=:x) = variable(FactoredPolynomial, Symbol(var)) (p::FactoredPolynomial)(x) = evalpoly(x, p) @@ -205,6 +207,8 @@ end ## ---- +domain(::Type{<:FactoredPolynomial}) = Interval{Open,Open}(-Inf, Inf) +mapdomain(::Type{<:FactoredPolynomial}, x::AbstractArray) = x Base.iszero(p::FactoredPolynomial) = iszero(p.c) Base.zero(::Type{FactoredPolynomial{T,X}}) where {T, X} = FactoredPolynomial{T,X}(Dict{T,Int}(), zero(T)) @@ -262,6 +266,19 @@ function Base.:+(p::P, q::P) where {T,X,P<:FactoredPolynomial{T,X}} convert(P, 𝒑 + 𝒒 ) end +# subtraction +function Base.:-(p::P, c::S) where {S<:Number, T,X, P<:FactoredPolynomial{T,X}} + R = promote_type(S,T) + 𝑷 = Polynomial{R,X} + 𝒑,𝒒 = convert(𝑷, p), convert(𝑷, c) + convert(P, 𝒑 - 𝒒) +end +function Base.:-(p::P, q::P) where {T,X,P<:FactoredPolynomial{T,X}} + 𝑷 = Polynomial{T,X} + 𝒑,𝒒 = convert(𝑷, p), convert(𝑷, q) + convert(P, 𝒑 - 𝒒 ) +end + # multiplication function Base.:*(p::P, q::P) where {T,X, P<:FactoredPolynomial{T,X}} d = copy(p.coeffs) @@ -352,9 +369,16 @@ function integrate(p::P) where {P <: FactoredPolynomial} convert(P, ∫𝒑) end -function derivative(p::P,n::Int) where {P <: FactoredPolynomial} +function derivative(p::P,n::Int=1) where {P <: FactoredPolynomial} 𝑷 = Polynomial 𝒑 = convert(𝑷, p) 𝒑⁽ⁿ⁾ = derivative(𝒑, n) convert(P, 𝒑⁽ⁿ⁾) end + +function Multroot.multroot(p::FactoredPolynomial) + d = p.coeffs + (values = collect(keys(d)), + multiplicities = collect(values(d)), + κ = 0.0, ϵ = 0.0) +end diff --git a/src/polynomials/multroot.jl b/src/polynomials/multroot.jl index 731dce7c..70f5703d 100644 --- a/src/polynomials/multroot.jl +++ b/src/polynomials/multroot.jl @@ -3,8 +3,12 @@ module Multroot export multroot using ..Polynomials + using LinearAlgebra + +import ..Polynomials: PnPolynomial, StandardBasisPolynomial + """ multroot(p; verbose=false, method=:direct, kwargs...) @@ -94,7 +98,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::StandardBasisPolynomial{T}; verbose=false, kwargs...) where {T} # degenerate case, constant @@ -133,7 +137,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::StandardBasisPolynomial{T,X}; method = :direct, θ = 1e-8, # zero singular-value threshold ρ = 1e-13, # initial residual tolerance, was 1e-10 @@ -142,15 +146,13 @@ function pejorative_manifold( ) where {T,X} S = float(T) - u = Polynomials.PnPolynomial{S,X}(S.(coeffs(p))) - + u = convert(PnPolynomial{S,X}, p) nu₂ = norm(u, 2) θ2, ρ2 = θ * nu₂, ρ * nu₂ - u, v, w, ρⱼ, κ = Polynomials.ngcd( - u, derivative(u), - satol = θ2, srtol = zero(real(T)), - atol = ρ2, rtol = zero(real(T))) + u, derivative(u); + satol = θ2, srtol = zero(θ2), + atol = ρ2, rtol = zero(ρ2)) ρⱼ /= nu₂ # root approximations @@ -173,9 +175,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 +215,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 +241,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::StandardBasisPolynomial, zs::Vector{S}, ls; kwargs...) where {S} ps = reverse(coeffs(p)) pejorative_root(ps, zs, ls; kwargs...) @@ -374,7 +376,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 @@ -393,7 +395,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::StandardBasisPolynomial{T,X}, k::Int; method = :direct, leastsquares = false, @@ -406,7 +408,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 +439,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::StandardBasisPolynomial{T,X}, l::Vector{Int}; method = :direct, leastsquares = false, @@ -448,7 +450,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) @@ -471,17 +473,17 @@ 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 A = Sy[1:end-1,1:end .∉ [[k,2*k+1]]] - x = zeros(S, 2*k-1) + x = zeros(eltype(u), 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 +497,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..f4e34fde 100644 --- a/src/polynomials/ngcd.jl +++ b/src/polynomials/ngcd.jl @@ -11,9 +11,9 @@ 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}} + S,Y,Q<:StandardBasisPolynomial{S,Y}} if (degree(q) > degree(p)) - u,w,v,Θ,κ = ngcd(q,p,args...;kwargs...) + u,w,v,Θ,κ = ngcd(q,p,args...; kwargs...) return (u=u,v=v,w=w, Θ=Θ, κ=κ) end if degree(p) > 5*(1+degree(q)) @@ -29,8 +29,8 @@ function ngcd(p::P, q::Q, p ≈ q && return (u=p,v=one(p), w=one(p), θ=NaN, κ=NaN) Polynomials.assert_same_variable(p,q) - R = promote_type(float(T), float(S)) - 𝑷 = Polynomials.constructorof(promote_type(P,Q)){R,X} + R = promote_type(float(T)) + 𝑷 = Polynomials.constructorof(P){R,X} ps = R[pᵢ for pᵢ ∈ coeffs(p)] qs = R[qᵢ for qᵢ ∈ coeffs(q)] @@ -45,12 +45,13 @@ 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′ = 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} + 𝑷 = Polynomials.constructorof(P){R,X} u,v,w = convert.(𝑷, (out.u,out.v,out.w)) if nz > 1 u *= variable(u)^(nz-1) @@ -91,7 +92,8 @@ end module NGCD using Polynomials, LinearAlgebra -import Polynomials: PnPolynomial, constructorof +import Polynomials: constructorof, PnPolynomial + """ ngcd(p::PnPolynomial{T,X}, q::PnPolynomial{T,X}, [k::Int]; @@ -291,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 @@ -300,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 @@ -447,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)) @@ -526,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/polynomials/pi_n_polynomial.jl b/src/polynomials/pi_n_polynomial.jl deleted file mode 100644 index 061f4a62..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, p.coeffs) - 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 diff --git a/src/polynomials/standard-basis/immutable-polynomial.jl b/src/polynomials/standard-basis/immutable-polynomial.jl new file mode 100644 index 00000000..c8819779 --- /dev/null +++ b/src/polynomials/standard-basis/immutable-polynomial.jl @@ -0,0 +1,134 @@ +## 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 immutable_polynomials +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`. + +""" +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) + +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(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) + ImmutableDensePolynomial{B,R,X,1}(NTuple{1,R}(p[0] + c)) +end +function scalar_add(c::S, p::ImmutableDensePolynomial{B,T,X,N}) 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)) + + cs = _tuple_combine(+, convert(NTuple{N,R}, p.coeffs), NTuple{1,R}((c,))) + q = P{N}(cs) + + return q +end + + +# return N*M +# intercept promotion call +# 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 + +Base.:*(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}) +Base.:*(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}) +Base.:*(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 Base.:*(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) + ImmutableDensePolynomial{B,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} + cs = evalpoly(q, p.coeffs) + convert(P, cs) +end + +# special cases of polynomial composition +# ... TBD ... + + +# special cases are much more performant +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 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) + ImmutableDensePolynomial{B,R,X,N-1}(cs) +end + +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 + 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) + ImmutableDensePolynomial{B,R,X,N+1}(cs) +end diff --git a/src/polynomials/standard-basis/laurent-polynomial.jl b/src/polynomials/standard-basis/laurent-polynomial.jl new file mode 100644 index 00000000..68e7d93f --- /dev/null +++ b/src/polynomials/standard-basis/laurent-polynomial.jl @@ -0,0 +1,266 @@ +# 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 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 Base.:*(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{T,X}) where {T,X} + m,n = firstindex(p), lastindex(p) + m != n && throw(ArgumentError("Only monomials can be inverted")) + c = 1/p[n] + return LaurentPolynomial(c, -m, X) +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/polynomials/standard-basis/pn-polynomial.jl b/src/polynomials/standard-basis/pn-polynomial.jl new file mode 100644 index 00000000..cb028163 --- /dev/null +++ b/src/polynomials/standard-basis/pn-polynomial.jl @@ -0,0 +1,30 @@ +""" + 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 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. + +""" +const PnPolynomial = MutableDenseViewPolynomial{StandardBasis} +_typealias(::Type{P}) where {P<:PnPolynomial} = "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 + 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 diff --git a/src/polynomials/standard-basis/polynomial.jl b/src/polynomials/standard-basis/polynomial.jl new file mode 100644 index 00000000..f871d7d8 --- /dev/null +++ b/src/polynomials/standard-basis/polynomial.jl @@ -0,0 +1,36 @@ +""" + 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) +``` +""" +const Polynomial = MutableDensePolynomial{StandardBasis} +export Polynomial + +_typealias(::Type{P}) where {P<:Polynomial} = "Polynomial" diff --git a/src/polynomials/standard-basis/sparse-polynomial.jl b/src/polynomials/standard-basis/sparse-polynomial.jl new file mode 100644 index 00000000..92fcbee6 --- /dev/null +++ b/src/polynomials/standard-basis/sparse-polynomial.jl @@ -0,0 +1,120 @@ +## 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) + for (i, cᵢ) ∈ p.coeffs + tot = muladd(cᵢ, x^i, tot) + end + return tot +end + +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 Base.:*(p::MutableSparsePolynomial{StandardBasis,T,X}, + q::MutableSparsePolynomial{StandardBasis,S,X}) where {T,S,X} + z = zero(p[0] + q[0]) + R = typeof(z) + cs = Dict{Int, R}() + dict_conv!(cs, p.coeffs, q.coeffs, z) + MutableSparsePolynomial{StandardBasis,R,X}(Val(false), cs) +end + + +# simple convolution save for handling of zeros +function dict_conv!(d::Dict{Int,T}, p, q, z=zero(T)) where {T} + for (i, pᵢ) ∈ pairs(p) + for (j, qⱼ) ∈ pairs(q) + cᵢⱼ = get(d, i + j, z) + val = muladd(pᵢ, qⱼ, cᵢⱼ) + iszero(val) ? delete!(d, i + j) : (d[i + j] = val) + end + end +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 + +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 diff --git a/src/polynomials/standard-basis.jl b/src/polynomials/standard-basis/standard-basis.jl similarity index 69% rename from src/polynomials/standard-basis.jl rename to src/polynomials/standard-basis/standard-basis.jl index 77774ab7..87a70eff 100644 --- a/src/polynomials/standard-basis.jl +++ b/src/polynomials/standard-basis/standard-basis.jl @@ -1,212 +1,187 @@ -abstract type StandardBasisPolynomial{T,X} <: AbstractPolynomial{T,X} end -abstract type LaurentBasisPolynomial{T,X} <: StandardBasisPolynomial{T,X} end +struct StandardBasis <: AbstractBasis end +const StandardBasisPolynomial = AbstractUnivariatePolynomial{<:Polynomials.StandardBasis,T,X} where {T,X} -function showterm(io::IO, ::Type{<:StandardBasisPolynomial}, pj::T, var, j, first::Bool, mimetype) where {T} +basis_symbol(::Type{P}) where {P<:StandardBasisPolynomial} = 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)) + hasone(typeof(j)) && isone(j) && return # no 2x^1, just 2x + print(io, exponent_text(j, m)) +end - if _iszero(pj) return false end - pj = printsign(io, pj, first, mimetype) +function Base.convert(P::Type{PP}, q::Q) where {B<:StandardBasis, PP <: AbstractUnivariatePolynomial{B}, Q<:AbstractUnivariatePolynomial{B}} + isa(q, PP) && return q + minimumexponent(P) <= firstindex(q) || + throw(ArgumentError("a $P can not have a minimum exponent of $(minimumexponent(q))")) - if hasone(T) - if !(_isone(pj) && !(showone(T) || j == 0)) - printcoefficient(io, pj, j, mimetype) - end + T = _eltype(P,q) + X = indeterminate(P,q) + + i₀ = firstindex(q) + if i₀ < 0 + return ⟒(P){T,X}([q[i] for i in eachindex(q)], i₀) else - printcoefficient(io, pj, j, mimetype) + return ⟒(P){T,X}([q[i] for i in 0:max(0,degree(q))]) # should trim trailing 0s end - - printproductsign(io, pj, j, mimetype) - printexponent(io, var, j, mimetype) - return true end -""" - evalpoly(x, p::StandardBasisPolynomial) - p(x) - -Evaluate the polynomial using [Horner's Method](https://en.wikipedia.org/wiki/Horner%27s_method), also known as synthetic division, as implemented in `evalpoly` of base `Julia`. - -# Examples -```jldoctest -julia> using Polynomials -julia> p = Polynomial([1, 0, 3]) -Polynomial(1 + 3*x^2) +Base.one(::Type{P}) where {B<:StandardBasis,T,X, P <: AbstractUnivariatePolynomial{B,T,X}} = ⟒(P){T,X}(ones(T,1)) -julia> p(0) -1 +variable(P::Type{<:AbstractUnivariatePolynomial{B,T,X}}) where {B<:StandardBasis,T,X} = basis(P, 1) -julia> p.(0:3) -4-element Vector{Int64}: - 1 - 4 - 13 - 28 -``` -""" -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 -end - -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) - if isa(q, P) - 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)]) - end -end +basis(P::Type{<:AbstractUnivariatePolynomial{B, T, X}}, i::Int) where {B<:StandardBasis,T,X} = P(ones(T,1), i) -# treat p as a *vector* of coefficients -Base.similar(p::StandardBasisPolynomial, args...) = similar(coeffs(p), args...) +constantterm(p::AbstractUnivariatePolynomial{B}) where {B <: StandardBasis} = p[0] -function Base.one(::Type{P}) where {P<:StandardBasisPolynomial} - T,X = eltype(P), indeterminate(P) - ⟒(P){T,X}(ones(T,1)) -end -function variable(::Type{P}) where {P<:StandardBasisPolynomial} - T,X = eltype(P), indeterminate(P) - ⟒(P){T,X}([zero(T), one(T)]) -end +domain(::Type{P}) where {B <: StandardBasis, P <: AbstractUnivariatePolynomial{B}} = Interval{Open,Open}(-Inf, Inf) -## ---- arithmetic +mapdomain(::Type{P}, x::AbstractArray) where {B <: StandardBasis, P <: AbstractUnivariatePolynomial{B}} = x -# can bypass c*one(P) -Base.:+(p::P, c::T) where {T, X, P<:StandardBasisPolynomial{T, X}} = p + ⟒(P)([c], X) -## multiplication algorithms for computing p * q. -## default multiplication between same type. -## subtypes might relax to match T,S to avoid one conversion -function Base.:*(p::P, q::P) where {T,X, P<:StandardBasisPolynomial{T,X}} - cs = ⊗(P, p.coeffs, q.coeffs) - P(cs) +## 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 ⊗(P::Type{<:StandardBasisPolynomial}, p::Vector{T}, q::Vector{S}) where {T<:Number,S<:Number} +function scalar_add(c::S, p::P) where {B<:StandardBasis, S, T, X, P<:AbstractDenseUnivariatePolynomial{B,T,X}} R = promote_type(T,S) - fastconv(convert(Vector{R}, p), convert(Vector{R},q)) -end + P′ = ⟒(P){R,X} -## put here, not with type definition, in case reuse is possible -## `conv` can be used with matrix entries, unlike `fastconv` -function conv(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], Σ) + 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 +scalar_add(c::S, p::AbstractLaurentUnivariatePolynomial{StandardBasis}) where {S} = throw(ArgumentError("Default method not defined")) + +# special cases are faster +# function Base.:*(p::AbstractUnivariatePolynomial{B,T,X}, +# q::AbstractUnivariatePolynomial{B,S,X}) where {B <: StandardBasis, T,S,X} +# # simple convolution with order shifted +# throw(ArgumentError("Default method not defined")) +# end + +# for dense 0-based polys with p.coeffs +for P ∈ (:MutableDensePolynomial, :MutableDenseViewPolynomial) + @eval begin + function Base.:*(p::P, q::Q) where {B <: StandardBasis,X, + T, P<:$P{B,T,X}, + S, Q<:$P{B,S,X}} + _standard_basis_multiplication(p,q) end - push!(as, Σ) end - as end -function ⊗(P::Type{<:StandardBasisPolynomial}, p::Vector{T}, q::Vector{S}) where {T,S} - conv(p, q) -end +function _standard_basis_multiplication(p::P,q::Q) where {T,X,P<:AbstractDenseUnivariatePolynomial{StandardBasis,T,X}, + S,Q<:AbstractDenseUnivariatePolynomial{StandardBasis,S,X}} + @assert constructorof(P) == constructorof(Q) -## Static size of product makes generated functions a good choice -## from https://github.com/tkoolen/StaticUnivariatePolynomials.jl/blob/master/src/monomial_basis.jl -## convolution of two tuples -@generated function ⊗(::Type{<:StandardBasisPolynomial}, p1::NTuple{N,T}, p2::NTuple{M,S}) where {T,N,S,M} - P = M + N - 1 - exprs = Any[nothing for i = 1 : P] - for i in 1 : N - for j in 1 : M - k = i + j - 1 - if isnothing(exprs[k]) - exprs[k] = :(p1[$i] * p2[$j]) - else - exprs[k] = :(muladd(p1[$i], p2[$j], $(exprs[k]))) - end - end - end + P′ = ⟒(P){promote_type(T,S),X} - return quote - Base.@_inline_meta # 1.8 deprecation - tuple($(exprs...)) - end + 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 ⊗(P::Type{<:StandardBasisPolynomial}, p::Dict{Int,T}, q::Dict{Int,S}) where {T,S} - R = promote_type(T,S) - c = Dict{Int,R}() - for (i,pᵢ) ∈ pairs(p) - for (j,qⱼ) ∈ pairs(q) - cᵢⱼ = get(c, i+j, zero(R)) - @inbounds c[i+j] = muladd(pᵢ, qⱼ, cᵢⱼ) +# up to caller to ensure cs is zeroed out +function LinearAlgebra.mul!(cs, p::AbstractDenseUnivariatePolynomial, q::AbstractDenseUnivariatePolynomial) + # TODO: check that p.coeffs, q.coeffs and cs aren't aliased. + m,n = length(p)-1, length(q)-1 + @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 - c + nothing end -## Define a dot product on vectors of polynomials, where polynomials are treated as -## as the scalar types -- the dot is the sum of pairwise products. Might change in -## the future if this causes confusion with the "vector" interpretation of polys. -LinearAlgebra.dot(xv::AbstractArray{T}, yv::AbstractArray{T}) where {T <: StandardBasisPolynomial} = - sum(conj(x)*y for (x,y) = zip(xv, yv)) -## --- - -function fromroots(P::Type{<:StandardBasisPolynomial}, r::AbstractVector{T}; var::SymbolLike = Var(:x)) where {T <: Number} - n = length(r) - c = zeros(T, n + 1) - c[1] = one(T) - for j in 1:n, i in j:-1:1 - c[(i + 1)] = c[(i + 1)] - r[j] * c[i] - end - #return P(c, var) - if sort(r[imag(r).>0], lt = (x,y) -> real(x)==real(y) ? imag(x) real(x)==real(y) ? imag(x) d && return 0*p - hasnan(p) && return ⟒(P)(zero(T)/zero(T), Var(X)) # NaN{T} - - n = d + 1 - dp = [reduce(*, (i - order + 1):i, init = p[i]) for i ∈ order:d] - return ⟒(P)(dp, Var(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] = 0 * (p[0]/1) + o = offset(p) + for (i, pᵢ) ∈ pairs(p) + cs[i+1+o] = pᵢ/(i+1) + end + Q(Val(false), cs) end -function integrate(p::P) where {T, X, P <: StandardBasisPolynomial{T, X}} - hasnan(p) && return ⟒(P)(NaN, Var(X)) - iszero(p) && return zero(p)/1 +function integrate(p::AbstractLaurentUnivariatePolynomial{B,T,X}) where {B <: StandardBasis,T,X} - as = [pᵢ/(i+1) for (i, pᵢ) ∈ pairs(p)] - pushfirst!(as, zero(constantterm(p))) - return ⟒(P)(as, Var(X)) + iszero(p) && return p/1 + N = lastindex(p) - firstindex(p) + 1 + z = 0*(p[0]/1) + R = eltype(z) + 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) + 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 + P(cs, firstindex(p)) end +## -------------------------------------------------- -function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, S, Q <: StandardBasisPolynomial{S}} +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) @@ -235,11 +210,11 @@ function Base.divrem(num::P, den::Q) where {T, P <: StandardBasisPolynomial{T}, end end resize!(r_coeff, min(length(r_coeff), m)) - - return _convert(num, q_coeff), _convert(num, r_coeff) + return PP(q_coeff), PP(r_coeff) end + """ gcd(p1::StandardBasisPolynomial, p2::StandardBasisPolynomial; method=:euclidean, kwargs...) @@ -254,13 +229,13 @@ Passing `method=:numerical` will call the internal method `NGCD.ngcd` for the nu function Base.gcd(p1::P, p2::Q, args...; method=:euclidean, kwargs... - ) where {T, P <: StandardBasisPolynomial{T}, Q <: StandardBasisPolynomial{T}} - + ) where {P <:StandardBasisPolynomial, + Q <:StandardBasisPolynomial} gcd(Val(method), p1, p2, args...; kwargs...) end function Base.gcd(::Val{:euclidean}, - r₀::StandardBasisPolynomial{T}, r₁; + r₀::AbstractUnivariatePolynomial{StandardBasis,T}, r₁; atol=zero(real(T)), rtol=Base.rtoldefault(real(T)), kwargs...) where {T} @@ -295,16 +270,17 @@ Author: Andreas Varga Note: requires Julia `v1.2` or greater. """ -function gcd_noda_sasaki(p::StandardBasisPolynomial{T}, q::StandardBasisPolynomial{S}; +function gcd_noda_sasaki(p::StandardBasisPolynomial{T,X}, q::StandardBasisPolynomial{S,Y}; atol::Real=zero(real(promote_type(T,S))), rtol::Real=Base.rtoldefault(real(promote_type(T,S))) - ) where {T,S} - ⟒(typeof(p)) == ⟒(typeof(q)) || return gcd_noda_sasaki(promote(p,q); atol=atol, rtol=rtol) + ) where {T,S,X,Y} + ⟒(typeof(p)) == ⟒(typeof(q)) || return gcd_noda_sasaki(promote(p,q)...; atol=atol, rtol=rtol) + (isconstant(p) || isconstant(q)) || assert_same_variable(X,Y) ## check symbol a, b = coeffs(p), coeffs(q) as = _gcd_noda_sasaki(a,b, atol=atol, rtol=rtol) - _convert(p, as) + ⟒(p)(as, X) end function _gcd_noda_sasaki(a::Vector{T}, b::Vector{S}; @@ -356,13 +332,12 @@ end Base.gcd(::Val{:numerical}, p, q, args...; kwargs...) = ngcd(p,q, args...; kwargs...).u -uvw(p::P, q::Q, args...; +uvw(p::P,q::P; method=:euclidean, kwargs... - ) where {T, P <: StandardBasisPolynomial{T}, Q <: StandardBasisPolynomial{T}} = - uvw(Val(method), p, q; kwargs...) + ) where {P<:StandardBasisPolynomial} = uvw(Val(method), p, q; kwargs...) -function uvw(::Val{:numerical}, p::P, q::P; kwargs...) where {P <: StandardBasisPolynomial} +function uvw(::Val{:numerical}, p::P, q::P; kwargs...) where {P <:StandardBasisPolynomial } u,v,w,Θ,κ = ngcd(p,q; kwargs...) u,v,w end @@ -373,13 +348,13 @@ function uvw(V::Val{:euclidean}, p::P, q::P; kwargs...) where {P <: StandardBasi end function uvw(::Any, p::P, q::P; kwargs...) where {P <: StandardBasisPolynomial} - throw(ArgumentError("not defined")) + throw(ArgumentError("not defined")) end # Some things lifted from # from https://github.com/jmichel7/LaurentPolynomials.jl/blob/main/src/LaurentPolynomials.jl. # This follows mostly that of intfuncs.jl which is specialized for integers. -function Base.gcdx(a::P, b::P) where {T,X,P<:StandardBasisPolynomial{T,X}} +function Base.gcdx(a::P, b::P) where {T,X,P<:AbstractUnivariatePolynomial{StandardBasis,T,X}} # a0, b0=a, b s0, s1=one(a), zero(a) t0, t1 = s1, s0 @@ -394,7 +369,7 @@ function Base.gcdx(a::P, b::P) where {T,X,P<:StandardBasisPolynomial{T,X}} (x, s0, t0)./x[end] end -Base.gcdx(a::StandardBasisPolynomial{T,X}, b::StandardBasisPolynomial{S,X}) where {S,T,X} = +Base.gcdx(a::AbstractUnivariatePolynomial{StandardBasis,T,X}, b::AbstractUnivariatePolynomial{StandardBasis,S,X}) where {S,T,X} = gcdx(promote(a, b)...) # p^m mod q @@ -417,8 +392,22 @@ end ## -------------------------------------------------- +## polynomial-roots +function fromroots(P::Type{<:AbstractDenseUnivariatePolynomial{StandardBasis}}, r::AbstractVector{T}; var::SymbolLike = Var(:x)) where {T <: Number} + n = length(r) + c = zeros(T, n + 1) + c[1] = one(T) + for j in 1:n, i in j:-1:1 + c[(i + 1)] = c[(i + 1)] - r[j] * c[i] + end + #return P(c, var) + if sort(r[imag(r).>0], lt = (x,y) -> real(x)==real(y) ? imag(x) real(x)==real(y) ? imag(x) [-p[0] / p[1]]) @@ -435,7 +424,7 @@ function companion(p::P) where {T, P <: StandardBasisPolynomial{T}} end # block companion matrix -function companion(p::P) where {T, M <: AbstractMatrix{T}, P<:StandardBasisPolynomial{M}} +function companion(p::P) where {T, M <: AbstractMatrix{T}, P<:AbstractUnivariatePolynomial{StandardBasis,M}} C₀, C₁ = companion_pencil(p) C₀ * inv(C₁) # could be more efficient end @@ -443,7 +432,7 @@ end # the companion pencil is `C₀`, `C₁` where `λC₁ - C₀` is singular for # eigen values of the companion matrix: `vᵀ(λC₁ - C₀) = 0` => `vᵀλ = vᵀ(C₀C₁⁻¹)`, where `C₀C₁⁻¹` # is the companion matrix. -function companion_pencil(p::P) where {T, P<:StandardBasisPolynomial{T}} +function companion_pencil(p::P) where {T, P<:AbstractUnivariatePolynomial{StandardBasis,T}} n = degree(p) C₁ = diagm(0 => ones(T, n)) C₁[end,end] = p[end] @@ -456,7 +445,7 @@ function companion_pencil(p::P) where {T, P<:StandardBasisPolynomial{T}} end # block version -function companion_pencil(p::P) where {T, M <: AbstractMatrix{T}, P<:StandardBasisPolynomial{M}} +function companion_pencil(p::P) where {T, M <: AbstractMatrix{T}, P<:AbstractUnivariatePolynomial{StandardBasis,M}} m,m′ = size(p[0]) @assert m == m′ # square matrix @@ -480,7 +469,7 @@ function companion_pencil(p::P) where {T, M <: AbstractMatrix{T}, P<:StandardBas C₀, C₁ end -function roots(p::P; kwargs...) where {T, P <: StandardBasisPolynomial{T}} +function roots(p::P; kwargs...) where {T, P <: AbstractUnivariatePolynomial{StandardBasis, T}} R = eltype(one(T)/one(T)) d = degree(p) @@ -506,6 +495,11 @@ function roots(p::P; kwargs...) where {T, P <: StandardBasisPolynomial{T}} L end +## -------------------------------------------------- +## Code for fitting polynomials + +## -------------------------------------------------- + function vander(P::Type{<:StandardBasisPolynomial}, x::AbstractVector{T}, n::Integer) where {T <: Number} vander(P, x, 0:n) end @@ -602,9 +596,9 @@ function fit(P::Type{<:StandardBasisPolynomial}, x::AbstractVector{T}, y::AbstractVector{T}, J, - cs::Dict{Int, S}; + cs::Dict; weights = nothing, - var = :x,) where {T,S} + var = :x,) where {T} for i ∈ J haskey(cs, i) && throw(ArgumentError("cs can't overlap with deg")) @@ -759,6 +753,7 @@ Base.convert(::Type{P}, p::ArnoldiFit{T,M,X}) where {P <: AbstractPolynomial,T,M + ## -------------------------------------------------- """ compensated_horner(p::P, x) @@ -778,7 +773,7 @@ As noted, this reflects the accuracy of a backward stable computation performed Pointed out in https://discourse.julialang.org/t/more-accurate-evalpoly/45932/5. """ -function compensated_horner(p::P, x) where {T, P <: StandardBasisPolynomial{T}} +function compensated_horner(p::P, x) where {P <: StandardBasisPolynomial} compensated_horner(coeffs(p), x) end @@ -835,10 +830,32 @@ end end -# Condition number of a standard basis polynomial -# rule of thumb: p̂ a compute value -# |p(x) - p̃(x)|/|p(x)| ≤ α(n)⋅u ⋅ cond(p,x), where u = finite precision of computation (2^-p) -function LinearAlgebra.cond(p::P, x) where {P <: StandardBasisPolynomial} + +## -------------------------------------------------- +## put here, not with type definition, in case reuse is possible +## `conv` can be used with matrix entries, unlike `fastconv` +function conv(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 + +## -------------------------------------------------- + +function LinearAlgebra.cond(p::StandardBasisPolynomial, x) p̃ = map(abs, p) p̃(abs(x))/ abs(p(x)) end + +## the future if this causes confusion with the "vector" interpretation of polys. +LinearAlgebra.dot(xv::AbstractArray{T}, yv::AbstractArray{T}) where {T <: StandardBasisPolynomial} = + sum(conj(x)*y for (x,y) = zip(xv, yv)) diff --git a/src/precompiles.jl b/src/precompiles.jl index 8efcfadc..627a2df0 100644 --- a/src/precompiles.jl +++ b/src/precompiles.jl @@ -3,4 +3,4 @@ p = fromroots(Polynomial, [1,1,2]) Multroot.multroot(p) gcd(p, derivative(p); method=:numerical) -uvw(p, derivative(p); method=:numerical) +#uvw(p, derivative(p); method=:numerical) diff --git a/src/promotions.jl b/src/promotions.jl new file mode 100644 index 00000000..50881568 --- /dev/null +++ b/src/promotions.jl @@ -0,0 +1,26 @@ +Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, + P<:AbstractUnivariatePolynomial{B,T,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<:AbstractDenseUnivariatePolynomial{B,T,X}, + Q<:AbstractDenseUnivariatePolynomial{B,S,X}} = MutableDensePolynomial{B,promote_type(T,S),X} + +Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, + P<:AbstractDenseUnivariatePolynomial{B,T,X}, + Q<:AbstractLaurentUnivariatePolynomial{B,S,X}} = MutableDenseLaurentPolynomial{B,promote_type(T,S),X} + +Base.promote_rule(::Type{P}, ::Type{Q}) where {B,T,S,X, + P<:AbstractLaurentUnivariatePolynomial{B,T,X}, + Q<:AbstractLaurentUnivariatePolynomial{B,S,X}} = MutableDenseLaurentPolynomial{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}} = + Polynomial{promote_type(T, S),X} + +Base.promote_rule(::Type{P},::Type{Q}) where {T,X, P<:AbstractPolynomial{T,X}, + S,Y, Q<:AbstractPolynomial{S,Y}} = + assert_same_variable(X,Y) diff --git a/src/rational-functions/common.jl b/src/rational-functions/common.jl index 16939651..fb0fdd42 100644 --- a/src/rational-functions/common.jl +++ b/src/rational-functions/common.jl @@ -26,7 +26,7 @@ end ## helper to make a rational function of given type function rational_function(::Type{R}, p::P, q::Q) where {R<:AbstractRationalFunction, - T,X, P<:AbstractPolynomial{T,X}, + T,X, P<:AbstractPolynomial{T,X}, S, Q<:AbstractPolynomial{S,X}} constructorof(R)(promote(p,q)...) end @@ -37,7 +37,7 @@ function Base.convert(::Type{PQ}, pq′::PQ′) where {T,X,P,PQ <: AbstractRatio T′,X′,P′,PQ′<:AbstractRationalFunction{T′,X′,P′} } !isconstant(pq′) && assert_same_variable(X,X′) p′,q′=pqs(pq′) - 𝑷 = isconstant(pq′) ? P : promote_type(P, P′) + 𝑷 = isconstant(pq′) ? P : promote_type(P, P′) p,q = convert(𝑷, p′), convert(𝑷, q′) rational_function(PQ, p, q) end @@ -96,7 +96,7 @@ function Base.promote_rule(::Type{PQ}, ::Type{PQ′}) where {T,X,P,PQ <: Abstrac assert_same_variable(X,X′) PQ_, PQ′_ = constructorof(PQ), constructorof(PQ′) 𝑷𝑸 = PQ_ == PQ′ ? PQ_ : RationalFunction - 𝑷 = constructorof(typeof(variable(P)+variable(P′))) + 𝑷 = constructorof(typeof(variable(P)+variable(P′))); 𝑷 = Polynomial 𝑻 = promote_type(T,T′) 𝑷𝑸{𝑻,X,𝑷{𝑻,X}} end @@ -298,7 +298,7 @@ Base.:+(p::Number, q::AbstractRationalFunction) = q + p Base.:+(p::AbstractRationalFunction, q::Number) = p + q*one(p) Base.:+(p::AbstractPolynomial, q::AbstractRationalFunction) = q + p Base.:+(p::AbstractRationalFunction, q::AbstractPolynomial) = p + (q//one(q)) -Base.:+(p::P, q::T) where {T<: AbstractRationalFunction, P<:StandardBasisPolynomial{T}} = throw(DomainError()) # avoid ambiguity (issue #435. +#XXXBase.:+(p::P, q::T) where {T<: AbstractRationalFunction, P<:StandardBasisPolynomial{T}} = throw(DomainError()) # avoid ambiguity (issue #435. Base.:+(p::AbstractRationalFunction, q::AbstractRationalFunction) = sum(promote(p,q)) # type should implement this function Base.:+(p::R, q::R) where {T,X,P,R <: AbstractRationalFunction{T,X,P}} @@ -434,7 +434,7 @@ By default, `AbstractRationalFunction` types do not cancel common factors. This """ function lowest_terms(pq::PQ; method=:numerical, kwargs...) where {T,X, - P<:StandardBasisPolynomial{T,X}, + P<:AbstractPolynomial{T,X}, #StandardBasisPolynomial{T,X}, PQ<:AbstractRationalFunction{T,X,P}} p,q = pqs(pq) u,v,w = uvw(p,q; method=method, kwargs...) diff --git a/src/rational-functions/rational-function.jl b/src/rational-functions/rational-function.jl index 30abbdfb..bc3e1526 100644 --- a/src/rational-functions/rational-function.jl +++ b/src/rational-functions/rational-function.jl @@ -2,7 +2,7 @@ RationalFunction(p::AbstractPolynomial, q::AbstractPolynomial) p // q -Create a rational expression (`p//q`) from the two polynomials. +Create a rational expression (`p//q`) from the two polynomials. Common factors are not cancelled by the constructor, as they are for the base `Rational` type. The [`lowest_terms(pq)`](@ref) function attempts @@ -37,7 +37,7 @@ julia> derivative(pq) ``` !!! note - The [RationalFunctions.jl](https://github.com/aytekinar/RationalFunctions.jl) package was a helpful source of ideas. + The [RationalFunctions.jl](https://github.com/aytekinar/RationalFunctions.jl) package was a helpful source of ideas. !!! note The `ImmutablePolynomial` type can not be used for rational functions, as the type requires the numerator and denominator to have the exact same type. @@ -58,8 +58,7 @@ struct RationalFunction{T, X, P<:AbstractPolynomial{T,X}} <: AbstractRationalFun end end -RationalFunction(p,q) = RationalFunction(promote(p,q)...) -RationalFunction(p::ImmutablePolynomial,q::ImmutablePolynomial) = throw(ArgumentError("Sorry, immutable #polynomials are not a valid polynomial type for RationalFunction")) +RationalFunction(p,q) = RationalFunction(convert(LaurentPolynomial,p), convert(LaurentPolynomial,q)) function RationalFunction(p::LaurentPolynomial,q::LaurentPolynomial) 𝐩 = convert(RationalFunction, p) 𝐪 = convert(RationalFunction, q) @@ -77,5 +76,3 @@ RationalFunction(p::AbstractPolynomial) = RationalFunction(p,one(p)) function Base.://(p::AbstractPolynomial,q::AbstractPolynomial) RationalFunction(p,q) end - - diff --git a/src/show.jl b/src/show.jl index 92e17bea..1102ba2c 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 @@ -167,7 +170,7 @@ Shows the j'th term of the given polynomial. Returns `true` after successfully p For example. for a `Polynomial` this would show the term `pj * var^j`. """ -function showterm(io::IO, ::Type{AbstractPolynomial}, pj::T, var, j, first::Bool, mimetype) where {T} end +function showterm(io::IO, ::Type{<:AbstractPolynomial}, pj::T, var, j, first::Bool, mimetype) where {T} end ## print the sign @@ -306,16 +309,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/test/ChebyshevT.jl b/test/ChebyshevT.jl index 752c4ab1..1a1effd4 100644 --- a/test/ChebyshevT.jl +++ b/test/ChebyshevT.jl @@ -10,12 +10,11 @@ @test p.coeffs == coeff @test coeffs(p) == coeff @test degree(p) == length(coeff) - 1 - @test (@test_deprecated Polynomials.order(p)) == length(coeff) - 1 # issue 457 @test Polynomials.indeterminate(p) == :x @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 typeof(p).parameters[2] == eltype(coeff) @test eltype(p) == eltype(coeff) end end @@ -34,7 +33,8 @@ end @test p.coeffs == [30] p = zero(ChebyshevT{Int}) - @test p.coeffs == [0] + @test_broken p.coeffs == [0] + @test p.coeffs == Int[] p = one(ChebyshevT{Int}) @test p.coeffs == [1] @@ -58,9 +58,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 @@ -159,6 +159,7 @@ end d, r = divrem(c2, c1) @test d.coeffs ≈ [0, 2] + @test coeffs(d) ≈ [0, 2] @test r.coeffs ≈ [-2, -4] # evaluation diff --git a/test/Poly.jl b/test/Poly.jl index 8345db4e..d0bfb388 100644 --- a/test/Poly.jl +++ b/test/Poly.jl @@ -155,8 +155,8 @@ pS3 = Poly([1, 2, 3, 4, 5], :s) @test pS1 == pS2 @test pS1 == pS3 @test_throws ErrorException pS1 + pX -@test_throws ErrorException pS1 - pX @test_throws ErrorException pS1 * pX +@test_throws ArgumentError pS1 - pX @test_throws ArgumentError pS1 ÷ pX @test_throws ArgumentError pS1 % pX diff --git a/test/Project.toml b/test/Project.toml index 7cb5019b..01312580 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" diff --git a/test/StandardBasis.jl b/test/StandardBasis.jl index b44c44ed..b9828317 100644 --- a/test/StandardBasis.jl +++ b/test/StandardBasis.jl @@ -1,6 +1,7 @@ using LinearAlgebra using OffsetArrays, StaticArrays -import Polynomials: indeterminate +import Polynomials: indeterminate, ⟒ + ## Test standard basis polynomials with (nearly) the same tests # compare upto trailing zeros @@ -45,7 +46,7 @@ Ps = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, Fact P == Polynomial && @test length(p) == length(coeff) P == Polynomial && @test size(p) == size(coeff) P == Polynomial && @test size(p, 1) == size(coeff, 1) - P == Polynomial && @test typeof(p).parameters[1] == eltype(coeff) + P == Polynomial && @test typeof(p).parameters[2] == eltype(coeff) if !(eltype(coeff) <: Real && P == FactoredPolynomial) # roots may be complex @test eltype(p) == eltype(coeff) end @@ -64,12 +65,11 @@ Ps = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, Fact ## issue #452 ps = (1, 1.0) P != FactoredPolynomial && @test eltype(P(ps)) == eltype(promote(ps...)) + ## issue 464 - @variable z + Polynomials.@variable z # not exported @test z^2 + 2 == Polynomial([2,0,1], :z) - ## issue 457 - @test (@test_deprecated Polynomials.order(p)) == length(coeff) - 1 end end end @@ -284,7 +284,7 @@ end @test_throws MethodError q * p == P(conv([a,b], [a,b, c])) # Ok, no * for T # poly powers - @test_throws MethodError p^2 # Ok, no * for T + VERSION >= v"1.8.0" && (@test_throws r"Error" p^2) # (Ok, no * for T) # XXX ArgumentError, not MethodError being thrown on nightly @test_throws MethodError p * p # evaluation @@ -310,58 +310,6 @@ end end end - - # eval(quote - # using StaticArrays - # end) - @testset "T=SA" begin - @testset for P ∈ (Polynomial, ImmutablePolynomial ) - a,b,c = SA[1 0; 1 1], SA[1 0; 2 1], SA[1 0; 3 1] - p = P([a,b,c]) - q = P([a,b]) - s = 2 - d = SA[4 1; 1 0] - - # scalar product - @test s*p == P([s*cᵢ for cᵢ ∈ [a,b,c]]) - @test p*s == P([cᵢ*s for cᵢ ∈ [a,b,c]]) - @test d*p == P([d*cᵢ for cᵢ ∈ [a,b,c]]) - @test p*d == P([cᵢ*d for cᵢ ∈ [a,b,c]]) - - # poly add - @test +p == p - @test p + q == P([a+a,b+b,c]) - @test p - p == P([0*a]) - - # poly mult - @test p * q == P(conv([a,b,c], [a,b])) - @test q * p == P(conv([a,b], [a,b, c])) - - # poly powers - @test p^2 == p * p - - - # evaluation - @test p(s) == a + b * s + c * s * s - @test p(c) == a + b * c + c * c * c - - # ∂, ∫ - @test derivative(p) == P([b, 2c]) - @test integrate(p) == P([0*a, a, b/2, c/3]) - - # matrix element - @test [p q][1] == p - @test [p q][2] == q - - # implicit promotion - # @test_broken p + s == P([a .+ s, b, c]) # should error, doesn't - @test p + d == P([a + d, b, c]) - @test p + P([d]) == P([a + d,b,c]) - - @test_throws MethodError [p s][1] == p # no promotion T(s) - @test_throws MethodError [p s][2] == s # - end - end end @testset "OffsetVector" begin @@ -371,7 +319,7 @@ end @testset for P in Ps # LaurentPolynomial accepts OffsetArrays; others throw warning - if P == LaurentPolynomial + if P ∈ (LaurentPolynomial,) @test LaurentPolynomial(as) == LaurentPolynomial(bs, 3) else @test P(as) == P(bs) @@ -428,13 +376,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]) @@ -442,6 +390,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) == 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) == 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 + 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 + P != ImmutablePolynomial && @inferred p^2 + P != ImmutablePolynomial && @inferred p^3 + end + end + + @testset "integrate/differentiation" begin + p = P(x) + @inferred integrate(p) + @inferred derivative(p) + end + + end + + @testset "generic arithmetics" begin P = Polynomial # define a set algebra @@ -480,16 +477,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 @@ -521,7 +529,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 != 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₁ @@ -540,8 +548,7 @@ end # p - p requires a zero @testset for P ∈ Ps - P ∈ (LaurentPolynomial, SparsePolynomial, - FactoredPolynomial) && 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]] @@ -559,7 +566,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]) @@ -831,7 +838,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 +852,7 @@ end X = :x @testset for P in Ps - if !(P == ImmutablePolynomial) + if !(P ∈ (ImmutablePolynomial,)) p = P([0,one(Float64)]) @test P{Complex{Float64},X} == typeof(p + 1im) @test P{Complex{Float64},X} == typeof(1im - p) @@ -888,13 +895,24 @@ end # conversions between pairs of polynomial types c = [1:5;] Psexact = (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial) - @testset for P1 in Ps - p = P1(c) - @testset for P2 in Psexact + # @testset for P1 in Ps + # p = P1(c) + # @testset for P2 in Psexact + # #@test convert(P2, p) == p + # @test convert(P2, p) ≈ p + # end + # @test convert(FactoredPolynomial, p) ≈ p + # end + @testset for P1 in Psexact + for P2 ∈ Psexact + p = P1(c) @test convert(P2, p) == p end - @test convert(FactoredPolynomial, p) ≈ p end + @testset for P2 ∈ Psexact + convert(FactoredPolynomial, P2(c)) ≈ P2(c) + end + # reinterpret coefficients for P in (ImmutablePolynomial, Polynomial, SparsePolynomial, LaurentPolynomial, FactoredPolynomial) @@ -977,7 +995,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 @@ -1032,20 +1050,11 @@ end c = [1, 2, 3, 4] p = P(c) - der = if P == ImmutablePolynomial # poor inference - derivative(p) - else - @inferred derivative(p) - end + der = derivative(p) @test coeffs(der) ==ᵗ⁰ [2, 6, 12] - int = if P == ImmutablePolynomial - integrate(der, 1) - else - @inferred integrate(der, 1) - end + int = @inferred integrate(der, 1) @test coeffs(int) ==ᵗ⁰ c - @test derivative(pR) == P([-2 // 1,2 // 1]) @test derivative(p3) == P([2,2]) @test derivative(p1) == derivative(p0) == derivative(pNULL) == pNULL @@ -1064,7 +1073,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]) @@ -1102,8 +1111,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 @@ -1159,7 +1168,7 @@ 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 != ImmutablePolynomial && diagm(0 => [1, p^3], 1=>[p^2], -1=>[p]) end # issue 206 with mixed variable types and promotion @@ -1167,7 +1176,7 @@ end λ = P([0,1],:λ) A = [1 λ; λ^2 λ^3] @test A == diagm(0 => [1, λ^3], 1=>[λ], -1=>[λ^2]) - @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 @@ -1180,10 +1189,10 @@ end end meths = (Base.vect, Base.vcat, Base.hcat) @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 - + #XXP′′ = P == LaurentPolynomial ? P : P′ # different promotion rule + # P′′ = P == Polynomial ? P : LaurentPolynomial # XXX different promotion rules + P′′ = P <: Polynomials.AbstractDenseUnivariatePolynomial ? Polynomial : LaurentPolynomial # * should promote to Polynomial type if mixed (save Laurent Polynomial) @testset "promote mixed polys" begin @testset for m ∈ meths @@ -1343,7 +1352,8 @@ end @testset for P in Ps p1 = P([1,2,0,3]) @test eltype(collect(p1)) <: eltype(p1) - @test eltype(collect(Float64, p1)) <: Float64 + P != FactoredPolynomial && @test eltype(collect(Float64, p1)) <: Float64 + P == FactoredPolynomial && @test_skip eltype(collect(Float64, p1)) <: Float64 @test_throws InexactError collect(Int, P([1.2])) end @@ -1403,6 +1413,13 @@ end pcpy2 = copy(pcpy1) @test pcpy1 == pcpy2 end + + # no alias + for P ∈ (Polynomial, LaurentPolynomial, SparsePolynomial,Polynomials.PnPolynomial) + p = P([1,2,3]) + q = copy(p) + @test !(p.coeffs === q.coeffs) + end end @testset "GCD" begin @@ -1530,27 +1547,66 @@ end p = Polynomial{Rational{Int}}([1, 4]) @test sprint(show, p) == "Polynomial(1//1 + 4//1*x)" - @testset for P in (Polynomial, ImmutablePolynomial) + # XXX use $(PP) not $(P) below + # @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)" + + # p = P([1.0, 2.0, 3.0]) + # @test sprint(show, p) == "$P(1.0 + 2.0*x + 3.0*x^2)" + + # p = P([1 + 1im, -2im]) + # @test sprint(show, p) == "$P((1 + im) - 2im*x)" + + + # p = P([1,2,3,1]) # leading coefficient of 1 + # @test repr(p) == "$P(1 + 2*x + 3*x^2 + x^3)" + # p = P([1.0, 2.0, 3.0, 1.0]) + # @test repr(p) == "$P(1.0 + 2.0*x + 3.0*x^2 + 1.0*x^3)" + # p = P([1, im]) + # @test repr(p) == "$P(1 + im*x)" + # 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)" + + # p = P([1,2,3]) + + # @test repr("text/latex", p) == "\$1 + 2\\cdot x + 3\\cdot x^{2}\$" + # p = P([1 // 2, 2 // 3, 1]) + # @test repr("text/latex", p) == "\$\\frac{1}{2} + \\frac{2}{3}\\cdot x + x^{2}\$" + # p = P([complex(1,1),complex(0,1),complex(1,0),complex(1,1)]) + # @test repr("text/latex", p) == "\$(1 + i) + i\\cdot x + x^{2} + (1 + i)x^{3}\$" + + # @test printpoly_to_string(P([1,2,3], "y")) == "1 + 2*y + 3*y^2" + # @test printpoly_to_string(P([1,2,3], "y"), descending_powers = true) == "3*y^2 + 2*y + 1" + # @test printpoly_to_string(P([2, 3, 1], :z), descending_powers = true, offset = -2) == "1 + 3*z^-1 + 2*z^-2" + # @test printpoly_to_string(P([-1, 0, 1], :z), offset = -1, descending_powers = true) == "z - z^-1" + # @test printpoly_to_string(P([complex(1,1),complex(1,-1)]),MIME"text/latex"()) == "(1 + i) + (1 - i)x" + # end + + @testset for P in (Polynomial, ImmutablePolynomial,) # ImmutablePolynomial prints with Basis! + PP = Polynomials._typealias(P) p = P([1, 2, 3]) - @test sprint(show, p) == "$P(1 + 2*x + 3*x^2)" + @test sprint(show, p) == "$PP(1 + 2*x + 3*x^2)" p = P([1.0, 2.0, 3.0]) - @test sprint(show, p) == "$P(1.0 + 2.0*x + 3.0*x^2)" + @test sprint(show, p) == "$PP(1.0 + 2.0*x + 3.0*x^2)" p = P([1 + 1im, -2im]) - @test sprint(show, p) == "$P((1 + im) - 2im*x)" + @test sprint(show, p) == "$PP((1 + im) - 2im*x)" p = P([1,2,3,1]) # leading coefficient of 1 - @test repr(p) == "$P(1 + 2*x + 3*x^2 + x^3)" + @test repr(p) == "$PP(1 + 2*x + 3*x^2 + x^3)" p = P([1.0, 2.0, 3.0, 1.0]) - @test repr(p) == "$P(1.0 + 2.0*x + 3.0*x^2 + 1.0*x^3)" + @test repr(p) == "$PP(1.0 + 2.0*x + 3.0*x^2 + 1.0*x^3)" p = P([1, im]) - @test repr(p) == "$P(1 + im*x)" + @test repr(p) == "$PP(1 + im*x)" 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)" + @test repr(p) == "$PP((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) == "$(PP)(1.0 + NaN*im*x + NaN*x^2 + Inf*x^3 - Inf*im*x^4)" p = P([1,2,3]) @@ -1567,6 +1623,7 @@ end @test printpoly_to_string(P([complex(1,1),complex(1,-1)]),MIME"text/latex"()) == "(1 + i) + (1 - i)x" end + ## closed issues ## issue 275 with compact mult symbol p = Polynomial([1.234567890, 2.34567890]) @@ -1632,7 +1689,7 @@ end T1,T2 = Ts[i],Ts[i+1] @testset for P in Ps P <: FactoredPolynomial && continue - if P != ImmutablePolynomial + if !(P ∈ (ImmutablePolynomial,)) p = P{T2}(T1.(rand(1:3,3))) @test typeof(p) == P{T2, :x} else @@ -1648,7 +1705,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,)) @testset for T in (Int32, Int64, BigInt) p₁ = P{T}(Float64.(rand(1:3,5))) @test typeof(p₁) == P{T,:x} # conversion works @@ -1673,9 +1730,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) @@ -1683,11 +1740,11 @@ end @testset "negative indices" begin d = Dict(-2=>4, 5=>10) p = SparsePolynomial(d) + q = LaurentPolynomial(p) @test length(p) == 8 @test firstindex(p) == -2 @test lastindex(p) == 5 @test eachindex(p) == -2:5 - q = LaurentPolynomial(p) @test p == q @test SparsePolynomial(q) == p @test_throws ArgumentError Polynomial(p) @@ -1702,11 +1759,13 @@ end # Chain rules -using ChainRulesTestUtils +if VERSION >= v"1.9.0" + using ChainRulesTestUtils -@testset "Test frule and rrule" begin - p = Polynomial([1,2,3,4]) - dp = derivative(p) + @testset "Test frule and rrule" begin + p = Polynomial([1,2,3,4]) + dp = derivative(p) - test_scalar(p, 1.0; check_inferred=true) + test_scalar(p, 1.0; check_inferred=true) + end end diff --git a/test/aqua.jl b/test/aqua.jl new file mode 100644 index 00000000..0e7eae2e --- /dev/null +++ b/test/aqua.jl @@ -0,0 +1,6 @@ +using Aqua + +Aqua.test_all(Polynomials; + unbound_args = false, + stale_deps = false + ) diff --git a/test/rational-functions.jl b/test/rational-functions.jl index 2f85c8f8..7f10ea93 100644 --- a/test/rational-functions.jl +++ b/test/rational-functions.jl @@ -7,20 +7,16 @@ using LinearAlgebra p,q = fromroots(Polynomial, [1,2,3]), fromroots(Polynomial, [2,3,4]) r,s = SparsePolynomial([1,2,3], :x), SparsePolynomial([1,2,3], :y) t,u = Polynomial([1,2,pi]), SparsePolynomial([1.1, 2.2, 3.4], :y) - + # constructor @test p // q isa RationalFunction @test p // r isa RationalFunction @test_throws ArgumentError r // s @test RationalFunction(p) == p // one(p) - # We expect p::P // q::P (same type polynomial). - # As Immutable Polynomials have N as type parameter, we disallow - @test_throws ArgumentError variable(ImmutablePolynomial) // variable(ImmutablePolynomial) - pq = p // t # promotes to type of t @test pq isa RationalFunction{Float64, :x} - + # iterations, broadcast pp,qq = p // q @test (pp,qq) == (p,q) @@ -28,7 +24,7 @@ using LinearAlgebra u = gcd(p//q...) @test u/u[end] ≈ fromroots(Polynomial, [2,3]) @test degree.(p//q) == degree(p//q) # no broadcast over rational functions - + # evaluation pq = p//q @test pq(2.5) ≈ p(2.5) / q(2.5) @@ -36,7 +32,7 @@ using LinearAlgebra @test zero(pq)(10) == 0 @test one(pq)(10) == 1 - + # arithmetic rs = r // (r-1) x = 1.2 @@ -45,8 +41,8 @@ using LinearAlgebra @test (-pq)(x) ≈ -p(x)/q(x) @test pq .* (pq, pq) == (pq*pq, pq*pq) @test pq .* [pq pq] == [pq*pq pq*pq] - - + + # derivative pq = p // one(p) x = 1.23 @@ -54,13 +50,13 @@ using LinearAlgebra pq = p // q dp,dq = derivative.((p,q)) @test derivative(pq)(x) ≈ (dp(x)*q(x) - p(x)*dq(x))/q(x)^2 - + # integral pq = p // (2*one(p)) @test iszero(derivative(integrate(pq)) - pq) pq = p // q @test_throws ArgumentError integrate(pq) # need logs terms in general - + # lowest terms pq = p // q pp, qq = Polynomials.pqs(lowest_terms(pq)) @@ -79,7 +75,7 @@ using LinearAlgebra @test p //q == 1/ (q//p) @test numerator(p) == p * variable(p)^2 @test denominator(p) == convert(Polynomial, variable(p)^2) - + end @testset "zeros, poles, residues" begin @@ -92,11 +88,11 @@ end zs = roots(pq) @test length(zs.zs) == 1 @test zs.multiplicities == [7] - + ## poles ps = poles(pq) @test length(ps.zs) == 3 - + ## residues d,r = residues(pq) @test d ≈ 9 - 4x + x^2 @@ -112,11 +108,11 @@ end end @testset "As matrix elements" begin - + p, q = Polynomial([1,2], :x), Polynomial([1,2],:y) pp = p // (p-1) PP = typeof(pp) - + r, s = SparsePolynomial([1,2], :x), SparsePolynomial([1,2],:y) rr = r // (r-1) @@ -143,7 +139,7 @@ end @testset "Rational function fit" begin - + p,q = Polynomial([1,1,1]), Polynomial([1,2]) xs = range(-0.25,stop=1, length=15) ys = (p//q).(xs) @@ -157,7 +153,7 @@ end ys = pq.(xs); v = fit(RationalFunction, xs, ys, 2, 2) @test maximum(abs, v(x)-pq(x) for x ∈ 2.1:0.1:3.0) <= sqrt(eps()) - + end @testset "Pade fit" begin @@ -168,19 +164,19 @@ end a = Polynomial( 1 .// factorial.(big(0):17) ) pq = fit(RationalFunction,a,8,8) @test pq(1.0) ≈ exp(1.0) - @test pq(-1.0) ≈ exp(-1.0) + @test pq(-1.0) ≈ exp(-1.0) # sine b = Polynomial(Int.(sinpi.((0:16)/2)) .// factorial.(big(0):16)) pq = fit(RationalFunction, b, 7, 8) @test pq(1.0) ≈ sin(1.0) - @test pq(-1.0) ≈ sin(-1.0) + @test pq(-1.0) ≈ sin(-1.0) # cosine c = Polynomial(Int.(sinpi.((1:17)/2)) .// factorial.(big(0):16)) pq = fit(RationalFunction, c, 8, 8) @test pq(1.0) ≈ cos(1.0) - @test pq(-1.0) ≈ cos(-1.0) + @test pq(-1.0) ≈ cos(-1.0) # summation of a factorially divergent series d = Polynomial( (-big(1)).^(0:60) .* factorial.(big(0):60) ) diff --git a/test/runtests.jl b/test/runtests.jl index a594329f..0502bc75 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,4 +14,5 @@ using OffsetArrays @testset "Poly, Pade (compatibility)" begin include("Poly.jl") end if VERSION >= v"1.9.0-" @testset "MutableArithmetics" begin include("mutable-arithmetics.jl") end + @testset "Aqua" begin include("aqua.jl") end end