Skip to content

Commit 60ba676

Browse files
authored
[WIP] Ring type (issue #349, #234) (#350)
* drop T <: Number * add `conv` alternative for `fastconv` * restrict tests by VERSION * doc updates
1 parent 11457cf commit 60ba676

15 files changed

+639
-191
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "Polynomials"
22
uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45"
33
license = "MIT"
44
author = "JuliaMath"
5-
version = "2.0.12"
5+
version = "2.0.13"
66

77
[deps]
88
Intervals = "d8418881-c3e1-53bb-8760-2df7ec849ed5"

docs/src/index.md

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ By design, this is not type-stable; the returned roots may be real or complex.
161161

162162
The default `roots` function uses the eigenvalues of the
163163
[companion](https://en.wikipedia.org/wiki/Companion_matrix) matrix for
164-
a polynomial. This is an `𝑶(n^3)` operation.
164+
a polynomial. This is an `𝑶(n^3)` operation.
165165

166166
For polynomials with `BigFloat` coefficients, the
167167
`GenericLinearAlgebra` package can be seamlessly used:
@@ -204,7 +204,7 @@ julia> PolynomialRoots.roots(coeffs(p))
204204
1.0000000000000002 + 0.0im
205205
```
206206

207-
The roots are always returned as complex numbers.
207+
The roots are always returned as complex numbers.
208208

209209

210210
* The
@@ -226,7 +226,7 @@ julia> AMRVW.roots(float.(coeffs(p)))
226226
2.9999999999999964 + 0.0im
227227
```
228228

229-
The roots are returned as complex numbers.
229+
The roots are returned as complex numbers.
230230

231231
Both `PolynomialRoots` and `AMRVW` are generic and work with
232232
`BigFloat` coefficients, for example.
@@ -429,7 +429,7 @@ Fit a polynomial (of degree `deg`) to `x` and `y` using polynomial interpolation
429429
using Plots, Polynomials
430430
xs = range(0, 10, length=10)
431431
ys = @. exp(-xs)
432-
f = fit(xs, ys) # degree = length(xs) - 1
432+
f = fit(xs, ys) # degree = length(xs) - 1
433433
f2 = fit(xs, ys, 2) # degree = 2
434434
435435
scatter(xs, ys, markerstrokewidth=0, label="Data")
@@ -583,7 +583,7 @@ Polynomial(4 + 2*x + 3*x^2)
583583

584584
If `q` is non-constant, such as `variable(Polynomial, :y)`, then there would be an error due to the mismatched symbols. (The mathematical result would need a multivariate polynomial, not a univariate polynomial, as this package provides.)
585585

586-
The same conversion is done for polynomial multiplication: constant polynomials are treated as numbers; non-constant polynomials must have their symbols match.
586+
The same conversion is done for polynomial multiplication: constant polynomials are treated as numbers; non-constant polynomials must have their symbols match.
587587

588588
There is an oddity -- though the following two computations look the same, they are technically different:
589589

@@ -595,7 +595,7 @@ julia> one(Polynomial, :y) + one(Polynomial, :x)
595595
Polynomial(2.0)
596596
```
597597

598-
Both are constant polynomials over `Int`, but the first has the indeterminate `:y`, the second `:x`.
598+
Both are constant polynomials over `Int`, but the first has the indeterminate `:y`, the second `:x`.
599599

600600
This technical difference causes no issues with polynomial addition or multiplication, as there constant polynomials are treated as numbers, but can be an issue when constant polynomials are used as array elements.
601601

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

672+
673+
## Non-number types for `T`
674+
675+
The coefficients of the polynomial may be non-number types, such as matrices or other polynomials, albeit not every operation is fully supported.
676+
677+
For example, a polynomial with matrix coefficients, might be constructed with:
678+
679+
```jldoctest non_number
680+
julia> using Polynomials
681+
682+
julia> a,b,c = [1 0;2 1], [1 0; 3 1], [1 0; 4 1]
683+
([1 0; 2 1], [1 0; 3 1], [1 0; 4 1])
684+
685+
julia> p = Polynomial([a,b,c])
686+
Polynomial([1 0; 2 1] + [1 0; 3 1]*x + [1 0; 4 1]*x^2)
687+
688+
julia> q = derivative(p)
689+
Polynomial([1 0; 3 1] + [2 0; 8 2]*x)
690+
```
691+
692+
Various operations are available, `derivative` was shown above, here are the vector-space operations:
693+
694+
```jldoctest non_number
695+
julia> 2p
696+
Polynomial([2 0; 4 2] + [2 0; 6 2]*x + [2 0; 8 2]*x^2)
697+
698+
julia> p + q
699+
Polynomial([2 0; 5 2] + [3 0; 11 3]*x + [1 0; 4 1]*x^2)
700+
```
701+
702+
polynomial multiplication:
703+
704+
```jldoctest non_number
705+
julia> p * q
706+
Polynomial([1 0; 5 1] + [3 0; 18 3]*x + [3 0; 21 3]*x^2 + [2 0; 16 2]*x^3)
707+
```
708+
709+
polynomial evaluation, here either with a scalar or a matrix:
710+
711+
```jldoctest non_number
712+
julia> p(2)
713+
2×2 Matrix{Int64}:
714+
7 0
715+
24 7
716+
717+
julia> p(b)
718+
2×2 Matrix{Int64}:
719+
3 0
720+
18 3
721+
```
722+
723+
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.
724+
725+
Similarly, using polynomials for `T` is a possibility:
726+
727+
```jldoctest non_number
728+
julia> a,b,c = Polynomial([1],:y), Polynomial([0,1],:y), Polynomial([0,0,1],:y)
729+
(Polynomial(1), Polynomial(y), Polynomial(y^2))
730+
731+
julia> p = Polynomial([a,b,c], :x)
732+
Polynomial(Polynomial(1) + Polynomial(y)*x + Polynomial(y^2)*x^2)
733+
734+
julia> q = derivative(p)
735+
Polynomial(Polynomial(y) + Polynomial(2*y^2)*x)
736+
```
737+
738+
Again, much works:
739+
740+
```jldoctest non_number
741+
julia> 2p
742+
Polynomial(Polynomial(2) + Polynomial(2*y)*x + Polynomial(2*y^2)*x^2)
743+
744+
julia> p + q
745+
Polynomial(Polynomial(1 + y) + Polynomial(y + 2*y^2)*x + Polynomial(y^2)*x^2)
746+
747+
julia> p(2)
748+
Polynomial(1 + 2*y + 4*y^2)
749+
750+
julia> p(b)
751+
Polynomial(1 + y^2 + y^4)
752+
```
753+
754+
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.
755+
756+
672757
## Rational functions
673758

674759
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.

src/abstract.jl

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,50 @@
11
export AbstractPolynomial
22

3+
4+
35
const SymbolLike = Union{AbstractString,Char,Symbol}
46

57
"""
68
AbstractPolynomial{T,X}
79
8-
An abstract container for various polynomials.
10+
An abstract type for various polynomials.
11+
12+
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.
913
1014
# Properties
1115
- `coeffs` - The coefficients of the polynomial
16+
17+
# The type `T`
18+
19+
`T` need not be `T <: Number`, at the moment it is not restricted
20+
21+
Some `T`s will not be successful
22+
23+
* scalar mult: `c::Number * p::Polynomial` should be defined
24+
* scalar mult: `c::T * p:Polynomial{T}` An ambiguity when `T <: AbstractPolynomial`
25+
* scalar mult: `p:Polynomial{T} * c::T` need not commute
26+
27+
* scalar add/sub: `p::Polynomial{T} + q::Polynomial{T}` should be defined
28+
* scalar sub: `p::Polynomial{T} - p::Polynomial{T}` generally needs `zeros(T,1)` defined for `zero(Polynomial{T})`
29+
30+
* poly mult: `p::Polynomial{T} * q::Polynomial{T}` Needs "`T * T`" defined (e.g. `Base.promote_op(*, Vector{Int}, Vector{Int}))` needs to be something.)
31+
* poly powers: `p::Polynomial{T}^2` needs "`T^2`" defined
32+
33+
* implicit promotion: `p::Polynomial{T} + c::Number` needs `convert(T, c)` defined
34+
* implicit promotion: `p::Polynomial{T} + c::T` ambiguity if `T <: AbstractPolynomial`
35+
36+
* evaluation: `p::Polynomial{T}(s::Number)`
37+
* evaluation `p::Polynomial{T}(c::T)` needs `T*T` defined
38+
39+
* derivatives: `derivative(p::Polynomial{T})`
40+
* integrals: `integrate(p::Polynomial{T})`
41+
42+
1243
"""
1344
abstract type AbstractPolynomial{T,X} end
1445

46+
47+
## -----
1548
# We want ⟒(P{α…,T}) = P{α…}; this default
1649
# works for most cases
1750
(P::Type{<:AbstractPolynomial}) = constructorof(P)
@@ -34,7 +67,7 @@ Polynomials.@register MyPolynomial
3467
```
3568
3669
# Implementations
37-
This will implement simple self-conversions like `convert(::Type{MyPoly}, p::MyPoly) = p` and creates two promote rules. The first allows promotion between two types (e.g. `promote(Polynomial, ChebyshevT)`) and the second allows promotion between parametrized types (e.g. `promote(Polynomial{T}, Polynomial{S})`).
70+
This will implement simple self-conversions like `convert(::Type{MyPoly}, p::MyPoly) = p` and creates two promote rules. The first allows promotion between two types (e.g. `promote(Polynomial, ChebyshevT)`) and the second allows promotion between parametrized types (e.g. `promote(Polynomial{T}, Polynomial{S})`).
3871
3972
For constructors, it implements the shortcut for `MyPoly(...) = MyPoly{T}(...)`, singleton constructor `MyPoly(x::Number, ...)`, conversion constructor `MyPoly{T}(n::S, ...)`, and `variable` alternative `MyPoly(var=:x)`.
4073
"""
@@ -44,15 +77,15 @@ macro register(name)
4477
Base.convert(::Type{P}, p::P) where {P<:$poly} = p
4578
function Base.convert(P::Type{<:$poly}, p::$poly{T,X}) where {T,X}
4679
isconstant(p) && return constructorof(P){eltype(P),indeterminate(P)}(constantterm(p))
47-
constructorof(P){eltype(P), indeterminate(P,p)}(coeffs(p))
80+
constructorof(P){eltype(P), indeterminate(P,p)}(_coeffs(p))
4881
end
4982
Base.promote(p::P, q::Q) where {X, T, P <:$poly{T,X}, Q <: $poly{T,X}} = p,q
5083
Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{<:$poly{S,X}}) where {T,S,X} = $poly{promote_type(T, S),X}
5184
Base.promote_rule(::Type{<:$poly{T,X}}, ::Type{S}) where {T,S<:Number,X} =
5285
$poly{promote_type(T, S),X}
5386
$poly(coeffs::AbstractVector{T}, var::SymbolLike = :x) where {T} =
5487
$poly{T, Symbol(var)}(coeffs)
55-
$poly{T}(x::AbstractVector{S}, var::SymbolLike = :x) where {T,S<:Number} =
88+
$poly{T}(x::AbstractVector{S}, var::SymbolLike = :x) where {T,S} =
5689
$poly{T,Symbol(var)}(T.(x))
5790
function $poly(coeffs::G, var::SymbolLike=:x) where {G}
5891
!Base.isiterable(G) && throw(ArgumentError("coeffs is not iterable"))
@@ -76,11 +109,11 @@ macro registerN(name, params...)
76109
αs = tuple(esc.(params)...)
77110
quote
78111
Base.convert(::Type{P}, q::Q) where {$(αs...),T, P<:$poly{$(αs...),T}, Q <: $poly{$(αs...),T}} = q
79-
Base.convert(::Type{$poly{$(αs...)}}, q::Q) where {$(αs...),T, Q <: $poly{$(αs...),T}} = q
112+
Base.convert(::Type{$poly{$(αs...)}}, q::Q) where {$(αs...),T, Q <: $poly{$(αs...),T}} = q
80113
Base.promote(p::P, q::Q) where {$(αs...),T, X, P <:$poly{$(αs...),T,X}, Q <: $poly{$(αs...),T,X}} = p,q
81114
Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{<:$poly{$(αs...),S,X}}) where {$(αs...),T,S,X} =
82115
$poly{$(αs...),promote_type(T, S),X}
83-
Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{S}) where {$(αs...),T,X,S<:Number} =
116+
Base.promote_rule(::Type{<:$poly{$(αs...),T,X}}, ::Type{S}) where {$(αs...),T,X,S<:Number} =
84117
$poly{$(αs...),promote_type(T,S),X}
85118

86119
function $poly{$(αs...),T}(x::AbstractVector{S}, var::SymbolLike = :x) where {$(αs...),T,S}
@@ -98,4 +131,3 @@ macro registerN(name, params...)
98131
(p::$poly)(x) = evalpoly(x, p)
99132
end
100133
end
101-

0 commit comments

Comments
 (0)