6
$\begingroup$

I frequently use StepMonitor to keep an eye on NDSolve while it's working. Usually it does the job just fine. Recently however, I've been working with sets of coupled ODEs that take several hundred iterations to solve. The business of oversight then gets rather tedious as you get hundreds of lines of intermediate values.

Is there a way to keep the output manageable by telling StepMonitor to monitor only every $n$-th step?


Simple Example

Even an ODE as simple as

n = 1;
NDSolve[{y'[x] == y[x], y[0] == 1}, y, {x, 0, 5},
    StepMonitor :> Print[{n, x, y[x]}] n++] // Timing

results in 53 lines of intermediate results. What if I want, say, just 5 lines? Also, performing so many intermediate evaluations clearly introduces significant overhead. The above takes 0.002964 seconds to evaluate. By comparison, the same command without any monitoring,

NDSolve[{y'[x] == y[x], y[0] == 1}, y, {x, 0, 5}] // Timing

takes 0.00095 seconds, i.e. less than a third of the time.

$\endgroup$
4
  • 1
    $\begingroup$ I'm unclear as to why you'd want to monitor $n$-th steps. Recall that the built-in integrators are adaptive, so it is possible that, say, the 50th step, would only be a tiny fraction of the integration interval. $\endgroup$ Commented Mar 21, 2017 at 3:33
  • 1
    $\begingroup$ @J.M. Exactly! That's also part of what I want to learn from monitoring during evaluation. If NDSolve encounters a stiff system or a singularity, I'd know about sooner rather than later. Waiting for NDSolve to eventually fail due to vanishing step size wastes quite a bit of time, especially for more involved computations. $\endgroup$
    – Janosh
    Commented Mar 21, 2017 at 8:27
  • 2
    $\begingroup$ On my machine, calling StepMonitor in itself adds about 10^-5 sec. per step (plus whatever time it takes to compute the monitor expression). If the RHS of an ODE $d{\bf X}/dx = RHS({\bf X}, t)$ evaluates quickly like the OP's example, this is longer than the time to compute the step (which can also depend on the Method). This makes it appear in the OP's example as if StepMonitor slows NDSolve down by a factor of 3-4, whereas I'm suggesting it be viewed as adding a certain amount. (This is aside from the slight slowness and excessive output of Print.) $\endgroup$
    – Michael E2
    Commented Mar 21, 2017 at 12:54
  • $\begingroup$ @MichaelE2 Good point. I may have jumped the gun there. :) $\endgroup$
    – Janosh
    Commented Jun 21, 2017 at 15:41

4 Answers 4

6
$\begingroup$

If only you need to reduce the number of printed lines, this Prints once every 10 instances.

n = 1;
NDSolve[
  {y'[x] == y[x], y[0] == 1}
  , y
  , {x, 0, 5}
  , StepMonitor :> If[
    Mod[++n, 10] === 0
    , Echo[
     TemplateApply[StringTemplate["n:``, x:``, y:``", {n, x, y[x]}]]
     , "Monitor"]]
  ] // Timing

Mathematica graphics

But the idea of filling the screen with updates seems sub-optimal. You could use a Dynamic update of a single line. To reduce the overhead, this limits the update to once per second.

AbsoluteTiming[
 DynamicModule[{n = 1, status},
  Dynamic[Refresh[Row@status, UpdateInterval -> 1]];
  sol = NDSolve[{y'[x] == y[x], y[0] == 1}, y, {x, 0, 5}, 
    StepMonitor :> (status = {n += 1, x, y[x]})];]]

Obviously you could combine the two solutions.

EDIT

All that said, after a better answer by @george2079 it seems the correct tool to use is Monitor.

$\endgroup$
2
  • $\begingroup$ Is there any particular reason to prefer Echo over Print? It seems to increase computation time. $\endgroup$
    – Janosh
    Commented Mar 20, 2017 at 17:08
  • $\begingroup$ @Casimir, Mainly a new toy, it has labels so you can distinguish outputs from different sources, it can be used with non-string expressions. Probably in this case may make sense to define an EchoFunction. ef = Echo[TemplateApply[StringTemplate["n:``, x:``, y:``", #]], "Monitor"] & and then StepMonitor :> If[ Mod[++n, 10] === 0 , ef[{n, x, y[x]}]] $\endgroup$
    – rhermans
    Commented Mar 20, 2017 at 17:18
6
$\begingroup$

if you just want a status monitor you can do like this:

Monitor[NDSolve[{y'[x] == x, y[0] == 1}, y, {x, 0, 5}, 
  StepMonitor :> (p = x; ++n)], {n, p}]

this prints every step but wont clutter your screen.

There is inevitably an overhead, but typically in a real case where you care about monitoring results it will be small compared to the function eval.

this is useful sometimes too:

p = 0; ProgressIndicator[Dynamic[p], {0, 5}]
NDSolve[{y'[x] == f[x], y[0] == 1}, y, {x, 0, 5}, 
 StepMonitor :> (p = x)]

Note if NDSolve needs to do recursion then the -x- val wont accurately reflect progress however.

$\endgroup$
2
$\begingroup$

As an alternative suggestion, I usually use Dynamic to monitor progress. It seems to add minimal overhead (beyond the overhead of StepMonitor). The use of InputForm lets one see small step sizes (how many digits are changed).

Dynamic@ InputForm@ foo
NDSolve[sys, y, {x, 0, 5}, StepMonitor :> (foo = x)]

Example from NDSolve returns solution with single point domain:

Here InputForm is not really needed or helpful since arbitrary precision numbers are used. Their digits are printed out to their precision by default.

time = 0; steps = 0;
Dynamic@Column@{time, steps}  (* monitor progress; it's educational *)

steps = 0;
{sol} = NDSolve[{Derivative[1][y][t] == (-2 E^(1/5 y[t]) + 1)/(-600 E^(1/5 y[t]) + 
       20338 + (2/10) t), y[101690] == 0}, y, {t, 0, 101690}, 
  MaxSteps -> 100000, WorkingPrecision -> 150, PrecisionGoal -> 8, 
  AccuracyGoal -> 8, StartingStepSize -> 0.001, 
  StepMonitor :> (time = t; steps++)]
$\endgroup$
1
$\begingroup$

Picking up on J.M.'s comment regarding adaptive step sizes, we can have StepMonitor only output a predetermined number of times while still keeping an eye on how many steps a given interval required:

runner = 1; counter = 0;
sol = NDSolve[{x'[t] == -Exp[x[t]], x[0] == 1}, x, {t, 0, 10}, 
  StepMonitor :> 
   counter++ If[t > runner, Print[{counter, t, x[t]}]; runner++]]
Plot[x[t] /. sol, {t, 0, 10}]

which gives

{50,1.03056,-0.335356}

{65,2.0474,-0.881814}

{74,3.01655,-1.21919}

{81,4.04807,-1.48522}

{87,5.19606,-1.71631}

{91,6.06848,-1.86196}

{95,7.22721,-2.0275}

{98,8.09626,-2.13584}

{101,9.25289,-2.26392}

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.