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,103 @@
|
|
1
|
+
"""honeybee radiance postprocess schedule commands."""
|
2
|
+
import click
|
3
|
+
import sys
|
4
|
+
import logging
|
5
|
+
|
6
|
+
from ..results.annual_daylight import AnnualDaylight
|
7
|
+
from ..dynamic import DynamicSchedule
|
8
|
+
|
9
|
+
_logger = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
|
12
|
+
@click.group(help='Commands to create schedules for Radiance results.')
|
13
|
+
def schedule():
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
@schedule.command('control-schedules')
|
18
|
+
@click.argument(
|
19
|
+
'folder',
|
20
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True)
|
21
|
+
)
|
22
|
+
@click.option(
|
23
|
+
'--base-schedule-file', '-bs', help='Path to a schedule file. A list of '
|
24
|
+
'8760 fractional values for the lighting schedule representing the usage '
|
25
|
+
'of lights without any daylight controls. The values of this schedule '
|
26
|
+
'will be multiplied by the hourly dimming fraction to yield the output '
|
27
|
+
'lighting schedules. If None, a schedule from 9AM to 5PM on weekdays will '
|
28
|
+
'be used.',
|
29
|
+
type=click.Path(exists=False, file_okay=True, dir_okay=False, resolve_path=True)
|
30
|
+
)
|
31
|
+
@click.option(
|
32
|
+
'--states', '-st', help='A JSON file with a dictionary of states. If states '
|
33
|
+
'are not provided the default states will be used for any aperture groups.',
|
34
|
+
default=None, show_default=True,
|
35
|
+
type=click.Path(exists=False, file_okay=True, dir_okay=False, resolve_path=True)
|
36
|
+
)
|
37
|
+
@click.option(
|
38
|
+
'--grids-filter', '-gf', help='A pattern to filter the grids.', default='*',
|
39
|
+
show_default=True
|
40
|
+
)
|
41
|
+
@click.option(
|
42
|
+
'--ill-setpoint',
|
43
|
+
help='A number for the illuminance setpoint in lux beyond which electric '
|
44
|
+
'lights are dimmed if there is sufficient daylight.', default=300,
|
45
|
+
type=float, show_default=True
|
46
|
+
)
|
47
|
+
@click.option(
|
48
|
+
'--min-power-in',
|
49
|
+
help='A number between 0 and 1 for the the lowest power the lighting '
|
50
|
+
'system can dim down to, expressed as a fraction of maximum input power.',
|
51
|
+
default=0.3, type=float, show_default=True
|
52
|
+
)
|
53
|
+
@click.option(
|
54
|
+
'--min-light-out',
|
55
|
+
help='A number between 0 and 1 the lowest lighting output the lighting '
|
56
|
+
'system can dim down to, expressed as a fraction of maximum light output. '
|
57
|
+
'Note that setting this to 1 means lights are not dimmed at all until the '
|
58
|
+
'illuminance setpoint is reached.', default=0.2, type=float,
|
59
|
+
show_default=True
|
60
|
+
)
|
61
|
+
@click.option(
|
62
|
+
'--off-at-min', is_flag=True, default=False, help='Boolean to '
|
63
|
+
'note whether lights should switch off completely when they get to the '
|
64
|
+
'minimum power input.'
|
65
|
+
)
|
66
|
+
@click.option(
|
67
|
+
'--sub-folder', '-sf', help='Optional relative path for subfolder to '
|
68
|
+
'write output schedule files.', default='schedules'
|
69
|
+
)
|
70
|
+
def control_schedules(
|
71
|
+
folder, states, grids_filter, base_schedule_file, ill_setpoint,
|
72
|
+
min_power_in, min_light_out, off_at_min, sub_folder
|
73
|
+
):
|
74
|
+
"""Generate electric lighting schedules from annual daylight results.
|
75
|
+
|
76
|
+
\b
|
77
|
+
Args:
|
78
|
+
folder: Results folder. This folder is an output folder of annual daylight
|
79
|
+
recipe. Folder should include grids_info.json and sun-up-hours.txt. The
|
80
|
+
command uses the list in grids_info.json to find the result files for each
|
81
|
+
sensor grid.
|
82
|
+
"""
|
83
|
+
try:
|
84
|
+
if base_schedule_file:
|
85
|
+
with open(base_schedule_file) as base_schedule:
|
86
|
+
base_schedule = [float(h) for h in base_schedule.readlines()]
|
87
|
+
else:
|
88
|
+
base_schedule = None
|
89
|
+
|
90
|
+
if states:
|
91
|
+
states = DynamicSchedule.from_json(states)
|
92
|
+
|
93
|
+
results = AnnualDaylight(folder)
|
94
|
+
results.daylight_control_schedules_to_folder(
|
95
|
+
sub_folder, states=states, grids_filter=grids_filter,
|
96
|
+
base_schedule=base_schedule, ill_setpoint=ill_setpoint,
|
97
|
+
min_power_in=min_power_in, min_light_out=min_light_out,
|
98
|
+
off_at_min=off_at_min)
|
99
|
+
except Exception:
|
100
|
+
_logger.exception('Failed to generate control schedules.')
|
101
|
+
sys.exit(1)
|
102
|
+
else:
|
103
|
+
sys.exit(0)
|
@@ -0,0 +1,216 @@
|
|
1
|
+
"""Commands to translate objects."""
|
2
|
+
import sys
|
3
|
+
import logging
|
4
|
+
from pathlib import Path
|
5
|
+
import shutil
|
6
|
+
import click
|
7
|
+
import json
|
8
|
+
try:
|
9
|
+
import cupy as np
|
10
|
+
is_gpu = True
|
11
|
+
except ImportError:
|
12
|
+
is_gpu = False
|
13
|
+
import numpy as np
|
14
|
+
|
15
|
+
from ..reader import binary_to_array
|
16
|
+
from ..util import get_delimiter
|
17
|
+
|
18
|
+
_logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
@click.group(help='Commands to translate objects.')
|
22
|
+
def translate():
|
23
|
+
pass
|
24
|
+
|
25
|
+
|
26
|
+
@translate.command('npy-to-txt')
|
27
|
+
@click.argument(
|
28
|
+
'npy-file', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
29
|
+
)
|
30
|
+
@click.option(
|
31
|
+
'--name', '-n', help='Output file name.', default='output', show_default=True
|
32
|
+
)
|
33
|
+
@click.option(
|
34
|
+
'--output-folder', '-of', help='Output folder.', default='.',
|
35
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True, resolve_path=True)
|
36
|
+
)
|
37
|
+
@click.option(
|
38
|
+
'--extension', '-ext', help='Output file extension', default='txt', show_default=True
|
39
|
+
)
|
40
|
+
@click.option(
|
41
|
+
'--output-format', '-fmt', help='Output format for each element in the array',
|
42
|
+
default='%.7e', show_default=True
|
43
|
+
)
|
44
|
+
@click.option(
|
45
|
+
'--delimiter', '-d', help='Delimiter in the text file.', default='\t',
|
46
|
+
type=click.Choice(['\t', ' ', ',', ';', 'tab', 'space', 'comma', 'semicolon'])
|
47
|
+
)
|
48
|
+
def npy_to_txt(npy_file, name, output_folder, extension, output_format, delimiter):
|
49
|
+
"""Convert a npy file to text file.
|
50
|
+
|
51
|
+
This command reads a NumPy array from a npy file and saves it as readable file. The
|
52
|
+
converted file is tab separated.
|
53
|
+
|
54
|
+
\b
|
55
|
+
Args:
|
56
|
+
npy-file: Path to npy file.
|
57
|
+
"""
|
58
|
+
try:
|
59
|
+
delimiter = get_delimiter(delimiter)
|
60
|
+
array = np.load(npy_file)
|
61
|
+
output = Path(output_folder, f'{name}.{extension}')
|
62
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
63
|
+
np.savetxt(output, array, fmt=output_format, delimiter=delimiter)
|
64
|
+
|
65
|
+
except Exception:
|
66
|
+
_logger.exception('Converting npy file to text file failed.')
|
67
|
+
sys.exit(1)
|
68
|
+
else:
|
69
|
+
sys.exit(0)
|
70
|
+
|
71
|
+
|
72
|
+
@translate.command('txt-to-npy')
|
73
|
+
@click.argument(
|
74
|
+
'txt-file', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
75
|
+
)
|
76
|
+
@click.option(
|
77
|
+
'--name', '-n', help='Output file name.', default='output', show_default=True
|
78
|
+
)
|
79
|
+
@click.option(
|
80
|
+
'--output-folder', '-of', help='Output folder.', default='.',
|
81
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True, resolve_path=True)
|
82
|
+
)
|
83
|
+
@click.option(
|
84
|
+
'--delimiter', '-d', help='Delimiter in the text file.', default='\t',
|
85
|
+
type=click.Choice(['\t', ' ', ',', ';', 'tab', 'space', 'comma', 'semicolon'])
|
86
|
+
)
|
87
|
+
def txt_to_npy(txt_file, name, output_folder, delimiter):
|
88
|
+
"""Convert a text file to npy file.
|
89
|
+
|
90
|
+
This command reads a separated text file and saves it as a NumPy file. As
|
91
|
+
an example the input file could be the annual illuminance values.
|
92
|
+
|
93
|
+
\b
|
94
|
+
Args:
|
95
|
+
txt-file: Path to text file.
|
96
|
+
"""
|
97
|
+
try:
|
98
|
+
delimiter = get_delimiter(delimiter)
|
99
|
+
array = np.genfromtxt(txt_file, dtype=np.float32, delimiter=delimiter)
|
100
|
+
output = Path(output_folder, name)
|
101
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
102
|
+
np.save(output, array)
|
103
|
+
|
104
|
+
except Exception:
|
105
|
+
_logger.exception('Converting text file to npy file failed.')
|
106
|
+
sys.exit(1)
|
107
|
+
else:
|
108
|
+
sys.exit(0)
|
109
|
+
|
110
|
+
|
111
|
+
@translate.command('binary-to-npy')
|
112
|
+
@click.argument(
|
113
|
+
'mtx-file', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
114
|
+
)
|
115
|
+
@click.option(
|
116
|
+
'--conversion', help='Conversion as a string. This option is useful to post-process '
|
117
|
+
'the results from 3 RGB components into one as part of this command.'
|
118
|
+
)
|
119
|
+
@click.option(
|
120
|
+
'--name', '-n', help='Output file name.', default='output', show_default=True
|
121
|
+
)
|
122
|
+
@click.option(
|
123
|
+
'--output-folder', '-of', help='Output folder.', default='.',
|
124
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True, resolve_path=True)
|
125
|
+
)
|
126
|
+
def binary_to_npy(mtx_file, conversion, name, output_folder):
|
127
|
+
"""Convert a binary Radiance file to a npy file.
|
128
|
+
|
129
|
+
This command reads a binary Radiance matrix file and saves it as a NumPy file.
|
130
|
+
|
131
|
+
\b
|
132
|
+
Args:
|
133
|
+
mtx-file: Path to binary Radiance file.
|
134
|
+
"""
|
135
|
+
try:
|
136
|
+
array = binary_to_array(mtx_file)
|
137
|
+
if conversion:
|
138
|
+
conversion = list(map(float, conversion.split(' ')))
|
139
|
+
conversion = np.array(conversion, dtype=np.float32)
|
140
|
+
array = np.dot(array, conversion)
|
141
|
+
output = Path(output_folder, name)
|
142
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
143
|
+
np.save(output, array)
|
144
|
+
|
145
|
+
except Exception:
|
146
|
+
_logger.exception('Converting binary Radiance file to npy file failed.')
|
147
|
+
sys.exit(1)
|
148
|
+
else:
|
149
|
+
sys.exit(0)
|
150
|
+
|
151
|
+
|
152
|
+
@translate.command('annual-daylight-npy-to-ill')
|
153
|
+
@click.argument(
|
154
|
+
'folder',
|
155
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True)
|
156
|
+
)
|
157
|
+
@click.option(
|
158
|
+
'--output-folder', '-of', help='Output folder. If not provided the output '
|
159
|
+
'folder will be created in the same directory as the results folder. The '
|
160
|
+
'new folder will be called results_ill.', default=None,
|
161
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True, resolve_path=True)
|
162
|
+
)
|
163
|
+
def annual_daylight_npy_to_ill(folder, output_folder):
|
164
|
+
"""Convert an annual daylight results folder to older version.
|
165
|
+
|
166
|
+
This command reads an annual daylight results folder with results saved as
|
167
|
+
npy files (NumPy), and converts the npy files to text files in the old
|
168
|
+
results folder format.
|
169
|
+
|
170
|
+
\b
|
171
|
+
Args:
|
172
|
+
folder: Results folder. This folder is an output folder of annual
|
173
|
+
daylight recipe. Folder should include grids_info.json and
|
174
|
+
sun-up-hours.txt.
|
175
|
+
"""
|
176
|
+
try:
|
177
|
+
folder = Path(folder)
|
178
|
+
static_ill_folder = folder.joinpath('__static_apertures__/default/total')
|
179
|
+
if not static_ill_folder.exists():
|
180
|
+
raise FileNotFoundError(
|
181
|
+
'No results were found for static apertures in the results '
|
182
|
+
'folder.')
|
183
|
+
grids_info_file = folder.joinpath('grids_info.json')
|
184
|
+
if not grids_info_file.exists():
|
185
|
+
raise FileNotFoundError(
|
186
|
+
'The file grids_info.json was not found in the results folder.')
|
187
|
+
sun_up_hours_file = folder.joinpath('sun-up-hours.txt')
|
188
|
+
if not sun_up_hours_file.exists():
|
189
|
+
raise FileNotFoundError(
|
190
|
+
'The file sun-up-hours.txt was not found in the results folder.')
|
191
|
+
|
192
|
+
if output_folder is None:
|
193
|
+
output_folder = folder.parent.joinpath('results_ill')
|
194
|
+
else:
|
195
|
+
output_folder = Path(output_folder)
|
196
|
+
output_folder.mkdir(parents=True, exist_ok=True)
|
197
|
+
|
198
|
+
with open(grids_info_file) as json_file:
|
199
|
+
grids_info = json.load(json_file)
|
200
|
+
|
201
|
+
for grid_info in grids_info:
|
202
|
+
full_id = grid_info['full_id']
|
203
|
+
npy_file = static_ill_folder.joinpath(f'{full_id}.npy')
|
204
|
+
|
205
|
+
array = np.load(npy_file)
|
206
|
+
output = Path(output_folder, full_id + '.ill')
|
207
|
+
np.savetxt(output, array, fmt='%.7e', delimiter='\t')
|
208
|
+
|
209
|
+
# copy grids_info and sun-up-hours
|
210
|
+
shutil.copy(grids_info_file, output_folder.joinpath('grids_info.json'))
|
211
|
+
shutil.copy(sun_up_hours_file, output_folder.joinpath('sun-up-hours.txt'))
|
212
|
+
except Exception:
|
213
|
+
_logger.exception('Converting annual daylight results folder failed.')
|
214
|
+
sys.exit(1)
|
215
|
+
else:
|
216
|
+
sys.exit(0)
|
@@ -0,0 +1,252 @@
|
|
1
|
+
"""Commands to work with two phase Radiance matrices using NumPy."""
|
2
|
+
import sys
|
3
|
+
import logging
|
4
|
+
from pathlib import Path
|
5
|
+
import click
|
6
|
+
try:
|
7
|
+
import cupy as np
|
8
|
+
is_gpu = True
|
9
|
+
except ImportError:
|
10
|
+
is_gpu = False
|
11
|
+
import numpy as np
|
12
|
+
|
13
|
+
from ..reader import binary_to_array, ascii_to_array
|
14
|
+
|
15
|
+
_logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
@click.group(help='Commands to work with two phase Radiance matrices using NumPy.')
|
19
|
+
def two_phase():
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
@two_phase.command('rgb-to-illuminance')
|
24
|
+
@click.argument(
|
25
|
+
'total-mtx', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
26
|
+
)
|
27
|
+
@click.argument(
|
28
|
+
'direct-mtx', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
29
|
+
)
|
30
|
+
@click.argument(
|
31
|
+
'direct-sunlight-mtx', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
32
|
+
)
|
33
|
+
@click.option(
|
34
|
+
'--binary/--ascii', is_flag=True, default=True, help='Switch between binary '
|
35
|
+
'and ascii input matrices. Default is binary.'
|
36
|
+
)
|
37
|
+
@click.option(
|
38
|
+
'--float32/--float16', is_flag=True, default=True, help='Switch between float32 '
|
39
|
+
'and float16 output matrices. Default is float32.'
|
40
|
+
)
|
41
|
+
@click.option(
|
42
|
+
'--total-name', '-n', help='Total output file name.', default='total',
|
43
|
+
show_default=True
|
44
|
+
)
|
45
|
+
@click.option(
|
46
|
+
'--direct-name', '-n', help='Direct output file name.', default='direct',
|
47
|
+
show_default=True
|
48
|
+
)
|
49
|
+
@click.option(
|
50
|
+
'--output-folder', '-of', help='Output folder.', default='.',
|
51
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True, resolve_path=True)
|
52
|
+
)
|
53
|
+
def rgb_to_illuminance(
|
54
|
+
total_mtx, direct_mtx, direct_sunlight_mtx, binary, float32, total_name,
|
55
|
+
direct_name, output_folder
|
56
|
+
):
|
57
|
+
"""Process results of two phase simulations (total, direct, direct sunlight).
|
58
|
+
|
59
|
+
The function will replace the direct illuminance with the direct sunlight
|
60
|
+
illuminance: total - direct + direct_sunlight.
|
61
|
+
|
62
|
+
The function has two output files. One for the illuminance of the above
|
63
|
+
calculation, and one for the direct sunlight illuminance. In both cases
|
64
|
+
the conversion from RGB to illuminance is executed.
|
65
|
+
|
66
|
+
\b
|
67
|
+
Args:
|
68
|
+
total-mtx: Path to total matrix.
|
69
|
+
direct-mtx: Path to direct matrix.
|
70
|
+
direct-sunlight-mtx: Path to direct sunlight matrix.
|
71
|
+
"""
|
72
|
+
try:
|
73
|
+
if binary:
|
74
|
+
total = binary_to_array(total_mtx)
|
75
|
+
direct = binary_to_array(direct_mtx)
|
76
|
+
direct_sunlight = binary_to_array(direct_sunlight_mtx)
|
77
|
+
else:
|
78
|
+
total = ascii_to_array(total_mtx)
|
79
|
+
direct = ascii_to_array(direct_mtx)
|
80
|
+
direct_sunlight = ascii_to_array(direct_sunlight_mtx)
|
81
|
+
|
82
|
+
data = total - direct + direct_sunlight
|
83
|
+
|
84
|
+
conversion = np.array([47.4, 119.9, 11.6], dtype=np.float32)
|
85
|
+
total_illuminance = np.dot(data, conversion)
|
86
|
+
direct_sunlight_illuminance = np.dot(direct_sunlight, conversion)
|
87
|
+
|
88
|
+
if not float32:
|
89
|
+
total_illuminance = np.minimum(
|
90
|
+
total_illuminance, np.finfo(np.float16).max).astype(
|
91
|
+
np.float16)
|
92
|
+
direct_sunlight_illuminance = np.minimum(
|
93
|
+
direct_sunlight_illuminance, np.finfo(np.float16).max).astype(
|
94
|
+
np.float16)
|
95
|
+
|
96
|
+
# save total illuminance
|
97
|
+
total_output = Path(output_folder, total_name)
|
98
|
+
total_output.parent.mkdir(parents=True, exist_ok=True)
|
99
|
+
np.save(total_output, total_illuminance)
|
100
|
+
|
101
|
+
# save direct sunlight illuminance
|
102
|
+
direct_output = Path(output_folder, direct_name)
|
103
|
+
direct_output.parent.mkdir(parents=True, exist_ok=True)
|
104
|
+
|
105
|
+
np.save(direct_output, direct_sunlight_illuminance)
|
106
|
+
|
107
|
+
except Exception:
|
108
|
+
_logger.exception('Processing annual results failed.')
|
109
|
+
sys.exit(1)
|
110
|
+
else:
|
111
|
+
sys.exit(0)
|
112
|
+
|
113
|
+
|
114
|
+
@two_phase.command('rgb-to-illuminance-file')
|
115
|
+
@click.argument(
|
116
|
+
'mtx-file', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
117
|
+
)
|
118
|
+
@click.option(
|
119
|
+
'--binary/--ascii', is_flag=True, default=True, help='Switch between binary '
|
120
|
+
'and ascii input matrices. Default is binary.'
|
121
|
+
)
|
122
|
+
@click.option(
|
123
|
+
'--float32/--float16', is_flag=True, default=True, help='Switch between float32 '
|
124
|
+
'and float16 output matrices. Default is float32.'
|
125
|
+
)
|
126
|
+
@click.option(
|
127
|
+
'--name', '-n', help='Name of output file.', default='illuminance',
|
128
|
+
show_default=True
|
129
|
+
)
|
130
|
+
@click.option(
|
131
|
+
'--output-folder', '-of', help='Output folder.', default='.',
|
132
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True, resolve_path=True)
|
133
|
+
)
|
134
|
+
def rgb_to_illuminance_file(
|
135
|
+
mtx_file, binary, float32, name, output_folder
|
136
|
+
):
|
137
|
+
"""Convert a RGB Radiance matrix to illuminance and save the array as a
|
138
|
+
NumPy file.
|
139
|
+
|
140
|
+
\b
|
141
|
+
Args:
|
142
|
+
mtx-file: Path to matrix file to convert.
|
143
|
+
"""
|
144
|
+
try:
|
145
|
+
if binary:
|
146
|
+
mtx = binary_to_array(mtx_file)
|
147
|
+
else:
|
148
|
+
mtx = ascii_to_array(mtx_file)
|
149
|
+
|
150
|
+
conversion = np.array([47.4, 119.9, 11.6], dtype=np.float32)
|
151
|
+
total_illuminance = np.dot(mtx, conversion)
|
152
|
+
|
153
|
+
if not float32:
|
154
|
+
total_illuminance = np.minimum(
|
155
|
+
total_illuminance, np.finfo(np.float16).max).astype(
|
156
|
+
np.float16)
|
157
|
+
|
158
|
+
# save total illuminance
|
159
|
+
total_output = Path(output_folder, name)
|
160
|
+
total_output.parent.mkdir(parents=True, exist_ok=True)
|
161
|
+
np.save(total_output, total_illuminance)
|
162
|
+
|
163
|
+
except Exception:
|
164
|
+
_logger.exception('Processing annual results failed.')
|
165
|
+
sys.exit(1)
|
166
|
+
else:
|
167
|
+
sys.exit(0)
|
168
|
+
|
169
|
+
|
170
|
+
@two_phase.command('add-remove-sky-matrix')
|
171
|
+
@click.argument(
|
172
|
+
'total-mtx', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
173
|
+
)
|
174
|
+
@click.argument(
|
175
|
+
'direct-mtx', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
176
|
+
)
|
177
|
+
@click.argument(
|
178
|
+
'direct-sunlight-mtx', type=click.Path(exists=True, dir_okay=False, resolve_path=True)
|
179
|
+
)
|
180
|
+
@click.option(
|
181
|
+
'--binary/--ascii', is_flag=True, default=True, help='Switch between binary '
|
182
|
+
'and ascii input matrices. Default is binary.'
|
183
|
+
)
|
184
|
+
@click.option(
|
185
|
+
'--float32/--float16', is_flag=True, default=True, help='Switch between float32 '
|
186
|
+
'and float16 output matrices. Default is float32.'
|
187
|
+
)
|
188
|
+
@click.option(
|
189
|
+
'--total-name', '-n', help='Total output file name.', default='total',
|
190
|
+
show_default=True
|
191
|
+
)
|
192
|
+
@click.option(
|
193
|
+
'--direct-name', '-n', help='Direct output file name.', default='direct',
|
194
|
+
show_default=True
|
195
|
+
)
|
196
|
+
@click.option(
|
197
|
+
'--output-folder', '-of', help='Output folder.', default='.',
|
198
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True, resolve_path=True)
|
199
|
+
)
|
200
|
+
def add_remove_sky_matrix(
|
201
|
+
total_mtx, direct_mtx, direct_sunlight_mtx, binary, float32, total_name,
|
202
|
+
direct_name, output_folder
|
203
|
+
):
|
204
|
+
"""Process results of two phase simulations (total, direct, direct sunlight).
|
205
|
+
|
206
|
+
The function will replace the direct values with the direct sunlight
|
207
|
+
values: total - direct + direct_sunlight.
|
208
|
+
|
209
|
+
The function has two output files. One for the results of the above
|
210
|
+
calculation, and one for the direct sunlight values.
|
211
|
+
|
212
|
+
\b
|
213
|
+
Args:
|
214
|
+
total-mtx: Path to total matrix.
|
215
|
+
direct-mtx: Path to direct matrix.
|
216
|
+
direct-sunlight-mtx: Path to direct sunlight matrix.
|
217
|
+
"""
|
218
|
+
try:
|
219
|
+
if binary:
|
220
|
+
total = binary_to_array(total_mtx)
|
221
|
+
direct = binary_to_array(direct_mtx)
|
222
|
+
direct_sunlight = binary_to_array(direct_sunlight_mtx)
|
223
|
+
else:
|
224
|
+
total = ascii_to_array(total_mtx)
|
225
|
+
direct = ascii_to_array(direct_mtx)
|
226
|
+
direct_sunlight = ascii_to_array(direct_sunlight_mtx)
|
227
|
+
|
228
|
+
data = total - direct + direct_sunlight
|
229
|
+
|
230
|
+
if not float32:
|
231
|
+
data = np.minimum(
|
232
|
+
data, np.finfo(np.float16).max).astype(
|
233
|
+
np.float16)
|
234
|
+
direct_sunlight = np.minimum(
|
235
|
+
direct_sunlight, np.finfo(np.float16).max).astype(
|
236
|
+
np.float16)
|
237
|
+
|
238
|
+
# save total values
|
239
|
+
total_output = Path(output_folder, total_name)
|
240
|
+
total_output.parent.mkdir(parents=True, exist_ok=True)
|
241
|
+
np.save(total_output, data)
|
242
|
+
|
243
|
+
# save direct values
|
244
|
+
direct_output = Path(output_folder, direct_name)
|
245
|
+
direct_output.parent.mkdir(parents=True, exist_ok=True)
|
246
|
+
np.save(direct_output, direct_sunlight)
|
247
|
+
|
248
|
+
except Exception:
|
249
|
+
_logger.exception('Processing annual results failed.')
|
250
|
+
sys.exit(1)
|
251
|
+
else:
|
252
|
+
sys.exit(0)
|
@@ -0,0 +1,121 @@
|
|
1
|
+
from ladybug.analysisperiod import AnalysisPeriod
|
2
|
+
from datetime import datetime
|
3
|
+
import copy
|
4
|
+
|
5
|
+
|
6
|
+
def get_hoys(start_date, start_time, end_date, end_time, timestep, leap_year):
|
7
|
+
"""Return list of hours from start date, star hour, end date and end hour.
|
8
|
+
|
9
|
+
Date should be formatted as MMM-DD (e.g JUL-21) and hours must be formatted
|
10
|
+
as HH:MM (e.g 18:30).
|
11
|
+
"""
|
12
|
+
# convert datetimes
|
13
|
+
try:
|
14
|
+
start_date = datetime.strptime(start_date, '%b-%d')
|
15
|
+
except ValueError as e:
|
16
|
+
raise ValueError('Wrong input for start date:\n\t{}'.format(e))
|
17
|
+
try:
|
18
|
+
start_time = datetime.strptime(start_time, '%H:%M')
|
19
|
+
except ValueError as e:
|
20
|
+
raise ValueError('Wrong input for start time:\n\t{}'.format(e))
|
21
|
+
try:
|
22
|
+
end_date = datetime.strptime(end_date, '%b-%d')
|
23
|
+
except ValueError as e:
|
24
|
+
raise ValueError('Wrong input for end date:\n\t{}'.format(e))
|
25
|
+
try:
|
26
|
+
end_time = datetime.strptime(end_time, '%H:%M')
|
27
|
+
except ValueError as e:
|
28
|
+
raise ValueError('Wrong input for end time:\n\t{}'.format(e))
|
29
|
+
|
30
|
+
org_end_time = copy.copy(end_time)
|
31
|
+
if end_time.minute != 0:
|
32
|
+
if end_time.hour != 23:
|
33
|
+
end_time = datetime(
|
34
|
+
end_time.year, end_time.month, end_time.day, end_time.hour + 1, 0
|
35
|
+
)
|
36
|
+
else:
|
37
|
+
end_time = datetime(
|
38
|
+
end_time.year, end_time.month, end_time.day + 1, 0, 0
|
39
|
+
)
|
40
|
+
ap = AnalysisPeriod(
|
41
|
+
start_date.month, start_date.day, start_time.hour,
|
42
|
+
end_date.month, end_date.day, end_time.hour,
|
43
|
+
timestep, leap_year
|
44
|
+
)
|
45
|
+
|
46
|
+
hoys = ap.hoys
|
47
|
+
|
48
|
+
# filter start and end hours if needed
|
49
|
+
start_index = 0
|
50
|
+
end_index = None
|
51
|
+
if start_time.minute != 0:
|
52
|
+
# remove the hours that are smaller than this hour
|
53
|
+
for start_index, h in enumerate(hoys):
|
54
|
+
if round(60 * h) % 60 >= start_time.minute:
|
55
|
+
break
|
56
|
+
|
57
|
+
if org_end_time.minute != 0:
|
58
|
+
for end_index, h in enumerate(reversed(hoys)):
|
59
|
+
if (60 * h) % 60 <= org_end_time.minute:
|
60
|
+
break
|
61
|
+
|
62
|
+
if start_index == 0 and end_index is None:
|
63
|
+
return hoys
|
64
|
+
elif end_index is None:
|
65
|
+
return hoys[start_index:]
|
66
|
+
else:
|
67
|
+
return hoys[start_index: -1 * (end_index + 1)]
|
68
|
+
|
69
|
+
|
70
|
+
def handle_operator(operator):
|
71
|
+
"""Handle operator for rmtxopt command."""
|
72
|
+
if operator == '+':
|
73
|
+
return '+'
|
74
|
+
elif operator == '-':
|
75
|
+
return '+ -s 1.0'
|
76
|
+
elif operator == '/':
|
77
|
+
return '/'
|
78
|
+
elif operator == '*':
|
79
|
+
return '"*"'
|
80
|
+
else:
|
81
|
+
raise ValueError('Invalid operator: %s' % operator)
|
82
|
+
|
83
|
+
|
84
|
+
def remove_header(input_file):
|
85
|
+
"""Remove the header text from a Radiance matrix file."""
|
86
|
+
inf = open(input_file)
|
87
|
+
first_line = next(inf)
|
88
|
+
if first_line[:10] == '#?RADIANCE':
|
89
|
+
for line in inf:
|
90
|
+
if line[:7] == 'FORMAT=':
|
91
|
+
# pass next empty line
|
92
|
+
next(inf)
|
93
|
+
first_line = next(inf)
|
94
|
+
break
|
95
|
+
continue
|
96
|
+
return first_line, inf
|
97
|
+
|
98
|
+
|
99
|
+
def get_compare_func(include_min, include_max, comply):
|
100
|
+
if include_max and include_min:
|
101
|
+
if comply:
|
102
|
+
compare = lambda value, minimum, maximum: minimum <= value <= maximum
|
103
|
+
else:
|
104
|
+
compare = lambda value, minimum, maximum: not (minimum <= value <= maximum)
|
105
|
+
elif not include_max and not include_min:
|
106
|
+
if comply:
|
107
|
+
compare = lambda value, minimum, maximum: minimum < value < maximum
|
108
|
+
else:
|
109
|
+
compare = lambda value, minimum, maximum: not (minimum < value < maximum)
|
110
|
+
elif include_max and not include_min:
|
111
|
+
if comply:
|
112
|
+
compare = lambda value, minimum, maximum: minimum < value <= maximum
|
113
|
+
else:
|
114
|
+
compare = lambda value, minimum, maximum: not (minimum < value <= maximum)
|
115
|
+
elif not include_max and include_min:
|
116
|
+
if comply:
|
117
|
+
compare = lambda value, minimum, maximum: minimum <= value < maximum
|
118
|
+
else:
|
119
|
+
compare = lambda value, minimum, maximum: not (minimum <= value < maximum)
|
120
|
+
|
121
|
+
return compare
|