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

1from ebcpy import TimeSeriesData 

2import pandas as pd 

3import pint_pandas 

4 

5from bim2sim.tasks.base import ITask 

6from bim2sim.elements.mapping.units import ureg 

7from bim2sim.utilities.common_functions import filter_elements 

8 

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} 

40 

41# bim2sim_teaser_indirect_mapping = { 

42# "n_persons_rooms": [ 

43# "*", 

44# "multizone.zone[numZones].humanSenHeaDependent.specificPersons", 

45# "multizone.zone[numZones].humanSenHeaDependent.roomArea"] 

46# } 

47 

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} 

63 

64 

65class CreateResultDF(ITask): 

66 """Creates result dataframe, run() method holds detailed information.""" 

67 

68 reads = ('sim_results_path', 'bldg_names', 'elements') 

69 touches = ('df_finals',) 

70 

71 def run(self, sim_results_path, bldg_names, elements): 

72 """Creates a result dataframe for BEPS simulations. 

73 

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. 

80 

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, 

114 

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. 

120 

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 

126 

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 

131 

132 Returns: 

133 df_final: converted dataframe in `bim2sim` result structure 

134 """ 

135 

136 # rename columns based on bim2sim_teaser_mapping 

137 df_final = df_original.rename( 

138 columns=bim2sim_teaser_mapping) 

139 

140 # update index to format MM/DD-hh:mm:ss 

141 df_final = self.convert_time_index(df_final) 

142 

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) 

148 

149 # convert negative cooling demands and energies to absolute values 

150 df_final = df_final.abs() 

151 

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) 

158 

159 return df_final 

160 

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 

168 

169 @staticmethod 

170 def map_zonal_results(bim2sim_teaser_mapping_selected, elements: dict): 

171 """Add zone/space guids/names to mapping dict. 

172 

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. 

179 

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] 

185 

186 Returns: 

187 dict: A mapping between simulation results and space guids, with 

188 appropriate adjustments for aggregated zones. 

189 

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 

206 

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') 

224 

225 # delete last value (which is first value of next year) to have 8760 

226 # time steps 

227 df = df[:-1] 

228 return df 

229 

230 @staticmethod 

231 def calc_indirect_result(bim2sim_teaser_mapping_selected): 

232 # TODO 

233 pass