Combining EnergyPlus and EnergyHub Evaluators

This notebook covers different ways to use EnergyPlus and PyEHub Evaluators together.

import numpy as np
import pandas as pd
from besos import eppy_funcs as ef, pyehub_funcs as pf
from besos.evaluator import EvaluatorEH, EvaluatorEP
from besos.objectives import MeterReader
from besos.parameters import (
    FieldSelector,
    Parameter,
    PathSelector,
    RangeParameter,
)
from besos.problem import EHProblem, EPProblem

Custom EnergyPlus Evaluator Functions

First we need to define a way to get a whole time series from EnergyPlus Evaluator, not just an objective function value. To extract a time series from an EnergyPlus Evaluator the default summation function must be replaced. The function timeseriesfunc returns the entire Pandas Series from the output of the EnergyPlus simulation.

def timeseriesfunc(result):
    return result.data["Value"]

Create an EnergyPlus Evaluator

Here is a standard EnergyPlus Evaluator for editing the lighting power density for the default building and getting the electricity demand time series. The output is a Pandas Series, and the units are Joules.

building = ef.get_building()
EPparameters = [
    Parameter(
        FieldSelector("Lights", "*", "Watts per Zone Floor Area"),
        value_descriptor=RangeParameter(8, 12),
        name="Lights Watts/Area",
    )
]
EPobjectives = MeterReader("Electricity:Facility", func=timeseriesfunc)
problem = EPProblem(EPparameters, EPobjectives)
evaluator = EvaluatorEP(problem, building)
result = evaluator([8])
result
/home/user/.local/lib/python3.7/site-packages/besos/parameters.py:425: FutureWarning: Use value_descriptors instead of value_descriptor.
  FutureWarning("Use value_descriptors instead of value_descriptor.")
(0     5.041708e+07
 1     5.142561e+07
 2     5.193728e+07
 3     5.111495e+07
 4     4.728000e+07
 5     4.371469e+07
 6     5.956208e+07
 7     5.870644e+07
 8     6.031615e+07
 9     6.127009e+07
 10    6.202866e+07
 11    6.294521e+07
 12    6.369996e+07
 13    6.431286e+07
 14    6.507253e+07
 15    6.522390e+07
 16    6.471212e+07
 17    6.365803e+07
 18    6.167204e+07
 19    6.648230e+07
 20    6.682431e+07
 21    6.601229e+07
 22    4.320751e+07
 23    4.910526e+07
 24    1.431370e+07
 25    1.310912e+07
 26    1.431370e+07
 27    1.310912e+07
 28    1.431370e+07
 29    1.310912e+07
 30    1.551827e+07
 31    8.609274e+06
 32    7.227474e+06
 33    7.227474e+06
 34    7.227474e+06
 35    7.227474e+06
 36    7.227474e+06
 37    7.227474e+06
 38    7.227474e+06
 39    7.227474e+06
 40    9.991074e+06
 41    1.551827e+07
 42    1.407278e+07
 43    1.310912e+07
 44    1.431370e+07
 45    1.310912e+07
 46    1.431370e+07
 47    1.310912e+07
 Name: Value, dtype: float64,)

EnergyPlus Evaluator Output conversions

To ensure the output of the EnergyPlus evaluator is in the correct format for the Energy Hub, some conversions are required.

First the result is converted from a Pandas Series to a dataframe.

act_result = result[0].to_frame()

Splitting into days

Then because EnergyPlus simulated a summer design day and a winter design day, the output is split and their indexes reset.

cold_result = act_result.head(24)
cold_result = cold_result.reset_index()
warm_result = act_result.tail(24)
warm_result = warm_result.reset_index()

Unit Conversions

The output for an energy Output:Meter in EnergyPlus is in Joules but EnergyHub deals with kWh so the entire dataframe for both days is converted. They are then turned into dictionaries with the keys being the time series index.

cold_result = cold_result / 3600000
cold_dict = cold_result.to_dict()
cold_dict = cold_dict["Value"]

warm_result = warm_result / 3600000
warm_dict = warm_result.to_dict()
warm_dict = warm_dict["Value"]

Wrapping in Dictionaries

Lastly the dictionaries are wrapped as lists to match the input format for EnergyHub Evaluators.

cold_input = [cold_dict]
warm_input = [warm_dict]

Create a PyEHub Evaluator

Here is a standard PyEHub Evaluator for editing the electrical load of a simple energy hub, minimizing the total cost and outputting both the total cost and total carbon emissions from the optimizied hub. See EHEvaluator for more details. It is applied to the Energy Hub model specified in config

EHparameters = [Parameter(PathSelector(["LOADS", "Elec"]))]
EHobjectives = ["total_cost", "total_carbon"]
EHproblem = EHProblem(EHparameters, EHobjectives)
hub = pf.get_hub()
EHevaluator = EvaluatorEH(EHproblem, hub)

Single timeseries for PyEHub Evaluator

The wrapped dictionary inputs can be used directly as input for the PyEHub Evaluator.

result1 = EHevaluator(cold_input)
result1
(1856.41, 77.5222)
result2 = EHevaluator(warm_input)
result2
(1839.45, 31.8574)

Dataframe of time series

These inputs can be combined into a single dataframe and used as input for the evaluators.

seasons_df = pd.DataFrame(np.array([warm_input, cold_input]), columns=["p1"])
result3 = EHevaluator.df_apply(seasons_df)
result3
HBox(children=(FloatProgress(value=0.0, description='Executing', max=2.0, style=ProgressStyle(description_widt…
total_cost total_carbon
0 1839.45 31.8574
1 1856.41 77.5222

Energy Plus Dataframe input

If the input of the EnergyPlus Evaluator is a dataframe, then df_apply can be used to execute the evaluator.

EPdf = pd.DataFrame(np.array([[8], [9], [10], [12]]), columns=["p1"])
df_results = evaluator.df_apply(EPdf)
df_results
HBox(children=(FloatProgress(value=0.0, description='Executing', max=4.0, style=ProgressStyle(description_widt…
Electricity:Facility
0 0 5.041708e+07 1 5.142561e+07 2 5....
1 0 5.377034e+07 1 5.481425e+07 2 5....
2 0 5.577863e+07 1 5.670179e+07 2 5....
3 0 6.187128e+07 1 5.976898e+07 2 6....

EnergyPlus Dataframe output conversion

The conversions must be done while maintaining a dataframe to be used with df_apply for the PyEHub Evaluator. The previous splitting of days, unit conversions, and wrapping dictionaries must be done for every output of the EnergyPlus Dataframe, then appended into a larger dataframe to be used as the input to the Energy Hub. The larger dataframe is prepared with the number of objectives from the EnergyPlus simulator.

results_dicts = df_results.to_dict()
columnnames = []
for j in results_dicts:
    columnnames.append(j)

df_input = pd.DataFrame(columns=columnnames)

for j in results_dicts:
    for i in results_dicts[j]:

        act_result = results_dicts[j][i].to_frame()

        cold_result = act_result.head(24)
        cold_result = cold_result.reset_index()
        warm_result = act_result.tail(24)
        warm_result = warm_result.reset_index()

        cold_result = cold_result / 3600000
        cold_dict = cold_result.to_dict()
        cold_dict = cold_dict["Value"]

        warm_result = warm_result / 3600000
        warm_dict = warm_result.to_dict()
        warm_dict = warm_dict["Value"]

        temp_df1 = pd.DataFrame(np.array(cold_input), columns=[j])
        temp_df2 = pd.DataFrame(np.array(warm_input), columns=[j])
        df_input = df_input.append(temp_df1, ignore_index=True)
        df_input = df_input.append(temp_df2, ignore_index=True)
df_input
Electricity:Facility
0 {0: 14.004744327135537, 1: 14.284890432124438,...
1 {0: 3.97602646966534, 1: 3.6414211757330794, 2...
2 {0: 14.004744327135537, 1: 14.284890432124438,...
3 {0: 3.97602646966534, 1: 3.6414211757330794, 2...
4 {0: 14.004744327135537, 1: 14.284890432124438,...
5 {0: 3.97602646966534, 1: 3.6414211757330794, 2...
6 {0: 14.004744327135537, 1: 14.284890432124438,...
7 {0: 3.97602646966534, 1: 3.6414211757330794, 2...

EnergyPlus to EnergyHub Dataframe output

The converted output from the EnergyPlus Evaluator can then be used as input for the PyEHub evaluator.

result4 = EHevaluator.df_apply(df_input)
result4
HBox(children=(FloatProgress(value=0.0, description='Executing', max=8.0, style=ProgressStyle(description_widt…
total_cost total_carbon
0 1856.41 77.5222
1 1839.45 31.8574
2 1856.41 77.5222
3 1839.45 31.8574
4 1856.41 77.5222
5 1839.45 31.8574
6 1856.41 77.5222
7 1839.45 31.8574