Skip to content

Commit

Permalink
Add inverse map implementation (#367)
Browse files Browse the repository at this point in the history
* Add inverse_map

* Add inverse_map tests

* Use in-place _evaluate!

* Fix docs

* Fixes, and additions in user guide

* Fixes in update!

* Fixes in update!

* Bump patch version
  • Loading branch information
lbenet authored Aug 2, 2024
1 parent e5b4989 commit d9ca589
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TaylorSeries"
uuid = "6aa5eb33-94cf-58f4-a9d0-e4b2c4fc25ea"
repo = "https://github.com/JuliaDiff/TaylorSeries.jl.git"
version = "0.18.0"
version = "0.18.1"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
1 change: 1 addition & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ constant_term
linear_polynomial
nonlinear_polynomial
inverse
inverse_map
abs
norm
isapprox
Expand Down
7 changes: 4 additions & 3 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ programs in Physics and in Mathematics at UNAM, during the second half of 2013.
We thank the participants of the course for putting up with the half-baked
material and contributing energy and ideas.

We acknowledge financial support from DGAPA-UNAM PAPIME grants PE-105911 and
PE-107114, and PAPIIT grants IG-101113 and IG-100616. LB acknowledges
support through a *Cátedra Marcos Moshinsky* (2013).
We acknowledge financial support from DGAPA-UNAM PAPIME grants
PE-105911 and PE-107114, and DGAPA-PAPIIT grants IG-101113,
IG-100616, IG-100819 and IG-101122.
LB acknowledges support through a *Cátedra Marcos Moshinsky* (2013).
4 changes: 3 additions & 1 deletion docs/src/userguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ differentiate(5, exp(shift_taylor(1.0))) == exp(1.0) # 5-th differentiate of
derivative(exp(1+t), 3) # Taylor1 polynomial of the 3-rd derivative of `exp(1+t)`
```

We also have methods to invert a `Taylor1` polynomial, using either [`inverse`](@ref), which uses Lagrange inversion, or [`inverse_map`](@ref), which uses an algorithm developed by M. Berz; the latter can also be used for `TaylorN` polynomials; see below.

To evaluate a Taylor series at a given point, Horner's rule is used via the
function `evaluate(a, dt)`. Here, `dt` is the increment from
the point ``t_0`` around which the Taylor expansion of `a` is calculated,
Expand Down Expand Up @@ -230,7 +232,7 @@ The former returns
the expansion of a function around a given value `t0`, mimicking the use
of `shift_taylor` above. In turn, `update!`
provides an in-place update of a given Taylor polynomial, that is, it shifts
it further by the provided amount.
it further by the provided amount. Note that the type of the `Taylor1` polynomial and the shifted point must be compatible, in the sense that the latter must be convertable into the parametric type of the former.

```@repl userguide
p = taylor_expand( x -> sin(x), pi/2, order=16) # 16-th order expansion of sin(t) around pi/2
Expand Down
2 changes: 1 addition & 1 deletion src/TaylorSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import Base.float
export Taylor1, TaylorN, HomogeneousPolynomial, AbstractSeries, TS

export getcoeff, derivative, integrate, differentiate,
evaluate, evaluate!, inverse, set_taylor1_varname,
evaluate, evaluate!, inverse, inverse_map, set_taylor1_varname,
show_params_TaylorN, show_monomials, displayBigO, use_show_default,
get_order, get_numvars,
set_variables, get_variables,
Expand Down
11 changes: 6 additions & 5 deletions src/evaluate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,7 @@ end

function _evaluate(a::TaylorN{T}, vals::NTuple{N,<:Number}) where {N,T<:Number}
R = promote_type(T, typeof(vals[1]))
a_length = length(a)
suma = zeros(R, a_length)
suma = zeros(R, length(a))
@inbounds for homPol in eachindex(a)
suma[homPol+1] = _evaluate(a[homPol], vals)
end
Expand Down Expand Up @@ -499,18 +498,20 @@ end

function evaluate!(x::AbstractArray{TaylorN{T}}, δx::Array{TaylorN{T},1},
x0::AbstractArray{TaylorN{T}}; sorting::Bool=true) where {T<:NumberNotSeriesN}
x0 .= _evaluate.( x, Ref(δx), Ref(Val(sorting)) )
x0 .= evaluate.( x, Ref(δx), sorting = sorting)
return nothing
end

function evaluate!(a::TaylorN{T}, vals::NTuple{N,TaylorN{T}}, dest::TaylorN{T}, valscache::Vector{TaylorN{T}}, aux::TaylorN{T}) where {N,T<:Number}
function evaluate!(a::TaylorN{T}, vals::NTuple{N,TaylorN{T}}, dest::TaylorN{T},
valscache::Vector{TaylorN{T}}, aux::TaylorN{T}) where {N,T<:Number}
@inbounds for homPol in eachindex(a)
_evaluate!(dest, a[homPol], vals, valscache, aux)
end
return nothing
end

function evaluate!(a::AbstractArray{TaylorN{T}}, vals::NTuple{N,TaylorN{T}}, dest::AbstractArray{TaylorN{T}}) where {N,T<:Number}
function evaluate!(a::AbstractArray{TaylorN{T}}, vals::NTuple{N,TaylorN{T}},
dest::AbstractArray{TaylorN{T}}) where {N,T<:Number}
# initialize evaluation cache
valscache = [zero(val) for val in vals]
aux = zero(dest[1])
Expand Down
79 changes: 70 additions & 9 deletions src/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1228,7 +1228,7 @@ end
inverse(f)
Return the Taylor expansion of ``f^{-1}(t)``, of order `N = f.order`,
for `f::Taylor1` polynomial if the first coefficient of `f` is zero.
for `f::Taylor1` polynomial, assuming the first coefficient of `f` is zero.
Otherwise, a `DomainError` is thrown.
The algorithm implements Lagrange inversion at ``t=0`` if ``f(0)=0``:
Expand All @@ -1246,24 +1246,85 @@ function inverse(f::Taylor1{T}) where {T<:Number}
if !iszero(f[0])
throw(DomainError(f,
"""
Evaluation of Taylor1 series at 0 is non-zero. For high accuracy, revert
a Taylor1 series with first coefficient 0 and re-expand about f(0).
Evaluation of Taylor1 series at 0 is non-zero; revert
a Taylor1 series with constant coefficient 0 and re-expand about f(0).
"""))
end
z = Taylor1(T,f.order)
z = Taylor1(T, f.order)
zdivf = z/f
zdivfpown = zdivf
S = TS.numtype(zdivf)
coeffs = zeros(S,f.order+1)
res = Taylor1(zero(TS.numtype(zdivf)), f.order)

@inbounds for n in 1:f.order
coeffs[n+1] = zdivfpown[n-1]/n
@inbounds for ord in 1:f.order
res[ord] = zdivfpown[ord-1]/ord
zdivfpown *= zdivf
end
Taylor1(coeffs, f.order)
return res
end


@doc doc"""
inverse_map(f)
Return the Taylor expansion of ``f^{-1}(t)``, of order `N = f.order`,
for `Taylor1` or `TaylorN` polynomials, assuming the first coefficient of `f` is zero.
Otherwise, a `DomainError` is thrown.
This method is based in the algorithm by M. Berz, Modern map methods in
Particle Beam Physics, Academic Press (1999), Sect 2.3.1.
See [`inverse`](@ref) (for `f::Taylor1`).
"""
function inverse_map(p::Taylor1)
if !iszero(constant_term(p))
throw(DomainError(p,
"""
Evaluation of Taylor1 series at 0 is non-zero; revert
a Taylor1 series with constant coefficient 0 and re-expand about f(0).
"""))
end
inv_m_pol = inv(linear_polynomial(p)[1])
n_pol = inv_m_pol * nonlinear_polynomial(p)
scaled_ident = inv_m_pol * Taylor1(p.order)
res = scaled_ident
aux1 = zero(res)
aux2 = zero(res)
for ord in 1:p.order
_horner!(aux2, n_pol, res, aux1)
subst!(res, scaled_ident, aux2, ord)
end
return res
end

function inverse_map(p::Vector{TaylorN{T}}) where {T<:NumberNotSeries}
if !iszero(constant_term(p))
throw(DomainError(p,
"""
Evaluation of Taylor1 series at 0 is non-zero; revert
a Taylor1 series with constant coefficient 0 and re-expand about f(0).
"""))
end
@assert length(p) == get_numvars()
inv_m_pol = inv(jacobian(p))
n_pol = inv_m_pol * nonlinear_polynomial(p)
scaled_ident = inv_m_pol * TaylorN.(1:get_numvars(), order=get_order(p[1]))
res = deepcopy(scaled_ident)
aux = zero.(res)
auxvec = [zero(res[1]) for val in eachindex(res[1])]
valscache = [zero(val) for val in res]
aaux = zero(res[1])
for ord in 1:get_order(p[1])
t_res = (res...,)
for i = 1:get_numvars()
zero!.(auxvec)
_evaluate!(auxvec, n_pol[i], t_res, valscache, aaux)
aux[i] = sum( auxvec )
subst!(res[i], scaled_ident[i], aux[i], ord)
end
end
return res
end


# Documentation for the recursion relations
@doc doc"""
Expand Down
16 changes: 12 additions & 4 deletions src/other_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -254,15 +254,23 @@ end
Takes `a <: Union{Taylo1,TaylorN}` and expands it around the coordinate `x0`.
"""
function update!(a::Taylor1, x0::T) where {T<:Number}
function update!(a::Taylor1{T}, x0::T) where {T<:Number}
a.coeffs .= evaluate(a, Taylor1([x0, one(x0)], a.order) ).coeffs
nothing
return nothing
end
function update!(a::Taylor1{T}, x0::S) where {T<:Number, S<:Number}
xx0 = convert(T, x0)
return update!(a, xx0)
end

#update! function for TaylorN
function update!(a::TaylorN, vals::Vector{T}) where {T<:Number}
function update!(a::TaylorN{T}, vals::Vector{T}) where {T<:Number}
a.coeffs .= evaluate(a, get_variables(a.order) .+ vals).coeffs
nothing
return nothing
end
function update!(a::TaylorN{T}, vals::Vector{S}) where {T<:Number, S<:Number}
vv = convert(Vector{T}, vals)
return update!(a, vv)
end

function update!(a::Union{Taylor1,TaylorN})
Expand Down
16 changes: 15 additions & 1 deletion test/manyvariables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,20 @@ end
txy[2:end-1] .= ( 1.0 - xT*yT + 0.5*xT^2*yT - (2/3)*xT*yT^3 - 0.5*xT^2*yT^2 + 7*xT^3*yT )[2:end-1]
@test txy[2:end-1] == ( 1.0 - xT*yT + 0.5*xT^2*yT - (2/3)*xT*yT^3 - 0.5*xT^2*yT^2 + 7*xT^3*yT )[2:end-1]

ident = [xT, yT]
pN = [x+y, x-y]
@test evaluate.(inverse_map(pN), Ref(pN)) == ident
@test evaluate.(pN, Ref(inverse_map(pN))) == ident
pN = [exp(xT)-1, log(1+yT)]
@test inverse_map(pN) [log(1+xT), exp(yT)-1]
@test evaluate.(pN, Ref(inverse_map(pN))) ident
pN = [tan(xT), atan(yT)]
@test evaluate.(inverse_map(pN), Ref(pN)) ident
@test evaluate.(pN, Ref(inverse_map(pN))) ident
pN = [sin(xT), asin(yT)]
@test evaluate.(inverse_map(pN), Ref(pN)) ident
@test evaluate.(pN, Ref(inverse_map(pN))) ident

a = -5.0 + sin(xT+yT^2)
b = deepcopy(a)
@test a[:] == a[0:end]
Expand Down Expand Up @@ -749,7 +763,7 @@ end
xysq = x^2 + y^2
update!(xysq,[1.0,-2.0])
@test xysq == (x+1.0)^2 + (y-2.0)^2
update!(xysq,[-1.0,2.0])
update!(xysq,[-1,2])
@test xysq == x^2 + y^2

#test function-like behavior for TaylorN
Expand Down
9 changes: 7 additions & 2 deletions test/onevariable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Base.iszero(::SymbNumber) = false
tsq = t^2
update!(tsq,2.0)
@test tsq == (t+2.0)^2
update!(tsq,-2.0)
update!(tsq,-2)
@test tsq == t^2

@test log(exp(tsquare)) == tsquare
Expand Down Expand Up @@ -514,9 +514,14 @@ Base.iszero(::SymbNumber) = false

@test promote(ta(0.0), t) == (ta(0.0),ta(0.0))

@test norm((inverse(exp(t)-1) - log(1+t)).coeffs) < 2tol1
@test inverse(exp(t)-1) log(1+t)
cfs = [(-n)^(n-1)/factorial(n) for n = 1:15]
@test norm(inverse(t*exp(t))[1:end]./cfs .- 1) < 4tol1
@test inverse(tan(t))(tan(t)) t
@test atan(inverse(atan(t))) t
@test inverse_map(sin(t))(sin(t)) t
@test sinh(inverse_map(sinh(t))) t
@test inverse_map(tanh(t)) inverse(tanh(t))

@test_throws ArgumentError Taylor1([1,2,3], -2)
@test_throws DomainError abs(ta(big(0)))
Expand Down

2 comments on commit d9ca589

@lbenet
Copy link
Member Author

@lbenet lbenet commented on d9ca589 Aug 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/112321

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.18.1 -m "<description of version>" d9ca58966c8eb65addce02545ff2aa0d21a7fe2c
git push origin v0.18.1

Please sign in to comment.