Using external variable as a variable inside WhenEvent & NDSolve

7

3

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?

jtbandes

Posted 2012-12-13T02:06:27.293

Reputation: 1 332

Answers

12

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

Posted 2012-12-13T02:06:27.293

Reputation: 31 084

As a general technique, would WhenEvent[event,action]/.event->PUT YOUR EVENT HERE work? – Chris K – 2016-03-07T03:25:20.920

My personal favourite is a replacement rule but the other way around, var /. var_ :> Reap[... – Rojo – 2012-12-13T08:36:37.430