NREL / EnergyPlus

EnergyPlus™ is a whole building energy simulation program that engineers, architects, and researchers use to model both energy consumption and water use in buildings.
https://energyplus.net
Other
1.13k stars 389 forks source link

[Python API] stop simulation #10041

Closed simonsays1980 closed 1 year ago

simonsays1980 commented 1 year ago

Issue overview

I am trying to stop EnergyPlus from running using the Python API. The simulation runs in a thread and I want to join() such a thread before EnergyPlus completes the Simulation (basically to reduce waiting time).

Here is my code:

import threading
from pyenergyplus.api import EnergyPlusAPI

eplus_api = EnergyPlusAPI()

# Create callback.
def callback_after_predictor_after_hvac_managers(state):
    # Just an example for a stopping condition.
    if eplus_api.exchange.current_sim_time(state) > 400:
        # Here I want to stop the simulation.
        eplus_api.runtime.issue_severe(state, "Severe issue. Stop.")          

# Create a run-function.
def run_eplus(index):
    idf = "/home/simon/Downloads/1ZoneUncontrolled.idf"
    epw = "/home/simon/Downloads/USA_CO_Denver-Stapleton_TMY.epw"
    output = "eplus_out_" + str(index)
    args = ["-w", epw, "-d", output, idf]
    state = eplus_api.state_manager.new_state()
    eplus_api.runtime.callback_after_predictor_after_hvac_managers(
        state, callback_after_predictor_after_hvac_managers
    )
    eplus_api.runtime.run_energyplus(state, args)

# Create a list of threads and call run-function
threads = []
for i in range(4):
    thread = threading.Thread(target=run_eplus, args=(i,))
    threads.append(thread)
    thread.start()

# Join the threads
for i in range(4):
    if threads[i].is_alive():
        threads[i].join(timeout=1)

I was looking for a fatal error that could be raised in EnergyPlus, but there is only the issue_severe() (see also the discussion in #4838 ). I saw also that there is a commented-out stop_simulation()-method in the Runtime API (is there planned anyting?).

Now, if I run the code above, it does not stop, but the `join()' has to wait until the simulations are all done. Is there any possibility to stop the simulation earlier and stop the corresponding thread?

Details

Some additional details for this issue (if relevant):

Checklist

Add to this list or remove from it as applicable. This is a simple templated set of guidelines.

Myoldmopar commented 1 year ago

@simonsays1980 thanks for posting this. I cannot recall exactly why I didn't expose the stopSimulation API function through the Python API. It is certainly there in the C API layer, just commented out in Python. It's possible I need to just uncomment it, test it, and it will be in the next version.

In the meantime, I have a solution, though it's not a beautiful solution. In Python, if you call sys.exit(), it will only kill the current thread, not the whole Python process. I was able to get your sample code working as expected by adding a call to sys.exit() right after issuing the severe error. Could you test that out as a temporary workaround?

I'll try to figure out why I didn't expose the stop_simulation function through Python, and hopefully there will be a more graceful way to deal with this in the next version. It's also possible that E+ acknowledges a return code from the callback functions, if one is provided. For Python Plugin operations, the plugin override function is required to return a zero to hint to EnergyPlus that everything is OK and the simulation should continue. The API callback functions could be modified to similarly handle return codes. I'll do some thinking about this.

As an aside, I'm always curious how folks are using the Python world. If you have interest in a chat, email me at edwin dot lee at nrel dot gov.

simonsays1980 commented 1 year ago

@Myoldmopar Thanks for answering so quickly and for your detailed description. I am sure you had some good reason to comment it out and maybe you get some good ideas how to provide a safe way to stop running simulations.

I also tested out the sys.exit() approach. In my case I get some exceptions as shown below. Could this maybe caused by the specific Python version or environment (I use Python 3.9.4 and from pyenv)?

EnergyPlus Starting
EnergyPlus, Version 22.1.0-43b09606e2 (No OpenGL) (No OpenGL), YMD=2023.05.25 11:04
EnergyPlus Starting
EnergyPlus, Version 22.1.0-43b09606e2 (No OpenGL) (No OpenGL), YMD=2023.05.25 11:04
EnergyPlus Starting
EnergyPlus, Version 22.1.0-43b09606e2 (No OpenGL) (No OpenGL), YMD=2023.05.25 11:04
EnergyPlus Starting
EnergyPlus, Version 22.1.0-43b09606e2 (No OpenGL) (No OpenGL), YMD=2023.05.25 11:04
Adjusting Air System Sizing
Adjusting Standard 62.1 Ventilation Sizing
Initializing Simulation
Adjusting Air System Sizing
Adjusting Standard 62.1 Ventilation Sizing
Initializing Simulation
Adjusting Air System Sizing
Adjusting Standard 62.1 Ventilation Sizing
Initializing Simulation
Adjusting Air System Sizing
Adjusting Standard 62.1 Ventilation Sizing
Initializing Simulation
Reporting SurfacesReporting Surfaces

Reporting Surfaces
Beginning Primary Simulation
Initializing New Environment Parameters
Warming up {1}
Beginning Primary Simulation
Initializing New Environment Parameters
Warming up {1}
Reporting Surfaces
Warming up {2}
Warming up {2}
Beginning Primary Simulation
Initializing New Environment Parameters
Warming up {1}
Beginning Primary Simulation
Initializing New Environment Parameters
Warming up {1}
Warming up {3}
Warming up {3}
Warming up {2}
Warming up {2}
Warming up {4}
Warming up {4}
Warming up {3}
Warming up {3}
Warming up {5}
Warming up {5}
Warming up {4}
Warming up {4}
Warming up {6}
Warming up {6}
Warming up {5}
Warming up {5}
Warming up {7}
Warming up {6}
Warming up {7}
Warming up {6}
Warming up {8}
Warming up {7}
Warming up {8}
Warming up {7}
Warming up {8}
Warming up {9}
Warming up {9}
Warming up {8}
Warming up {9}
Warming up {10}
Warming up {10}
Warming up {10}
Warming up {9}
Warming up {11}
Warming up {11}
Warming up {11}
Warming up {10}
Warming up {12}
Warming up {12}
Warming up {12}
Warming up {13}
Warming up {13}
Warming up {11}
Warming up {13}
Warming up {14}
Warming up {14}
Warming up {12}
Warming up {14}
Warming up {15}
Warming up {15}
Warming up {13}
Warming up {15}
Warming up {16}
Warming up {16}
Warming up {14}
Warming up {16}
Warming up {17}
Warming up {17}
Warming up {15}
Warming up {17}
Warming up {18}
Warming up {18}
Warming up {16}
Warming up {18}
Warming up {19}
Warming up {19}
Warming up {17}
Warming up {19}
Warming up {20}
Warming up {20}
Warming up {18}
Warming up {20}
Warming up {21}
Warming up {21}
Warming up {19}
Warming up {21}
Warming up {22}
Warming up {20}
Warming up {22}
Warming up {22}
Starting Simulation at 12/21 for DENVER CENTENNIAL  GOLDEN   N ANN HTG 99% CONDNS DB
Starting Simulation at 12/21 for DENVER CENTENNIAL  GOLDEN   N ANN HTG 99% CONDNS DB
Warming up {21}
Starting Simulation at 12/21 for DENVER CENTENNIAL  GOLDEN   N ANN HTG 99% CONDNS DB
Initializing New Environment Parameters
Warming up {1}
Warming up {22}
Initializing New Environment Parameters
Warming up {1}
Initializing New Environment Parameters
Warming up {1}
Warming up {2}
Starting Simulation at 12/21 for DENVER CENTENNIAL  GOLDEN   N ANN HTG 99% CONDNS DB
Warming up {2}
Warming up {2}
Warming up {3}
Warming up {3}
Initializing New Environment Parameters
Warming up {1}
Warming up {3}
Warming up {4}
Warming up {4}
Warming up {4}
Warming up {2}
Warming up {5}
Warming up {5}
Warming up {5}
Warming up {3}
Warming up {6}
Warming up {6}
Warming up {6}
Warming up {7}
Warming up {4}
Warming up {7}
Warming up {7}
Warming up {8}
Warming up {5}
Warming up {8}
Warming up {8}
Warming up {9}
Warming up {6}
Warming up {9}
Warming up {9}
Warming up {10}
Warming up {10}
Warming up {10}
Warming up {7}
Warming up {11}
Warming up {11}
Warming up {11}
Warming up {8}
Warming up {12}
Warming up {12}
Warming up {12}
Warming up {9}
Warming up {13}
Warming up {13}
Warming up {13}
Warming up {10}
Warming up {14}
Warming up {14}
Warming up {14}
Warming up {11}
Warming up {15}
Warming up {15}
Warming up {15}
Warming up {12}
Warming up {16}
Warming up {16}
Warming up {16}
Warming up {13}
Starting Simulation at 07/21 for DENVER CENTENNIAL  GOLDEN   N ANN CLG 1% CONDNS DB=>MWB
Starting Simulation at 07/21 for DENVER CENTENNIAL  GOLDEN   N ANN CLG 1% CONDNS DB=>MWB
Starting Simulation at 07/21 for DENVER CENTENNIAL  GOLDEN   N ANN CLG 1% CONDNS DB=>MWB
Warming up {14}
Initializing New Environment Parameters
Warming up {1}
Initializing New Environment Parameters
Warming up {1}
Initializing New Environment Parameters
Warming up {1}
Warming up {15}
Warming up {2}
Warming up {2}
Warming up {16}
Warming up {2}
Warming up {3}
Warming up {3}
Starting Simulation at 07/21 for DENVER CENTENNIAL  GOLDEN   N ANN CLG 1% CONDNS DB=>MWB
Warming up {3}
Warming up {4}
Warming up {4}
Initializing New Environment Parameters
Warming up {1}
Warming up {4}
Warming up {5}
Warming up {5}
Warming up {5}
Warming up {2}
Warming up {6}
Warming up {6}
Warming up {6}
Warming up {3}
Warming up {7}
Warming up {7}
Warming up {7}
Warming up {4}
Warming up {8}
Warming up {8}
Warming up {5}
Warming up {8}
Warming up {9}
Warming up {9}
Warming up {9}
Warming up {6}
Warming up {10}
Warming up {10}
Warming up {7}
Warming up {10}
Warming up {11}
Warming up {8}
Warming up {11}
Warming up {11}
Warming up {12}
Warming up {12}
Warming up {9}
Warming up {12}
Warming up {13}
Warming up {13}
Warming up {10}
Warming up {13}
Warming up {14}
Warming up {14}
Warming up {11}
Warming up {14}
Warming up {15}
Warming up {15}
Warming up {12}
Warming up {15}
Warming up {16}
Warming up {16}
Warming up {16}
Warming up {13}
Warming up {17}
Warming up {17}
Warming up {14}
Warming up {17}
Warming up {18}
Warming up {18}
Warming up {18}
Warming up {15}
Warming up {19}
Warming up {19}
Warming up {16}
Warming up {19}
Starting Simulation at 01/01/2013 for RUN PERIOD 1
Starting Simulation at 01/01/2013 for RUN PERIOD 1
Warming up {17}
Starting Simulation at 01/01/2013 for RUN PERIOD 1
Warming up {18}
Warming up {19}
Starting Simulation at 01/01/2013 for RUN PERIOD 1
Updating Shadowing Calculations, Start Date=01/21/2013
Updating Shadowing Calculations, Start Date=01/21/2013
Continuing Simulation at 01/21/2013 for RUN PERIOD 1
Continuing Simulation at 01/21/2013 for RUN PERIOD 1
Updating Shadowing Calculations, Start Date=01/21/2013
Updating Shadowing Calculations, Start Date=01/21/2013
Continuing Simulation at 01/21/2013 for RUN PERIOD 1
Continuing Simulation at 01/21/2013 for RUN PERIOD 1
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
    sys.exit()
SystemExit: 
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
    sys.exit()
SystemExit: 
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()
SystemExit: 
    sys.exit()
SystemExit: 
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
Exception ignored on calling ctypes callback function: <function callback_after_predictor_after_hvac_managers at 0x7fd0040b6160>
Traceback (most recent call last):
  File "/home/simon/git-projects/eplus/eplus_gym/test_multithreading.py", line 11, in callback_after_predictor_after_hvac_managers
    sys.exit()

The return codes was also something I tried out. I picked them up from the nice example PythonPluginTestMathAndKill.py, but the return codes in the API do not have any effect (I tried out 0, 1, False, and True). Maybe because the functions are defined as returning None and therefore EnergyPlus dos not expect anything to check there.

I also guess there is no easy answer to how to best stop energyplus. In my opinion it is probably nice to have a stop_simulation() method as it could serve two things: (1) it increases user experience by providing an intuitively clear exit to the simulation (if intended so by the user) and (2) it improves readability of code as it is quite clear what happens when we call this function. Furthermore, after using EnergyPlus now for around 1 year programmatically, I can tell this is a complex software with a steep learning curve and so it is specifically to new and intermediate users not very clear what effect certain actions have for all the executions in a run. If there are specific things to be taken care of when stopping a running simulation a method that takes care of this might be a good way to guide the user.

Using the return argument as an indicator to exit the simulation is somehow twofold. On one side it increaes the possibilities how to granularly control execution as EnergyPlus can detect at which points exactly something has happened and EnergyPlus developers can decide what to do with these signals. On the other side this might be able to be signaled via issue_severe() and issue_warning() (maybe even an issue_fatal() for things that went wrong outside of EnergyPlus but still inside a whole simulation, which might be a stack of different softwares). It might be good to have the possibility to stop EnergyPlus either by error or regularly.

I haven't said this, yet: This is just such a great development! Having the Python API instead of Plugins (or before communicating via BCVTB) makes programming so much easier.

Myoldmopar commented 1 year ago

Thanks for trying it out. I'm not sure why it didn't work. The sys.exit() should raise a signal that kills the current thread, which stops EnergyPlus from continuing. So no more callbacks should be attempted. Anyway...I put the stop_simulation function in place in the Python bindings, and it works perfectly! I have it in place in pull request #10044 which should merge without too much trouble, so expect to see this in 23.2 this fall.

simonsays1980 commented 1 year ago

Thanks a lot @Myoldmopar ! This simplifies a lot as stopping is now handled gracefully inside of EnergyPlus and we users do not have to worry about any strange behavior. after killing processes. Can't wait for the fall release :)