Coverage for bim2sim/tasks/bps/combine_tz.py: 28%

149 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 17:09 +0000

1from bim2sim.elements.aggregation.bps_aggregations import AggregatedThermalZone 

2from bim2sim.tasks.base import ITask 

3from bim2sim.utilities.common_functions import filter_elements 

4from bim2sim.utilities.types import ZoningCriteria 

5from typing import Callable 

6 

7 

8class CombineThermalZones(ITask): 

9 """Combine thermal zones to reduce the amount of thermal zones. 

10 

11 As the zoning of simulation models is a time-consuming task we decided to 

12 automate it with the tasks. 

13 This task will combine multiple thermal zones into one zone based on the 

14 criteria selected in the simulation type settings and the decisions made. 

15 We do this by giving the user multiple criteria to select from: 

16 * External/Internal 

17 * Orientation 

18 * Usage 

19 * Window to wall ratio 

20 """ 

21 reads = ('elements',) 

22 

23 def run(self, elements: dict): 

24 tz_elements = filter_elements(elements, 'ThermalZone') 

25 n_zones_before = len(tz_elements) 

26 self.logger.info("Try to reduce number of thermal zones by combining.") 

27 if len(tz_elements) == 0: 

28 self.logger.warning("Found no ThermalZones to combine.") 

29 # No aggregation for ZoningCriteria.individual_spaces: 

30 elif (self.playground.sim_settings.zoning_criteria is not 

31 ZoningCriteria.individual_spaces): 

32 self.combine_tzs_based_on_criteria(tz_elements, elements) 

33 tz_elements_after = filter_elements( 

34 elements, 'ThermalZone') 

35 self.logger.info(f"Reduced number of ThermalZone elements from" 

36 f" {n_zones_before} to {len(tz_elements_after)}") 

37 

38 

39 

40 @staticmethod 

41 def combine_tzs_to_one_zone(thermal_zones: list, elements: dict): 

42 """groups together all the thermal zones as one building""" 

43 tz_group = {'one_zone_building': thermal_zones} 

44 new_aggregations = AggregatedThermalZone.find_matches( 

45 tz_group, elements) 

46 

47 def combine_tzs_based_on_criteria(self, thermal_zones: list, elements: dict): 

48 """groups together all the thermal zones based on selected criteria 

49 (answer)""" 

50 if self.playground.sim_settings.zoning_criteria == ZoningCriteria.combined_single_zone: 

51 self.combine_tzs_to_one_zone(thermal_zones, elements) 

52 else: 

53 mapping = { 

54 ZoningCriteria.external: 

55 self.group_thermal_zones_by_is_external, 

56 ZoningCriteria.external_orientation: 

57 self.group_thermal_zones_by_is_external_and_orientation, 

58 ZoningCriteria.usage: 

59 self.group_thermal_zones_by_usage, 

60 ZoningCriteria.external_orientation_usage: 

61 self.group_thermal_zones_by_is_external_orientation_and_usage, 

62 ZoningCriteria.all_criteria: 

63 self.group_thermal_zones_by_use_all_criteria, 

64 } 

65 

66 criteria_function = \ 

67 mapping[self.playground.sim_settings.zoning_criteria] 

68 tz_groups = criteria_function(thermal_zones) 

69 new_aggregations = AggregatedThermalZone.find_matches( 

70 tz_groups, elements) 

71 

72 @classmethod 

73 def group_thermal_zones_by_is_external(cls, thermal_zones: list): 

74 """groups together the thermal zones based on mixed criteria: 

75 * is_external""" 

76 

77 return cls.group_by_is_external(thermal_zones) 

78 

79 @classmethod 

80 def group_thermal_zones_by_usage(cls, thermal_zones: list): 

81 """groups together the thermal zones based on mixed criteria 

82 * usage""" 

83 

84 return cls.group_by_usage(thermal_zones) 

85 

86 @classmethod 

87 def group_thermal_zones_by_is_external_and_orientation( 

88 cls, thermal_zones: list): 

89 """groups together the thermal zones based on mixed criteria 

90 * is_external 

91 * orientation criteria""" 

92 

93 grouped_elements = cls.group_by_is_external(thermal_zones) 

94 return cls.group_grouped_tz(grouped_elements, 

95 cls.group_by_external_orientation) 

96 

97 @classmethod 

98 def group_thermal_zones_by_is_external_orientation_and_usage( 

99 cls, thermal_zones: list): 

100 """groups together the thermal zones based on mixed criteria: 

101 * is_external 

102 * usage 

103 * orientation criteria""" 

104 

105 grouped_elements = cls.group_by_is_external(thermal_zones) 

106 grouped_elements = cls.group_grouped_tz(grouped_elements, 

107 cls.group_by_usage) 

108 return cls.group_grouped_tz(grouped_elements, 

109 cls.group_by_external_orientation) 

110 

111 @classmethod 

112 def group_by_is_external(cls, thermal_zones: list) -> dict: 

113 """groups together the thermal zones based on is_external criterion""" 

114 grouped_tz = {'external': [], 'internal': []} 

115 for tz in thermal_zones: 

116 if tz.is_external: 

117 grouped_tz['external'].append(tz) 

118 else: 

119 grouped_tz['internal'].append(tz) 

120 cls.discard_1_element_groups(grouped_tz) 

121 return grouped_tz 

122 

123 @classmethod 

124 def group_by_usage(cls, thermal_zones: list) -> dict: 

125 """groups together the thermal zones based on usage criterion""" 

126 grouped_tz = {} 

127 for tz in thermal_zones: 

128 value = getattr(tz, 'usage') 

129 if value not in grouped_tz: 

130 grouped_tz[value] = [] 

131 grouped_tz[value].append(tz) 

132 cls.discard_1_element_groups(grouped_tz) 

133 return grouped_tz 

134 

135 @classmethod 

136 def group_thermal_zones_by_use_all_criteria(cls, thermal_zones: list): 

137 """groups together the thermal zones based on mixed criteria: 

138 * is_external 

139 * usage 

140 * external_orientation 

141 * glass percentage 

142 * neighbors criterion 

143 * not grouped tz""" 

144 

145 grouped_elements = cls.group_by_is_external( 

146 thermal_zones) 

147 grouped_elements = cls.group_grouped_tz( 

148 grouped_elements, cls.group_by_usage) 

149 grouped_elements = cls.group_grouped_tz( 

150 grouped_elements, cls.group_by_external_orientation) 

151 grouped_elements = cls.group_grouped_tz( 

152 grouped_elements, cls.group_by_glass_percentage) 

153 # neighbors - filter criterion 

154 grouped_elements = cls.group_grouped_tz( 

155 grouped_elements, cls.group_by_is_neighbor) 

156 grouped_elements = cls.group_not_grouped_tz( 

157 grouped_elements, thermal_zones) 

158 

159 return grouped_elements 

160 

161 @classmethod 

162 def group_by_external_orientation(cls, thermal_zones: list) -> dict: 

163 """groups together the thermal zones based on external_orientation 

164 criterion""" 

165 grouped_tz = {} 

166 for tz in thermal_zones: 

167 value = cls.external_orientation_group( 

168 getattr(tz, 'external_orientation')) 

169 if value not in grouped_tz: 

170 grouped_tz[value] = [] 

171 grouped_tz[value].append(tz) 

172 cls.discard_1_element_groups(grouped_tz) 

173 return grouped_tz 

174 

175 @classmethod 

176 def group_by_glass_percentage(cls, thermal_zones: list) -> dict: 

177 """groups together the thermal zones based on glass percentage 

178 criterion""" 

179 grouped_tz: dict = {} 

180 for tz in thermal_zones: 

181 value = cls.glass_percentage_group(getattr(tz, 'glass_percentage')) 

182 if value not in grouped_tz: 

183 grouped_tz[value] = [] 

184 grouped_tz[value].append(tz) 

185 cls.discard_1_element_groups(grouped_tz) 

186 return grouped_tz 

187 

188 @classmethod 

189 def group_by_is_neighbor(cls, thermal_zones: list) -> dict: 

190 """groups together the thermal zones based on is_neighbor criterion""" 

191 grouped_tz = {'': list(thermal_zones)} 

192 for tz in thermal_zones: 

193 neighbor_statement = False 

194 for neighbor in tz.space_neighbors: 

195 if neighbor in thermal_zones: 

196 neighbor_statement = True 

197 break 

198 if not neighbor_statement: 

199 grouped_tz[''].remove(tz) 

200 cls.discard_1_element_groups(grouped_tz) 

201 return grouped_tz 

202 

203 @staticmethod 

204 def discard_1_element_groups(grouped: dict): 

205 """discard 1 element group, since a group only makes sense if it has 

206 more than 1 thermal zone""" 

207 for k in list(grouped.keys()): 

208 if len(grouped[k]) <= 1: 

209 del grouped[k] 

210 

211 @classmethod 

212 def group_grouped_tz(cls, grouped_thermal_zones: dict, group_function: Callable) -> \ 

213 dict: 

214 """groups together thermal zones, that were already grouped in previous 

215 steps""" 

216 grouped_tz = {} 

217 external_functions = [cls.group_by_external_orientation, 

218 cls.group_by_glass_percentage] 

219 for group, items in grouped_thermal_zones.items(): 

220 if group_function in external_functions and 'internal' in group: 

221 grouped_tz[group] = items 

222 else: 

223 sub_grouped = group_function(items) 

224 for sub_group, sub_items in sub_grouped.items(): 

225 grouped_name = '%s_%s' % (group, sub_group) 

226 grouped_tz[grouped_name] = sub_items 

227 return grouped_tz 

228 

229 @staticmethod 

230 def group_not_grouped_tz(grouped_thermal_zones: dict, thermal_zones: list): 

231 """groups together thermal zones, that are not already grouped in 

232 previous steps based on Norm DIN_V_18599_1""" 

233 # list of all thermal elements grouped: 

234 grouped_thermal_elements = [] 

235 for criteria in grouped_thermal_zones: 

236 grouped_thermal_elements += grouped_thermal_zones[criteria] 

237 # check not grouped elements for fourth criterion 

238 not_grouped_elements: list = [] 

239 for tz in thermal_zones: 

240 if tz not in grouped_thermal_elements: 

241 not_grouped_elements.append(tz) 

242 if len(not_grouped_elements) > 1: 

243 grouped_thermal_zones['not_combined'] = not_grouped_elements 

244 return grouped_thermal_zones 

245 

246 @staticmethod 

247 def glass_percentage_group(value: float): 

248 """locates glass percentage value on one of four groups based on 

249 Norm DIN_V_18599_1 

250 * 0-30% 

251 * 30-50% 

252 * 50-70% 

253 * 70-100%""" 

254 if 0 <= value < 30: 

255 value = '0-30%' 

256 elif 30 <= value < 50: 

257 value = '30-50%' 

258 elif 50 <= value < 70: 

259 value = '50-70%' 

260 else: 

261 value = '70-100%' 

262 return value 

263 

264 @staticmethod 

265 def external_orientation_group(value: float): 

266 """locates external orientation value on one of two groups: 

267 * S-W 

268 * N-E""" 

269 if 135 <= value < 315: 

270 value = 'S-W' 

271 else: 

272 value = 'N-E' 

273 return value