Coverage for bim2sim/plugins/PluginTEASER/bim2sim_teaser/task/create_result_df.py: 0%
75 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
1from ebcpy import TimeSeriesData
2import pandas as pd
3import pint_pandas
5from bim2sim.tasks.base import ITask
6from bim2sim.elements.mapping.units import ureg
7from bim2sim.utilities.common_functions import filter_elements
9# Important: if these are adjusted, also adjust export_vars in ExportTEASER
10bim2sim_teaser_mapping_base = {
11 "multizonePostProcessing.PHeaterSum": "heat_demand_total",
12 "multizonePostProcessing.PHeater[numZones]": "heat_demand_rooms",
13 "multizonePostProcessing.PCoolerSum": "cool_demand_total",
14 "multizonePostProcessing.PCooler[numZones]": "cool_demand_rooms",
15 "multizonePostProcessing.WHeaterSum": "heat_energy_total",
16 "multizonePostProcessing.WCoolerSum": "cool_energy_total",
17 "multizonePostProcessing.WHeater[numZones].y": "heat_energy_rooms",
18 "multizonePostProcessing.WCooler[numZones].y": "cool_energy_rooms",
19 "weaDat.weaBus.TDryBul": "air_temp_out",
20 "multizonePostProcessing.TOperativeAverageCalc.u[numZones]":
21 "operative_temp_rooms",
22 "multizonePostProcessing.TAir[numZones]": "air_temp_rooms",
23 # TODO check if the array indexing works correctly
24 "multizonePostProcessing.QIntGains_flow[numZones,1]":
25 "internal_gains_lights_rooms",
26 "multizonePostProcessing.QIntGains_flow[numZones,2]":
27 "internal_gains_machines_rooms",
28 "multizonePostProcessing.QIntGains_flow[numZones,3]":
29 "internal_gains_persons_rooms",
30 # TODO calculate by specificPersons*roomArea
31 # "multizone.zone[numZones].humanSenHeaDependent.specificPersons *"
32 # " multizone.zone[numZones].humanSenHeaDependent.roomArea":
33 # "n_persons_rooms",
34 "multizone.zone[numZones].ventCont.y": "infiltration_rooms",
35 "multizone.zone[numZones].ventRate": "mech_ventilation_rooms",
36 "tableTSet.y[numZones]": "heat_set_rooms",
37 "tableTSetCool.y[numZones]": "cool_set_rooms",
38 "CPUtime": "cpu_time"
39}
41# bim2sim_teaser_indirect_mapping = {
42# "n_persons_rooms": [
43# "*",
44# "multizone.zone[numZones].humanSenHeaDependent.specificPersons",
45# "multizone.zone[numZones].humanSenHeaDependent.roomArea"]
46# }
48pint_pandas.PintType.ureg = ureg
49unit_mapping = {
50 "heat_demand": ureg.watt,
51 "cool_demand": ureg.watt,
52 "heat_energy": ureg.joule,
53 "cool_energy": ureg.joule,
54 "operative_temp": ureg.kelvin,
55 "air_temp": ureg.kelvin,
56 "infiltration": ureg.hour ** -1,
57 "mech_ventilation": ureg.hour ** -1,
58 "heat_set": ureg.kelvin,
59 "cool_set": ureg.kelvin,
60 "internal_gains": ureg.watt,
61 "cpu_time": ureg.second
62}
65class CreateResultDF(ITask):
66 """Creates result dataframe, run() method holds detailed information."""
68 reads = ('sim_results_path', 'bldg_names', 'elements')
69 touches = ('df_finals',)
71 def run(self, sim_results_path, bldg_names, elements):
72 """Creates a result dataframe for BEPS simulations.
74 The created dataframe holds the data for the generic plot
75 functionality for BEPS simulation in `bim2sim` post-processing,
76 regardless if the simulation results come from TEASER
77 or EnergyPlus. Therefore, the simulation results are mapped into a
78 generic form with unified timestamps and only the results wanted,
79 defined by the sim_settings, are exported to the dataframe.
81 Args:
82 teaser_mat_result_paths: path to simulation result file
83 bldg_names (list): list of all buildings
84 elements: bim2sim elements created based on ifc data
85 Returns:
86 df_final: final dataframe that holds only relevant data, with
87 generic `bim2sim` names and index in form of MM/DD-hh:mm:ss
88 """
89 if not self.playground.sim_settings.dymola_simulation:
90 self.logger.warning("Skipping task CreateResultDF as sim_setting "
91 "'dymola_simulation' is set to False and no "
92 "simulation was performed.")
93 return None,
94 if not self.playground.sim_settings.create_plots:
95 self.logger.warning("Skipping task CreateResultDF as sim_setting "
96 "'create_plots' is set to False and no "
97 "DataFrame ist needed.")
98 return None,
99 # ToDO handle multiple buildings/ifcs #35
100 df_finals = {}
101 for bldg_name in bldg_names:
102 result_path = sim_results_path / bldg_name / "teaser_results.mat"
103 bim2sim_teaser_mapping_selected = self.select_wanted_results()
104 # bim2sim_teaser_mapping = self.calc_indirect_result()
105 bim2sim_teaser_mapping = self.map_zonal_results(
106 bim2sim_teaser_mapping_selected, elements)
107 relevant_vars = list(bim2sim_teaser_mapping.keys())
108 df_original = TimeSeriesData(
109 result_path, variable_names=relevant_vars).to_df()
110 df_final = self.format_dataframe(
111 df_original, bim2sim_teaser_mapping, relevant_vars)
112 df_finals[bldg_name] = df_final
113 return df_finals,
115 def format_dataframe(
116 self, df_original: pd.DataFrame,
117 bim2sim_teaser_mapping: dict,
118 relevant_vars: list) -> pd.DataFrame:
119 """Formats the dataframe to generic bim2sim output structure.
121 This function:
122 - adds the space GUIDs or bim2sim aggregated room names to the results
123 - selects only the selected simulation outputs from the result
124 - sets the index to the format MM/DD-hh:mm:ss
125 - uses absolute values instead negative ones for cooling
127 Args:
128 df_original: original dataframe directly taken from simulation
129 bim2sim_teaser_mapping: dict[simulation var name:
130 relevant_vars: list of simulation variables to have in dataframe
132 Returns:
133 df_final: converted dataframe in `bim2sim` result structure
134 """
136 # rename columns based on bim2sim_teaser_mapping
137 df_final = df_original.rename(
138 columns=bim2sim_teaser_mapping)
140 # update index to format MM/DD-hh:mm:ss
141 df_final = self.convert_time_index(df_final)
143 # make energy consumptions hourly instead cumulated
144 for column in df_final.columns:
145 if "_energy_" in column:
146 df_final[column] = df_final[column].diff()
147 df_final.fillna(0, limit=1, inplace=True)
149 # convert negative cooling demands and energies to absolute values
150 df_final = df_final.abs()
152 # handle units
153 for column in df_final:
154 for key, unit in unit_mapping.items():
155 if key in column:
156 df_final[column] = pint_pandas.PintArray(
157 df_final[column], unit)
159 return df_final
161 def select_wanted_results(self):
162 """Selected only the wanted outputs based on sim_setting sim_results"""
163 bim2sim_teaser_mapping = bim2sim_teaser_mapping_base.copy()
164 for key, value in bim2sim_teaser_mapping_base.items():
165 if value not in self.playground.sim_settings.sim_results:
166 del bim2sim_teaser_mapping[key]
167 return bim2sim_teaser_mapping
169 @staticmethod
170 def map_zonal_results(bim2sim_teaser_mapping_selected, elements: dict):
171 """Add zone/space guids/names to mapping dict.
173 Dymola outputs the results just via a counting of zones/rooms
174 starting with [1]. This function adds the real zone/space guids or
175 aggregation names to the dict for easy readable results.
176 Rooms are mapped with their space GUID, aggregated zones are mapped
177 with their zone name. Therefore, we load the additional information
178 like what spaces are aggregated from elements structure.
180 Args:
181 bim2sim_teaser_mapping_selected: dict holds the mapping between
182 simulation outputs and generic `bim2sim` output names. Only
183 outputs selected via sim_results sim-setting are included.
184 elements: dict[guid: element]
186 Returns:
187 dict: A mapping between simulation results and space guids, with
188 appropriate adjustments for aggregated zones.
190 """
191 bim2sim_teaser_mapping = {}
192 space_guid_list = []
193 thermal_zones = filter_elements(elements, 'ThermalZone')
194 for tz in thermal_zones:
195 space_guid_list.append(tz.guid)
196 for key, value in bim2sim_teaser_mapping_selected.items():
197 # add entry for each room/zone
198 if "numZones" in key:
199 for i, space_guid in enumerate(space_guid_list, 1):
200 new_key = key.replace("numZones", str(i))
201 new_value = value.replace("rooms", "rooms_" + space_guid)
202 bim2sim_teaser_mapping[new_key] = new_value
203 else:
204 bim2sim_teaser_mapping[key] = value
205 return bim2sim_teaser_mapping
207 @staticmethod
208 def convert_time_index(df):
209 """This converts the index of the result df to "days hh:mm:ss format"""
210 # Convert the index to a timedelta object
211 df.index = pd.to_timedelta(df.index, unit='s')
212 # handle leap years
213 if len(df.index) > 8761:
214 year = 2020
215 else:
216 year = 2021
217 # Add the specified year to the date
218 # df.index = pd.date_range(start=f'{year}-01-01', end=f'{year+1}-01-01', freq='H')
219 df.index = pd.to_datetime(
220 df.index.total_seconds(), unit='s', origin=f'{year}-01-01')
221 # TODO remove this in EP as well if correct
222 # # Format the date to [mm/dd-hh:mm:ss]
223 # df.index = df.index.strftime('%m/%d-%H:%M:%S')
225 # delete last value (which is first value of next year) to have 8760
226 # time steps
227 df = df[:-1]
228 return df
230 @staticmethod
231 def calc_indirect_result(bim2sim_teaser_mapping_selected):
232 # TODO
233 pass