Input Output Reference — EnergyPlus 9.4

<< Prev | Table of Contents | Object Index | Next >>

Using EnergyPlus as a Library[LINK]

This section is specifically related to calling EnergyPlus as a function from other clients, including C programs and Python scripts. For information on using Python Plugins (Python EMS), see section [group-plugins].

Since its birth, interacting with EnergyPlus has been essentially limited to creating an input file and calling on the EnergyPlus binary to run and produce results. For a long time, there has been a very small API made available to allow limited access to running a simulation from a client by calling a function, however, it was never widely publicized, and had significant issues. As of version 9.3, a proper API has been developed, which builds on a number of important developments, including:

  • The Energy Management System, which was the first method to allow reading and writing simulation data while a simulation was running, and allowing user-defined scripts to be executed to alter simulation data.

  • The EnergyPlus unit test structure, which has, as a side-effect, enabled the ability to “reset” the state of a simulation, and re-run another, in the same memory space.

As of version 9.4, the API has been improved, as well as the ability to more reliably “reset” the simulation state. After 9.3 was released and users began toying with the API usage, it was revealed that many (most?) files would not actually run well during series runs of EnergyPlus via API in the same thread because the simulation state was not fully “cleared”. Although the simulation state is still not guaranteed to be fully cleared, we are now seeing greater than 95% pass rate on our example files when running them serially multiple times. This situation will make it much more suited for integrating into workflows as the Python thread does not have to be destroyed to get a clean EnergyPlus state for the next run. Work is continuing to get the state of the simulation completely managed, and will be completed prior to the next release, ensuring this situation continues improving.

Note that the effort to improve the simulation state for the API resulted in a break in many API functions. It was expected that in this first release of the API, issues would be identified that would cause breaks in the API. Moving forward, it is not expected that API endpoints will change, at least not often. As the API gains more adoption, EnergyPlus may adopt a more semantic versioning approach where the API is only broken in major version number changes.

Interface developers that have been building on top of EnergyPlus have primarily interacted with the simulation in the traditional manner. The interface would create an input file in a directory, copy in weather data and any other supporting data, and then kick off a simulation in that directory. The interface could grab output messages from the simulation, but that was essentially it. The interface just had to wait until EnergyPlus completed, check data in the output files, and decide what to do next. Grabbing any sort of data from the simulation during a run, even just progress, was highly difficult.

The EnergyPlus API has been created to change that situation and open new doors into the simulation program and the opportunity to embed EnergyPlus into vastly more workflows and applications. A new formal EnergyPlus API is implemented that allows interacting with different “categories” of the simulation. Technically speaking, the categories are not different, as they operate using the same API mechanics. The categorization is purely organizational. In the following subsections, the API is laid out with examples in both C and Python. Full API documentation will be provided separate from this document. Check the release notes for your specific release for more information.

State API[LINK]

The state of an EnergyPlus represents the entire contents of a running simulation. Everything from the current time step to the water side economizer operating part load ratio is tracked in the state of a running simulation. Since the birth of EnergyPlus, the state of the simulation was hung on the global program state, with no explicit object owning and managing the data. This was perfectly acceptable because the only real use of EnergyPlus was to execute the binary (energyplus.exe) and wait for it to finish and inspect the outputs. However, this has proved to be a burden when trying to push EnergyPlus into new applications, and as such, the team has worked to move much of the global state into a state class which can then manage the data on itself. From an internal-to-EnergyPlus perspective, this means that functions will access variables on a class that is passed around throughout the program. From an API-client perspective, this means that the client will need to create a state instance and pass it into the simulation when asking the simulation to do most operations.

Prior to calling into most of the API functions, the API client must create a new state instance. This state instance is not to be directly accessed by the client, but instead the client must simply be a courier of this state instance and pass it in and out of the API calls. Whenever the client is done with that instance, the instance can be reset to be prepped for another run of EnergyPlus, or destroyed if the client is done with it.

An example of this in C is listed here:

#include <EnergyPlus/api/state.h>

int main(int argc, const char * argv[]) {
  EnergyPlusState state = stateNew();
  stateReset(state);
  stateDelete(state);
  printf("Wow this was a boring use case\n");
}

That does not do much. But moving through the rest of the API functions, it will become clear that this is at the core of an EnergyPlus API workflow.

In Python, that same example is similar:

from pyenergyplus.api import EnergyPlusAPI

api = EnergyPlusAPI()
state = api.state_manager.new_state()
api.state_manager.reset_state(state)
api.state_manager.delete_state(state)
print("Wow even boring in Python!?")

Functional API[LINK]

The “Functional” API allows for users to call into the EnergyPlus binary and request static information. Examples of this information include fluid property data (currently only pure water, but glycol mixtures will be added), refrigerant property data (currently only steam), and psychrometric properties.

To interact with the fluid property data, the user will create a fluid property class instance. Once an instance is available, the user will call methods on that instance. To interact with the refrigerant property data, the user will also create a refrigerant class and access data through methods. For psychrometrics, in C the function calls do not need to create an instance, however, in Python, a small class captures the psychrometric functions for organization.

While some of the functionality in these property routines are stateless from the perspective of the EnergyPlus simulation engine, an instance of the EnergyPlus “state” class is required to be passed in.

An example of exercising the functional API in C is listed here:

#include <EnergyPlus/api/func.h>
#include <EnergyPlus/api/state.h>

int main(int argc, const char * argv[]) {
 EnergyPlusState state = stateNew();
 initializeFunctionalAPI(state);

 Glycol glycol = NULL;
 glycol = glycolNew(state, "WatEr");
 Real64 specificHeat = glycolSpecificHeat(state, glycol, 35.0);
 glycolDelete(state, glycol);

 Refrigerant refrig = NULL;
 refrig = refrigerantNew(state, "SteaM");
 Real64 satPress = refrigerantSaturationPressure(state, refrig, 100.0);
 refrigerantDelete(state, refrig);

 Real64 rh = psyRhFnTdbWPb(state, 24, 0.009, 101325); // psychrometrics are evaluated directly
}

It is quite minimal, objects are constructed, methods are called, and objects are destructed. Note that the initializeFunctionalAPI function must be called once to setup the program. Note that the constructor function arguments are case-insensitive. Also note that no simulation has been executed here, this is purely calling into the library to evaluate functions.

The same operations, but in Python are listed here:

from pyenergyplus.api import EnergyPlusAPI

api = EnergyPlusAPI()
state = api.state_manager.new_state()

glycol = api.functional.glycol(state, u"water")
cp = glycol.specific_heat(state, 35.0)

refrigerant = api.functional.refrigerant(state, "steam")
satPress = refrigerant.saturation_pressure(state, 100.0)

psychrometrics = api.functional.psychrometrics(state, )
rh = psychrometrics.relative_humidity_b(state, 24, 0.009, 101325)

Some subtle differences are present. In Python, the client creates an instance of an EnergyPlusAPI class to access all methods. Functional category API methods are accessed via the EnergyPlusAPI.functional variable. Note that in Python, in contrast to C, the psychrometric functions also live on a class that must be constructed.

Runtime API[LINK]

The “Runtime” API allows for users to hook into a running simulation. The client creates functions, and using this runtime API, they can register these functions to be called back at specific points in a simulation. Once registered, this API includes a function to start the simulation. There are a number of calling points available to hook in a callback function. For information on the different available calling points, see the EMSApplicationGuide documentation packaged with EnergyPlus. For exact naming conventions on the C and Python registration functions, see either the C header or Python API file, or the API documentation that will be made available separate from the installer. See the release notes for information on subsequent documentation.

A minimal approach demonstrating the runtime API in C would be the following:

Note that as of EnergyPlus 9.4, due to the change in handling simulation state, the form of the callback function has changed. While a simulation is running, there is a matched state object that must be passed in and out of the program to perform operations. This has already been demonstrated in the above functional API example, but in this case, the state change is more pronounced because the callback function must accept a state argument. The reason the callback must accept the state argument is because while inside a callback, it is expected that the client code will then call into EnergyPlus to read/write data, and the state must be passed back in. Basically, the client must be a reliable courier of the state object.

#include <EnergyPlus/api/runtime.h>
#include <EnergyPlus/api/state.h>

void dummyCallbackFunction(EnergyPlusState state) {
  printf("Hmm I could use my state argument to call back into EnergyPlus to do stuff, great!\n");
}

int main(int argc, const char * argv[]) {
 callbackAfterNewEnvironmentWarmupComplete(state, dummyCallbackFunction);
 energyplus(state, argc, argv);
 stateReset(state); // note previously registered callbacks are cleared here
 callbackAfterNewEnvironmentWarmupComplete(state, dummyCallbackFunction);
 energyplus(state, argc, argv);
}

The code is straightforward, though excessively minimal. A minimal function is created, which accepts one argument - a state object, and does nothing. This function is then passed to a callback registration function via the runtime API. EnergyPlus is executed, passing along any command line arguments that are received by the test program. This is a convenience because the energyplus function expects arguments just like the EnergyPlus(.exe) program, so the test script would have the same command line capabilities as EnergyPlus itself. For this code, it could be executed with something like: program.exe -D /path/to/input.idf. Once EnergyPlus is complete, the state object is reset, the callback is re-registered, and EnergyPlus is run again. Since the test script is complete at this point, there is no need to reset the state again.

The code is strikingly similar in Python:

import sys
from pyenergyplus.api import EnergyPlusAPI

def dummy_callback_function(state_argument):
  print("My argument is called state_argument to avoid duplicating the outer variable below called state")

api = EnergyPlusAPI()
state = api.state_manager.new_state()
api.runtime.callback_begin_new_environment(state, dummy_callback_function)
api.runtime.run_energyplus(state, sys.argv[1:])
api.state_manager.reset_state(state)
api.runtime.callback_begin_new_environment(state, dummy_callback_function)
api.runtime.run_energyplus(state, sys.argv[1:])

Once again, a dummy function is created, then registered, followed by a call to run EnergyPlus (passing along the relevant command line arguments), the state is cleared, then the function is registered again and EnergyPlus is run a second time. Note that when calling EnergyPlus as a library, you should just pass the arguments, not the filename itself. In Python, the argv variable will have the filename as the first item in the list, so this example trims that off.

Data Exchange API[LINK]

The “Data Exchange” API allows for users to interact with simulation data. There are some functions in this API that are suitable only for Python Plugin workflows, and they will not be discussed here. For more information on using that workflow, see section [group-plugins]. For this API, five areas are available: variables, meters, “internal” variables, simulation parameters, and actuators. They are described in a language-agnostic sense first, then actual examples are provided in C and Python below.

Variables

Variables represent time series output variables in the simulation. There are thousands of variables made available based on the specific configuration. A user typically requests variables to be in their output files by adding Output:Variable objects to the input file. It is important to note that if the user does not request these variables, they are not tracked, and thus not available on the API.

In an API workflow, the client would interact with variables in three steps. First the user will either specify the output as requested in the input file provided, or call a variable request function to mark the variable as requested. Second the user will call to lookup a variable ID/handle, passing in the variable type name and key. Third the variable value can be looked up while a simulation is running by calling a get-value function on the API.

Meters

Meters represent groups of variables which are collected together, much like a meter on a building which represents multiple energy sources. Meters are handled the same way as variables, except that meters do not need to be requested prior running a simulation. From an API standpoint, a client must simply get a handle to a meter by name, and then access the meter value by using a get-value function on the API.

Internal Variables

The name “internal variable” is used here as it is what these variables were called in the original EMS implementation. Another name for these variables could be “static” variables. Basically, these variables represent data that does not change throughout a simulation period. Examples include calculated zone volume or autosized equipment values. These values are treated just like meters, you use one function to access a handle ID, and then use this handle to lookup the value.

Simulation Parameters

A number of parameters are made available as they vary through the simulation, including the current simulation day of week, day of year, hour, and many other things. These do not require a handle, but are available through direct function calls.

With these read-only data exchange items available, there are already a number of new possibilities. As an example, an EnergyPlus interface developer could very easily change from just executing the EnergyPlus program, to calling EnergyPlus as a library function. This would unlock the potential to not only get better progress status updates, but the interface could also request and lookup values of some energy meters or other output variables, and present this to the user graphically while the simulation is running. However, much more power comes with the addition of actuators, described next.

Actuators

Actuators are the way that users modify the program at runtime using custom logic and calculations. Not every variable inside EnergyPlus can be actuated. This is intentional, because opening that door could allow the program to run at unrealistic conditions, with flow imbalances or energy imbalances, and many other possible problems. Instead, a specific set of items are available to actuate, primarily control functions, flow requests, and environmental boundary conditions. These actuators, when used in conjunction with the runtime API and data exchange variables, allow a user to read data, make decisions and perform calculations, then actuate control strategies for subsequent time steps.

Actuator functions are similar, but not exactly the same, as for variables. An actuator handle/ID is still looked up, but it takes the actuator type, component name, and control type, since components may have more than one control type available for actuation. The actuator can then be “actuated” by calling a set-value function, which overrides an internal value, and informs EnergyPlus that this value is currently being externally controlled. To allow EnergyPlus to resume controlling that value, there is an actuator reset function as well.

A special note about data exchange. Variables, meters, and actuators are not immediately available as soon as the program starts. The memory associated with these along with the bookkeeping, must be set up during program initialization. There is a risk that the variables requested are not set up by the first API callback point. This is intentional because some API callbacks may want to be called this early. To avoid problems, a function is available on this API that will allow a client to check if the API data is “fully ready”. For almost all applications, if this is not ready, the client should just return from the callback and let EnergyPlus continue, and wait until it is ready before doing any manipulation.

It is difficult (impossible?) to exercise the data exchange API without at least also demonstrating the runtime API. In order to exchange data with the simulation, you must first create a runtime callback function and register that, then execute EnergyPlus, and finally wait until EnergyPlus calls your callback function. At this point, you can then perform data exchange. Minimal examples that tie the runtime and data exchange APIs together are shown here, first in C:

#include <EnergyPlus/api/datatransfer.h>
#include <EnergyPlus/api/runtime.h>
#include <EnergyPlus/api/state.h>

int outdoorDewPointActuator = -1;
int outdoorTempSensor = -1;
int handlesRetrieved = 0;

void afterZoneTimeStepHandler(EnergyPlusState state)
{
 if (handlesRetrieved == 0) {
   if (!apiDataFullyReady(state)) return;
   outdoorDewPointActuator = getActuatorHandle(state, "Weather Data", "Outdoor Dew Point", "Environment");
   outdoorTempSensor = getVariableHandle(state, "SITE OUTDOOR AIR DRYBULB TEMPERATURE", "ENVIRONMENT");
   handlesRetrieved = 1;
 }
 Real64 oa_temp = getVariableValue(state, outdoorTempSensor);
 setActuatorValue(state, outdoorDewPointActuator, oa_temp - 4);
}

int main(int argc, const char * argv[]) {
 callbackEndOfZoneTimeStepAfterZoneReporting(state, afterZoneTimeStepHandler);
 requestVariable(state, "SITE OUTDOOR AIR DRYBULB TEMPERATURE", "ENVIRONMENT");
 energyplus(state, argc, argv);
}

The actual operations happening in this example are completely fictional, but nevertheless demonstrate a possible minimal case. Note that the variable to be used is requested, and if the api data is not fully ready in the callback, it simply returns and waits.

And now in Python:

from pyenergyplus.api import EnergyPlusAPI

one_time = True
outdoor_temp_sensor = 0
outdoor_dew_point_actuator = 0

def time_step_handler(state):
  global one_time, outdoor_temp_sensor, outdoor_dew_point_sensor, outdoor_dew_point_actuator
  if one_time:
    if not api.exchange.api_data_fully_ready(state):
      return
    outdoor_temp_sensor = api.exchange.get_variable_handle(
      state, u"SITE OUTDOOR AIR DRYBULB TEMPERATURE", u"ENVIRONMENT"
    )
    outdoor_dew_point_actuator = api.exchange.get_actuator_handle(
      state, "Weather Data", "Outdoor Dew Point", "Environment"
    )
    one_time = False
  oa_temp = api.exchange.get_variable_value(state, outdoor_temp_sensor)
  api.exchange.set_actuator_value(state, outdoor_dew_point_actuator, oa_temp-4)

api = EnergyPlusAPI()
state = api.state_manager.new_state()
api.runtime.callback_end_zone_timestep_after_zone_reporting(state, time_step_handler)
api.exchange.request_variable(state, "SITE OUTDOOR AIR DRYBULB TEMPERATURE", "ENVIRONMENT")
api.runtime.run_energyplus(state, sys.argv[1:])

Note that when strings are passed through the Python API, they are both case-insensitive, and type-insensitive. By type-insensitive, this means they can be either Python strings, or Python bytes objects. In this example, both are used, as well as mixed-casing, to demonstrate the flexibility.

Full Examples[LINK]

Demonstrations of these APIs are available on the EnergyPlus repository in both Python and C form. For the latest development version, they are at this page.

Building and Linking[LINK]

Once you have your C or Python code scripted up, you need to actually link that code to the EnergyPlus library. The EnergyPlus install includes several relevant pieces:

EnergyPlusInstallRoot
 - include
   - EnergyPlus
      - api
         - datatransfer.h
         - EnergyPlusAPI.h
         - func.h
         - runtime.h
         - state.h
 - libenergyplusapi.so
 - pyenergyplus
 - api.py

When building C applications, the build should include the EnergyPlusInstallRoot path in your include path, so that when the client has an #include<EnergyPlus/api/func.h>, it will be able to find it relative to the EnergyPlus install root path. Once the code is compiled, it should be linked to the EnergyPlus shared library, which also lives in the root of the EnergyPlus install. The actual command will be different based on your system and compiler, but using gcc on Linux, for example, the command passed to the linker would be -l/path/to/libenergyplusapi.so.

When building Python applications, the EnergyPlusInstallRoot should be added to the search path prior to trying to import anything else. This can be accomplished through the use of environment variables, but it is also easy to do at the beginning of scripts, for example:

import sys
sys.path.insert(0, '/path/to/EnergyPlusInstallRoot')
from pyenergyplus import api