Skip to content

Commit e64e542

Browse files
authored
Factored polynomial (#340)
* add factored polynomial type * update README, docs, fix bug in divrem * work around v1.0-1.2
1 parent 4d7455a commit e64e542

File tree

9 files changed

+542
-69
lines changed

9 files changed

+542
-69
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ Basic arithmetic, integration, differentiation, evaluation, and root finding ove
1919
* `ImmutablePolynomial` –⁠ standard basis polynomials backed by a [Tuple type](https://docs.julialang.org/en/v1/manual/functions/#Tuples-1) for faster evaluation of values
2020
* `SparsePolynomial` –⁠ standard basis polynomial backed by a [dictionary](https://docs.julialang.org/en/v1/base/collections/#Dictionaries-1) to hold sparse high-degree polynomials
2121
* `LaurentPolynomial` –⁠ [Laurent polynomials](https://docs.julialang.org/en/v1/base/collections/#Dictionaries-1), `a(x) = aₘ xᵐ + … + aₙ xⁿ` `m ≤ n`, `m,n ∈ ℤ` backed by an [offset array](https://github.com/JuliaArrays/OffsetArrays.jl); for example, if `m<0` and `n>0`, `a(x) = aₘ xᵐ + … + a₋₁ x⁻¹ + a₀ + a₁ x + … + aₙ xⁿ`
22+
* `FactoredPolynomial` –⁠ standard basis polynomials, storing the roots, with multiplicity, and leading coefficient of a polynomial
2223
* `ChebyshevT` –⁠ [Chebyshev polynomials](https://en.wikipedia.org/wiki/Chebyshev_polynomials) of the first kind
24+
* `RationalFunction` - a type for ratios of polynomials.
2325

2426
## Usage
2527

docs/src/index.md

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,19 @@ julia> roots(Polynomial([0, 0, 1]))
159159
0.0
160160
```
161161

162+
For polynomials with multplicities, the non-exported `Polynomials.Multroot.multroot` method can avoid some numerical issues that `roots` will have.
163+
164+
The `FactoredPolynomial` type stores the roots (with multiplicities) and the leading coefficent of a polynomial. In this example, the `multroot` method is used internally to identify the roots of `p` below, in the conversion from the `Polynomial` type to the `FactoredPolynomial` type:
165+
166+
```jldoctest
167+
julia> p = Polynomial([24, -50, 35, -10, 1])
168+
Polynomial(24 - 50*x + 35*x^2 - 10*x^3 + x^4)
169+
170+
julia> q = convert(FactoredPolynomial, p) # noisy form of `factor`:
171+
FactoredPolynomial((x - 4.0000000000000036) * (x - 2.9999999999999942) * (x - 1.0000000000000002) * (x - 2.0000000000000018))
172+
```
173+
174+
162175
### Fitting arbitrary data
163176

164177
Fit a polynomial (of degree `deg`) to `x` and `y` using polynomial interpolation or a (weighted) least-squares approximation.
@@ -274,6 +287,25 @@ julia> collect(Polynomials.monomials(p))
274287
Polynomial(4*u^3)
275288
```
276289

290+
The `map` function for polynomials is idiosyncratic, as iteration over
291+
polynomials is over the vector of coefficients, but `map` will also
292+
maintain the type of the polynomial. Here we use `map` to smooth out
293+
the round-off error coming from the root-finding algorithm used
294+
internally when converting to the `FactoredPolynomial` type:
295+
296+
297+
```jldoctest
298+
julia> p = Polynomial([24, -50, 35, -10, 1])
299+
Polynomial(24 - 50*x + 35*x^2 - 10*x^3 + x^4)
300+
301+
julia> q = convert(FactoredPolynomial, p) # noisy form of `factor`:
302+
FactoredPolynomial((x - 4.0000000000000036) * (x - 2.9999999999999942) * (x - 1.0000000000000002) * (x - 2.0000000000000018))
303+
304+
julia> map(round, q, digits=10)
305+
FactoredPolynomial((x - 4.0) * (x - 2.0) * (x - 3.0) * (x - 1.0))
306+
```
307+
308+
277309
## Relationship between the `T` and `P{T,X}`
278310

279311
The addition of a polynomial and a scalar, such as
@@ -344,7 +376,7 @@ ERROR: ArgumentError: Polynomials have different indeterminates
344376
[...]
345377
```
346378

347-
an error thrown.
379+
an error is thrown.
348380

349381
In general, arrays with mixtures of non-constant polynomials with *different* indeterminates will error. By default, an error will occur when constant polynomials with different indeterminates are used as components. However, for *typed* arrays, conversion will allow such constructs to be used.
350382

@@ -388,6 +420,53 @@ julia> [1 p; p 1] + [1 2one(q); 3 4] # array{P{T,:x}} + array{P{T,:y}}
388420
Though were a non-constant polynomial with indeterminate `y` replacing
389421
`2one(q)` above, that addition would throw an error.
390422

423+
## Rational functions
424+
425+
The package provides support for rational functions -- fractions of polynomials (for most types). The construction of the basic type mirrors the construction of rational numbers.
426+
427+
```jldoctest
428+
julia> P = FactoredPolynomial
429+
FactoredPolynomial
430+
431+
julia> p,q = fromroots(P, [1,2,3,4]), fromroots(P, [2,2,3,5])
432+
(FactoredPolynomial((x - 4) * (x - 2) * (x - 3) * (x - 1)), FactoredPolynomial((x - 5) * (x - 2)² * (x - 3)))
433+
434+
julia> pq = p // q
435+
((x - 4) * (x - 2) * (x - 3) * (x - 1)) // ((x - 5) * (x - 2)² * (x - 3))
436+
437+
julia> lowest_terms(pq)
438+
((x - 4.0) * (x - 1.0)) // ((x - 5.0) * (x - 2.0))
439+
440+
julia> d,r = residues(pq); r
441+
Dict{Float64, Vector{Float64}} with 2 entries:
442+
5.0 => [1.33333]
443+
2.0 => [0.666667]
444+
445+
julia> x = variable(p);
446+
447+
julia> for (λ, rs) ∈ r # reconstruct p/q from output of `residues`
448+
for (i,rᵢ) ∈ enumerate(rs)
449+
d += rᵢ//(x-λ)^i
450+
end
451+
end
452+
453+
julia> d
454+
((x - 4.0) * (x - 1.0000000000000002)) // ((x - 5.0) * (x - 2.0))
455+
```
456+
457+
A basic plot recipe is provided.
458+
459+
```@example
460+
using Plots, Polynomials
461+
P = FactoredPolynomial
462+
p,q = fromroots(P, [1,2,3]), fromroots(P, [2,3,3,0])
463+
plot(p//q)
464+
savefig("rational_function.svg"); nothing # hide
465+
```
466+
467+
![](rational_function.svg)
468+
469+
391470
## Related Packages
392471

393472
* [StaticUnivariatePolynomials.jl](https://github.com/tkoolen/StaticUnivariatePolynomials.jl) Fixed-size univariate polynomials backed by a Tuple

src/Polynomials.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ include("polynomials/ImmutablePolynomial.jl")
2525
include("polynomials/SparsePolynomial.jl")
2626
include("polynomials/LaurentPolynomial.jl")
2727
include("polynomials/pi_n_polynomial.jl")
28+
include("polynomials/factored_polynomial.jl")
2829
include("polynomials/ngcd.jl")
2930
include("polynomials/multroot.jl")
3031

src/common.jl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,14 @@ function Base.gcd(p1::AbstractPolynomial{T}, p2::AbstractPolynomial{T};
996996
return r₀
997997
end
998998

999+
"""
1000+
uvw(p,q; kwargs...)
1001+
1002+
return `u` the gcd of `p` and `q`, and `v` and `w`, where `u*v = p` and `u*w = q`.
1003+
"""
1004+
uvw(p::AbstractPolynomial, q::AbstractPolynomial; kwargs...) = uvw(promote(p,q)...; kwargs...)
1005+
uvw(p1::P, p2::P; kwargs...) where {P <:AbstractPolynomial} = throw(ArgumentError("uvw not defind"))
1006+
9991007
"""
10001008
div(::AbstractPolynomial, ::AbstractPolynomial)
10011009
"""
@@ -1015,8 +1023,8 @@ Base.:(==)(p::AbstractPolynomial, n::Number) = degree(p) <= 0 && p[0] == n
10151023
Base.:(==)(n::Number, p::AbstractPolynomial) = p == n
10161024

10171025
function Base.isapprox(p1::AbstractPolynomial{T,X},
1018-
p2::AbstractPolynomial{S,Y};
1019-
rtol::Real = (Base.rtoldefault(T, S, 0)),
1026+
p2::AbstractPolynomial{S,Y};
1027+
rtol::Real = (Base.rtoldefault(T, S, 0)),
10201028
atol::Real = 0,) where {T,X,S,Y}
10211029
assert_same_variable(p1, p2)
10221030
(hasnan(p1) || hasnan(p2)) && return false # NaN poisons comparisons

0 commit comments

Comments
 (0)