Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(postprocess): Add grid summary for annual daylight #117

Merged
merged 2 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 70 additions & 8 deletions honeybee_radiance_postprocess/cli/postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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.')
Expand Down
140 changes: 79 additions & 61 deletions honeybee_radiance_postprocess/helper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Helper functions."""
import json
import numpy as np

from honeybee.model import Model
Expand All @@ -25,19 +26,30 @@ 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.

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'),
Expand Down Expand Up @@ -67,76 +79,29 @@ 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))

# create structured array
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):
Expand All @@ -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