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

1import json 

2from pathlib import Path 

3 

4import pandas as pd 

5import pint_pandas 

6from geomeppy import IDF 

7from pint_pandas import PintArray 

8 

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 

16 

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} 

45 

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} 

61 

62 

63class CreateResultDF(ITask): 

64 """This ITask creates a result dataframe for EnergyPlus BEPS simulations 

65 

66 See detailed explanation in the run function below. 

67 """ 

68 

69 reads = ('idf', 'sim_results_path', 'elements') 

70 touches = ('df_finals',) 

71 

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. 

75 

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. 

82 

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 

94 

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) 

112 

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) 

127 

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 

135 

136 return df_finals, 

137 

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. 

142 

143 This function: 

144 - adds the space GUIDs to the results 

145 - selects only the selected simulation outputs from the result 

146 

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) 

165 

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) 

179 

180 return df_final 

181 

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 

189 

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. 

194 

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. 

201 

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. 

211 

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