Applying TimeStamp Dates on EvaluatorEP Outputs

import eppy
import numpy as np
import pandas as pd
import pvlib
from besos import config, eppy_funcs as ef, objectives
from besos.evaluator import EvaluatorEP
from besos.objectives import MeterReader
from besos.parameters import FieldSelector, Parameter, RangeParameter
from besos.problem import EPProblem

Recovering the dates for the index

Different modes of inputs

As the EnergyPlus version currently used on Besos supports both IDF files that turn into eppy models and IDF files that turn into JSON examples of both are provided below along with their corresponding epw files.

epw_file_JSON = config.files["epw"]
epw_file_IDF = "USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw"
building_JSON = ef.get_building()
building_IDF = ef.get_building(building="RefBldgWarehouseNew2004_Chicago.idf")

building = building_IDF
epw_file = epw_file_IDF

Getting Annual Timestamps from EPW

The annual timestamps that are associated with the results of the evaluator can be pulled from the EPW file

df, metadata = pvlib.iotools.read_epw(epw_file)

df2 = df.reset_index()
df2["index"]
0      1986-01-01 00:00:00-06:00
1      1986-01-01 01:00:00-06:00
2      1986-01-01 02:00:00-06:00
3      1986-01-01 03:00:00-06:00
4      1986-01-01 04:00:00-06:00
                  ...
8755   1981-12-31 19:00:00-06:00
8756   1981-12-31 20:00:00-06:00
8757   1981-12-31 21:00:00-06:00
8758   1981-12-31 22:00:00-06:00
8759   1981-12-31 23:00:00-06:00
Name: index, Length: 8760, dtype: datetime64[ns, pytz.FixedOffset(-360)]

Getting Sizing Period Dates and merging with EPW Dates

It is possible that the idf has been set to run the simulation across sizing periods. If so those design days dates are grabed from the IDF and hourly timestamps are generated to match the timestamps from the EPW. The design days are the first outputed in the results so the EPW timestamps are appended after teh design days.

date_index = []

if type(building) == dict:
    # There can be multiple simulation controls for some reason but rarely is this used and we don't use it.
    #     for simulation in building['SimulationControl']:
    #         if(building['SimulationControl'][simulation]['run_simulation_for_sizing_periods'] == 'Yes'):\
    #         print(simulation + ' Uses design days')
    if (
        building["SimulationControl"]["SimulationControl 1"][
            "run_simulation_for_sizing_periods"
        ]
        == "Yes"
    ):
        for design_day in building["SizingPeriod:DesignDay"]:
            day = building["SizingPeriod:DesignDay"][design_day]["day_of_month"]
            month = building["SizingPeriod:DesignDay"][design_day]["month"]
            for hour in range(24):
                date_index.append(
                    pd.Timestamp(year=1900, month=month, day=day, hour=hour)
                )

elif type(building) == eppy.modeleditor.IDF:
    # There can be multiple simulation controls for some reason but rarely is this used and we don't use it.
    #     for i, simulation in enumerate(building.idfobjects['SIMULATIONCONTROL']):
    #         if(simulation.Run_Simulation_for_Sizing_Periods == 'YES'):
    #             print('SimulationControl ' + str(i+1) + ' Uses design days')
    if (
        building.idfobjects["SIMULATIONCONTROL"][0].Run_Simulation_for_Sizing_Periods
        == "YES"
    ):
        for j, design_day in enumerate(
            building_IDF.idfobjects["SIZINGPERIOD:DESIGNDAY"]
        ):
            day = design_day.Day_of_Month
            month = design_day.Month
            for hour in range(24):
                date_index.append(
                    pd.Timestamp(year=1900, month=month, day=day, hour=hour)
                )

epw_datelist = df.index.tolist()
date_index.extend(epw_datelist)
dates = pd.DataFrame(date_index)

Creating and running a single input of an Evaluator

EPparameters = [
    Parameter(
        FieldSelector("Lights", "*", "Watts per Zone Floor Area"),
        value_descriptor=RangeParameter(8, 12),
        name="Lights Watts/Area",
    )
]
EPobjectives = [
    MeterReader("Electricity:Facility", func=objectives.time_series_values),
    MeterReader("Gas:Facility", func=objectives.time_series_values),
]
problem = EPProblem(EPparameters, EPobjectives)
evaluator = EvaluatorEP(problem, building)
result = evaluator([8])
/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.")
/home/user/.local/lib/python3.7/site-packages/besos/objectives.py:225: UserWarning: time_series_values is incomplete, and returns raw values, not time series values.
  "time_series_values is incomplete, and returns raw values, not time series values."

Applying dates to the single result

Each objective in a single result is outputted in a tuple. Each element of the tuple is a Pandas Series. To ensure the output format is unchanged the result is iterated through, the dates are applied as the index, and then the results are converted back to a series and combined back in a tuple.

dated_output = ()
for output in result:
    output_frame = output.to_frame()
    df = dates.merge(output_frame, left_index=True, right_index=True).set_index(0)
    del df.index.name
    ds = df.squeeze()
    dated_output = dated_output + (ds,)
dated_output
(1986-01-01 00:00:00-06:00    5.729183e+07
 1986-01-01 01:00:00-06:00    5.221348e+07
 1986-01-01 02:00:00-06:00    5.732566e+07
 1986-01-01 03:00:00-06:00    5.226460e+07
 1986-01-01 04:00:00-06:00    5.735000e+07
                                  ...
 1981-12-31 19:00:00-06:00    6.735182e+07
 1981-12-31 20:00:00-06:00    6.366284e+07
 1981-12-31 21:00:00-06:00    6.801351e+07
 1981-12-31 22:00:00-06:00    6.436717e+07
 1981-12-31 23:00:00-06:00    6.954238e+07
 Name: Value, Length: 8760, dtype: float64,
 1986-01-01 00:00:00-06:00    9.514979e+08
 1986-01-01 01:00:00-06:00    8.438877e+08
 1986-01-01 02:00:00-06:00    9.526037e+08
 1986-01-01 03:00:00-06:00    8.452071e+08
 1986-01-01 04:00:00-06:00    9.534275e+08
                                  ...
 1981-12-31 19:00:00-06:00    5.001711e+08
 1981-12-31 20:00:00-06:00    4.360091e+08
 1981-12-31 21:00:00-06:00    5.290179e+08
 1981-12-31 22:00:00-06:00    4.823138e+08
 1981-12-31 23:00:00-06:00    6.210604e+08
 Name: Value, Length: 8760, dtype: float64)

Running a df_apply of the Evaluator

df_input = pd.DataFrame(np.array([[8], [9], [10], [11]]), columns=["p1"])
results = evaluator.df_apply(df_input)
HBox(children=(FloatProgress(value=0.0, description='Executing', max=4.0, style=ProgressStyle(description_widt…
results
Electricity:Facility Gas:Facility
0 0 5.729183e+07 1 5.221348e+07 2 ... 0 9.514979e+08 1 8.438877e+08 2 ...
1 0 5.729183e+07 1 5.221348e+07 2 ... 0 9.514979e+08 1 8.438877e+08 2 ...
2 0 5.729183e+07 1 5.221348e+07 2 ... 0 9.514979e+08 1 8.438877e+08 2 ...
3 0 5.729183e+07 1 5.221348e+07 2 ... 0 9.514979e+08 1 8.438877e+08 2 ...

Applying Dates to df_apply results

When the evaluator is called with df_apply the results are in a Pandas Dataframe. Each cell is an objective’s Pandas Series. To ensure the output format is unchanged each cell is iterated across and the dates are applied as the index. Then the results are converted back to a series and the cell is updated with that series.

for x, key in enumerate(results):
    for y, output in enumerate(results[key]):
        output_frame = output.to_frame()
        df = dates.merge(output_frame, left_index=True, right_index=True).set_index(0)
        del df.index.name
        ds = df.squeeze()
        results.iat[y, x] = ds
results
Electricity:Facility Gas:Facility
0 1986-01-01 00:00:00-06:00 5.729183e+07 1986... 1986-01-01 00:00:00-06:00 9.514979e+08 1986...
1 1986-01-01 00:00:00-06:00 5.729183e+07 1986... 1986-01-01 00:00:00-06:00 9.514979e+08 1986...
2 1986-01-01 00:00:00-06:00 5.729183e+07 1986... 1986-01-01 00:00:00-06:00 9.514979e+08 1986...
3 1986-01-01 00:00:00-06:00 5.729183e+07 1986... 1986-01-01 00:00:00-06:00 9.514979e+08 1986...