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.
- honeybee_radiance_postprocess/__init__.py +1 -0
- honeybee_radiance_postprocess/__main__.py +4 -0
- honeybee_radiance_postprocess/annual.py +73 -0
- honeybee_radiance_postprocess/annualdaylight.py +289 -0
- honeybee_radiance_postprocess/annualirradiance.py +35 -0
- honeybee_radiance_postprocess/breeam/__init__.py +1 -0
- honeybee_radiance_postprocess/breeam/breeam.py +552 -0
- honeybee_radiance_postprocess/cli/__init__.py +33 -0
- honeybee_radiance_postprocess/cli/abnt.py +392 -0
- honeybee_radiance_postprocess/cli/breeam.py +96 -0
- honeybee_radiance_postprocess/cli/datacollection.py +133 -0
- honeybee_radiance_postprocess/cli/grid.py +295 -0
- honeybee_radiance_postprocess/cli/leed.py +143 -0
- honeybee_radiance_postprocess/cli/merge.py +161 -0
- honeybee_radiance_postprocess/cli/mtxop.py +161 -0
- honeybee_radiance_postprocess/cli/postprocess.py +1092 -0
- honeybee_radiance_postprocess/cli/schedule.py +103 -0
- honeybee_radiance_postprocess/cli/translate.py +216 -0
- honeybee_radiance_postprocess/cli/two_phase.py +252 -0
- honeybee_radiance_postprocess/cli/util.py +121 -0
- honeybee_radiance_postprocess/cli/viewfactor.py +157 -0
- honeybee_radiance_postprocess/cli/well.py +110 -0
- honeybee_radiance_postprocess/data_type.py +102 -0
- honeybee_radiance_postprocess/dynamic.py +273 -0
- honeybee_radiance_postprocess/electriclight.py +24 -0
- honeybee_radiance_postprocess/en17037.py +304 -0
- honeybee_radiance_postprocess/helper.py +266 -0
- honeybee_radiance_postprocess/ies/__init__.py +1 -0
- honeybee_radiance_postprocess/ies/lm.py +224 -0
- honeybee_radiance_postprocess/ies/lm_schedule.py +248 -0
- honeybee_radiance_postprocess/leed/__init__.py +1 -0
- honeybee_radiance_postprocess/leed/leed.py +801 -0
- honeybee_radiance_postprocess/leed/leed_schedule.py +256 -0
- honeybee_radiance_postprocess/metrics.py +439 -0
- honeybee_radiance_postprocess/reader.py +80 -0
- honeybee_radiance_postprocess/results/__init__.py +4 -0
- honeybee_radiance_postprocess/results/annual_daylight.py +752 -0
- honeybee_radiance_postprocess/results/annual_irradiance.py +196 -0
- honeybee_radiance_postprocess/results/results.py +1416 -0
- honeybee_radiance_postprocess/type_hints.py +38 -0
- honeybee_radiance_postprocess/util.py +211 -0
- honeybee_radiance_postprocess/vis_metadata.py +49 -0
- honeybee_radiance_postprocess/well/__init__.py +1 -0
- honeybee_radiance_postprocess/well/well.py +509 -0
- honeybee_radiance_postprocess-0.4.555.dist-info/METADATA +79 -0
- honeybee_radiance_postprocess-0.4.555.dist-info/RECORD +50 -0
- honeybee_radiance_postprocess-0.4.555.dist-info/WHEEL +5 -0
- honeybee_radiance_postprocess-0.4.555.dist-info/entry_points.txt +2 -0
- honeybee_radiance_postprocess-0.4.555.dist-info/licenses/LICENSE +661 -0
- 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
|