2
$\begingroup$

Mathematica provides $Post, whose value, if set, is applied to every output expression.
In particular, if one sets

$Post = (f; #)&

then at the completion of any cell evaluation (e.g., one started by doing Shift+Enter on an Input cell), the expression f will be evaluated.

My question

Is there a simple implementation of an analogous functionality for selection evaluations ?

The difference is that a selection evaluation may involve a queue of many cell evaluations, but I only want f to be evaluated once, immediately after the last cell in the queue has finished evaluating. I.e., when the evaluation queue becomes empty.

I know of one way to do this (and it is works perfectly well for my usage) but my solution seems overly tricky. (A nice thing about writing the A for this Q is that I'll never again have to spend excessive time figuring out the code before making minor edits.) Moreover, my code
uses $Pre. This is messy, because I'm also using $Pre elsewhere in the same software, so I already need to compose several different values for $Pre.

My suspicion is that there exists a built-in (hidden) function for detecting whether the evaluation queue is empty or not, because it is sort of obvious.... If the notebook window title says "Running..." then the evaluation queue is not empty. A function, let's call it RunningQ, that would would tell me if the notebook was running would suffice because a ScheduledTask could be used to evaluate f as soon as RunningQ is detected as having changed from True to False.

Perhaps there is something in WSTP that does what I want? I have no experience in that area, so perhaps this is a completely trivial question for someone else who does (which is why I've tagged it).

An alternative presentation of the same question

Suppose I want to be notified by a bell when a long evaluation in my notebook finishes running. Suppose also that I am not aware of this answer by "rm -rf" for that problem, so instead I use the following code:

$Post = (EmitSound[SoundNote["G"]]; #)&

Now, every time I do Shift+Enter on a cell, I get a bell notification at the end of the cell evaluation. I can put up with this because I sometimes find it useful if a cell evaluation takes more than a minute or so.

But then I discover why this $Post idea is not OK: $Post is applied post cell evaluation not post selection evaluation. If, for example, I select 10 consecutive cells that each evaluate Pause[0.1], then when I evaluate this selection I get 10 bells rather than just one bell at the end of the selection evaluation. Since I often use a mouse-drag to select 100s of cells to be evaluated as one selection, this $Post idea is of no use to me.

My question

How can I get just one bell to ring at the end of a selection evaluation?

For my application, it is important that no additional user-interaction be required, such as clicking a button, doing Shift+Enter on something in another notebook, selecting a menu item, etc.

BTW, after you try the above code, normal evaluation (without a bell) can be restored by setting $Post = ..

$\endgroup$
  • 1
    $\begingroup$ Related: Automatic e-mail/text message when a calculation has finished? The idea in that thread is to first queue the evaluations you want to run and then queue another evaluation on top of those that will alert you when it gets evaluated, i.e. when all evaluations before it have completed. $\endgroup$ – C. E. Jul 13 at 6:02
  • $\begingroup$ @C.E. Thank you for that reference. I've used it to update and better explain my answer. $\endgroup$ – Andrew Norton Jul 15 at 4:21
  • $\begingroup$ Why can't you simply add your EmitSound[SoundNote["G"]]; as the final cell in the queue? You can even do it in a separate Untitled window if you don't want to litter your main notebook, and even after you've already started your 100 cells of main calculation. $\endgroup$ – Ruslan Jul 15 at 15:47
  • $\begingroup$ If my problem really was about getting a bell notification then the additional step that is required to "click the bell button" (in the nice answer), or to the same effect, Shift+Enter on EmitSound in another notebook, would not matter, and this would be the best solution. However, that is not my usage, and the extra step does matter. $\endgroup$ – Andrew Norton Jul 15 at 16:42
2
$\begingroup$

Perhaps you can do something like the following:

CurrentValue[EvaluationNotebook[], NotebookEventActions] = {
    {"MenuCommand","HandleShiftReturn"} :> Replace[
        Cells[NotebookSelection[EvaluationNotebook[]], CellStyle->"Input"],
        {___, last_} :> SetOptions[
            last,
            CellEpilog :> (Beep[]; SetOptions[EvaluationCell[], CellEpilog->None])
        ]
    ],
    PassEventsDown->True
};

This adds a CellEpilog to the last input cell so that it beeps when finished, and then removes the epilog. You can enhance it by appending code to the existing epilog, and then removing that code.

$\endgroup$
  • $\begingroup$ I like that! One can also replace Beep[] by, say, f1 for cells of style1 and f2 for cells of style2. E.g., in my application, styles "TeX: Text"and "Input". With 12 styles and Pause[] you can play a tune :) I'll wait for a day or two before accepting to see if anyone can answer with a one-liner. $\endgroup$ – Andrew Norton Jul 21 at 1:42
1
$\begingroup$

The short answer

$Pre can be used to save the $CompletionTime of the last cell evaluation and a ScheduledTask can be used to monitor how long ago that was. With the help of two logical variables $fDone and $busy, one can thereby deduce if the evaluation queue became empty since the last time the two logical variables were checked by the ScheduledTask. If the queue did become empty, and if f has not already been evaluated, then the code f; $fDone = True is evaluated.

As above, step-by-step

We shall first define a function PreDefn[x_] and then set $Pre = PreDefn; (you can't just do $Pre[x_]:= Module[ ]).

It is best to Clear[$Pre, PreDefn] together, so that we don't accidentally get stuck with $Pre = PreDefn but with no value for PreDefn (which then requires menu Evaluation -> Quit Kernel to get unstuck),

Clear[$Pre, PreDefn]

PreDefn needs to be HoldAll else $Pre will work like $Post,

SetAttributes[PreDefn, HoldAll];

PreDefn[x_]:= Module[{result},
  $fDone = False;
  $busy = True;
  result = x;
  $busy = False;
  $CompletionTime = AbsoluteTime[];
  result]

$Pre = PreDefn;

We now have

 $busy
 (* returns True *) 

which perhaps needs some explaining. The reason why $busy evaluates to True (in the above) is that it was given the value True just before result = x was evaluated (and the input x was $busy) so result gets set to True, which is what is returned.

Since this is getting confusing, let's make a monitor to see what is happening, (BTW, there is a built-in function called Monitorwhich does something similar, but is of no use here.)

monitor := Print["{$TimeSinceCompletion, $fDone ,$busy} = ",
                  {$TimeSinceCompletion = AbsoluteTime[] - $CompletionTime,  
                  $fDone, $busy}]

monitorTask = SessionSubmit[ScheduledTask[monitor, Quantity[1, "Seconds"]]];

While the monitor is running it prints the monitored values once per second in the message window that will automatically pop-up when monitorTask is evaluated (because that is where Print goes to from within a ScheduledTask). We can now see that the value of $busy is (as expected) False all the time, except while doing an evaluation. For example,

Pause[3]

then wait for another 3 seconds or so before stopping the monitor with

TaskRemove[monitorTask];

so as to make it easier to read the message window (which is constantly scrolling with new output once per second while the monitor is running).

So far so good. Now let's redefine our monitor to instead print "Evaluate f now." in the message window at appropriate times.

The plan is to simulate the evaluation queue for a multi-cell selection by pretending to be the queue by doing Shift+Enter on cells one-by-one at a typing speed of about 1 cell/sec. We shall define the simulated evaluation queue to be "empty" if the time elapsed since we last typed Shift+Enter is greater than 2 sec, and the kernel is not $busy. Since we only want to print "Evaluate f now." once, when the queue is first detected as being "empty", we shall set $fDone = True after the Print is done ($fDone = False is reset in PreDefn),

monitor := (
  $TimeSinceCompletion = AbsoluteTime[] - $CompletionTime;
  If[!$busy && !$fDone && $TimeSinceCompletion > 2, 
  Print["$TimeSinceCompletion = ", $TimeSinceCompletion, ": evaluate f now."]; 
  $fDone = True])

Let's also speed up the monitor cycle by so much that it becomes irrelevant to our simulation,

monitorTask = SessionSubmit[ScheduledTask[monitor, Quantity[0.01, "Seconds"]]];

You should now test that the above simulation code works as required by repeatedly evaluating

anything;

Try doing Shift+Enter on anything; about once per second for around 10 seconds, and then stopping. Observe that about 2 seconds later, "Evaluate f now." is printed to the message window.

This code is also working for the real evaluation queue.

For example, select (e.g. by mouse-drag) the following 6 cells (type them in separate Input cells) and evaluate them as one selection to verify that "Evaluate f now." is printed only once, and that $TimeSinceCompletion is 2.0 seconds,

Pause[1.0]

Pause[0.4]

Pause[0.3]

Pause[1.2]

Pause[0.2]

Pause[0.5]

The code is also working for fast calculations (again in separate Input cells),

1;
2;
3;
4;

If we are done with simulating, then our 2 second definition for an empty queue can now be tuned to make the code a little more responsive. For an "i7" processor a value of

$EmptyByDefn = 0.1

seems perfectly safe, in the sense that we will almost certainly never trigger a false "Evaluate f now." Putting it all together, here is the answer we wanted:

$Pre =.
Clear[PreDefn]

Clear[PreDefn]
SetAttributes[PreDefn, HoldAll];
PreDefn[x_]:= Module[{result},
  $fDone = False;
  $busy = True;
  result = x;
  $busy = False;
  $CompletionTime = AbsoluteTime[];
  result]

$Pre = PreDefn;

$EmptyByDefn = 0.1;

monitor := (
  $TimeSinceCompletion = AbsoluteTime[] - $CompletionTime;
  If[! $busy && ! $fDone && $TimeSinceCompletion >  $EmptyByDefn, 
   f; $fDone = True])

monitorTask = SessionSubmit[ScheduledTask[monitor,   Quantity[$EmptyByDefn/10.0, "Seconds"]]];

f := EmitSound[SoundNote["G"]]

My actual usage

I came across this problem while writing EnableTeX (my commercial software). The "bell" in that context is a pdflatex compilation for a PDF preview of a LaTeX file that is written by the evaluating selection. For fast response to user input, such compilations must be triggered only once at the end of a selection evaluation, because the user's selections might include 100s of cells.

$\endgroup$
  • $\begingroup$ You could have your CellEvaluationFunction add the current code to an asynchronously processed queue and have a function that’s called once the queue is dry. That’d be the most robust and least wasteful way to do it. $\endgroup$ – b3m2a1 Jul 13 at 8:16
  • $\begingroup$ @b3m2a1 Yes, I agree. I've updated my answer to explain how I'm doing that. $\endgroup$ – Andrew Norton Jul 15 at 4:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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