honeybee-radiance-postprocess 0.4.555__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. honeybee_radiance_postprocess/__init__.py +1 -0
  2. honeybee_radiance_postprocess/__main__.py +4 -0
  3. honeybee_radiance_postprocess/annual.py +73 -0
  4. honeybee_radiance_postprocess/annualdaylight.py +289 -0
  5. honeybee_radiance_postprocess/annualirradiance.py +35 -0
  6. honeybee_radiance_postprocess/breeam/__init__.py +1 -0
  7. honeybee_radiance_postprocess/breeam/breeam.py +552 -0
  8. honeybee_radiance_postprocess/cli/__init__.py +33 -0
  9. honeybee_radiance_postprocess/cli/abnt.py +392 -0
  10. honeybee_radiance_postprocess/cli/breeam.py +96 -0
  11. honeybee_radiance_postprocess/cli/datacollection.py +133 -0
  12. honeybee_radiance_postprocess/cli/grid.py +295 -0
  13. honeybee_radiance_postprocess/cli/leed.py +143 -0
  14. honeybee_radiance_postprocess/cli/merge.py +161 -0
  15. honeybee_radiance_postprocess/cli/mtxop.py +161 -0
  16. honeybee_radiance_postprocess/cli/postprocess.py +1092 -0
  17. honeybee_radiance_postprocess/cli/schedule.py +103 -0
  18. honeybee_radiance_postprocess/cli/translate.py +216 -0
  19. honeybee_radiance_postprocess/cli/two_phase.py +252 -0
  20. honeybee_radiance_postprocess/cli/util.py +121 -0
  21. honeybee_radiance_postprocess/cli/viewfactor.py +157 -0
  22. honeybee_radiance_postprocess/cli/well.py +110 -0
  23. honeybee_radiance_postprocess/data_type.py +102 -0
  24. honeybee_radiance_postprocess/dynamic.py +273 -0
  25. honeybee_radiance_postprocess/electriclight.py +24 -0
  26. honeybee_radiance_postprocess/en17037.py +304 -0
  27. honeybee_radiance_postprocess/helper.py +266 -0
  28. honeybee_radiance_postprocess/ies/__init__.py +1 -0
  29. honeybee_radiance_postprocess/ies/lm.py +224 -0
  30. honeybee_radiance_postprocess/ies/lm_schedule.py +248 -0
  31. honeybee_radiance_postprocess/leed/__init__.py +1 -0
  32. honeybee_radiance_postprocess/leed/leed.py +801 -0
  33. honeybee_radiance_postprocess/leed/leed_schedule.py +256 -0
  34. honeybee_radiance_postprocess/metrics.py +439 -0
  35. honeybee_radiance_postprocess/reader.py +80 -0
  36. honeybee_radiance_postprocess/results/__init__.py +4 -0
  37. honeybee_radiance_postprocess/results/annual_daylight.py +752 -0
  38. honeybee_radiance_postprocess/results/annual_irradiance.py +196 -0
  39. honeybee_radiance_postprocess/results/results.py +1416 -0
  40. honeybee_radiance_postprocess/type_hints.py +38 -0
  41. honeybee_radiance_postprocess/util.py +211 -0
  42. honeybee_radiance_postprocess/vis_metadata.py +49 -0
  43. honeybee_radiance_postprocess/well/__init__.py +1 -0
  44. honeybee_radiance_postprocess/well/well.py +509 -0
  45. honeybee_radiance_postprocess-0.4.555.dist-info/METADATA +79 -0
  46. honeybee_radiance_postprocess-0.4.555.dist-info/RECORD +50 -0
  47. honeybee_radiance_postprocess-0.4.555.dist-info/WHEEL +5 -0
  48. honeybee_radiance_postprocess-0.4.555.dist-info/entry_points.txt +2 -0
  49. honeybee_radiance_postprocess-0.4.555.dist-info/licenses/LICENSE +661 -0
  50. honeybee_radiance_postprocess-0.4.555.dist-info/top_level.txt +1 -0
@@ -0,0 +1,224 @@
1
+ """Functions for IES LM post-processing."""
2
+ from typing import Tuple, Union
3
+ from collections import defaultdict
4
+ import itertools
5
+ try:
6
+ import cupy as np
7
+ is_gpu = True
8
+ except ImportError:
9
+ is_gpu = False
10
+ import numpy as np
11
+
12
+ from honeybee_radiance.postprocess.annual import filter_schedule_by_hours
13
+
14
+ from ..annual import schedule_to_hoys, occupancy_schedule_8_to_6
15
+ from ..results.annual_daylight import AnnualDaylight
16
+ from ..util import filter_array
17
+ from ..dynamic import DynamicSchedule, ApertureGroupSchedule
18
+ from .lm_schedule import shd_trans_schedule_descending, states_schedule_descending
19
+
20
+
21
+ def shade_transmittance_per_light_path(
22
+ light_paths: list, shade_transmittance: Union[float, dict],
23
+ shd_trans_dict: dict) -> dict:
24
+ """Filter shade_transmittance by light paths and add default multiplier.
25
+
26
+ Args:
27
+ light_paths: A list of light paths.
28
+ shade_transmittance: A value to use as a multiplier in place of solar
29
+ shading. This input can be either a single value that will be used
30
+ for all aperture groups, or a dictionary where aperture groups are
31
+ keys, and the value for each key is the shade transmittance. Values
32
+ for shade transmittance must be 1 > value > 0.
33
+ shd_trans_dict: A dictionary used to store shade transmittance value
34
+ for each aperture group.
35
+
36
+ Returns:
37
+ A dictionary with filtered light paths.
38
+ """
39
+ shade_transmittances = {}
40
+ if isinstance(shade_transmittance, dict):
41
+ for light_path in light_paths:
42
+ # default multiplier
43
+ shade_transmittances[light_path] = [1]
44
+ # add custom shade transmittance
45
+ if light_path in shade_transmittance:
46
+ shade_transmittances[light_path].append(
47
+ shade_transmittance[light_path])
48
+ shd_trans_dict[light_path] = shade_transmittance[light_path]
49
+ # add default shade transmittance (0.02)
50
+ elif light_path != '__static_apertures__':
51
+ shade_transmittances[light_path].append(0.02)
52
+ shd_trans_dict[light_path] = 0.02
53
+ else:
54
+ shade_transmittances[light_path].append(1)
55
+ shd_trans_dict[light_path] = 1
56
+ else:
57
+ shd_trans = float(shade_transmittance)
58
+ for light_path in light_paths:
59
+ # default multiplier
60
+ shade_transmittances[light_path] = [1]
61
+ # add custom shade transmittance
62
+ if light_path != '__static_apertures__':
63
+ shade_transmittances[light_path].append(shd_trans)
64
+ shd_trans_dict[light_path] = shd_trans
65
+ else:
66
+ shade_transmittances[light_path].append(1)
67
+ shd_trans_dict[light_path] = 1
68
+
69
+ return shade_transmittances, shd_trans_dict
70
+
71
+
72
+ def dynamic_schedule_direct_illuminance(
73
+ results: Union[str, AnnualDaylight], grids_filter: str = '*',
74
+ shade_transmittance: Union[float, dict] = 0.02,
75
+ use_states: bool = False
76
+ ) -> Tuple[dict, dict]:
77
+ """Calculate a schedule of each aperture group.
78
+
79
+ This function calculates an annual shading schedule of each aperture
80
+ group. Hour by hour it will select the least shaded aperture group
81
+ configuration, so that no more than 2% of the sensors points receive
82
+ direct illuminance of 1000 lux or more.
83
+
84
+ Args:
85
+ results: Path to results folder or a Results class object.
86
+ grids_filter: The name of a grid or a pattern to filter the grids.
87
+ Defaults to '*'.
88
+ shade_transmittance: A value to use as a multiplier in place of solar
89
+ shading. This input can be either a single value that will be used
90
+ for all aperture groups, or a dictionary where aperture groups are
91
+ keys, and the value for each key is the shade transmittance. Values
92
+ for shade transmittance must be 1 > value > 0.
93
+ Defaults to 0.02.
94
+ use_states: A boolean to note whether to use the simulated states. Set
95
+ to True to use the simulated states. The default is False which will
96
+ use the shade transmittance instead.
97
+
98
+ Returns:
99
+ Tuple: A tuple with a dictionary of the annual schedule and a
100
+ dictionary of hours where no shading configuration comply with the
101
+ 2% rule.
102
+ """
103
+ if not isinstance(results, AnnualDaylight):
104
+ results = AnnualDaylight(results)
105
+
106
+ grids_info = results._filter_grids(grids_filter=grids_filter)
107
+ schedule = occupancy_schedule_8_to_6(as_list=True)
108
+ occ_pattern = \
109
+ filter_schedule_by_hours(results.sun_up_hours, schedule=schedule)[0]
110
+ occ_mask = np.array(occ_pattern)
111
+
112
+ states_schedule = defaultdict(list)
113
+ fail_to_comply = {}
114
+ shd_trans_dict = {}
115
+
116
+ for grid_info in grids_info:
117
+ grid_states_schedule = defaultdict(list)
118
+
119
+ grid_count = grid_info['count']
120
+ light_paths = []
121
+ for lp in grid_info['light_path']:
122
+ for _lp in lp:
123
+ if _lp == '__static_apertures__' and len(lp) > 1:
124
+ pass
125
+ else:
126
+ light_paths.append(_lp)
127
+
128
+ shade_transmittances, shd_trans_dict = (
129
+ shade_transmittance_per_light_path(
130
+ light_paths, shade_transmittance, shd_trans_dict
131
+ )
132
+ )
133
+
134
+ if len(light_paths) > 6:
135
+ if use_states:
136
+ grid_states_schedule, fail_to_comply = states_schedule_descending(
137
+ results, grid_info, light_paths, occ_mask,
138
+ grid_states_schedule, fail_to_comply)
139
+ else:
140
+ grid_states_schedule, fail_to_comply = shd_trans_schedule_descending(
141
+ results, grid_info, light_paths, shade_transmittances, occ_mask,
142
+ grid_states_schedule, fail_to_comply)
143
+ else:
144
+ if use_states:
145
+ combinations = results._get_state_combinations(grid_info)
146
+ else:
147
+ shade_transmittances, shd_trans_dict = shade_transmittance_per_light_path(
148
+ light_paths, shade_transmittance, shd_trans_dict)
149
+ keys, values = zip(*shade_transmittances.items())
150
+ combinations = [dict(zip(keys, v)) for v in itertools.product(*values)]
151
+
152
+ array_list_combinations = []
153
+ for combination in combinations:
154
+ combination_arrays = []
155
+ for light_path, value in combination.items():
156
+ if use_states:
157
+ combination_arrays.append(
158
+ results._get_array(grid_info, light_path, state=value,
159
+ res_type='direct')
160
+ )
161
+ else:
162
+ array = results._get_array(
163
+ grid_info, light_path, res_type='direct')
164
+ if value == 1:
165
+ combination_arrays.append(array)
166
+ else:
167
+ combination_arrays.append(array * value)
168
+ combination_array = sum(combination_arrays)
169
+
170
+ combination_percentage = \
171
+ (combination_array >= 1000).sum(axis=0) / grid_count
172
+ array_list_combinations.append(combination_percentage)
173
+ array_combinations = np.array(array_list_combinations)
174
+ array_combinations[array_combinations > 0.02] = -np.inf
175
+
176
+ grid_comply = np.where(np.all(array_combinations==-np.inf, axis=0))[0]
177
+ if grid_comply.size != 0:
178
+ grid_comply = np.array(results.sun_up_hours)[grid_comply]
179
+ fail_to_comply[grid_info['name']] = \
180
+ [int(hoy) for hoy in grid_comply]
181
+
182
+ array_combinations_filter = np.apply_along_axis(
183
+ filter_array, 1, array_combinations, occ_mask
184
+ )
185
+ max_indices = array_combinations_filter.argmax(axis=0)
186
+ # select the combination for each hour
187
+ combinations = [combinations[idx] for idx in max_indices]
188
+
189
+ # merge the combinations of dicts
190
+ for combination in combinations:
191
+ for light_path, value in combination.items():
192
+ if light_path != '__static_apertures__':
193
+ grid_states_schedule[light_path].append(value)
194
+
195
+ for key, value in grid_states_schedule.items():
196
+ if key not in states_schedule:
197
+ states_schedule[key] = value
198
+ else:
199
+ if use_states:
200
+ merged_array = np.logical_or(states_schedule[key], value).astype(int)
201
+ else:
202
+ merged_array = np.minimum(states_schedule[key], value)
203
+ states_schedule[key] = merged_array
204
+
205
+ occupancy_hoys = schedule_to_hoys(schedule, results.sun_up_hours)
206
+
207
+ # map states to 8760 values
208
+ if use_states:
209
+ aperture_group_schedules = []
210
+ for identifier, values in states_schedule.items():
211
+ mapped_states = results.values_to_annual(
212
+ occupancy_hoys, values, results.timestep, dtype=np.int32)
213
+ aperture_group_schedules.append(
214
+ ApertureGroupSchedule(identifier, mapped_states.tolist())
215
+ )
216
+ states_schedule = \
217
+ DynamicSchedule.from_group_schedules(aperture_group_schedules)
218
+ else:
219
+ for light_path, shd_trans in states_schedule.items():
220
+ mapped_states = results.values_to_annual(
221
+ occupancy_hoys, shd_trans, results.timestep)
222
+ states_schedule[light_path] = mapped_states
223
+
224
+ return states_schedule, fail_to_comply, shd_trans_dict
@@ -0,0 +1,248 @@
1
+ """Module for dynamic LM schedules."""
2
+ from typing import Tuple
3
+ try:
4
+ import cupy as np
5
+ is_gpu = True
6
+ except ImportError:
7
+ is_gpu = False
8
+ import numpy as np
9
+
10
+ from ..results.annual_daylight import AnnualDaylight
11
+ from ..util import filter_array
12
+
13
+
14
+ def shd_trans_schedule_descending(
15
+ results: AnnualDaylight, grid_info, light_paths, shade_transmittances, occ_mask,
16
+ states_schedule, fail_to_comply
17
+ ) -> Tuple[dict, dict]:
18
+ grid_count = grid_info['count']
19
+ full_direct = []
20
+ full_thresh = []
21
+ full_shd_trans_array = []
22
+ for light_path in light_paths:
23
+ array = results._get_array(grid_info, light_path, res_type="direct")
24
+ array = np.apply_along_axis(filter_array, 1, array, occ_mask)
25
+ full_direct.append(array)
26
+ full_thresh.append((array >= 1000).sum(axis=0))
27
+ full_shd_trans_array.append(shade_transmittances[light_path][1])
28
+
29
+ # Sum the array element-wise.
30
+ # This array is the sum of all direct illuminance without shade
31
+ # transmittance.
32
+ full_direct_sum = sum(full_direct)
33
+
34
+ # Create base list of shading combinations (all set to 1).
35
+ # We will replace the 1s later.
36
+ combinations = [
37
+ {light_path: 1 for light_path in light_paths}
38
+ for i in range(full_direct_sum.shape[1])
39
+ ]
40
+
41
+ # Find the percentage of floor area >= 1000 lux.
42
+ # This array is the percentage for each hour (axis=0).
43
+ direct_pct_above = (full_direct_sum >= 1000).sum(axis=0) / grid_count
44
+
45
+ # Find the indices where the percentage of floor area is > 2%.
46
+ # This array is the problematic hours.
47
+ above_2_indices = np.where(direct_pct_above > 0.02)[0]
48
+
49
+ # Use the indices to get the relevant hours.
50
+ direct_sum = np.take(full_direct_sum, above_2_indices, axis=1)
51
+
52
+ # Use the indices to get the relevant hours.
53
+ direct = np.take(full_direct, above_2_indices, axis=2)
54
+
55
+ # Use the indices to get the relevant hours.
56
+ thresh = np.take(full_thresh, above_2_indices, axis=1)
57
+
58
+ # Sort and get indices. Negate the array to get descending order.
59
+ # Descending order puts the "highest offender" light path first.
60
+ sort_thresh = np.argsort(-thresh, axis=0).transpose()
61
+
62
+ _combinations = []
63
+ _combinations.insert(
64
+ 0, (np.arange(full_direct_sum.shape[1]), combinations)
65
+ )
66
+
67
+ if np.any(above_2_indices):
68
+ # There are hours where the percentage of floor area is > 2%.
69
+ for idx, lp in enumerate(light_paths):
70
+ # Take column. For each iteration it will take the next column
71
+ # in descending order, i.e., the "highest offender" is the first
72
+ # column.
73
+ sort_indices = np.take(sort_thresh, idx, axis=1)
74
+
75
+ # Map light path identifiers to indices.
76
+ light_path_ids = np.take(light_paths, sort_indices)
77
+
78
+ # Map shade transmittance to indices.
79
+ shd_trans_array = np.take(full_shd_trans_array, sort_indices)
80
+
81
+ # Create combination for the subset.
82
+ _subset_combination = [
83
+ {light_path: _shd_trans} for light_path, _shd_trans in
84
+ zip(light_path_ids, shd_trans_array)
85
+ ]
86
+ _combinations.insert(0, (above_2_indices, _subset_combination))
87
+
88
+ # Take the values from each array by indexing.
89
+ direct_array = \
90
+ direct[sort_indices, :, range(len(sort_indices))].transpose()
91
+
92
+ # Subtract the illuminance values.
93
+ direct_sum = direct_sum - (direct_array * (1 - shd_trans_array))
94
+
95
+ # Find the percentage of floor area >= 1000 lux.
96
+ direct_pct_above = (direct_sum >= 1000).sum(axis=0) / grid_count
97
+
98
+ # Find the indices where the percentage of floor area is > 2%.
99
+ above_2_indices = np.where(direct_pct_above > 0.02)[0]
100
+
101
+ # Break if there are no hours above 2%.
102
+ if not np.any(above_2_indices):
103
+ break
104
+
105
+ # Update variables for the next iteration.
106
+ direct_sum = np.take(direct_sum, above_2_indices, axis=1)
107
+ direct = np.take(direct, above_2_indices, axis=2)
108
+ thresh = np.take(thresh, above_2_indices, axis=1)
109
+ sort_thresh = np.take(sort_thresh, above_2_indices, axis=0)
110
+
111
+ if np.any(above_2_indices):
112
+ # There are hours not complying with the 2% rule.
113
+ previous_indices = []
114
+ previous_combination = []
115
+ grid_comply = []
116
+ # Merge the combinations from the iterations of the subsets.
117
+ for i, subset in enumerate(_combinations):
118
+ if i == 0:
119
+ previous_indices = subset[0]
120
+ else:
121
+ _indices = subset[0]
122
+ grid_comply = []
123
+ for _pr_idx in previous_indices:
124
+ grid_comply.append(_indices[_pr_idx])
125
+ previous_indices = grid_comply
126
+ # Convert indices to sun up hours indices.
127
+ filter_indices = np.where(occ_mask.astype(bool))[0]
128
+ grid_comply = [filter_indices[_gc] for _gc in grid_comply]
129
+ grid_comply = np.array(results.sun_up_hours)[grid_comply]
130
+ fail_to_comply[grid_info['name']] = \
131
+ [int(hoy) for hoy in grid_comply]
132
+
133
+ previous_indices = None
134
+ previous_combination = None
135
+ # Merge the combinations from the iterations of the subsets.
136
+ for i, subset in enumerate(_combinations):
137
+ if i == 0:
138
+ previous_indices, previous_combination = subset
139
+ else:
140
+ _indices, _combination = subset
141
+ for _pr_idx, _pr_comb in \
142
+ zip(previous_indices, previous_combination):
143
+ for light_path, _shd_trans in _pr_comb.items():
144
+ _combination[_pr_idx][light_path] = _shd_trans
145
+ previous_indices = _indices
146
+ previous_combination = _combination
147
+
148
+ combinations = _combination
149
+
150
+ # Merge the combinations of dicts.
151
+ for combination in combinations:
152
+ for light_path, shd_trans in combination.items():
153
+ if light_path != "__static_apertures__":
154
+ states_schedule[light_path].append(shd_trans)
155
+
156
+ return states_schedule, fail_to_comply
157
+
158
+
159
+ def states_schedule_descending(
160
+ results: AnnualDaylight, grid_info, light_paths, occ_mask,
161
+ states_schedule, fail_to_comply
162
+ ) -> Tuple[dict, dict]:
163
+ grid_count = grid_info['count']
164
+ full_direct = []
165
+ full_thresh = []
166
+ full_direct_blinds = []
167
+ for light_path in light_paths:
168
+ array = results._get_array(
169
+ grid_info, light_path, state=0, res_type="direct")
170
+ array = np.apply_along_axis(filter_array, 1, array, occ_mask)
171
+ full_direct.append(array)
172
+ full_thresh.append((array >= 1000).sum(axis=0))
173
+
174
+ array = results._get_array(
175
+ grid_info, light_path, state=1, res_type="direct")
176
+ array = np.apply_along_axis(filter_array, 1, array, occ_mask)
177
+ full_direct_blinds.append(array)
178
+
179
+ full_direct = np.array(full_direct)
180
+ full_direct_blinds = np.array(full_direct_blinds)
181
+ full_direct_sum = full_direct.sum(axis=0)
182
+
183
+ new_array = full_direct.copy()
184
+
185
+ percentage_sensors = (full_direct_sum >= 1000).sum(axis=0) / grid_count
186
+ if not np.any(percentage_sensors > 0.02):
187
+ combinations = [
188
+ {light_path: 0 for light_path in light_paths}
189
+ for i in range(full_direct_sum.shape[1])]
190
+ else:
191
+ tracking_array = np.zeros(
192
+ (new_array.shape[0], new_array.shape[2]), dtype=int)
193
+
194
+ percentage_sensors = (full_direct >= 1000).sum(axis=1) / grid_count
195
+
196
+ ranking_indices = np.argsort(-percentage_sensors, axis=0)
197
+
198
+ for rank in range(ranking_indices.shape[0]):
199
+ # Calculate the percentage of sensors with values >= 1000 for the current new_array
200
+ summed_array = np.sum(new_array, axis=0)
201
+ percentage_sensors_summed = np.sum(
202
+ summed_array >= 1000, axis=0) / grid_count
203
+ indices_above_2_percent = np.where(
204
+ percentage_sensors_summed > 0.02)[0]
205
+
206
+ # Exit if there are no more hours exceeding the threshold
207
+ if len(indices_above_2_percent) == 0:
208
+ break
209
+
210
+ # Array indices to use for replacement for these hours
211
+ replace_indices = indices_above_2_percent
212
+ array_indices = ranking_indices[rank, replace_indices]
213
+
214
+ # Use advanced indexing to replace values in new_array for these hours
215
+ for hour_idx, array_idx in zip(replace_indices, array_indices):
216
+ new_array[array_idx, :, hour_idx] = full_direct_blinds[
217
+ array_idx, :, hour_idx
218
+ ]
219
+
220
+ # Update the tracking array
221
+ tracking_array[array_indices, replace_indices] = 1
222
+
223
+ combinations = []
224
+ for hour in range(new_array.shape[2]):
225
+ hour_dict = {
226
+ light_paths[i]: tracking_array[i, hour]
227
+ for i in range(tracking_array.shape[0])}
228
+ combinations.append(hour_dict)
229
+
230
+ final_summed_array = np.sum(new_array, axis=0)
231
+ final_percentage_sensors_summed = (
232
+ final_summed_array >= 1000).sum(
233
+ axis=0) / grid_count
234
+ final_indices_above_2_percent = np.where(
235
+ final_percentage_sensors_summed > 0.02)[0]
236
+ if np.any(final_indices_above_2_percent):
237
+ sun_up_hours_indices = np.where(occ_mask == 1)[0][
238
+ final_indices_above_2_percent]
239
+ grid_comply = np.array(results.sun_up_hours)[sun_up_hours_indices]
240
+ fail_to_comply[grid_info['name']] = [
241
+ int(hoy) for hoy in grid_comply]
242
+
243
+ for combination in combinations:
244
+ for light_path, value in combination.items():
245
+ if light_path != '__static_apertures__':
246
+ states_schedule[light_path].append(value)
247
+
248
+ return states_schedule, fail_to_comply
@@ -0,0 +1 @@
1
+ """honeybee-radiance-postprocess library."""