I am interested in ensuring that certain outputs in an ODE problem reach a steady state except for a few because they follow an oscillatory behavior. I am familiar with the SteadyStateProblem
but in my case does not work because of the oscillations of certain variables. Is there any way around it? Thank you!
You can use the TerminateSteadyState
callback with a custom test
function implementing your termination condition: Steady State Callbacks · DiffEqCallbacks.jl
Use it as follows:
using OrdinaryDiffEq, DiffEqCallbacks
# define my_test_function, abstol, reltol
prob = ODEProblem(...)
callback = TerminateSteadyState(abstol, reltol, my_test_function)
sol = solve(prob, alg; callback)
For more details about callback usage, see Event Handling and Callback Functions · DifferentialEquations.jl
Thank you for your answer! I want to make sure that my_test_function is correct:
abstol = 1e-8;
reltol = 1e-6;
function test_function(integrator, abstol, reltol, min_t)
testval = SciMLBase.get_du(integrator);
testval = testval - integrator.u;
(abs(testval[68] + testval[73]) < abstol) | (abs(testval[68] + testval[73]) < reltol*abs(integrator.u[68] + integrator.u[73]));
(abs(testval[76]) < abstol) | (abs(testval[76]) < reltol*abs(integrator.u[76]));
(abs(testval[34] + testval[65]) < abstol) | (abs(testval[34] + testval[65]) < reltol*abs(integrator.u[34] + integrator.u[65]));
(abs(testval[3] + testval[4] + testval[54]) < abstol) | (abs(testval[3] + testval[4] + testval[54]) < reltol*abs(integrator.u[3] + integrator.u[4] + integrator.u[54]));
(abs(testval[35]) < abstol) | (abs(testval[35]) < reltol*abs(integrator.u[35]));
(abs(testval[28] + testval[30]) < abstol) | (abs(testval[28] + testval[30]) < reltol*abs(integrator.u[28] + integrator.u[30]));
end
cbT = TerminateSteadyState(abstol, reltol, test_function);
Essentially I changed the allDerivPass
function which is the default. Do you think that is correct?
allDerivPass
is a great starting point, but I’m not sure your modification is correct.
This line should only be included if your problem is a discrete problem, i.e., if you’re studying a discrete dynamical system and using DiscreteProblem
instead of ODEProblem
in your code. Is this your use case?
Only the last of these lines will be the return value from the function and determine when your integration terminates. The other lines are discarded. I assume you want to take the union of these conditions and terminate if all of them are true. In that case, I would write your function something like this (assuming you’re working with an ODEProblem, not a DiscreteProblem):
function checktol(testval, u, indices, abstol, reltol)
val = abs(sum(testval[i] for i in indices))
scale = abs(sum(u[i] for i in indices))
return (val < abstol) | (val < reltol * scale)
end
function test_function(integrator, abstol, reltol, min_t)
if (min_t !== nothing) && (integrator.t < min_t)
return false
end
testval = SciMLBase.get_du(integrator)
return all(
checktol(testval, integrator.u, indices, abstol, reltol)
for indices in [[68, 73], [76], [34], [3, 4, 54], [28, 30]]
)
end
I’ve simplified allDerivPass
somewhat in that you can only pass scalars as abstol
and reltol
here. If you want to pass arrays to specify different tolerances for different components, you need to make a small modification to the iteration. You can look at allDerivPass
to figure it out (they use Iterators.cycle
to allow both scalars and arrays).
Note that semicolons are not needed in Julia.
(Also note that I had pasted the wrong link at the end of my previous post. See the updated version.)
Thank you very much for your response! The code works smoothly. I also compared its output with the original way I was doing with the SteadyStateProblem
and both give almost identical results but it is much faster now.