-
Notifications
You must be signed in to change notification settings - Fork 21
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
Re-implement common interface bindings #114
Conversation
Updates: I found a way, thanks to @SebastianM-C's work in #108, to finally handle |
Thanks for the great effort! Please ping me, and perhaps @SebastianM-C too, whenever you think this is ready for review. |
The latest update is that I think I found a way to make continuous and vector callbacks work (discrete callbacks were already working), which I hope will help users of TaylorIntegration.jl through the common interface. I'm still not 100% percent sure about the FSAL thing, but things appear to be working well, so... 🤷♂️. The keyword warning list perhaps needs to be updated in some way as well, but here's where some feedback might help to know what's the best way to proceed going forward. I think we should add support, respectively, for in-place format in scalar problems and out-of-place format in array problems, but we might tackle that in another PR. Finally, in the tests some funny warnings started to appear:
but that seems to be related to FiniteDiff and DiffEqDiffTools, since we're not overwriting any Otherwise, this PR is ready for review! cc: @lbenet @SebastianM-C |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding support for DynamicalODEProblems
. I was thinking if a more general approach where the conversion to ODEProblem
is no longer required would be possible.
src/common.jl
Outdated
sizeu = size(prob.u0) | ||
# This if block handles DynamicalODEProblems | ||
# credit to @SebastianM-C for coming up with this solution | ||
if prob.f isa DynamicalODEFunction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I understand, this special casing is needed because the jetcoeffs!
function calls the user function with xaux
which is an Array
. I was wondering if it would be possible to just use the DiffEqFunction
interface (i.e. call prob.f
with f(du,u,p,t)
and u
with the same type as prob.u0
), but I'm not sure I understand how the jetcoeffs!
code works and if the above suggestion would make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your review and your suggestions! You're right, it'd be nice if we didn't have to convert everything to an ODEProblem
... after reading your comment here I gave it a shot, and I think just got to overcome this conversion, at least for the in-place case (I should push that commit in a couple of minutes). The out-of-place version won't work as of now, since TaylorIntegration.jl
currently does not support oop for prob.u0 <: AbstractArray
. But once we add that, perhaps in another PR, we'll be able to get rid of most of the if
s and else
s, while avoiding the conversion from DynamicalODEProblem
to ODEProblem
.
Just pushed a commit which fixed an error in the casing for out-of-place DynamicalODEProblem (the returned value from f wasn't being passed correctly) and added some tests. |
@SebastianM-C I was thinking... is there a way to convert an out-of-place |
I was trying stuff like if prob.f isa DynamicalODEFunction && !isinplace
f1! = (dv, v, u, p, t) -> (dv .= prob.f.f1(v, u, p, t); 0)
f2! = (du, v, u, p, t) -> (du .= prob.f.f2(v, u, p, t); 0)
prob.f = DynamicalODEFunction{true}(f1!, f2!)
# ... which is similar to what we do for out-of-place
In the tests, if I try oop_prob.f = iip_prob.f the same error shows up. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a much better solution as it preserves the problem type and avoids converting the user input to an Array
. This should improve both performance and generality/composability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The translation of out-of-place problems to in-place ones is fundamentally incompatible with immutable types. From what I understand from the DiffEq code, the OOP code path is made specifically for immutable types.
f = prob.f | ||
if !isinplace && typeof(prob.u0) <: AbstractArray | ||
if prob.f isa DynamicalODEFunction | ||
f1! = (dv, v, u, p, t) -> (dv .= prob.f.f1(v, u, p, t); 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what causes test to fail in the OOP case with immutable types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks! I'm proposing, as a temporary solution for this specific case, to convert the initial condition prob.u0
from an immutable to a mutable array type (see below)
Ok, so I think I found a compromise: I found a way to make things to work with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the tremendous work that there is in this PR! I have left mostly trivial remarks to clarify a couple of points.
LGTM and I am in favor of merging this, though maybe we may wait for @SebastianM-C's opinion.
src/common.jl
Outdated
import OrdinaryDiffEq: OrdinaryDiffEqAdaptiveAlgorithm, | ||
OrdinaryDiffEqConstantCache, OrdinaryDiffEqMutableCache, | ||
alg_order, alg_cache, initialize!, perform_step!, @unpack, | ||
@cache, stepsize_controller!, step_accept_controller! | ||
|
||
# TODO: check which keywords work fine | ||
const warnkeywords = (:save_idxs, :d_discontinuities, :unstable_check, :save_everystep, | ||
:save_end, :initialize_save, :adaptive, :dt, :reltol, :dtmax, | ||
:dtmin, :force_dtmin, :internalnorm, :gamma, :beta1, :beta2, | ||
:qmax, :qmin, :qsteady_min, :qsteady_max, :qoldinit, :failfactor, | ||
:isoutofdomain, :unstable_check, | ||
:calck, :progress, :timeseries_steps, :dense) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you include some indentation; makes nicer reading the code.
Can you open an issue to have present the TO DO indicated here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, added indentation and also opened the issue corresponding to this TODO
src/common.jl
Outdated
struct _TaylorMethod <: TaylorAlgorithm | ||
order::Int | ||
parse_eqs::Bool | ||
end | ||
|
||
_TaylorMethod(order; parse_eqs=true) = _TaylorMethod(order, parse_eqs) # set `parse_eqs` to `true` by default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naive question: why do we need two (essentially identical) structs, instead of only one? Perhaps by adjusting some constructors, everything works with only one...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Yes, it was redundant to have those two structures, so I removed one of them
src/common.jl
Outdated
# interpolation may be helpful (since Taylor interpolation is memory-intensive), | ||
# so let's set isfsal to true for now, which is the default, so no method | ||
# definition needed here, so we in principle should support FSAL | ||
# isfsal(::TaylorMethod) = true # see discussion above |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yet, this line is commented... Is it needed? We can certainly have it there commented, but then clarifying a bit would be very enlightening.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was more of a personal reminder when trying to figure the internals of DiffEqBase and OrdinaryDiffEq; I've now removed these commented lines
src/common.jl
Outdated
# @show macroexpand(@__MODULE__, :(@cache struct TaylorMethodCache{uType,rateType,tTType,uTType} <: OrdinaryDiffEqMutableCache | ||
# u::uType | ||
# uprev::uType | ||
# tmp::uType | ||
# k::rateType | ||
# fsalfirst::rateType | ||
# tT::tTType | ||
# uT::uTType | ||
# duT::uTType | ||
# uauxT::uTType | ||
# parse_eqs::Bool | ||
# end) ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this? Seems it is a left-over of something you were testing...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😅 sorry, thanks! Yes, this was a leftover, it's now gone
src/common.jl
Outdated
if typeof(cache) <: OrdinaryDiffEqMutableCache | ||
rtmp = similar(u, eltype(eltype(k))) | ||
f(rtmp,uprev,p,t) | ||
copyat_or_push!(k,1,rtmp) | ||
f(rtmp,u,p,t+dt) | ||
copyat_or_push!(k,2,rtmp) | ||
else | ||
copyat_or_push!(k,1,f(uprev,p,t)) | ||
copyat_or_push!(k,2,f(u,p,t+dt)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you include some indentation here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, done!
src/common.jl
Outdated
# This function was modified from TaylorSeries.jl; MIT-licensed | ||
# evaluate! overload to handle AbstractArray | ||
import TaylorSeries: evaluate! | ||
function evaluate!(x::AbstractArray{Taylor1{T},N}, δt::S, | ||
x0::AbstractArray{T,N}) where {T<:Number, S<:Number, N} | ||
|
||
tstops_internal, saveat_internal, d_discontinuities_internal | ||
# @assert length(x) == length(x0) | ||
@inbounds for i in eachindex(x, x0) | ||
x0[i] = evaluate( x[i], δt ) | ||
end | ||
nothing | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not adding it directly to TaylorSeries?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only point which I haven't addressed yet; I will open the corresponding PR in TaylorSeries, and it will perhaps require a new TaylorSeries release
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: just opened the corresponding PR. See: JuliaDiff/TaylorSeries.jl#252
test/common.jl
Outdated
@@ -1,16 +1,24 @@ | |||
using TaylorIntegration, Test, DiffEqBase | |||
using Test, DiffEqBase, TaylorIntegration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the order matter here? If so, i think it is worth adding a couple of sentences to the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order makes no difference, I just forgot to leave that line untouched. I've now gone back to the original order, so that this line doesn't show up when doing diffs in the future
end | ||
# DiffEqBase.solve(prob, _alg, args...; kwargs...) | ||
integrator = DiffEqBase.__init(_prob, _alg, args...; kwargs...) | ||
integrator.dt = stepsize(integrator.cache.uT, integrator.opts.abstol) # override handle_dt! setting of initial dt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, there was a default value set for abstol
; I cannot see it now. So the question is if the user must now to specify it, or is it set somewhere else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! If the user doesn't specify abstol
it is set automatically by DiffEqBase.__init
to abstol=1e-6
Thanks @lbenet for your review! I addressed all your comments except one, which will have to wait a bit while we add the new
I agree we should wait to hear Sebastian's opinion. |
…ty warnkeywords, add verbose kwarg
…od for high-dim arrays
[skip ci]
[skip ci]
[ci skip]
via overloading DiffEqBase.addsteps! for ::TaylorMethodCache
[skip ci]
…itial condition to mutable array
[skip ci]
9e2b0e2
to
c3eebe9
Compare
Tests are passing, except in nightly; I am just including the (first) problem spotted by travis here to address it later. |
Shall I go ahead and merge? |
Sorry for the late reply, sure, I agree that we should merge! |
Merged! Thanks a lot! |
This PR proposes to re-implement the common interface bindings to the
DifferentialEquations.jl
ecosystem essentially by addingTaylorMethod
as an ODE solver method ofOrdinaryDiffEq.jl
in the senseTaylorAlgorithm <: OrdinaryDiffEqAdaptiveAlgorithm
. So the proposed approach is to extendinitialize!
andperform_step!
methods inOrdinaryDiffEq.jl
, while handling step size control via corresponding method extensions ofstepsize_controller!
andstep_accept_controller!
. By doing this, callingsolve
yields the same results as a direct call totaylorinteg
with comparable performance, while allowing the use of a larger set of keywords withTaylorMethod
, includingsaveat
,tstops
and evencallback
(currently, discrete callbacks seem to work okay while continuous callbacks do not).On the
TaylorIntegration.jl
side, I extendedjetcoeffs!
andstepsize
, as well asTaylorSeries.evaluate!
to allow use of high-dimension arrays. Following this strategy, current tests inmaster
pass, and also added some more tests. In particular, this PR might help to find a way out of #103 and #109.There are some issues remaining: besides the continuous callbacks (which I'm currently trying to sort out) I'm not completely sure how to handle the FSAL stuff, but since it is used in 3rd-order Hermite interpolation in
OrdinaryDiffEq
, I added FSAL-related updates ininitialize!
andperform_step!
, although I'm not sure if I did it right. Also, I left most of the keywords in the warning listwarnkeywords
, although a better approach might now be the inverse: to ditch the keyword warning list altogether (or at least most of it?), and handle any issues that will arise in the future.Surely there are lots of things that can be improved, so feedback is appreciated!
cc: @ChrisRackauckas @lbenet