I loop over some systems of DAEs with NDSolve
; some solutions are periodic, some are not. When integrating with NDSolve
, I integrate on {t,-1000,1000}
starting from t=0
because I don't want to compute only one part of the non-periodic solutions. The problem is that when orbits are periodic, it computes the solution on several periods. To avoid this, I used EventLocator
with the following testing function:
isPeriodic[vals_, ic_] := (Norm[vals - ic] < .02) && (t < -1 || t > 1)
The 0.02
instead of 0
is due to numerical inaccuracies which makes the solution not perfectly periodic, but "almost". The condition t < -1 || t > 1
is used to prevent NDSolve
from stopping immediately after starting ($f(0.000001)$ and $f(0)$ would trigger the periodicity condition since $\|f(0.000001)-f(0)\|$ is very likely to be lower than $0.02$).
This works pretty well, except that I it returns the solution over two periods: $f(t)==f(0)$ is checked both for positive and negative t
.
I could of course split the interval in two and keep only one part, but for some reasons I'd prefer to avoid that; additionally, it would avoid computing the periodic solution on two periods.
Full code, which may seem sligthly complex but is not really:
(* defines variables and system of DAE *)
vars = Array[s, 2];
varst = Through[vars[t]];
ic = {56.30672012303853`, 1.9416110387254666`};
sysDAE0 = {{0.2763932022500211` Sin[
0.8090169943749473` (-s[1][t] + s[2][t])] Sin[
0.3090169943749473` (s[1][t] + s[2][t])] +
0.7236067977499788` Sin[
0.3090169943749473` (-s[1][t] + s[2][t])] Sin[
0.8090169943749473` (s[1][t] + s[2][t])]} == {0},
Derivative[1][s[1]][t]^2 + Derivative[1][s[2]][t]^2 ==
1.`, {s[1][0], s[2][0]} ==
ic, {Derivative[1][s[1]][0],
Derivative[1][s[2]][0]} == {-0.8903443552034469`,
0.45528774325404187`}};
(* test of periodicity *)
isPeriodic[vals_,
ci_] := (Norm[vals - ci] < .02) && (t < -1 || t > 1)
(* solves the system *)
{solDAE} =
NDSolve[sysDAE0, vars, {t, -1000, 1000},
Method -> {"EventLocator", "Event" -> isPeriodic[varst, ic],
"EventAction" :> Throw[Null, "StopIntegration"]}]
sol[t_] := Through[(vars /. solDAE)[t]];
{tmin, tmax} = Flatten@solDAE[[1]][[2, 1]];
And the output, which is a closed curve (periodicity) plotted twice:
ParametricPlot[sol[t], {t, tmin, tmax}]
Question How to use events to compute only one period of the solution? One possibility would be to stop integration in both positive and negative t
when the first periodicity condition is found, but "StopIntegration"
does not do that (luckily).
Another possibility is to introduce a boolean boolPeriodic
, turning it to True
when the first periodic event is detected, and adding this boolean as an event to kill immediately the integration in the other direction (hence, avoiding computing a second time the period). But it does not help: it seems the EventLocator
method is run in parallel for positive t
and negative t
. This is confirmed by adding a Print
in the EventAction
:
boolPeriodic = False; {solDAE} =
NDSolve[sysDAE0, vars, {t, -1000, 1000},
Method -> {"EventLocator",
"Event" -> isPeriodic[varst, ic] || boolPeriodic,
"EventAction" :> Throw[boolPeriodic = True;Print["periodic"], "StopIntegration"]}]
sol[t_] := Through[(vars /. solDAE)[t]];
(* prints TWO "periodic" *)
WhenEvent[]
superseded `"EventLocator" in V9 -- what version are you using? $\endgroup$WhenEvent[]
but did not manage to make it work. I'm going to add the code I tried (but I don't think it will change something). $\endgroup${t, 0, 1000}
(or{t, 0 Infinity}
), won't that give you what you want? $\endgroup$t == 0
in this case). So the forward integration0 < t < 1000
captures one period and the backward integration0 > t > -1000
get another period. $\endgroup${t, 0, 1000}
. $\endgroup$