0

In jupyter-lab I have a long running function long_runner() doing some heavy numeric computation. I would like to be able to stop this function by clicking on an ipywidget button. Interrupting the kernel is not an option since other parts of the GUI should continue working.

Here is a code example with a button which sets a stop flag. This stop flag is used in long_runner to break out of the loop. When long_runner is active, the Button seems to be reactive to clicks, but the associated function set_stop_flag is only executed after long_runner has finished normally and thus no intermediate stop is possible.

I looked at some examples with asyncio but could not find something fitting this use-case with a heavy-compute function.

How could the desired behavior be acchieved?

from ipywidgets import Button

stop_flag = 0
def set_stop_flag(b):
    global stop_flag
    print("stop flag activated")
    stop_flag = 1
    
b=Button(description="stop")
b.on_click(set_stop_flag)
display(b)

def long_runner():
    x=0
    print("starting ...")
    for i in range(100000000):
        if stop_flag==1:
            print("stop=",stop_flag)
            break
        x += i*i
    print("stopping",i)
    return(x,i)

print(long_runner())

Here is the program output when the stop-button is clicked during the execution of long-runner:

enter image description here

Barden
  • 1,020
  • 1
  • 10
  • 17
  • 1
    I've got some links to examples available [here](https://discourse.jupyter.org/t/how-to-make-start-and-stop-buttons/8331/4?u=fomightez). I think the timer one is closest. You may want to look more around the links and discussion on that thread as well for refining your code. For understanding the use of multiprocessing, you might like this [simple example](https://discourse.jupyter.org/t/running-a-cell-in-a-background-thread/12267/3?u=fomightez) that works in JupyterLab. – Wayne Feb 16 '22 at 20:29
  • 1
    And I have an example of using multiprocessing to monitor a long-running 'external' process and then terminate it when it seems it should be done [here](https://github.com/fomightez/AnimatePymolWithJmol/blob/e710cab10bddddd63880baf2d48a77119bad39d1/AnimatePyMOL2Jmol.ipy#L114) when the output file seems to be done increasing in size. It is part of this effort that you can run the demo of in Jupyter by pressing the badge that says `launch making animations from PyMOL using Jmol` [here](https://github.com/fomightez/AnimatePymolWithJmol). – Wayne Feb 16 '22 at 21:22
  • 1
    A [related stackoverflow question](https://stackoverflow.com/q/71205664/8508004). – Wayne Feb 22 '22 at 17:29

1 Answers1

1

following the first comment (thanks!), specifically this [link][1] I was able to modify the example such that one can now interrupt the long_runner with a button. The key is to start key_runner in a thread. Below is the program followed by the output when clicking the button after a few iterations. The sleep(1.0) is only made to limit the output freqency and is non-essential.

import threading
import numpy as np
from ipywidgets import Button
import time

stop_flag = 0
def set_stop_flag(b):
    global stop_flag
    print("stop flag activated")
    stop_flag = 1
    
b=Button(description="stop")
b.on_click(set_stop_flag)
display(b)

def long_runner():
    x=0
    print("starting ...")
    for i in range(100000000):
        time.sleep(1.0)
        print(f"x={x} i={i} stop_flag={stop_flag}")
        if stop_flag==1:
            print("stopped at i=",i)
            break
        x += i*i
    print("stopping")
    return(x,i)

thread = threading.Thread(target=long_runner)
thread.start()

Output:

(imagine button here)

starting ...
x=0 i=0 stop_flag=0
x=0 i=1 stop_flag=0
x=1 i=2 stop_flag=0
x=5 i=3 stop_flag=0
x=14 i=4 stop_flag=0
x=30 i=5 stop_flag=0
stop flag activated
x=55 i=6 stop_flag=1
stopped at i= 6
stopping```


  [1]: https://discourse.jupyter.org/t/how-to-make-start-and-stop-buttons/8331/4?u=fomightez
  [2]: https://i.stack.imgur.com/1Bchu.png
Barden
  • 1,020
  • 1
  • 10
  • 17