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,256 @@
1
+ """Module for dynamic LEED 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_array2d
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 = filter_array2d(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
+ del full_direct, full_thresh
59
+ # Sort and get indices. Negate the array to get descending order.
60
+ # Descending order puts the "highest offender" light path first.
61
+ sort_thresh = np.argsort(-thresh, axis=0).transpose()
62
+
63
+ _combinations = []
64
+ _combinations.insert(
65
+ 0, (np.arange(full_direct_sum.shape[1]), combinations)
66
+ )
67
+
68
+ del full_direct_sum
69
+
70
+ if np.any(above_2_indices):
71
+ # There are hours where the percentage of floor area is > 2%.
72
+ for idx, lp in enumerate(light_paths):
73
+ # Take column. For each iteration it will take the next column
74
+ # in descending order, i.e., the "highest offender" is the first
75
+ # column.
76
+ sort_indices = np.take(sort_thresh, idx, axis=1)
77
+
78
+ # Map light path identifiers to indices.
79
+ light_path_ids = np.take(light_paths, sort_indices)
80
+
81
+ # Map shade transmittance to indices.
82
+ shd_trans_array = np.take(full_shd_trans_array, sort_indices)
83
+
84
+ # Create combination for the subset.
85
+ _subset_combination = [
86
+ {light_path: _shd_trans} for light_path, _shd_trans in
87
+ zip(light_path_ids, shd_trans_array)
88
+ ]
89
+ _combinations.insert(0, (above_2_indices, _subset_combination))
90
+
91
+ # Take the values from each array by indexing.
92
+ direct_array = \
93
+ direct[sort_indices, :, range(len(sort_indices))].transpose()
94
+
95
+ # Subtract the illuminance values.
96
+ direct_sum = direct_sum - (direct_array * (1 - shd_trans_array))
97
+
98
+ # Find the percentage of floor area >= 1000 lux.
99
+ direct_pct_above = (direct_sum >= 1000).sum(axis=0) / grid_count
100
+
101
+ # Find the indices where the percentage of floor area is > 2%.
102
+ above_2_indices = np.where(direct_pct_above > 0.02)[0]
103
+
104
+ # Break if there are no hours above 2%.
105
+ if not np.any(above_2_indices):
106
+ break
107
+
108
+ # Update variables for the next iteration.
109
+ direct_sum = np.take(direct_sum, above_2_indices, axis=1)
110
+ direct = np.take(direct, above_2_indices, axis=2)
111
+ thresh = np.take(thresh, above_2_indices, axis=1)
112
+ sort_thresh = np.take(sort_thresh, above_2_indices, axis=0)
113
+
114
+ if np.any(above_2_indices):
115
+ # There are hours not complying with the 2% rule.
116
+ previous_indices = []
117
+ previous_combination = []
118
+ grid_comply = []
119
+ # Merge the combinations from the iterations of the subsets.
120
+ for i, subset in enumerate(_combinations):
121
+ if i == 0:
122
+ previous_indices = subset[0]
123
+ else:
124
+ _indices = subset[0]
125
+ grid_comply = []
126
+ for _pr_idx in previous_indices:
127
+ grid_comply.append(_indices[_pr_idx])
128
+ previous_indices = grid_comply
129
+ # Convert indices to sun up hours indices.
130
+ filter_indices = np.where(occ_mask.astype(bool))[0]
131
+ grid_comply = [filter_indices[_gc] for _gc in grid_comply]
132
+ grid_comply = np.array(results.sun_up_hours)[grid_comply]
133
+ fail_to_comply[grid_info['name']] = \
134
+ [int(hoy) for hoy in grid_comply]
135
+
136
+ previous_indices = None
137
+ previous_combination = None
138
+ # Merge the combinations from the iterations of the subsets.
139
+ for i, subset in enumerate(_combinations):
140
+ if i == 0:
141
+ previous_indices, previous_combination = subset
142
+ else:
143
+ _indices, _combination = subset
144
+ for _pr_idx, _pr_comb in \
145
+ zip(previous_indices, previous_combination):
146
+ for light_path, _shd_trans in _pr_comb.items():
147
+ _combination[_pr_idx][light_path] = _shd_trans
148
+ previous_indices = _indices
149
+ previous_combination = _combination
150
+
151
+ combinations = _combination
152
+
153
+ del full_shd_trans_array, direct_sum, direct, thresh, sort_thresh
154
+
155
+ # Merge the combinations of dicts.
156
+ for combination in combinations:
157
+ for light_path, shd_trans in combination.items():
158
+ if light_path != "__static_apertures__":
159
+ states_schedule[light_path].append(shd_trans)
160
+
161
+ return states_schedule, fail_to_comply
162
+
163
+
164
+ def states_schedule_descending(
165
+ results: AnnualDaylight, grid_info, light_paths, occ_mask,
166
+ states_schedule, fail_to_comply
167
+ ) -> Tuple[dict, dict]:
168
+ grid_count = grid_info['count']
169
+ full_direct = []
170
+ full_thresh = []
171
+ full_direct_blinds = []
172
+ for light_path in light_paths:
173
+ array = results._get_array(
174
+ grid_info, light_path, state=0, res_type="direct")
175
+ array = filter_array2d(array, occ_mask)
176
+ full_direct.append(array)
177
+ full_thresh.append((array >= 1000).sum(axis=0))
178
+
179
+ array = results._get_array(
180
+ grid_info, light_path, state=1, res_type="direct")
181
+ array = filter_array2d(array, occ_mask)
182
+ full_direct_blinds.append(array)
183
+
184
+ full_direct = np.array(full_direct)
185
+ full_direct_blinds = np.array(full_direct_blinds)
186
+ full_direct_sum = full_direct.sum(axis=0)
187
+
188
+ percentage_sensors = (full_direct_sum >= 1000).sum(axis=0) / grid_count
189
+ if not np.any(percentage_sensors > 0.02):
190
+ combinations = [
191
+ {light_path: 0 for light_path in light_paths}
192
+ for i in range(full_direct_sum.shape[1])]
193
+ else:
194
+ new_array = full_direct.copy()
195
+ tracking_array = np.zeros(
196
+ (new_array.shape[0], new_array.shape[2]), dtype=int)
197
+
198
+ percentage_sensors = (full_direct >= 1000).sum(axis=1) / grid_count
199
+
200
+ ranking_indices = np.argsort(-percentage_sensors, axis=0)
201
+
202
+ for rank in range(ranking_indices.shape[0]):
203
+ # Calculate the percentage of sensors with values >= 1000 for the current new_array
204
+ summed_array = np.sum(new_array, axis=0)
205
+ percentage_sensors_summed = np.sum(
206
+ summed_array >= 1000, axis=0) / grid_count
207
+ indices_above_2_percent = np.where(
208
+ percentage_sensors_summed > 0.02)[0]
209
+
210
+ # Exit if there are no more hours exceeding the threshold
211
+ if len(indices_above_2_percent) == 0:
212
+ break
213
+
214
+ # Array indices to use for replacement for these hours
215
+ replace_indices = indices_above_2_percent
216
+ array_indices = ranking_indices[rank, replace_indices]
217
+
218
+ # Use advanced indexing to replace values in new_array for these hours
219
+ for hour_idx, array_idx in zip(replace_indices, array_indices):
220
+ new_array[array_idx, :, hour_idx] = full_direct_blinds[
221
+ array_idx, :, hour_idx
222
+ ]
223
+
224
+ # Update the tracking array
225
+ tracking_array[array_indices, replace_indices] = 1
226
+
227
+ combinations = []
228
+ for hour in range(new_array.shape[2]):
229
+ hour_dict = {
230
+ light_paths[i]: tracking_array[i, hour]
231
+ for i in range(tracking_array.shape[0])}
232
+ combinations.append(hour_dict)
233
+
234
+ final_summed_array = np.sum(new_array, axis=0)
235
+ final_percentage_sensors_summed = (
236
+ final_summed_array >= 1000).sum(
237
+ axis=0) / grid_count
238
+ final_indices_above_2_percent = np.where(
239
+ final_percentage_sensors_summed > 0.02)[0]
240
+ if np.any(final_indices_above_2_percent):
241
+ sun_up_hours_indices = np.where(occ_mask == 1)[0][
242
+ final_indices_above_2_percent]
243
+ grid_comply = np.array(results.sun_up_hours)[sun_up_hours_indices]
244
+ fail_to_comply[grid_info['name']] = [
245
+ int(hoy) for hoy in grid_comply]
246
+
247
+ del new_array, tracking_array, percentage_sensors, ranking_indices, final_summed_array
248
+
249
+ del full_direct, full_thresh, full_direct_blinds, full_direct_sum
250
+
251
+ for combination in combinations:
252
+ for light_path, value in combination.items():
253
+ if light_path != '__static_apertures__':
254
+ states_schedule[light_path].append(value)
255
+
256
+ return states_schedule, fail_to_comply
@@ -0,0 +1,439 @@
1
+ """Functions to calculate various metrics for 1D and 2D NumPy arrays."""
2
+ from typing import Tuple, Union
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 .util import check_array_dim
11
+
12
+ is_cpu = not is_gpu
13
+
14
+
15
+ def da_array2d(
16
+ array: np.ndarray, total_occ: int = None, threshold: float = 300) -> np.ndarray:
17
+ """Calculate daylight autonomy for a 2D NumPy array.
18
+
19
+ Args:
20
+ array: A 2D NumPy array.
21
+ total_occ: Integer indicating the number of occupied hours. If not
22
+ given any input the number of occupied hours will be found by the
23
+ array shape, i.e., it is assumed that the array is filtered by
24
+ occupied hours.
25
+ threshold: Threshold value for daylight autonomy. Default: 300.
26
+
27
+ Returns:
28
+ A 1-dimensional NumPy array with the daylight autonomy for each row in
29
+ the input array.
30
+ """
31
+ check_array_dim(array, 2)
32
+ if total_occ is None:
33
+ # set total_occ to number of columns in array
34
+ total_occ = array.shape[1]
35
+
36
+ da = np.sum(array >= threshold, axis=1) / total_occ * 100
37
+
38
+ return da
39
+
40
+
41
+ def da_array1d(
42
+ array: np.ndarray, total_occ: int = None,
43
+ threshold: float = 300) -> np.float64:
44
+ """Calculate daylight autonomy for a 1D NumPy array.
45
+
46
+ Args:
47
+ array: A 1D NumPy array.
48
+ total_occ: Integer indicating the number of occupied hours. If not given any
49
+ input the number of occupied hours will be found by the array shape.
50
+ threshold: Threshold value for daylight autonomy. Default: 300.
51
+
52
+ Returns:
53
+ A NumPy float of the daylight autonomy.
54
+ """
55
+ check_array_dim(array, 1)
56
+ if total_occ is None:
57
+ # set total_occ to number of columns in array
58
+ total_occ = array.size
59
+
60
+ return np.float64((array >= threshold).sum() / total_occ * 100)
61
+
62
+
63
+ def cda_array2d(
64
+ array: np.ndarray, total_occ: int = None,
65
+ threshold: float = 300) -> np.ndarray:
66
+ """Calculate continuos daylight autonomy for a 2D NumPy array.
67
+
68
+ Args:
69
+ array: A 2D NumPy array.
70
+ total_occ: Integer indicating the number of occupied hours. If not given any
71
+ input the number of occupied hours will be found by the array shape.
72
+ threshold: Threshold value for continuos daylight autonomy. Default: 300.
73
+
74
+ Returns:
75
+ A 1-dimensional NumPy array with the continuos daylight autonomy for
76
+ each row in the input array.
77
+ """
78
+ check_array_dim(array, 2)
79
+ if total_occ is None:
80
+ # set total_occ to number of columns in array
81
+ total_occ = array.shape[1]
82
+
83
+ if is_cpu:
84
+ cda = np.apply_along_axis(
85
+ cda_array1d, 1, array, total_occ=total_occ, threshold=threshold)
86
+ else:
87
+ cda = np.where(array >= threshold, 1, array / threshold).sum(axis=1) / total_occ * 100
88
+
89
+ return cda
90
+
91
+
92
+ def cda_array1d(
93
+ array: np.ndarray, total_occ: int = None,
94
+ threshold: float = 300) -> np.float64:
95
+ """Calculate continuos daylight autonomy for a 1D NumPy array.
96
+
97
+ Args:
98
+ array: A 1D NumPy array.
99
+ total_occ: Integer indicating the number of occupied hours. If not given any
100
+ input the number of occupied hours will be found by the array shape.
101
+ threshold: Threshold value for continuos daylight autonomy. Default: 300.
102
+
103
+ Returns:
104
+ A NumPy float of the continuos daylight autonomy.
105
+ """
106
+ check_array_dim(array, 1)
107
+ if total_occ is None:
108
+ # set total_occ to number of columns in array
109
+ total_occ = array.size
110
+
111
+ return np.float64(
112
+ np.where(array >= threshold, 1, array / threshold).sum() / total_occ * 100)
113
+
114
+
115
+ def udi_array2d(
116
+ array: np.ndarray, total_occ: int = None, min_t: float = 100,
117
+ max_t: float = 3000) -> np.ndarray:
118
+ """Calculate useful daylight illuminance for a 2D NumPy array.
119
+
120
+ Args:
121
+ array: A 2D NumPy array.
122
+ total_occ: Integer indicating the number of occupied hours. If not given any
123
+ input the number of occupied hours will be found by the array shape.
124
+ min_t: Minimum threshold for useful daylight illuminance. Default: 100.
125
+ max_t: Maximum threshold for useful daylight illuminance. Default: 3000.
126
+
127
+ Returns:
128
+ A 1-dimensional NumPy array with the useful daylight illuminance for
129
+ each row in the input array.
130
+ """
131
+ check_array_dim(array, 2)
132
+ if total_occ is None:
133
+ # set total_occ to number of columns in array
134
+ total_occ = array.shape[1]
135
+
136
+ if is_cpu:
137
+ udi = np.apply_along_axis(
138
+ udi_array1d, 1, array, total_occ=total_occ, min_t=min_t, max_t=max_t)
139
+ else:
140
+ udi = ((array >= min_t) & (array <= max_t)).sum(axis=1) / total_occ * 100
141
+
142
+ return udi
143
+
144
+
145
+ def udi_array1d(
146
+ array: np.ndarray, total_occ: int = None, min_t: float = 100,
147
+ max_t: float = 3000) -> np.float64:
148
+ """Calculate useful daylight illuminance for a 1D NumPy array.
149
+
150
+ Args:
151
+ array: A 1D NumPy array.
152
+ total_occ: Integer indicating the number of occupied hours. If not given any
153
+ input the number of occupied hours will be found by the array shape.
154
+ min_t: Minimum threshold for useful daylight illuminance. Default: 100.
155
+ max_t: Maximum threshold for useful daylight illuminance. Default: 3000.
156
+
157
+ Returns:
158
+ A NumPy float of the useful daylight illuminance.
159
+ """
160
+ check_array_dim(array, 1)
161
+ if total_occ is None:
162
+ # set total_occ to number of columns in array
163
+ total_occ = array.size
164
+
165
+ return np.float64(((array >= min_t) & (array <= max_t)).sum() / total_occ * 100)
166
+
167
+
168
+ def udi_lower_array2d(
169
+ array: np.ndarray, total_occ: int = None, min_t: float = 100,
170
+ sun_down_occ_hours: int = 0) -> np.ndarray:
171
+ """Calculate lower than useful daylight illuminance for a 2D NumPy array.
172
+
173
+ Args:
174
+ array: A 2D NumPy array.
175
+ total_occ: Integer indicating the number of occupied hours. If not given any
176
+ input the number of occupied hours will be found by the array shape.
177
+ min_t: Minimum threshold for useful daylight illuminance. Default: 100.
178
+ sun_down_occ_hours: Number of occupied hours where the sun is down.
179
+
180
+ Returns:
181
+ A 1-dimensional NumPy array with the lower than useful daylight
182
+ illuminance for each row in the input array.
183
+ """
184
+ check_array_dim(array, 2)
185
+ if total_occ is None:
186
+ # set total_occ to number of columns in array
187
+ total_occ = array.shape[1]
188
+
189
+ if is_cpu:
190
+ udi = np.apply_along_axis(
191
+ udi_lower_array1d, 1, array, total_occ=total_occ, min_t=min_t,
192
+ sun_down_occ_hours=sun_down_occ_hours)
193
+ else:
194
+ if min_t == 0:
195
+ return np.zeros(array.shape[0], dtype=np.float32)
196
+
197
+ udi = ((array < min_t).sum(axis=1) + sun_down_occ_hours) / total_occ * 100
198
+
199
+ return udi
200
+
201
+
202
+ def udi_lower_array1d(
203
+ array: np.ndarray, total_occ: int = None, min_t: float = 100,
204
+ sun_down_occ_hours: int = 0) -> np.float64:
205
+ """Calculate lower than useful daylight illuminance for a 1D NumPy array.
206
+
207
+ Args:
208
+ array: A 1D NumPy array.
209
+ total_occ: Integer indicating the number of occupied hours. If not given any
210
+ input the number of occupied hours will be found by the array shape.
211
+ min_t: Minimum threshold for useful daylight illuminance. Default: 100.
212
+ sun_down_occ_hours: Number of occupied hours where the sun is down.
213
+
214
+ Returns:
215
+ A NumPy float of the lower than useful daylight illuminance.
216
+ """
217
+ check_array_dim(array, 1)
218
+ if total_occ is None:
219
+ # set total_occ to number of columns in array
220
+ total_occ = array.size
221
+
222
+ if min_t == 0:
223
+ return np.float64(0)
224
+
225
+ return np.float64(((array < min_t).sum() + sun_down_occ_hours) / total_occ * 100)
226
+
227
+
228
+ def udi_upper_array2d(
229
+ array: np.ndarray, total_occ: int = None,
230
+ max_t: float = 3000) -> np.ndarray:
231
+ """Calculate higher than useful daylight illuminance for a 2D NumPy array.
232
+
233
+ Args:
234
+ array: A 2D NumPy array.
235
+ total_occ: Integer indicating the number of occupied hours. If not given any
236
+ input the number of occupied hours will be found by the array shape.
237
+ max_t: Maximum threshold for useful daylight illuminance. Default: 3000.
238
+
239
+ Returns:
240
+ A 1-dimensional NumPy array with the higher than useful daylight
241
+ illuminance for each row in the input array.
242
+ """
243
+ check_array_dim(array, 2)
244
+ if total_occ is None:
245
+ # set total_occ to number of columns in array
246
+ total_occ = array.shape[1]
247
+
248
+ if is_cpu:
249
+ udi = np.apply_along_axis(
250
+ udi_upper_array1d, 1, array, total_occ=total_occ, max_t=max_t)
251
+ else:
252
+ udi = (array > max_t).sum(axis=1) / total_occ * 100
253
+
254
+ return udi
255
+
256
+
257
+ def udi_upper_array1d(
258
+ array: np.ndarray, total_occ: int = None,
259
+ max_t: float = 3000) -> np.float64:
260
+ """Calculate higher than useful daylight illuminance for a 1D NumPy array.
261
+
262
+ Args:
263
+ array: A 1D NumPy array.
264
+ total_occ: Integer indicating the number of occupied hours. If not given any
265
+ input the number of occupied hours will be found by the array shape.
266
+ max_t: Maximum threshold for higher than useful daylight illuminance.
267
+ Default: 3000.
268
+
269
+ Returns:
270
+ A NumPy float of the higher than useful daylight illuminance.
271
+ """
272
+ check_array_dim(array, 1)
273
+ if total_occ is None:
274
+ # set total_occ to number of columns in array
275
+ total_occ = array.size
276
+
277
+ return np.float64((array > max_t).sum() / total_occ * 100)
278
+
279
+
280
+ def sda_array2d(
281
+ array: np.ndarray, target_time: float = 50, threshold: float = 300,
282
+ total_occ: int = None) -> np.ndarray:
283
+ """Calculate spatial daylight autonomy for a 2D NumPy array.
284
+
285
+ Args:
286
+ array: A 2D NumPy array.
287
+ target_time: A minimum threshold of occupied time (eg. 50% of the
288
+ time), above which a given sensor passes and contributes to the
289
+ spatial daylight autonomy. Defaults to 50.
290
+ threshold: Threshold value for daylight autonomy. Default: 300.
291
+ total_occ: Integer indicating the number of occupied hours. If not
292
+ given any input the number of occupied hours will be found by the
293
+ array shape, i.e., it is assumed that the array is filtered by
294
+ occupied hours.
295
+
296
+ Returns:
297
+ A NumPy float of the sDA as a percentage (decimal)
298
+ """
299
+ da = np.sum(array >= threshold, axis=1) / total_occ * 100
300
+ sda = (da >= target_time).mean()
301
+
302
+ return sda
303
+
304
+
305
+ def ase_array2d(
306
+ array: np.ndarray, occ_hours: int = 250,
307
+ direct_threshold: float = 1000) -> Tuple[np.ndarray, np.ndarray]:
308
+ """Calculate annual sunlight exposure for a 2D NumPy array.
309
+
310
+ Args:
311
+ array: A 2D NumPy array.
312
+ occ_hours: The number of occupied hours that cannot receive more than
313
+ the direct_threshold. Defaults to 250.
314
+ direct_threshold: The threshold that determines if a sensor is overlit.
315
+ Defaults to 1000.
316
+
317
+ Returns:
318
+ A Tuple with two values.
319
+
320
+ - ase: NumPy float of the ASE as a percentage (decimal).
321
+
322
+ - h_above: 1D NumPy array of the number of hours above the direct
323
+ threshold.
324
+ """
325
+ check_array_dim(array, 2)
326
+ h_above = (array > direct_threshold).sum(axis=1)
327
+ ase = (h_above >= occ_hours).sum() / array.shape[0] * 100
328
+
329
+ return ase, h_above
330
+
331
+
332
+ def average_values_array2d(
333
+ array: np.ndarray, full_length: int = 8760) -> np.ndarray:
334
+ """Calculate average values for a 2D NumPy array.
335
+
336
+ Args:
337
+ array: A 2D NumPy array.
338
+ full_length: Integer to use as divisor.
339
+
340
+ Returns:
341
+ A 1-dimensional NumPy array with the average value for each row in the
342
+ input array.
343
+ """
344
+ check_array_dim(array, 2)
345
+
346
+ avg_values = array.sum(axis=1) / full_length
347
+
348
+ return avg_values
349
+
350
+
351
+ def average_values_array1d(
352
+ array: np.ndarray, full_length: int = 8760) -> np.float64:
353
+ """Calculate average value for a 1D NumPy array.
354
+
355
+ Args:
356
+ array: A 1D NumPy array.
357
+ full_length: Integer to use as divisor.
358
+
359
+ Returns:
360
+ A NumPy float of the average value.
361
+ """
362
+ check_array_dim(array, 1)
363
+
364
+ return array.sum() / full_length
365
+
366
+
367
+ def cumulative_values_array2d(
368
+ array: np.ndarray, timestep: int = 1, t_step_multiplier: float = 1
369
+ ) -> np.ndarray:
370
+ """Calculate cumulative values for a 2D NumPy array.
371
+
372
+ Args:
373
+ array: A 2D NumPy array.
374
+ timestep: Integer for the timestep of the analysis.
375
+ t_step_multiplier: A value that will be multiplied with the timestep.
376
+
377
+ Returns:
378
+ A 1-dimensional NumPy array with the cumulative value for each row in
379
+ the input array.
380
+ """
381
+ check_array_dim(array, 2)
382
+
383
+ cumulative_values = array.sum(axis=1) / (timestep * t_step_multiplier)
384
+
385
+ return cumulative_values
386
+
387
+
388
+ def cumulative_values_array1d(
389
+ array: np.ndarray, timestep: int = 1, t_step_multiplier: float = 1
390
+ ) -> np.float64:
391
+ """Calculate daylight autonomy for a 1D NumPy array.
392
+
393
+ Args:
394
+ array: A 1D NumPy array.
395
+ timestep: Integer for the timestep of the analysis.
396
+ t_step_multiplier: A value that will be multiplied with the timestep.
397
+
398
+ Returns:
399
+ A NumPy float of the cumulative value.
400
+ """
401
+ check_array_dim(array, 1)
402
+
403
+ return array.sum() / (timestep * t_step_multiplier)
404
+
405
+
406
+ def peak_values_array2d(
407
+ array: np.ndarray, coincident: bool = False
408
+ ) -> Tuple[np.ndarray, Union[int, None]]:
409
+ """Calculate peak values for a 2D NumPy array.
410
+
411
+ Args:
412
+ array: A 2D NumPy array.
413
+ coincident: Boolean to indicate whether output values represent the
414
+ peak value for each sensor throughout the entire analysis (False)
415
+ or they represent the highest overall value across each sensor grid
416
+ at a particular timestep (True).
417
+
418
+ Returns:
419
+ A 1-dimensional NumPy array with the peak value for each row in the
420
+ input array, and the index of the maximum value representing the
421
+ timestep in the array with the largest value.
422
+ """
423
+ check_array_dim(array, 2)
424
+
425
+ max_i = None
426
+ if coincident:
427
+ array_summed = array.sum(axis=0)
428
+ if np.any(array_summed):
429
+ max_i = np.argmax(array_summed)
430
+ peak_values = array[:, max_i]
431
+ else:
432
+ peak_values = np.zeros(array.shape[0])
433
+ else:
434
+ if np.any(array):
435
+ peak_values = np.amax(array, axis=1)
436
+ else:
437
+ peak_values = np.zeros(array.shape[0])
438
+
439
+ return peak_values, max_i