SciML / DiffEqBase.jl

The lightweight Base library for shared types and functionality for defining differential equation and scientific machine learning (SciML) problems
Other
319 stars 116 forks source link

Remove / Ignore Callbacks in CallbackSet #179

Open QuantumBits opened 5 years ago

QuantumBits commented 5 years ago

I'm curious if it is possible to add/remove a specific Callback from a CallbackSet during runtime in the body of an affect! function. For example, I might have a large list of Callback objects that I only need to run a finite (possibly unknown) number of times before knowing for certain I don't need to apply them again. If these have expensive condition functions, I would like to ensure I don't run them unnecessarily. It's possible I could have condition check some kind of global array before running, but (a) I would rather not create unnecessary global variables and (b) removing a Callback from a CallbackSet sounds like a much cleaner solution.

As a simple example, here's a Callback that removes the kth Callback from an (assumed) CallbackSet:


function removeKthCB(tk::Float64, k::Int64)

    return ContinuousCallback(
        function condition(u, t, integrator)
            return t - tk
        end,
        function affect!(integrator)  
            integrator.opts.callback.remove(k) # This line is the important line
        end
    )
end

I know this isn't a perfect solution, or if there's a more "proper" way of going about this, but something along these lines would be great to have!

ChrisRackauckas commented 5 years ago

One way of doing this is to just make the condition impossible, for example using an internal state to make it always Inf after a certain point. Or maybe https://github.com/JuliaDiffEq/DifferentialEquations.jl/issues/331 is what you're looking for?

QuantumBits commented 5 years ago

I like your first suggestion! The second suggestion is indeed intriguing... but for the purposes of this question I think it'd be overkill.

So just to clarify your suggestion, I should do something like the following, where I reference a vector p defined in my integrator that holds a list of booleans (assumed all false to start) that remembers whether each condition has been met:

function myCallBack(tk::Float64, ID::Int64)

    return ContinuousCallback(
        function condition(u, t, integrator)
            # integrator.p is a Vector of Bool types
            return integrator.p[ID] ? Inf : veryExpensiveConditionFunction(t, tk)
        end,
        function affect!(integrator)
            # Turn off the condition once affect! has been called
            integrator.p[ID] = true
            # My affect function
            someAffectFunction(integrator)
        end
    )
end

Unsure if eventually there couldn't be a more(?) proper way to remove a Callback from a CallbackSet, but this is more than adequate to solve my problem. Thanks!!

ChrisRackauckas commented 5 years ago

We could add an extra boolean in the callback for handling this.

QuantumBits commented 5 years ago

That sounds like a better solution than trying to keep track of things in the integrator. I'll see if I can figure out how to do that and then make a pull request 😃