22export differentiate
33
44"""
5- differentiate(p::AbstractPolynomialLike, v::AbstractVariable, deg::Int=1)
5+ differentiate(p::AbstractPolynomialLike, v::AbstractVariable, deg::Union{ Int, Val} =1)
66
77Differentiate `deg` times the polynomial `p` by the variable `v`.
88
9- differentiate(p::AbstractPolynomialLike, vs, deg::Int=1)
109
11- Differentiate `deg` times the polynomial `p` by the variables of the vector or tuple of variable `vs` and return an array of dimension ` deg`.
10+ differentiate(p::AbstractPolynomialLike, vs, deg::Union{Int, Val}=1)
1211
13- differentiate(p::AbstractArray{<:AbstractPolynomialLike, N}, vs, deg::Int=1) where N
12+ Differentiate `deg` times the polynomial `p` by the variables of the vector or
13+ tuple of variable `vs` and return an array of dimension `deg`. It is recommended
14+ to pass `deg` as a `Val` instance when the degree is known at compile time, e.g.
15+ `differentiate(p, v, Val{2}())` instead of `differentiate(p, x, 2)`, as this
16+ will help the compiler infer the return type.
17+
18+ differentiate(p::AbstractArray{<:AbstractPolynomialLike, N}, vs, deg::Union{Int, Val}=1) where N
1419
1520Differentiate the polynomials in `p` by the variables of the vector or tuple of variable `vs` and return an array of dimension `N+deg`.
1621
@@ -19,63 +24,84 @@ Differentiate the polynomials in `p` by the variables of the vector or tuple of
1924```julia
2025p = 3x^2*y + x + 2y + 1
2126differentiate(p, x) # should return 6xy + 1
27+ differentiate(p, x, Val{1}()) # equivalent to the above
2228differentiate(p, (x, y)) # should return [6xy+1, 3x^2+1]
2329```
2430"""
2531function differentiate end
2632
2733# Fallback for everything else
28- _diff_promote_op (:: Type{T} , :: Type{<:AbstractVariable} ) where T = T
2934differentiate (α:: T , v:: AbstractVariable ) where T = zero (T)
30-
31- _diff_promote_op (:: Type{<:AbstractVariable} , :: Type{<:AbstractVariable} ) = Int
3235differentiate (v1:: AbstractVariable , v2:: AbstractVariable ) = v1 == v2 ? 1 : 0
33-
34- _diff_promote_op (:: Type{TT} , :: Type{<:AbstractVariable} ) where {T, TT<: AbstractTermLike{T} } = changecoefficienttype (TT, Base. promote_op (* , T, Int))
3536differentiate (t:: AbstractTermLike , v:: AbstractVariable ) = coefficient (t) * differentiate (monomial (t), v)
36-
37- _diff_promote_op (:: Type{PT} , :: Type{<:AbstractVariable} ) where {T, PT<: APL{T} } = polynomialtype (PT, Base. promote_op (* , T, Int))
3837# The polynomial function will take care of removing the zeros
3938differentiate (p:: APL , v:: AbstractVariable ) = polynomial (differentiate .(terms (p), v), SortedState ())
40-
4139differentiate (p:: RationalPoly , v:: AbstractVariable ) = (differentiate (p. num, v) * p. den - p. num * differentiate (p. den, v)) / p. den^ 2
4240
4341const ARPL = Union{APL, RationalPoly}
4442
45- _vec_diff_promote_op (:: Type{PT} , :: AbstractVector{VT} ) where {PT, VT} = _diff_promote_op (PT, VT)
46- _vec_diff_promote_op (:: Type{PT} , :: NTuple{N, VT} ) where {PT, N, VT} = _diff_promote_op (PT, VT)
47- _vec_diff_promote_op (:: Type{PT} , :: VT , xs... ) where {PT, VT} = _diff_promote_op (PT, VT)
48- _vec_diff_promote_op (:: Type{PT} , xs:: Tuple ) where PT = _vec_diff_promote_op (PT, xs... )
49-
50- # even if I annotate with ::Array{_diff_promote_op(T, PolyVar{C}), N+1}, it cannot detect the type since it seems to be unable to determine the dimension N+1 :(
51- function differentiate (ps:: AbstractArray{PT, N} , xs:: Union{AbstractArray, Tuple} ) where {N, PT<: ARPL }
52- qs = Array {_vec_diff_promote_op(PT, xs), N+1} (length (xs), size (ps)... )
53- for (i, x) in enumerate (xs)
54- for j in linearindices (ps)
55- J = ind2sub (ps, j)
56- qs[i, J... ] = differentiate (ps[J... ], x)
57- end
58- end
59- qs
43+ function differentiate (ps:: AbstractArray{PT} , xs:: AbstractArray ) where {PT <: ARPL }
44+ differentiate .(reshape (ps, (1 , size (ps)... )), reshape (xs, :))
45+ end
46+
47+ function differentiate (ps:: AbstractArray{PT} , xs:: Tuple ) where {PT <: ARPL }
48+ differentiate .(reshape (ps, (1 , size (ps)... )), xs)
6049end
6150
51+
52+ # TODO : this signature is probably too wide and creates the potential
53+ # for stack overflows
6254differentiate (p:: ARPL , xs) = [differentiate (p, x) for x in xs]
6355
6456# differentiate(p, [x, y]) with TypedPolynomials promote x to a Monomial
6557differentiate (p:: ARPL , m:: AbstractMonomial ) = differentiate (p, variable (m))
6658
67- # In Julia v0.5, Base.promote_op returns Any for PolyVar, Monomial and MatPolynomial
68- # Even on Julia v0.6 and Polynomial, Base.promote_op returns Any...
69- _diff_promote_op (:: Type{PT} , :: Type{VT} ) where {PT, VT} = Base. promote_op (differentiate, PT, VT)
70- _diff_promote_op (:: Type{MT} , :: Type{<:AbstractVariable} ) where {MT<: AbstractMonomialLike } = termtype (MT, Int)
71-
72- function differentiate (p, x, deg:: Int )
59+ # The `R` argument indicates a desired result type. We use this in order
60+ # to attempt to preserve type-stability even though the value of `deg` cannot
61+ # be known at compile time. For scalar `p` and `x`, we set R to be the type
62+ # of differentiate(p, x) to give a stable result type regardless of `deg`. For
63+ # vectors p and/or x this is impossible (since differentiate may return an array),
64+ # so we just set `R` to `Any`
65+ function (_differentiate_recursive (p, x, deg:: Int , :: Type{R} ):: R ) where {R}
7366 if deg < 0
7467 throw (DomainError ())
7568 elseif deg == 0
76- # Need the conversion with promote_op to be type stable for PolyVar, Monomial and MatPolynomial
77- return convert (_diff_promote_op (typeof (p), typeof (x)), p)
69+ return p
7870 else
7971 return differentiate (differentiate (p, x), x, deg- 1 )
8072 end
8173end
74+
75+ differentiate (p, x, deg:: Int ) = _differentiate_recursive (p, x, deg, Base. promote_op (differentiate, typeof (p), typeof (x)))
76+ differentiate (p:: AbstractArray , x, deg:: Int ) = _differentiate_recursive (p, x, deg, Any)
77+ differentiate (p, x:: Union{AbstractArray, Tuple} , deg:: Int ) = _differentiate_recursive (p, x, deg, Any)
78+ differentiate (p:: AbstractArray , x:: Union{AbstractArray, Tuple} , deg:: Int ) = _differentiate_recursive (p, x, deg, Any)
79+
80+
81+ # This is alternative, Val-based interface for nested differentiation.
82+ # It has the advantage of not requiring an conversion or calls to
83+ # Base.promote_op, while maintaining type stability for any argument
84+ # type.
85+ differentiate (p, x, :: Val{0} ) = p
86+ differentiate (p, x, :: Val{1} ) = differentiate (p, x)
87+
88+ @static if VERSION < v " v0.7.0-"
89+ # Marking this @pure helps julia v0.6 figure this out
90+ Base. @pure _reduce_degree (:: Val{N} ) where {N} = Val {N - 1} ()
91+ function differentiate (p, x, deg:: Val{N} ) where N
92+ if N < 0
93+ throw (DomainError (deg))
94+ else
95+ differentiate (differentiate (p, x), x, _reduce_degree (deg))
96+ end
97+ end
98+ else
99+ # In Julia v0.7 and above, we can remove the _reduce_degree trick
100+ function differentiate (p, x, deg:: Val{N} ) where N
101+ if N < 0
102+ throw (DomainError (deg))
103+ else
104+ differentiate (differentiate (p, x), x, Val {N - 1} ())
105+ end
106+ end
107+ end
0 commit comments