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

taylor_expand function for Taylor1 and TaylorN #121

Merged
merged 36 commits into from
Oct 8, 2017

Conversation

blas-ko
Copy link
Contributor

@blas-ko blas-ko commented Aug 31, 2017

This makes reference to #68. A taylor_expand function is added for Taylor1 and TaylorN expansions around a Number or Vector, respectively.

Obs: The way "common" functions are expanded for arrays such as taylor_expand(cos.[0.0,1.0]) for julia 0.6 output a warning about a method deprecation for arrays. They suggest to use now the dot operator instead, but this generates some problems with user defined functions.

@coveralls
Copy link

coveralls commented Aug 31, 2017

Coverage Status

Coverage increased (+0.5%) to 94.692% when pulling 65b5596 on blas-ko:taylor_expand into 2381ad4 on JuliaDiff:master.

@lbenet
Copy link
Member

lbenet commented Aug 31, 2017

I like the idea of this PR, though I am still not sure about the name of the function (and perhaps other things), see below.

My proposal here is to twist a little the essence, and address #80. That is, given x::Taylor1 (for concreteness I'll focus on Taylor1s), the idea is to return x (probably update it in place), such that the new x is the polynomial evaluated at a different location. Then, update{T}(x::Taylor1{T}, t0::T) would be equivalent to evaluate(x, Taylor1( [t0,one(t0)], x.order )). The tricky part (and I am not sure if this is possible or not to accomplish) is to only update x; maybe the following works:

function update!(x::Taylor1, t0::TaylorSeries.NumberNotSeries)
    x.coeffs .= evaluate(x, Taylor1( [t0,one(t0)], x.order ))[:]
    return x
end

If this works, I guess this could also be extended to HomogeneousPolynomial and TaylorN, but I am not sure.

Surely, the original idea of this PR is more general, in the sense that it returns the evaluated expansion of a given function f around a point. This could still be considered.

The distinction of returning a Taylor1 or a TaylorN is by dispatch on x0. That's ok. Yet, I don't think that taylor_evaluate should change the number of variables nor the order for TaylorN; I think this should be done by the user explicitly, and taylor_evaluate should rely on get_order() and get_numvars().

@lbenet
Copy link
Member

lbenet commented Aug 31, 2017

I think the idea above works

julia> using TaylorSeries

julia> function update!(x::Taylor1, t0::TaylorSeries.NumberNotSeries)
           x.coeffs .= evaluate(x, Taylor1( [t0,one(t0)], x.order ))[:]
           return nothing
       end
update! (generic function with 1 method)

julia> x = Taylor1([1.0,2.0,3.0])
 1.0 + 2.0 t + 3.0+ 𝒪(t³)

julia> update!(x, 1.0)

julia> x
 6.0 + 8.0 t + 3.0+ 𝒪(t³)

julia> update!(x, -1.0)

julia> x
 1.0 + 2.0 t + 3.0+ 𝒪(t³)

@lbenet
Copy link
Member

lbenet commented Aug 31, 2017

Incidentally, the fact that tests are not passing may be related to changing the order and the number of variables; see above.

@blas-ko
Copy link
Contributor Author

blas-ko commented Aug 31, 2017

given x::Taylor1 (for concreteness I'll focus on Taylor1s), the idea is to return x (probably update it in place), such that the new x is the polynomial evaluated at a different location.

I think this is a great feature (that I think should be called something like shift! or displace!), but it's, in essence, different from what taylor_expand does. I can incorporate the update! feature with the name that we decide in this PR, but it will be defined via evaluate as you suggest and not via taylor_expand. What do you think @lbenet ?

Methods for evaluating HomogeneousPolynomial and TaylorN in HomogenousPolynomial and TaylorN are not yet defined. This should be constructed in order to implement the update! function accordingly.

Yet, I don't think that taylor_evaluate should change the number of variables nor the order for TaylorN; I think this should be done by the user explicitly, and taylor_evaluate should rely on get_order() and get_numvars().

The reason for this was that it took the dimension of the function f to expand it accordingly, but maybe it's a good practice for the user for him to change the set_variables environment a priori of the expansion.

Incidentally, the fact that tests are not passing may be related to changing the order and the number of variables; see above.

Output for travis (both Linux nightly & Mac nightly) is:

while loading /home/travis/.julia/v0.7/TaylorSeries/test/manyvariables.jl, in expression starting on line 7
Tests for HomogeneousPolynomial and TaylorN: Error During Test
  Test threw an exception of type MethodError
  Expression: taylor_expand(exp, [0, 0.0]) == exp.(set_variables("x", numvars=2))
  MethodError: no method matching exp(::Array{TaylorSeries.TaylorN{Float64},1})
  Closest candidates are:
    exp(!Matched::Float16) at math.jl:993
    exp(!Matched::Complex{Float16}) at math.jl:994
    exp(!Matched::BigFloat) at mpfr.jl:527
    ...

I think it has to do to broadcasting as exp.(x) still has some compatibility issues, but I'm not sure.

@lbenet
Copy link
Member

lbenet commented Aug 31, 2017

I agree that there is a difference between update! (or displace!, but not shift!, since it has a different meaning in Base) and taylor_evaluate; the former overwrittes the given Taylor1, while the latter creates a new one, but centered at some given value. In any case, implementing both is maybe the best.

For the tests, I think it's better to check the actual series constructed directly, either using taylor_expand(f, x0, order) or as f(Taylor1([zero(x0),one(x0)],order)); they should be identic.

Regarding the TaylorN case, we should perhaps leave it aside, to begin, and then latter address it.

@lbenet
Copy link
Member

lbenet commented Sep 1, 2017

I was checking this PR, and there is something odd in the many-variable case. For simplicity I will consider two variables. I hope the following comments are helpful.

My expectation is that taylor_expand should correspond the proper translation around the new point. That is, the expansion of f(x,y) around [x0,y0] should be f(x0+xT, y0+yT) where xT and yT are TaylorNs.

Yet, this is not the case currently.

julia> using TaylorSeries

julia> xT, yT = set_variables("x y");

julia> f(x,y) = (x+y)^2
f (generic function with 1 method)

julia> f(xT,yT)
 1.0+ 2.0 x y + 1.0+ 𝒪(‖x‖⁷)

julia> taylor_expand(f, [0.0,0.0])
ERROR: MethodError: no method matching f(::Array{TaylorSeries.TaylorN{Float64},1})
Closest candidates are:
  f(::Any, ::Any) at REPL[3]:1
Stacktrace:
 [1] #taylor_expand#33(::Int64, ::Function, ::#f, ::Array{Float64,1}) at /Users/benet/.julia/v0.6/TaylorSeries/src/other_functions.jl:146
 [2] taylor_expand(::Function, ::Array{Float64,1}) at /Users/benet/.julia/v0.6/TaylorSeries/src/other_functions.jl:143

The error is because in taylor_expand, X is actually a vector of the TaylorN independent variables, instead of being some sort of tuple. I guess that splatting (...) the argument of the function could be helpful here.

Currently, the function f is broadcasted, which means that f(X) returns a vector of the form [f(xT), f(yT)], which is not useful for the example above. This explains that

julia> taylor_expand(exp,[0,0.])
...
2-element Array{TaylorSeries.TaylorN{Float64},1}:
  1.0 + 1.0 x₁ + 0.5 x₁² + 0.16666666666666666 x₁³ + 0.041666666666666664 x₁⁴ + 0.008333333333333333 x₁⁵ + 0.0013888888888888887 x₁⁶ + 𝒪(‖x‖⁷)
  1.0 + 1.0 x₂ + 0.5 x₂² + 0.16666666666666666 x₂³ + 0.041666666666666664 x₂⁴ + 0.008333333333333333 x₂⁵ + 0.0013888888888888887 x₂⁶ + 𝒪(‖x‖⁷)

is a vector, and each entry depends only on one of the TaylorN variables. (I supressed some irrelevant warnings.)

I think we should consider the function to be defined as in the first example above. Broadcasting (f.(x,y)) may be considered to extend this to the case where f(x,y) returns a vector.

@blas-ko
Copy link
Contributor Author

blas-ko commented Sep 5, 2017

I've covered the case you're telling about @lbenet ! It works as

julia> using TaylorSeries

julia> xT, yT = set_variables("x y");

julia> f(x,y) = (x+y)^2
f (generic function with 1 method)

julia> f(xT,yT)
 1.0+ 2.0 x y + 1.0+ 𝒪(‖x‖⁷)

julia> taylor_expand(f, 0.0, 0.0)
1.0+ 2.0 x y + 1.0+ 𝒪(‖x‖⁷)

julia> @which taylor_expand(f, 0.0, 0.0)
taylor_expand(f::Function, x0...) at .../src/other_functions.jl:152

Additionally, taylor_expand for TaylorN throws a warning if the set variables are note equal to the required for the expansion.

EDIT: I think I'll address #80 in a new PR if you agree with me in this.

EDIT 2: Rethinking this issue, I feel that taylor_expand! would make sense for shifting an existing Taylor1 or TaylorN around a new point x0. In this fashion, taylor_expand would take a Function and x0 as arguments to make the expansion, while taylor_expand! would take an existing Taylor{1,N} and update it around its second argument x0. What do you think, @lbenet?

By the way, taylor_expand! for TaylorN would need an evaluate method for TaylorN in a Vector{TaylorN}, as @PerezHz points out in #119.

@coveralls
Copy link

coveralls commented Sep 5, 2017

Coverage Status

Coverage decreased (-0.4%) to 93.814% when pulling 3c914ae on blas-ko:taylor_expand into 2381ad4 on JuliaDiff:master.

@blas-ko
Copy link
Contributor Author

blas-ko commented Sep 8, 2017

I edited my last comment, @lbenet. I'm not sure if Github notifies this...
Cheers!

blas-ko and others added 7 commits October 1, 2017 18:36
JuliaDiff#118)

* Add function-like behavior for Taylor1

* Relocate new code

* Add function-like behavior for TaylorN

* Fix TaylorN functor methods

* Add tests for Taylor1

* Add more Taylor1 tests

* Add TaylorN tests; more Taylor1 tests; add missing evaluate methods

* Add function-like behavior for HomogeneousPolynomial and corresponding tests

* Add missing tests for HomogeneousPolynomial

* Add another test for Taylor1s

* Fix test

* Add extra evaluate method for Taylor1 (suggested by @blas-ko)

* Add an evaluate test for mixtures (more to come)

* Add tests for mixtures; add/fix evaluate methods

* A small fix

* Add missing evaluate methods for mixtures and tests

* Update docs

* Add evaluate method and tests

* Fix new method

* Update docstrings

* Changes suggested by @lbenet 's review
@coveralls
Copy link

coveralls commented Oct 2, 2017

Coverage Status

Coverage decreased (-1.0%) to 94.943% when pulling 5d978eb on blas-ko:taylor_expand into 3b68fb3 on JuliaDiff:master.

@lbenet
Copy link
Member

lbenet commented Oct 6, 2017

@blas-ko Can you rebase this branch to current master? This will also restart travis check and then I'll review it.

@blas-ko
Copy link
Contributor Author

blas-ko commented Oct 6, 2017

Rebased! (Hope I did it correctly since I don't have experience rebasing yet).

They use set_variables internally.
Copy link
Member

@lbenet lbenet left a comment

Choose a reason for hiding this comment

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

In general, LGTM. Yet, i have some objection with respect to the use of set_variables, for which I suggest an alternative. Also I suggest that taylor_expand! is renamed as update! (see #80) and is also exported.

Cc: @dpsanders

x0::Array{TaylorN{T},1})
@assert length(x) == length(x0)
@inbounds for i in eachindex(x)
x0[i] = evaluate( x[i], δx )
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps using here (as well in similar occurrences in other methods) evaluate!(x[i], δx, x0[i]) instead, yields better performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed for evaluate methods but not for evaluate! methods as evaluate!, as the docs say, just work for arrays.

Copy link
Member

Choose a reason for hiding this comment

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

Good point!

src/evaluate.jl Outdated
@assert length(vals) == get_numvars()

num_vars = get_numvars()
ct = coeff_table
Copy link
Member

Choose a reason for hiding this comment

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

This creates a copy of coeff_table. Perhaps using a view of it, or directly coeff_table saves this.

I didn't notice this before, sorry, but I think this should also be changed in other occurrences.


#taylor_expand function for Taylor1
doc"""
taylor_expand(f,x0;order)
Copy link
Member

Choose a reason for hiding this comment

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

Can you add white spaces? I mean taylor_expand(f, x0; order).

As commented below, I am not in favor of changing the number of variables inside the function.

computed, changing the number of `TaylorN` variables
according to the dimension of `f`.
"""
function taylor_expand(f::Function; order::Int64=15)
Copy link
Member

Choose a reason for hiding this comment

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

Note that this will return a Taylor1{Float64} expansion. My point focuses in the Float64 part.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you think taylor_expand(T::Type,f::Function;order::Int64) is a good idea? In this case, should the same be done when passing an explicit x0 to taylor_expand?

end

#taylor_expand function for TaylorN
function taylor_expand{T<:Number}(f::Function, x0::Vector{T}; order::Int64=get_order()) #a Taylor expansion around x0
Copy link
Member

Choose a reason for hiding this comment

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

I agree that, for Taylor1 expansions it makes sense to allow the user to set the order of the expansion; I am not in favor of this for TaylorN expansions, at least as it is now. My objection is related to the use of set_variables, since it changes some internals, in particular, the maximum order of the TaylorN expansions. This costs time; also, the idea of the current implementation is to set the parameters related to TaylorN internals only once at the very beginning.

I think this method should assert that the the length of x0 corresponds to get_numvars(), as you do it, and only when this is fulfilled evaluate f(X .+ x0) (notice the spaces). In this case, using set_variables will not change anything internally.

An alternative that avoids using set_variables is for example:

function taylor_expand{T<:Number}(f::Function, x0::Vector{T}; order::Int64=get_order())
    ll = length(x0)
    # check that x0 has the correct length and `order` does not exceed the maximum order of the expansions
    @assert ll == get_numvars() && order <= get_order()

    X = Array{TaylorN{T}}(length(x0)
    for i in eachindex(X)
        X[i] = TaylorN(T, i, order=order)
    end

    return f( X .+ x0 )
end

This alternative permits to have expansions of given order, smaller or equal to get_order(), e.g., order=2 when get_order() is 6.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Really in favor of this. By the way, really good "type preserving strategy" doing the X[i] = TaylorN(T, i, order).

Copy link
Member

Choose a reason for hiding this comment

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

Regarding the problem with travis in Julia 0.5 (and considering your comment about this), I think a possible solution is to have X as the shifted vector. I mean, define X[i] = x0[i] + TaylorN(T, i, order=order) inside the for-loop, and then only return the evaluation of f( X ). This could be used in each method of taylor_expand.

I think this could actually solve the problem with travis, and yield a tiny performance improvement, since only one loop would be needed.

function taylor_expand(f::Function, x0...; order::Int64=get_order()) #a Taylor expansion around x0
ll = length(x0)
ll == get_numvars() ? X = get_variables() : begin
X = set_variables("x",order=order,numvars=ll)
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as above.


Takes `a <: Union{Taylo1,TaylorN}` and expands it around the coordinate `x0`.
"""
function taylor_expand!(a::Taylor1)
Copy link
Member

Choose a reason for hiding this comment

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

What about naming this function update! (as proposed in #80), and export it? Since this method is some sort of fallback, i think it can be written so a::Union{Taylor1, TaylorN}, which also covers another method below.

@@ -224,7 +233,6 @@ function evaluate{T<:Number,S<:NumberNotSeries}(a::TaylorN{T},
suma = Taylor1(zeros(R, ord))

for homPol in 1:length(a)
sun = zero(R)
Copy link
Member

Choose a reason for hiding this comment

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

Good catch!

@blas-ko
Copy link
Contributor Author

blas-ko commented Oct 6, 2017

Great! I've read all the changes and I'll make them as you propose. I wanted you to see the warning for set_variables in the TaylorN case, but I agree it's better to use an @assert. I'm gonna push this changes in a moment, so you can re-review.

@lbenet
Copy link
Member

lbenet commented Oct 6, 2017

Incidentally, I just noticed this line, which seems to me unnecessary, since there is a method real(T::Type) (in Base at complex.jl:97) which does exactly this.

@PerezHz
Copy link
Contributor

PerezHz commented Oct 6, 2017

Incidentally, I just noticed this line, which seems to me unnecessary, since there is a method real(T::Type) (in Base at complex.jl:97) which does exactly this.

I added it in order to keep support for 0.5; once we drop 0.5 support, we can erase this line

* Added 1 more test...
@coveralls
Copy link

coveralls commented Oct 7, 2017

Coverage Status

Coverage decreased (-0.7%) to 95.288% when pulling 9d71757 on blas-ko:taylor_expand into 2111846 on JuliaDiff:master.

@blas-ko
Copy link
Contributor Author

blas-ko commented Oct 7, 2017

Seems that for Julia 0.5 broadcasting between Arrays and Tuples is not compatible...

Tests for HomogeneousPolynomial and TaylorN: Error During Test
  Test threw an exception of type MethodError
  Expression: taylor_expand(f11,1.0,2.0) == taylor_expand(f22,[1,2.0])
  MethodError: no method matching .+(::Tuple{Float64,Float64}, ::Array{TaylorSeries.TaylorN{Float64},1})

This is, in particular, for line 216 of src/other_functions.jl for function taylor_expand(f::Function, x0...; order::Int64=get_order()) method.
What can be done here?

@coveralls
Copy link

coveralls commented Oct 7, 2017

Coverage Status

Coverage decreased (-0.6%) to 95.366% when pulling 33a23a1 on blas-ko:taylor_expand into 2111846 on JuliaDiff:master.

end

function taylor_expand(f::Function, x0...; order::Int64=get_order()) #a Taylor expansion around x0
T = eltype(x0[1])
Copy link
Member

Choose a reason for hiding this comment

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

Since x0 is a tuple, each entry could have a different type. I think that T should be defined as T = eltype( promote(x0...)[1] ).

@coveralls
Copy link

coveralls commented Oct 8, 2017

Coverage Status

Coverage decreased (-0.4%) to 95.516% when pulling f8046ba on blas-ko:taylor_expand into 2111846 on JuliaDiff:master.

@blas-ko
Copy link
Contributor Author

blas-ko commented Oct 8, 2017

Tests are passing!

@lbenet
Copy link
Member

lbenet commented Oct 8, 2017

Great! Thanks for all! I'm merging right away!

@lbenet lbenet merged commit 909689b into JuliaDiff:master Oct 8, 2017
This was referenced Oct 8, 2017
@PerezHz
Copy link
Contributor

PerezHz commented Oct 8, 2017

Really cool stuff! Thanks for fixing #119 @blas-ko!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants