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
« 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
8class CombineThermalZones(ITask):
9 """Combine thermal zones to reduce the amount of thermal zones.
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',)
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)}")
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)
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 }
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)
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"""
77 return cls.group_by_is_external(thermal_zones)
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"""
84 return cls.group_by_usage(thermal_zones)
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"""
93 grouped_elements = cls.group_by_is_external(thermal_zones)
94 return cls.group_grouped_tz(grouped_elements,
95 cls.group_by_external_orientation)
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"""
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)
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
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
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"""
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)
159 return grouped_elements
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
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
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
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]
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
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
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
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