Coverage for bim2sim/plugins/PluginEnergyPlus/bim2sim_energyplus/task/create_result_df.py: 0%
87 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +0000
1import json
2from pathlib import Path
4import pandas as pd
5import pint_pandas
6from geomeppy import IDF
7from pint_pandas import PintArray
9from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus.task import \
10 IdfPostprocessing
11from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus.utils import \
12 PostprocessingUtils
13from bim2sim.tasks.base import ITask
14from bim2sim.elements.mapping.units import ureg
15from bim2sim.utilities.common_functions import filter_elements
17bim2sim_energyplus_mapping_base = {
18 "NOT_AVAILABLE": "heat_demand_total",
19 "SPACEGUID IDEAL LOADS AIR SYSTEM:Zone Ideal Loads Zone Total Heating "
20 "Rate [W](Hourly)": "heat_demand_rooms",
21 "NOT_AVAILABLE": "cool_demand_total",
22 "SPACEGUID IDEAL LOADS AIR SYSTEM:Zone Ideal Loads Zone Total Cooling "
23 "Rate [W](Hourly)": "cool_demand_rooms",
24 "Heating:EnergyTransfer [J](Hourly)": "heat_energy_total",
25 "Cooling:EnergyTransfer [J](Hourly) ": "cool_energy_total",
26 "SPACEGUID IDEAL LOADS AIR SYSTEM:Zone Ideal Loads Zone Total Heating Energy [J](Hourly)":
27 "heat_energy_rooms",
28 "SPACEGUID IDEAL LOADS AIR SYSTEM:Zone Ideal Loads Zone Total Cooling Energy [J](Hourly)":
29 "cool_energy_rooms",
30 "Environment:Site Outdoor Air Drybulb Temperature [C](Hourly)":
31 "air_temp_out",
32 "SPACEGUID:Zone Operative Temperature [C](Hourly)":
33 "operative_temp_rooms",
34 "SPACEGUID:Zone Mean Air Temperature [C](Hourly)": "air_temp_rooms",
35 "SPACEGUID:Zone Electric Equipment Total Heating Rate [W](Hourly)": "internal_gains_machines_rooms",
36 "SPACEGUID:Zone People Total Heating Rate [W](Hourly)": "internal_gains_persons_rooms",
37 "SPACEGUID:Zone People Occupant Count [](Hourly)": "n_persons_rooms",
38 "SPACEGUID:Zone Lights Total Heating Rate [W](Hourly)": "internal_gains_lights_rooms",
39 "SPACEGUID:Zone Infiltration Air Change Rate [ach](Hourly)": "infiltration_rooms",
40 "SPACEGUID:Zone Ventilation Standard Density Volume Flow Rate [m3/s](Hourly)": "mech_ventilation_rooms",
41 "SPACEGUID:Zone Thermostat Heating Setpoint Temperature [C](Hourly)": "heat_set_rooms",
42 "SPACEGUID:Zone Thermostat Cooling Setpoint Temperature [C](Hourly)": "cool_set_rooms",
43 "BOUNDGUID:Surface Inside Face Temperature [C](Hourly)": "surf_inside_temp",
44}
46pint_pandas.PintType.ureg = ureg
47unit_mapping = {
48 "heat_demand": ureg.watt,
49 "cool_demand": ureg.watt,
50 "heat_energy": ureg.joule,
51 "cool_energy": ureg.joule,
52 "operative_temp": ureg.degree_Celsius,
53 "air_temp": ureg.degree_Celsius,
54 "heat_set": ureg.degree_Celsius,
55 "cool_set": ureg.degree_Celsius,
56 "internal_gains": ureg.watt,
57 "n_persons": ureg.dimensionless,
58 "infiltration": ureg.hour ** (-1),
59 "mech_ventilation": (ureg.meter ** 3) / ureg.second,
60}
63class CreateResultDF(ITask):
64 """This ITask creates a result dataframe for EnergyPlus BEPS simulations
66 See detailed explanation in the run function below.
67 """
69 reads = ('idf', 'sim_results_path', 'elements')
70 touches = ('df_finals',)
72 def run(self, idf: IDF, sim_results_path: Path, elements: dict) \
73 -> dict[str: pd.DataFrame]:
74 """ Create a result DataFrame for EnergyPlus BEPS results.
76 This function transforms the EnergyPlus simulation results to the
77 general result data format used in this bim2sim project. The
78 simulation results stored in the EnergyPlus result file (
79 "eplusout.csv") are shifted by one hour to match the simulation
80 results of the modelica simulation. Afterwards, the simulation
81 results are formatted to match the bim2sim dataframe format.
83 Args:
84 idf (IDF): eppy idf
85 sim_results_path (Path): path to the simulation results from
86 EnergyPlus
87 elements (dict): dictionary in the format dict[guid: element],
88 holds preprocessed elements including space boundaries.
89 Returns:
90 df_finals (dict): dictionary in the format
91 dict[str(project name): pd.DataFrame], final dataframe
92 that holds only relevant data, with generic `bim2sim` names and
93 index in form of MM/DD-hh:mm:ss
95 """
96 # ToDO handle multiple buildings/ifcs #35
97 df_finals = {}
98 if not self.playground.sim_settings.create_plots:
99 self.logger.warning("Skipping task CreateResultDF as sim_setting "
100 "'create_plots' is set to False and no "
101 "DataFrame ist needed.")
102 return df_finals,
103 raw_csv_path = sim_results_path / self.prj_name / 'eplusout.csv'
104 # TODO @Veronika: the zone_dict.json can be removed and instead the
105 # elements structure can be used to get the zone guids
106 zone_dict_path = sim_results_path / self.prj_name / 'zone_dict.json'
107 if not zone_dict_path.exists():
108 IdfPostprocessing.write_zone_names(idf, elements,
109 sim_results_path / self.prj_name)
110 with open(zone_dict_path) as j:
111 zone_dict = json.load(j)
113 # create dict for mapping surfaces to spaces
114 space_bound_dict = {}
115 spaces = filter_elements(elements, 'ThermalZone')
116 for space in spaces:
117 space_guids = []
118 for bound in space.space_boundaries:
119 if isinstance(bound, str):
120 space_guids.append(bound)
121 else:
122 space_guids.append(bound.guid)
123 space_bound_dict[space.guid] = space_guids
124 with open(sim_results_path / self.prj_name / 'space_bound_dict.json',
125 'w+') as file:
126 json.dump(space_bound_dict, file, indent=4)
128 df_original = PostprocessingUtils.read_csv_and_format_datetime(
129 raw_csv_path)
130 df_original = (
131 PostprocessingUtils.shift_dataframe_to_midnight(df_original))
132 df_final = self.format_dataframe(df_original, zone_dict,
133 space_bound_dict)
134 df_finals[self.prj_name] = df_final
136 return df_finals,
138 def format_dataframe(
139 self, df_original: pd.DataFrame, zone_dict: dict,
140 space_bound_dict: dict) -> pd.DataFrame:
141 """Formats the dataframe to generic bim2sim output structure.
143 This function:
144 - adds the space GUIDs to the results
145 - selects only the selected simulation outputs from the result
147 Args:
148 df_original: original dataframe directly taken from simulation
149 zone_dict: dictionary with all zones, in format {GUID : Zone Usage}
150 space_bound_dict: dictionary with space_guid and a list of space
151 boundary guids of this space.
152 Returns:
153 df_final: converted dataframe in `bim2sim` result structure
154 """
155 bim2sim_energyplus_mapping = self.map_zonal_results(
156 bim2sim_energyplus_mapping_base, zone_dict, space_bound_dict,
157 self.playground.sim_settings.plot_singe_zone_guid)
158 # select only relevant columns
159 short_list = \
160 list(bim2sim_energyplus_mapping.keys())
161 short_list.remove('NOT_AVAILABLE')
162 df_final = df_original[df_original.columns[
163 df_original.columns.isin(short_list)]].rename(
164 columns=bim2sim_energyplus_mapping)
166 # convert negative cooling demands and energies to absolute values
167 energy_and_demands = df_final.filter(like='energy').columns.union(
168 df_final.filter(like='demand').columns)
169 df_final[energy_and_demands].abs()
170 heat_demand_columns = df_final.filter(like='heat_demand')
171 cool_demand_columns = df_final.filter(like='cool_demand')
172 df_final['heat_demand_total'] = heat_demand_columns.sum(axis=1)
173 df_final['cool_demand_total'] = cool_demand_columns.sum(axis=1)
174 # handle units
175 for column in df_final:
176 for key, unit in unit_mapping.items():
177 if key in column:
178 df_final[column] = PintArray(df_final[column], unit)
180 return df_final
182 def select_wanted_results(self):
183 """Selected only the wanted outputs based on sim_setting sim_results"""
184 bim2sim_energyplus_mapping = bim2sim_energyplus_mapping_base.copy()
185 for key, value in bim2sim_energyplus_mapping_base.items():
186 if value not in self.playground.sim_settings.sim_results:
187 del bim2sim_energyplus_mapping[key]
188 return bim2sim_energyplus_mapping
190 @staticmethod
191 def map_zonal_results(bim2sim_energyplus_mapping_base, zone_dict,
192 space_bound_dict=None, plot_single_zone_guid=None):
193 """Add zone/space guids/names to mapping dict.
195 EnergyPlus outputs the results referencing to the IFC-GlobalId. This
196 function adds the real zone/space guids or
197 aggregation names to the dict for easy readable results.
198 Rooms are mapped with their space GUID, aggregated zones are mapped
199 with their zone name. The mapping between zones and rooms can be taken
200 from tz_mapping.json file with can be found in export directory.
202 Args:
203 bim2sim_energyplus_mapping_base: Holds the mapping between
204 simulation outputs and generic `bim2sim` output names.
205 zone_dict: dictionary with all zones, in format {GUID : Zone Usage}
206 space_bound_dict: dictionary mapping space guids and their bounds
207 plot_single_zone_guid: guid of single space that should be analyzed
208 Returns:
209 dict: A mapping between simulation results and space guids, with
210 appropriate adjustments for aggregated zones.
212 """
213 bim2sim_energyplus_mapping = {}
214 space_guid_list = list(zone_dict.keys())
215 for key, value in bim2sim_energyplus_mapping_base.items():
216 # add entry for each room/zone
217 if "SPACEGUID" in key:
218 # TODO write case sensitive GUIDs into dataframe
219 for i, space_guid in enumerate(space_guid_list):
220 new_key = key.replace("SPACEGUID", space_guid.upper())
221 # todo: according to #497, names should keep a _zone_ flag
222 new_value = value.replace("rooms", 'rooms_' + space_guid)
223 bim2sim_energyplus_mapping[new_key] = new_value
224 elif "BOUNDGUID" in key and space_bound_dict is not None:
225 for i, space in enumerate(space_bound_dict):
226 if plot_single_zone_guid and \
227 space not in plot_single_zone_guid:
228 # avoid loading space boundary data of multiple
229 # zones unless all zones should be considered to
230 # avoid an unreasonably large dataframe
231 continue
232 for bound in space_bound_dict[space]:
233 guid = bound
234 new_key = key.replace("BOUNDGUID", guid.upper())
235 new_value = value.replace("temp", "temp_" + guid)
236 bim2sim_energyplus_mapping[new_key] = new_value
237 else:
238 bim2sim_energyplus_mapping[key] = value
239 return bim2sim_energyplus_mapping