Monitor the computation only if a certain Option is given to the function

6

2

Suppose I have the function

heavyFunction[x_]:=Module[{n},Do[Pause[0.1],{n,100}]]

representing some complicated and time-demanding computation. I want in some circumstances to Monitor the progress of this function. Other times I don't, for example because heavyFunction is used by other computationally-heavy functions and I'd rather monitor the progress of those instead.

I'm also not looking for a solution like the simple

Monitor[heavyFunction[whatever],n]

because the quantity I'd use to monitor the progress can be not as simple as the loop index, and because the computation of such a quantity may depend on local variables defined in a Module inside the function.

The naive solution I'd have liked to use is something like

Options[heavyFunction]={monitor->True};
heavyFunction[x_,OptionsPattern[]]:=Module[{n},
  If[OptionValue@monitor,Monitor[#,n],Identity]&@
    Do[Pause[0.1],{n,10}]
  ]

but it doesn't show the progress.

How can this be achieved?

glS

Posted 2015-08-30T18:41:59.070

Reputation: 7 002

Answers

9

In general, it looks like you need to separate the process of execution and monitoring. The only general way I see to do this is to make your function an object (a pair of functions sharing a mutable state), so that one function would execute the code, while the other would report the internal state to the user.

Here is one possibility:

obj = 
  Module[{n},
    <|
      "execute" :> Do[Pause[0.1], {n, 100}],
      "monitor" :> n
    |>
  ];

and then

Dynamic[obj["monitor"]]

to monitor and

obj["execute"]

to execute the code. You will notice that the output of Dynamic[..] will reflect the changes.

In this method, one has to be careful with the possible leaking of local symbols n. However, when one sets $HistoryLength to some small finite number, and provided that these variables are not captured in some dynamic interfaces still being in use, these symbols are garbage-collected once no longer referenced. Still, this is something to watch out for.

The good part of this method is that the code execution and monitoring aspects are completely decoupled, so you could also monitor these variables when your function is being used inside some other code.

Leonid Shifrin

Posted 2015-08-30T18:41:59.070

Reputation: 108 027

Brilliant! I was working on the idea of having an "implementation" function containing the heavy-lifting code that would be called by two wrapper functions of the kind I outlined in an answer above. Your solution is miles ahead of what I had. I'm glad the question caught your eye. (+1) – MarcoB – 2015-08-30T20:08:11.987

@MarcoB Thanks! Glad you liked it. I hesitated a bit to post it, since I am not fully convinced that the issue with leaking of local symbols can be always easily avoided. But objectively, the general formulation of the problem requires state - sharing by more than one function, so there is no way to avoid this issue in the general formulation of this problem. The problem caught my eye also because I do need similar things myself. – Leonid Shifrin – 2015-08-30T20:15:05.930

4

The problem is that the Do loop is computed before being passed to Monitor. Use HoldAll (or HoldFirst) with Function to achieve your desired goal:

Options[heavyFunction] = {monitor -> True};
heavyFunction[x_, OptionsPattern[]] := Module[{n},
  Function[code,
    If[OptionValue@monitor, Monitor[code, n], code],
    HoldAll]@ Do[Pause[0.1], {n, 10}]]

Michael E2

Posted 2015-08-30T18:41:59.070

Reputation: 190 928

exactly what I was looking for. Thanks! – glS – 2015-08-30T19:10:00.533

@glance You're welcome. I'm thinking that you might want TrueQ@OptionValue@monitor in the If statement, in case someone passes a bogus option value that is neither True nor False. In that case, the If statement won't execute either code. (Thanks for the accept, but you might want to wait a while. There are others who might try to come up with better answer, if the question wasn't marked "answered." ) – Michael E2 – 2015-08-30T19:16:30.370

thanks for the tip, I didn't think about that. I'll wait a little longer before accepting then :) – glS – 2015-08-30T19:19:29.393

@MichaelE2 very nice indeed. I had overlooked the need for a Hold attribute when trying to fix OP's code, so I took a different route, but this should do quite nicely (+1). – MarcoB – 2015-08-30T20:12:13.763

1

You could use two alternative definitions of your heavyFunction instead of trying to pack all definitions into one. The first definition (monitored) will be used when the option monitor -> True is included in the call; the second definition (not monitored) applies to calls to this function without options.

Clear[heavyFunction]

heavyFunction[x_, monitor -> True] := Module[
   {n},
   Monitor[Do[Pause[0.1], {n, 10}], n]
  ]

heavyFunction[x_] := Module[
   {n},
   Do[Pause[0.1], {n, 10}]
  ]

MarcoB

Posted 2015-08-30T18:41:59.070

Reputation: 53 573

I sow this coming :). That is certainly a solution, but I really hoped not to have duplicates of the same exact code. Apart from it being rather inelegant, it's also harder to mantain. – glS – 2015-08-30T19:02:11.437

Of course I understand your concern with code duplication. You might circumvent that by having the heavy-lifting code in a third "implementation function" that is called by either one of the heavyFunction wrappers shown above. @Leonid presents a much more refined execution of this concept in his answer below; as he says, execution and monitoring had best be kept separate. – MarcoB – 2015-08-30T20:05:14.020