Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inverse map implementation #367

Merged
merged 8 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading