18

Consider the following simple example:

sol = NDSolve[ {x''[t] == 1, x'[0] == 0, x[0] == 0,
               WhenEvent[Abs[x[t]] > 1 && x[t] > 2, {x'[t] -> -3 x'[t]}]}, 
               x[t], {t, 0, 4}];
Plot[x[t] /. sol[[1]], {t, 0, 4}]

Mathematica graphics

Clearly WhenEvent[] is missing the x[t] > 2 occurrence. This may happens (my interpretation) if the event detection routine checks for the occurrence of the first part of the AND clause and then for the other AND components, instead of checking them as an OR event trigger. As the Abs[x[t]] > 1 event isn't fired again between 1 and 2, the rest of the AND clause doesn't get tested again.

In testing this interpretation, we can reverse the clause:

sol = NDSolve[{x''[t] == 1, x'[0] == 0, x[0] == 0,
              WhenEvent[x[t] > 2 && Abs[x[t]] > 1, {x'[t] -> -3 x'[t]}]}, 
               x[t], {t, 0, 4}];
Plot[x[t] /. sol[[1]], {t, 0, 4}]

Mathematica graphics

which now works as expected. Of course this isn't a proof.

But the strange thing is that reverting to the first version, but encapsulating the clause in a function, also works as expected:

clause[p_] := Abs[p] > 1 && p > 2
sol = NDSolve[{x''[t] == 1, x'[0] == 0, x[0] == 0,
              WhenEvent[clause[x[t]], {x'[t] -> -3 x'[t]}]}, x[t], {t, 0, 4}];
Plot[x[t] /. sol[[1]], {t, 0, 4}]

Mathematica graphics

So, what is the moral? Always put your WhenEvent[] clause as a function?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Dr. belisarius
  • 115,881
  • 13
  • 203
  • 453

1 Answers1

13

From WhenEvent:

Examples of events and how $t_a$ is determined include:
...
f == 0 && pred -- f crosses zero and pred is True

I believe that the same special case is applied to And for forms f > 0 && pred, etc.

So

WhenEvent[Abs[x[t]] > 1 && x[t] > 2, {x'[t] -> -3 x'[t]}]

means the event will be triggered when Abs[x[t]] crosses 1 and x[t] > 2, which is never.

Next,

 WhenEvent[x[t] > 2 && Abs[x[t]] > 1, {x'[t] -> -3 x'[t]}]

means the event will be triggered when x[t] crosses 2 and Abs[x[t]] > 1, which will be whenever x[t] crosses 2.

Finally, both

WhenEvent[clause[x[t]], {x'[t] -> -3 x'[t]}]
WhenEvent[Null; Abs[x[t]] > 1 && x[t] > 2, {x'[t] -> -3x'[t]}]

do not have the head And, so the special case is not applied; they will trigger an event whenever they change from False to True.

Michael E2
  • 235,386
  • 17
  • 334
  • 747
  • 3
    I've read the docs, but I understood them as a simple application of logic. Your interpretation (probably correct) seems dangerous because you have to be sure to cover all the possible timelines with OR clauses ... or just encapsulate the whole thing as a function which is probably worse from the performance POV – Dr. belisarius Dec 25 '13 at 04:23
  • 2
    @belisarius It could be that only crossings, such as f == 0, were meant to be special-cased, and you've found a bug. Clearly f == 0 has to be a special case, and one would want to allow conditions to be placed on which crossings trigger events. But && here seems to behave logically like "when", and that's unintuitive. – Michael E2 Dec 25 '13 at 04:42
  • With Equal[ ] there are not ambiguities. It will trigger the event when it's true, so everything is OK. But in the case of inequalities (or other conditions) the "condition true" zone wont (in general) coincide with the rest of the testing. So yes, it is at least confusing. – Dr. belisarius Dec 25 '13 at 04:48
  • 1
    @belisarius Practically speaking, the Equal statements are never true. The solver looks for a change in sign (or a crossing). Try it with clause[p_] := p == 2 && Abs[p] > 1. – Michael E2 Dec 25 '13 at 05:01
  • 1
    There is a saying in Spanish. I don't really know if it makes sense in English "Among firefighters we will not step on the hose." – Dr. belisarius Dec 25 '13 at 05:13
  • @belisarius I get it, but I doubt I'll be repeating it. :) – Michael E2 Dec 25 '13 at 05:14
  • Oh! It doesn't have that meaning in Spanish :) – Dr. belisarius Dec 25 '13 at 05:15
  • 2
    @belisarius I didn't mean that either. I just meant I don't think my friends will understand it. :) – Michael E2 Dec 25 '13 at 05:16
  • 1
    Thank you. This WhenEvent issue bothers me all the time. However, why is WhenEvent designed in this way? The logic behind it is clear but very counterintuitive to me. – luyuwuli Jan 08 '15 at 04:55
  • I dont't think wheather the Head is And maters. Check this: Reap[NDSolve[{y'[x] == y[x] Cos[x + y[x]], y[0] == 1, WhenEvent[(Null; (y[x] < 0.3) && (y'[x] == 0)), Sow[{x, y[x]}]]}, y, {x, 0, 30}]] This can only work if reverse the order. – luyuwuli Jan 08 '15 at 07:25
  • @luyuwuli I'm unclear what your example is meant to show and what you mean by the reverse order. The head of the expression (Null; (y'[x] == 0) && (y[x] < 0.3)) is CompoundExpression, so WhenEvent processes the code as a simple logical statement and looks for it to change truth value at consecutive sample points. But the logical expression itself is False at all sample points. (It's highly unlikely for y'[x] == 0 to be True with floating point numbers.) So no events, which is what the output shows. The same happens if the order of the arguments in the And statement is reversed. – Michael E2 Jan 08 '15 at 14:24
  • 1
    @luyuwuli Why is it designed this way: First, in an approximate solver, an equation f[x] == 0 is almost never satisfied. So we certainly do not want f[x] == 0 && pred treated strictly logically. Second, we sometimes want to specify the direction of crossing (e.g., pos -> neg), and Wolfram chose to use < and >. The old "EventLocator" method had a "Direction" option, but WRI chose to discard that. They might have chosen a syntax like WhenEvent[{event, pred}, action] with List instead of And, but they didn't. Basically their choices reduce typing at some expense of clarity. – Michael E2 Jan 08 '15 at 14:48
  • @MichaelE2 I run my code again; it doesn't behave as I said in the comment; I probably messed up the code at that time, sorry about this. Do you mean by changing the head, Mathematica will treat p1 and p2 equally in the WhenEvent[p1&&p2,...] ? BTW, I prefer the syntax you said in your comment, which is more clear. – luyuwuli Jan 08 '15 at 15:51
  • @luyuwuli "Do you mean by changing the head, Mathematica will treat p1 and p2 equally in the WhenEvent[p1&&p2,...]?" Yes, and it won't do any special processing of equations or inequalities. It strictly looks for a change from False to True. Compare with WhenEvent[p1 && p2 == True,...] (head is Equal) for y'[x] == 0 && y[x] < 0.3 in your ODE. BTW, I like my syntax, too. It seems consistent with optimization functions, e.g. {fn, constraints}, too. Maybe they will add that syntax someday. – Michael E2 Jan 08 '15 at 16:16
  • @MichaelE2 Now I get it. Thank you very much:) – luyuwuli Jan 09 '15 at 01:17