diff --git a/honeybee_radiance_postprocess/cli/postprocess.py b/honeybee_radiance_postprocess/cli/postprocess.py index b0756278..ebe5ae52 100644 --- a/honeybee_radiance_postprocess/cli/postprocess.py +++ b/honeybee_radiance_postprocess/cli/postprocess.py @@ -697,6 +697,7 @@ def annual_metrics_file( @click.option( '--grids-info', '-gi', help='An optional JSON file with grid information. ' 'If no file is provided the command will look for a file in the folder.', + default=None, show_default=True, type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True) ) @click.option( @@ -726,27 +727,88 @@ def grid_summary_metric( if grids_info: with open(grids_info) as gi: grids_info = json.load(gi) - else: - gi_file = folder.joinpath('grids_info.json') - with open(gi_file) as gi: - grids_info = json.load(gi) # get grid metrics if grid_metrics: with open(grid_metrics) as gm: grid_metrics = json.load(gm) + + # check to see if there is a HBJSON with sensor grid meshes for areas + if model: + grid_areas = model_grid_areas(model, grids_info) else: - gi_file = folder.joinpath('grids_info.json') - with open(gi_file) as gi: + grid_areas = None + + grid_summary(folder, extension, grid_areas, grids_info, name, grid_metrics) + + except Exception: + _logger.exception('Failed to calculate grid summary.') + sys.exit(1) + else: + sys.exit(0) + + +@post_process.command('grid-summary-annual-daylight') +@click.argument( + 'folder', + type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True) +) +@click.option( + '--model', '-m', help='An optional HBJSON model file. This will be used to ' + 'find the area of the grids. The area is used when calculating percentages ' + 'of floor area.', + type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True) +) +@click.option( + '--grids-info', '-gi', help='An optional JSON file with grid information. ' + 'If no file is provided the command will look for a file in the folder.', + default=None, show_default=True, + type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True) +) +@click.option( + '--name', '-n', help='Optional filename of grid summary.', + type=str, default='grid_summary', show_default=True +) +@click.option( + '--grid-metrics', '-gm', help='An optional JSON file with additional ' + 'custom metrics to calculate.', default=None, + type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True) +) +def grid_summary_annual_daylight_metric( + folder, model, grids_info, name, grid_metrics +): + """Calculate a grid summary. + + \b + Args: + folder: A folder with annual daylight metrics. + """ + try: + # create Path object + folder = Path(folder) + + # get grids information + if grids_info: + with open(grids_info) as gi: grids_info = json.load(gi) + # get grid metrics + if grid_metrics: + with open(grid_metrics) as gm: + grid_metrics = json.load(gm) + # check to see if there is a HBJSON with sensor grid meshes for areas if model: grid_areas = model_grid_areas(model, grids_info) else: - grid_areas = [None] * len(grids_info) + grid_areas = None - grid_summary(folder, extension, grids_info, grid_areas, name, grid_metrics) + for metric in ['da', 'cda', 'udi', 'udi_lower', 'udi_upper']: + metric_folder = folder.joinpath(metric) + extension = metric.split('_')[0] + grid_summary( + metric_folder, extension, grids_info, grid_areas, name, + grid_metrics) except Exception: _logger.exception('Failed to calculate grid summary.') diff --git a/honeybee_radiance_postprocess/helper.py b/honeybee_radiance_postprocess/helper.py index 24a54a0f..4b5ff054 100644 --- a/honeybee_radiance_postprocess/helper.py +++ b/honeybee_radiance_postprocess/helper.py @@ -1,4 +1,5 @@ """Helper functions.""" +import json import numpy as np from honeybee.model import Model @@ -25,7 +26,7 @@ def model_grid_areas(model, grids_info): def grid_summary( - folder, extension, grids_info, grid_areas, name='grid_summary', + folder, extension, grid_areas=None, grids_info=None, name='grid_summary', grid_metrics=None ): """Calculate a grid summary for a single metric. @@ -33,11 +34,22 @@ def grid_summary( Args: folder: A folder with results. extension: Extension of the files to collect data from. + grid_areas: A list of area of each sensor. grids_info: Grid information as a dictionary. - grid_areas: name: Optional filename of grid summary. grid_metrics: Additional customized metrics to calculate. """ + if grids_info is None: + gi_file = folder.joinpath('grids_info.json') + if not gi_file.exists(): + raise FileNotFoundError( + f'The file grids_info.json was not found in the folder: {folder}.') + with open(gi_file) as gi: + grids_info = json.load(gi) + + if grid_areas is None: + grid_areas = [None] * len(grids_info) + # set up the default data types dtype = [ ('Sensor Grid', 'O'), @@ -67,58 +79,11 @@ def grid_summary( data = [full_id, _mean, _min, _max, _uniformity_ratio] - grid_metrics_data = [] if grid_metrics is not None: - for gr_metric in grid_metrics: - if len(gr_metric) == 1: - for k, v in gr_metric.items(): - if k == 'anyOff' or k == 'allOff': - gr_metric_arrays = [] - for vv in v: - for kk, threshold in vv.items(): - if kk == 'minimum': - gr_metric_arrays.append(array > threshold) - elif kk == 'exclusiveMinimum': - gr_metric_arrays.append(array >= threshold) - elif kk == 'maximum': - gr_metric_arrays.append(array < threshold) - elif kk == 'exclusiveMaximum': - gr_metric_arrays.append(array <= threshold) - if k == 'anyOff': - gr_metric_bool = np.any(gr_metric_arrays, axis=0) - else: - gr_metric_bool = np.all(gr_metric_arrays, axis=0) - gr_metric_pct = \ - _calculate_percentage(gr_metric_bool, grid_info, grid_area) - else: - threshold = v - if k == 'minimum': - gr_metric_bool = array > threshold - elif k == 'exclusiveMinimum': - gr_metric_bool = array >= threshold - elif k == 'maximum': - gr_metric_bool = array < threshold - elif k == 'exclusiveMaximum': - gr_metric_bool = array <= threshold - gr_metric_pct = \ - _calculate_percentage(gr_metric_bool, grid_info, grid_area) - elif len(gr_metric) == 2: - gr_metric_arrays = [] - for k, threshold in gr_metric.items(): - if k == 'minimum': - gr_metric_arrays.append(array > threshold) - elif k == 'exclusiveMinimum': - gr_metric_arrays.append(array >= threshold) - elif k == 'maximum': - gr_metric_arrays.append(array < threshold) - elif k == 'exclusiveMaximum': - gr_metric_arrays.append(array <= threshold) - gr_metric_bool = np.all(gr_metric_arrays, axis=0) - gr_metric_pct = \ - _calculate_percentage(gr_metric_bool, grid_info, grid_area) - grid_metrics_data.append(gr_metric_pct) - - data.extend(grid_metrics_data) + # get grid metrics + grid_metrics_data = \ + _get_grid_metrics(array, grid_metrics, grid_info, grid_area) + data.extend(grid_metrics_data) arrays.append(tuple(data)) @@ -126,17 +91,17 @@ def grid_summary( struct_array = np.array(arrays, dtype=dtype) # write header to file - with open(folder.joinpath(f'{name}.csv'), 'w') as file: - file.write(','.join(header)) - # write structured array to file + with open(folder.joinpath(f'{name}.csv'), 'w') as grid_summary_file: + grid_summary_file.write(','.join(header)) + # write structured array to grid_summary_file fmt = ['%s' , '%.2f', '%.2f', '%.2f', '%.2f'] if grid_metrics is not None: - fmt.extend(['%.2f' for d in grid_metrics]) - with open(folder.joinpath(f'{name}.csv'), 'a') as file: - file.write('\n') - np.savetxt(file, struct_array, delimiter=',', fmt=fmt) + fmt.extend(['%.2f' for _gr_m in grid_metrics]) + with open(folder.joinpath(f'{name}.csv'), 'a') as grid_summary_file: + grid_summary_file.write('\n') + np.savetxt(grid_summary_file, struct_array, delimiter=',', fmt=fmt) - return file + return grid_summary_file def _calculate_percentage(gr_metric_bool, grid_info, grid_area=None): @@ -157,3 +122,56 @@ def _calculate_percentage(gr_metric_bool, grid_info, grid_area=None): gr_metric_pct = \ gr_metric_bool.sum() / grid_info['count'] * 100 return gr_metric_pct + + +def _numeric_type(array, gr_metric): + if 'minimum' in gr_metric: + gr_metric_bool = array > gr_metric['minimum'] + elif 'exclusiveMinimum' in gr_metric: + gr_metric_bool = array >= gr_metric['minimum'] + elif 'maximum' in gr_metric: + gr_metric_bool = array < gr_metric['maximum'] + elif 'exclusiveMaximum' in gr_metric: + gr_metric_bool = array <= gr_metric['exclusiveMaximum'] + return gr_metric_bool + + +def _grid_summary_all_any(array, gr_metric, grid_info, grid_area, keyword): + gr_metric_arrays = [] + for gr_m in gr_metric[keyword]: + assert len(gr_m) == 1 + gr_metric_arrays.append(_numeric_type(array, gr_m)) + if keyword == 'allOf': + gr_metric_bool = np.all(gr_metric_arrays, axis=0) + else: + gr_metric_bool = np.any(gr_metric_arrays, axis=0) + gr_metric_pct = \ + _calculate_percentage(gr_metric_bool, grid_info, grid_area) + return gr_metric_pct + + +def _get_grid_metrics(array, grid_metrics, grid_info, grid_area): + grid_metrics_data = [] + for gr_metric in grid_metrics: + if len(gr_metric) == 1: + if 'allOf' in gr_metric: + gr_metric_pct = \ + _grid_summary_all_any( + array, gr_metric, grid_info, grid_area, 'allOf') + elif 'anyOf' in gr_metric: + gr_metric_pct = \ + _grid_summary_all_any( + array, gr_metric, grid_info, grid_area, 'anyOf') + else: + gr_metric_bool = _numeric_type(array, gr_metric) + gr_metric_pct = \ + _calculate_percentage(gr_metric_bool, grid_info, grid_area) + elif len(gr_metric) == 2: + gr_metric_arrays = [] + for k, threshold in gr_metric.items(): + gr_metric_arrays.append(_numeric_type(array, {k: threshold})) + gr_metric_bool = np.all(gr_metric_arrays, axis=0) + gr_metric_pct = \ + _calculate_percentage(gr_metric_bool, grid_info, grid_area) + grid_metrics_data.append(gr_metric_pct) + return grid_metrics_data