0

A question I have seen posed often, but have not yet found a solution that fits my problem. I would therefore appreciate some help to improve both my code and my understanding of Python.

I'm working on a large chunk of code that features Grammatical Evolution and runs in several nested (unavoidable at the moment) classes.

The base class (Genetic) handles the population, each member of the population is an instance of Individual class and is evaluated using the Algorithm class. To avoid over-complicating - a code that runs in several layers (with Spice simulated added to the mix) and runs for... quite a long time - hours at some times.

This is the reason why I started to search for a solution that would allow me to stop the execution in a way that would not mess everything up. If I use the usual ctrl+c option it simply destroys everything and forces me to restart the kernel and loose all the data.

What I'm thinking about is adding a sort of a monitor to the code that would allow me to enter a keypress sequence and then say "Ok, I see you want to finish, I'll simply complete the current evaluation loop and exit".

In my belief I cannot use a keyboard interrupt since this would immediately exit the code execution. More likely I would need a flag that would change if the key is detected....

Any help would be appreciated.

So to summarize my pseudoIdea would be like this:

for each generation:
  if exit.flag != true:
    for each individual:
      evaluate individual
  else:
    write result
    finish and exit

when key detected set exit.flag to true

Thank you!

2 Answers2

0

While it is possible to detect a keypress, you don't have to. Because it is possible to "capture" a Ctrl-c!

try:
    code_execution()
except KeyboardInterrupt:
    print("Ctrl-c was pressed, ready to exit")

Basically, the exception type KeyboardInterrupt is raised when Ctrl-c is pressed. And this is something you can catch and handle any way you see fit. You can read more about this exception in particular here and in case you're new to exception handling, this is a good starting point.

Bonus point - rare exceptions are faster than if statements.

From your comments I understand you can't wrap main execution and prevent it from stopping so I'm including another method. Signal trapping.
Ctrl-c sends the process SIGINT which we can configure to "trap" and handle it differently than normal (which is exiting).

import signal, os
import time

FLAG = False


def handler(signum, frame):
    """
    This function will execute each time Ctrl-c is pressed or SIGINT
    is sent to the process (such as `kill <pid>`).
    """
    global FLAG
    print('Signal handler called with signal', signum)
    FLAG = True

# Setup so we process signal SIGINT (includes Ctrl-C) with handler
signal.signal(signal.SIGINT, handler)


while True and not FLAG:
    time.sleep(0.5)
    print("Working hard!")
else:
    print("Done!")

You can read a little more about signal in Python in their docs. It is a standard method of inter-process communication, too, which you can utilize.

edd
  • 1,307
  • 10
  • 10
  • But doesn't this stop the code_execution()? Like I said I want it to complete the current series of evaluations and only then stop. – Matevž Kunaver May 02 '20 at 08:21
  • It stops `code_execution`, yes. But you can wrap any code block with the `try ... except` clause. You can use it, for example, around the core execution to set a flag and continue normal execution. But, next iteration, as you suggested, will stop at the first convenient option if the flag is set. – edd May 02 '20 at 08:30
  • I will have to think / experiment with this. The problem is that I only have one "active" code execution function which is the genetic.run() method. I cannot interrupt it like this. I might be able to use this for individuals evaluation process, but this can be a problem since this involves calling an external program and I'm not sure if an interrupt would close the sim in the correct way. But OK it is a possibility - interrupt an individual, set the flag, evaluate the res... – Matevž Kunaver May 02 '20 at 08:36
  • @MatevžKunaver That's completely fair. I added another built-in standard method that doesn't stop execution but requires the GIL to be freed. The best way to ascertain this without diving deep about GIL is to try it! Good luck. – edd May 02 '20 at 15:18
0

Found a solution using pynput

from genetic.mainCore import Genetic
from grammar.basicDict import params
from algorithm.core import Algorithm
from pynput import keyboard


def on_activate_h():
    print('<ctrl>+<alt>+h pressed')
    global gen
    gen.forcEnd = True

listener = keyboard.GlobalHotKeys({'<ctrl>+<alt>+h': on_activate_h})
listener.start()

algi = Algorithm()
gen = Genetic(algi)
gen.initGrammar(params)
gen.initPopulation()
gen.run()

listener.stop()

I modified the gen.run() method to include a forcEnd check. If it is set to True we skip the evaluation of next generation and exit. Tested this and it works, even when using an external simulator!

  • While the solution provided by edd below works, the above piece of code seems to work better for my uber complicated class structure. – Matevž Kunaver Jun 17 '20 at 10:31