DifferentialEquations.jl icon indicating copy to clipboard operation
DifferentialEquations.jl copied to clipboard

[feature request] Linear event handling

Open samuela opened this issue 3 years ago • 12 comments

The event handling support in DifferentialEquations.jl is very robust and so far so great! However I have a situation in which my dynamics function is very expensive to compute. Therefore I'd like to minimize the number of root finding steps to the bare minimum. But the good news is that all my events are linear in the state of the system. So with a interpolant between t1 and t2 it should be possible to analytically compute the event time; no root finding necessary! My situation is analogous to the example here: https://diffeq.sciml.ai/stable/features/callback_functions/#Example-1:-Bouncing-Ball-with-multiple-walls.

Would it be possible to accomplish something like this with DifferentialEquations.jl? Either in DifferentialEquations.jl itself or with some custom hackery?

samuela avatar Sep 21 '20 07:09 samuela

I think the best thing to do would be to use a DiscreteCallback and basically do event location yourself inside of it. The function that you need to do the effect correctly is https://diffeq.sciml.ai/stable/basics/integrator/#DiffEqBase.change_t_via_interpolation! which is how the ContinuousCallback moves back in time to the event location.

ChrisRackauckas avatar Sep 21 '20 07:09 ChrisRackauckas

The event handling support in DifferentialEquations.jl is very robust and so far so great!

Unfortunately it's not 😢 and we're working on it at least. There are some weird bracketing issues in the rootfinding library where it can give roots that are outside of the original brackets... and other weird behaviors... so we're releasing our own rootfinding library relatively soon (in the next month or so).

ChrisRackauckas avatar Sep 21 '20 07:09 ChrisRackauckas

Unfortunately it's not cry and we're working on it at least. There are some weird bracketing issues in the rootfinding library where it can give roots that are outside of the original brackets... and other weird behaviors... so we're releasing our own rootfinding library relatively soon (in the next month or so).

Oh :grimacing: well at least it looked like it was working for me...

samuela avatar Sep 21 '20 07:09 samuela

Yeah it works for most cases. People do weird stuff with floating point numbers though... library writing teaches you crazy stuff about things like 1e-6 - 1f-6...

ChrisRackauckas avatar Sep 21 '20 07:09 ChrisRackauckas

I think the best thing to do would be to use a DiscreteCallback and basically do event location yourself inside of it. The function that you need to do the effect correctly is https://diffeq.sciml.ai/stable/basics/integrator/#DiffEqBase.change_t_via_interpolation! which is how the ContinuousCallback moves back in time to the event location.

So DiffEqBase.change_t_via_interpolation! will walk time backward in the internal state of the solver and set the position at the new point in time according to the interpolant? That def seems handy.

Is there a convenient way to get the interpolant between the "current" time step when the DiscreteCallback fired, and the previous time step right before the DiscreteCallback fired?

samuela avatar Sep 21 '20 07:09 samuela

integrator(t)

ChrisRackauckas avatar Sep 21 '20 07:09 ChrisRackauckas

Is there a way to get t_prev, so that I know how far I should be backtracking?

samuela avatar Sep 21 '20 07:09 samuela

Also is there a way to get the parameters of the interpolant? That will be necessary to do this in closed-form.

samuela avatar Sep 21 '20 07:09 samuela

The straddle interval is [integrator.t,integrator.t+integrator.dt].

Also is there a way to get the parameters of the interpolant? That will be necessary to do this in closed-form.

A lot of the methods have a specific interpolation method, each defined here: https://github.com/SciML/OrdinaryDiffEq.jl/blob/master/src/dense/interpolants.jl

ChrisRackauckas avatar Sep 21 '20 07:09 ChrisRackauckas

The straddle interval is [integrator.t,integrator.t+integrator.dt].

I'm trying this with a simple example but it's not quite operating the way I'd expect:

cb = DiscreteCallback((u, t, integrator) -> u[1] <= 0, (integrator) -> begin
    @show integrator.t
    @show integrator.t + integrator.dt
end)
solve(ODEProblem((u, _, _) -> [u[2], 0], [0.75, -1], (0.0, 1.0)), callback = cb)

shows

integrator.t = 1.0
integrator.t + integrator.dt = 2.0

But what I'm really after is an interval that straddles the zero-crossing at t = 0.75. Is there a way to get that interval?

Edit: Ok, I think integrator.tprev is more or less what I want.

samuela avatar Sep 23 '20 07:09 samuela

However I have a situation in which my dynamics function is very expensive to compute. Therefore I'd like to minimize the number of root finding steps to the bare minimum.

Rootfinding does not require evaluating the dynamics function.

tprev is what you want. And I think someone is contributing a callback for this case.

ChrisRackauckas avatar Sep 28 '20 19:09 ChrisRackauckas

Rootfinding does not require evaluating the dynamics function.

Ah, ok I was mistaken! Happy to hear that. I may be able to get away with using VectorContinuousCallback for the time being then.

samuela avatar Sep 28 '20 20:09 samuela