Source code for honeybee_radiance.postprocess.annualdaylight

"""Functions for post-processing annual daylight outputs.

Note: These functions will most likely be moved to a separate package in the near future.
"""
import json
import os

from ladybug.datatype.fraction import Fraction
from ladybug.legend import LegendParameters

from .annual import filter_schedule_by_hours, _process_input_folder


def _metrics(values, occ_pattern, threshold, min_t, max_t, total_hours,
             sun_down_occ_hours):
    """Calculate annual metrics for a sensor.

    Args:
        values: Hourly illuminance values as numbers.
        occ_pattern: A list of 0 and 1 values for hours of occupancy.
        threshold: Threshold value for daylight autonomy. Default: 300.
        min_t: Minimum threshold for useful daylight illuminance. Default: 100.
        max_t: Maximum threshold for useful daylight illuminance. Default: 3000.
        total_hours: An integer for the total number of occupied hours,
            which can be used to avoid having to sum occ pattern each time.
        sun_down_occ_hours: An integer for the total number of occupied hours where
            the sun is down. This is often needed to properly tally all hours that
            have illuminance below the threshold.

    Returns:
        Tuple -- daylight autonomy, continuous daylight autonomy, lower useful daylight
            illuminance, useful daylight illuminance, higher useful daylight illuminance
    """
    def _percentage(in_v, occ_hours):
        return round(100.0 * in_v / occ_hours, 2)

    da = 0
    cda = 0
    udi_lower = sun_down_occ_hours
    udi = 0
    udi_upper = 0

    for is_occ, value in zip(occ_pattern, values):
        if is_occ == 0:
            continue
        if value > threshold:
            da += 1
            cda += 1
        else:
            cda += value / threshold

        if min_t > value:
            udi_lower += 1
        elif value > max_t:
            udi_upper += 1
        else:
            udi += 1

    return _percentage(da, total_hours), _percentage(cda, total_hours), \
        _percentage(udi_lower, total_hours), _percentage(udi, total_hours), \
        _percentage(udi_upper, total_hours)


[docs]def metrics(ill_file, occ_pattern, threshold=300, min_t=100, max_t=3000, total_hours=None, sun_down_occ_hours=0): """Compute annual metrics for a given result file. Args: ill_file: Path to an ill file generated by Radiance. The ill file should be tab separated and shot NOT have a header. The results for each sensor point should be available in a row and and each column should be the illuminance value for a sun_up_hour. The number of columns should match the number of sun up hours. occ_pattern: A list of 0 and 1 values for hours of occupancy. threshold: Threshold illuminance level for daylight autonomy. Default: 300. min_t: Minimum threshold for useful daylight illuminance. Default: 100. max_t: Maximum threshold for useful daylight illuminance. Default: 3000. total_hours: An integer for the total number of occupied hours in the occupancy schedule. If None, it will be assumed that all of the occupied hours are sun-up hours and are already accounted for in the the occ_pattern. sun_down_occ_hours: An integer for the total number of occupied hours where the sun is down. This is often needed to properly tally all hours that have illuminance below the threshold. Returns: Tuple(List, List, List, List, List) -- 5 lists for daylight autonomy, continuous daylight autonomy, lower than useful daylight illuminance, useful daylight illuminance and higher than useful daylight illuminance. Number of results in each list matches the number of lines in ill input file. """ da = [] cda = [] udi = [] udi_lower = [] udi_upper = [] total_occupied_hours = sum(occ_pattern) if total_hours is None else total_hours with open(ill_file) as results: for pt_res in results: values = (float(res) for res in pt_res.split()) da_v, cda_v, udi_lower_v, udi_v, udi_upper_v = _metrics( values, occ_pattern, threshold, min_t, max_t, total_occupied_hours, sun_down_occ_hours ) da.append(da_v) cda.append(cda_v) udi_lower.append(udi_lower_v) udi.append(udi_v) udi_upper.append(udi_upper_v) return da, cda, udi_lower, udi, udi_upper
[docs]def metrics_to_files(ill_file, occ_pattern, output_folder, threshold=300, min_t=100, max_t=3000, grid_name=None, total_hours=None, sun_down_occ_hours=0): """Compute annual metrics for an ill file and write the results to a folder. This function generates 5 different files or daylight autonomy, continuous daylight autonomy, lower than useful daylight illuminance, useful daylight illuminance and higher than useful daylight illuminance. Args: ill_file: Path to an ill file generated by Radiance. The ill file should be tab separated and shot NOT have a header. The results for each sensor point should be available in a row and and each column should be the illuminance value for a sun_up_hour. The number of columns should match the number of sun up hours. occ_pattern: A list of 0 and 1 values for hours of occupancy. output_folder: An output folder where the results will be written to. The folder will be created if not exist. threshold: Threshold illuminance level for daylight autonomy. Default: 300. min_t: Minimum threshold for useful daylight illuminance. Default: 100. max_t: Maximum threshold for useful daylight illuminance. Default: 3000. grid_name: An optional name for grid name which will be used to name the output files. If None the name of the input file will be used. total_hours: An integer for the total number of occupied hours in the occupancy schedule. If None, it will be assumed that all of the occupied hours are sun-up hours and are already accounted for in the the occ_pattern. sun_down_occ_hours: An integer for the total number of occupied hours where the sun is down. This is often needed to properly tally all hours that have illuminance below the threshold. Returns: Tuple(file.da, file.cda, file.luid, file.uid, file.hudi) """ if not os.path.isdir(output_folder): os.makedirs(output_folder) grid_name = grid_name or os.path.split(ill_file)[-1][-4:] da = os.path.join(output_folder, 'da', '%s.da' % grid_name).replace('\\', '/') cda = os.path.join(output_folder, 'cda', '%s.cda' % grid_name).replace('\\', '/') udi = os.path.join(output_folder, 'udi', '%s.udi' % grid_name).replace('\\', '/') udi_lower = \ os.path.join(output_folder, 'udi_lower', '%s.udi' % grid_name).replace('\\', '/') udi_upper = \ os.path.join(output_folder, 'udi_upper', '%s.udi' % grid_name).replace('\\', '/') for file_path in [da, cda, udi, udi_upper, udi_lower]: folder = os.path.dirname(file_path) if not os.path.isdir(folder): os.makedirs(folder) total_occupied_hours = sum(occ_pattern) if total_hours is None else total_hours with open(ill_file) as results, open(da, 'w') as daf, open(cda, 'w') as cdaf, \ open(udi, 'w') as udif, open(udi_lower, 'w') as udi_lowerf, \ open(udi_upper, 'w') as udi_upperf: for pt_res in results: values = (float(res) for res in pt_res.split()) dar, cdar, udi_lowerr, udir, udi_upperr = _metrics( values, occ_pattern, threshold, min_t, max_t, total_occupied_hours, sun_down_occ_hours ) daf.write(str(dar) + '\n') cdaf.write(str(cdar) + '\n') udi_lowerf.write(str(udi_lowerr) + '\n') udif.write(str(udir) + '\n') udi_upperf.write(str(udi_upperr) + '\n') return da, cda, udi_lower, udi, udi_upper
# TODO - support a list of schedules/schedule folder to match the input grids
[docs]def metrics_from_folder(results_folder, schedule=None, threshold=300, min_t=100, max_t=3000, grids_filter='*'): """Compute annual metrics for a folder. This folder is an output folder of annual daylight recipe. Folder should include grids_info.json and sun-up-hours.txt - the script uses the list in grids_info.json to find the result files for each sensor grid. Args: results_folder: Results folder. schedule: An annual schedule for 8760 hours of the year as a list of values. threshold: Threshold illuminance level for daylight autonomy. Default: 300. min_t: Minimum threshold for useful daylight illuminance. Default: 100. max_t: Maximum threshold for useful daylight illuminance. Default: 3000. grids_filter: A pattern to filter the grids. By default all the grids will be processed. Returns: Tuple[Tuple] - There will be a tuple for each input sensor grid which is a Tuple(List, List, List, List, List) -- 5 lists for daylight autonomy, continuous daylight autonomy, lower than useful daylight illuminance, useful daylight illuminance and higher than useful daylight illuminance. Number of results in each list matches the number of lines in ill input file. """ da = [] cda = [] udi = [] udi_lower = [] udi_upper = [] grids, sun_up_hours = _process_input_folder(results_folder, grids_filter) occ_pattern, total_occ, sun_down_occ_hours = \ filter_schedule_by_hours(sun_up_hours=sun_up_hours, schedule=schedule) for grid in grids: ill_file = os.path.join(results_folder, '%s.ill' % grid['full_id']) da_r, cda_r, udi_lower_r, udi_r, udi_upper_r = \ metrics(ill_file, occ_pattern, threshold, min_t, max_t, total_occ, sun_down_occ_hours) da.append(da_r) cda.append(cda_r) udi_lower.append(udi_lower_r) udi.append(udi_r) udi_upper.append(udi_upper_r) return da, cda, udi_lower, udi, udi_upper
# TODO - support a list of schedules/schedule folder to match the input grids
[docs]def metrics_to_folder( results_folder, schedule=None, threshold=300, min_t=100, max_t=3000, grids_filter='*', sub_folder='metrics' ): """Compute annual metrics in a folder and write them in a subfolder. This folder is an output folder of annual daylight recipe. Folder should include grids_info.json and sun-up-hours.txt - the script uses the list in grids_info.json to find the result files for each sensor grid. Args: results_folder: Results folder. schedule: An annual schedule for 8760 hours of the year as a list of values. threshold: Threshold illuminance level for daylight autonomy. Default: 300. min_t: Minimum threshold for useful daylight illuminance. Default: 100. max_t: Maximum threshold for useful daylight illuminance. Default: 3000. grids_filter: A pattern to filter the grids. By default all the grids will be processed. sub_folder: An optional relative path for subfolder to copy results files. Default: metrics Returns: str -- Path to results folder. """ grids, sun_up_hours = _process_input_folder(results_folder, grids_filter) occ_pattern, total_occ, sun_down_occ_hours = \ filter_schedule_by_hours(sun_up_hours=sun_up_hours, schedule=schedule) metrics_folder = os.path.join(results_folder, sub_folder) if not os.path.isdir(metrics_folder): os.makedirs(metrics_folder) for grid in grids: ill_file = os.path.join(results_folder, '%s.ill' % grid['full_id']) metrics_to_files( ill_file, occ_pattern, metrics_folder, threshold, min_t, max_t, grid['full_id'], total_occ, sun_down_occ_hours ) # copy info.json to all results folders for folder_name in ['da', 'cda', 'udi_lower', 'udi', 'udi_upper']: grid_info = os.path.join(metrics_folder, folder_name, 'grids_info.json') with open(grid_info, 'w') as outf: json.dump(grids, outf) metric_info_dict = _annual_daylight_vis_metadata() for metric, data in metric_info_dict.items(): file_path = os.path.join(metrics_folder, metric, 'vis_metadata.json') with open(file_path, 'w') as fp: json.dump(data, fp, indent=4) return metrics_folder
def _annual_daylight_vis_metadata(): """Return visualization metadata for annual daylight.""" annual_daylight_lpar = LegendParameters(min=0, max=100) metric_info_dict = { 'udi_lower': { 'type': 'VisualizationMetaData', 'data_type': Fraction('Useful Daylight Illuminance Lower').to_dict(), 'unit': '%', 'legend_parameters': annual_daylight_lpar.to_dict() }, 'udi_upper': { 'type': 'VisualizationMetaData', 'data_type': Fraction('Useful Daylight Illuminance Upper').to_dict(), 'unit': '%', 'legend_parameters': annual_daylight_lpar.to_dict() }, 'udi': { 'type': 'VisualizationMetaData', 'data_type': Fraction('Useful Daylight Illuminance').to_dict(), 'unit': '%', 'legend_parameters': annual_daylight_lpar.to_dict() }, 'cda': { 'type': 'VisualizationMetaData', 'data_type': Fraction('Continuous Daylight Autonomy').to_dict(), 'unit': '%', 'legend_parameters': annual_daylight_lpar.to_dict() }, 'da': { 'type': 'VisualizationMetaData', 'data_type': Fraction('Daylight Autonomy').to_dict(), 'unit': '%', 'legend_parameters': annual_daylight_lpar.to_dict() } } return metric_info_dict def _annual_daylight_config(): """Return vtk-config for annual daylight. """ cfg = { "data": [ { "identifier": "Useful Daylight Illuminance Lower", "object_type": "grid", "unit": "Percentage", "path": "udi_lower", "hide": False, "legend_parameters": { "hide_legend": False, "min": 0, "max": 100, "color_set": "nuanced", }, }, { "identifier": "Useful Daylight Illuminance Upper", "object_type": "grid", "unit": "Percentage", "path": "udi_upper", "hide": False, "legend_parameters": { "hide_legend": False, "min": 0, "max": 100, "color_set": "glare_study", "label_parameters": { "color": [34, 247, 10], "size": 0, "bold": True, }, }, }, { "identifier": "Useful Daylight Illuminance", "object_type": "grid", "unit": "Percentage", "path": "udi", "hide": False, "legend_parameters": { "hide_legend": False, "min": 0, "max": 100, "color_set": "annual_comfort", "label_parameters": { "color": [34, 247, 10], "size": 0, "bold": True, }, }, }, { "identifier": "Continuous Daylight Autonomy", "object_type": "grid", "unit": "Percentage", "path": "cda", "hide": False, "legend_parameters": { "hide_legend": False, "min": 0, "max": 100, "color_set": "annual_comfort", "label_parameters": { "color": [34, 247, 10], "size": 0, "bold": True, }, }, }, { "identifier": "Daylight Autonomy", "object_type": "grid", "unit": "Percentage", "path": "da", "hide": False, "legend_parameters": { "hide_legend": False, "min": 0, "max": 100, "color_set": "annual_comfort", "label_parameters": { "color": [34, 247, 10], "size": 0, "bold": True, }, }, }, ] } return cfg