I am trying to use 2 callbacks when solving a differential equation.
I am trying to solve for a pendulum, so I have a cyclic coordinate that goes from (-\pi, \pi). So when the coordinate goes above \pi I want it to shift by -2\pi, and when it goes below -\pi I want it to shift by +2\pi.
I tried to have two callbacks:
function condition_pi(u, t, integrator)
u[1] - π
end
function condition_min_pi(u, t, integrator)
u[1] + π
end
function affect_pi!(integrator)
integrator.u[1] = integrator.u[1] - 2π
end
function affect_min_pi!(integrator)
integrator.u[1] = integrator.u[1] + 2π
end
cb_pi = ContinuousCallback(condition_pi, affect_pi!)
cb_min_pi = ContinuousCallback(condition_min_pi, affect_min_pi!)
cb_set = CallbackSet(cb_pi, cb_min_pi)
This does not work. I think the reason is that when the solution hits x=\pi, it sends it back by -2\pi, which then makes it less than -\pi, and then it wants to send it forward by +2\pi, and there is an infinite loop.
So the question is, how can I implement these boundary conditions?
Seems a lot easier to allow your angles \theta to be arbitrary, write your equations in terms of functions like \sin(\theta) that are insensitive to shifts of 2\pi, and then transform the coordinates modulo 2\pi afterwards if you like (e.g. for plotting solutions).
1 Like
AFAIK, you need to set affect_neg
argument to nothing
.
I know. But I have other reasons that I want the coordinate to be between (-\pi, \pi). I have some functions that cannot be defined beyond this range.
What is affect_neg
? I couldn’t find it in the documentation.
If you can change your calculation domain to go from [0, 2π) instead, then you can simply do
u -> mod(u[1], 2π)
This would be a pain for me since I have some other complicated functions that are defined for the range (-\pi, \pi).
Callbacks have both increasing and decreasing crossing affect
s. You don’t want negative crossing. By default, they are both set to the same affect
.
Would this be numerically accurate enough for your usecase?
u -> mod(u[1] + π, 2π) - π
Could you elaborate? I don’t understand what you mean.
Nice idea!
I will try, though I need very good numerical accuracy.
Thanks!
That actually works:
Here is the code:
function affect_pi!(integrator)
integrator.u[1] = integrator.u[1] - 2π
end
function affect_pi_neg!(integrator)
integrator.u[1] = integrator.u[1]
end
function affect_min_pi!(integrator)
integrator.u[1] = integrator.u[1]
end
function affect_min_pi_neg!(integrator)
integrator.u[1] = integrator.u[1] + 2π
end
cb_pi = ContinuousCallback(condition_pi, affect_pi!, affect_pi_neg!)
cb_min_pi = ContinuousCallback(condition_min_pi, affect_min_pi!, affect_min_pi_neg!)
Why not just call ũ = mod2pi(u[1] + π) - π
before calling those functions? Why does the ODE solver have to know that you are interpreting a coordinate cyclically?
2 Likes
That’s also a nice idea. I tested this, but it works less well than using the callback. Perhaps it’s an issue of the numerical accuracy of mod?
I am using ArbFloat
to solve to try to achieve good accuracy.