8

I'm trying to use the new WhenEvent functionality of NDSolve in Mathematica 9, in order to perform an action when a variable in the system reaches a certain value. I would like this variable to come from an external variable, like so:

var = y; (* this could come from somewhere else *)
list = {};

(* keep track of values of x for which y[x] == 1 *)
NDSolve[{y''[x] == -y[x], y[0] == 1, y'[0] == 0, 
  WhenEvent[Round[var[x], 0.1] == 1, AppendTo[list, x]]},
  y, {x, 0, 10}]

But this produces the errors

NDSolve::nbnum1: The function value Round[y[0.],0.1]==1 is not True or False when the arguments are {0.,1.,0.,0.,-1.}. >>

NDSolve::nbnum1: The function value Round[y[0.000143737],0.1]==1 is not True or False when the arguments are {0.000143737,1.,-0.000143737,-0.000143737,-1.}. >>

NDSolve::nbnum1: The function value Round[y[0.000287473],0.1]==1 is not True or False when the arguments are {0.000287473,1.,-0.000287473,-0.000287473,-1.}. >>

General::stop: Further output of NDSolve::nbnum1 will be suppressed during this calculation. >>

How can I use an "external" variable to specify which variable to watch in WhenEvent?

Michael E2
  • 235,386
  • 17
  • 334
  • 747
jtbandes
  • 1,422
  • 1
  • 11
  • 20

1 Answers1

13

The problem is that WhenEvent has the attribute HoldAll, so var is never translated to y. Here's a standard trick to inject the actual value of var into the code:

var = y;
Reap[NDSolve[{y''[x] == -y[x], y[0] == 1, y'[0] == 0, 
  WhenEvent[Round[#[x], 0.1] == 1, Sow[x]]},
  y, {x, 0, 10}]]&[var]

(* Out: {{{y->InterpolatingFunction[{{0.,10.}},<>]}},{{5.99762}}} *)

Also, I replaced your AppendTo with a Reap/Sow combination, which is generally a bit more efficient.

There are other ways to inject the variable value into the code as well. For example:

var = y;
Reap[NDSolve[{y''[x] == -y[x], y[0] == 1, y'[0] == 0, 
  WhenEvent[Round[var2[x], 0.1] == 1, Sow[x]] /. var2 -> var},
  y, {x, 0, 10}]]

The ever popular Evaluate (although placement of the Evaluate is a bit tricky):

var = y;
Reap[NDSolve[{y''[x] == -y[x], y[0] == 1, y'[0] == 0, 
  WhenEvent[Evaluate[Round[var[x], 0.1] == 1], Sow[x]]},
  y, {x, 0, 10}]]

And, my personal favorite:

var = y;
With[{var=var},
  Reap[NDSolve[{y''[x] == -y[x], y[0] == 1, y'[0] == 0, 
    WhenEvent[Round[var[x], 0.1] == 1, Sow[x]]},
    y, {x, 0, 10}]]]
Mark McClure
  • 32,469
  • 3
  • 103
  • 161
  • My personal favourite is a replacement rule but the other way around, var /. var_ :> Reap[... – Rojo Dec 13 '12 at 08:36
  • As a general technique, would WhenEvent[event,action]/.event->PUT YOUR EVENT HERE work? – Chris K Mar 07 '16 at 03:25