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(well): Add module for WELL #193

Merged
merged 4 commits into from
Oct 29, 2024
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
1 change: 1 addition & 0 deletions honeybee_radiance_postprocess/ies/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""honeybee-radiance-postprocess library."""
214 changes: 214 additions & 0 deletions honeybee_radiance_postprocess/ies/lm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
"""Functions for IES LM post-processing."""
from typing import Tuple, Union
from pathlib import Path
from collections import defaultdict
import json
import itertools
import numpy as np

from ladybug.analysisperiod import AnalysisPeriod
from ladybug.datatype.generic import GenericType
from ladybug.color import Colorset
from ladybug.datacollection import HourlyContinuousCollection
from ladybug.datatype.fraction import Fraction
from ladybug.datatype.time import Time
from ladybug.legend import LegendParameters
from ladybug.header import Header
from honeybee.model import Model
from honeybee.units import conversion_factor_to_meters
from honeybee_radiance.writer import _filter_by_pattern
from honeybee_radiance.postprocess.annual import filter_schedule_by_hours

from ..metrics import da_array2d, ase_array2d
from ..annual import schedule_to_hoys, occupancy_schedule_8_to_6
from ..results.annual_daylight import AnnualDaylight
from ..util import filter_array, recursive_dict_merge
from ..dynamic import DynamicSchedule, ApertureGroupSchedule
from .lm_schedule import shd_trans_schedule_descending, states_schedule_descending


def shade_transmittance_per_light_path(
light_paths: list, shade_transmittance: Union[float, dict],
shd_trans_dict: dict) -> dict:
"""Filter shade_transmittance by light paths and add default multiplier.

Args:
light_paths: A list of light paths.
shade_transmittance: A value to use as a multiplier in place of solar
shading. This input can be either a single value that will be used
for all aperture groups, or a dictionary where aperture groups are
keys, and the value for each key is the shade transmittance. Values
for shade transmittance must be 1 > value > 0.
shd_trans_dict: A dictionary used to store shade transmittance value
for each aperture group.

Returns:
A dictionary with filtered light paths.
"""
shade_transmittances = {}
if isinstance(shade_transmittance, dict):
for light_path in light_paths:
# default multiplier
shade_transmittances[light_path] = [1]
# add custom shade transmittance
if light_path in shade_transmittance:
shade_transmittances[light_path].append(
shade_transmittance[light_path])
shd_trans_dict[light_path] = shade_transmittance[light_path]
# add default shade transmittance (0.02)
elif light_path != '__static_apertures__':
shade_transmittances[light_path].append(0.02)
shd_trans_dict[light_path] = 0.02
else:
shade_transmittances[light_path].append(1)
shd_trans_dict[light_path] = 1
else:
shd_trans = float(shade_transmittance)
for light_path in light_paths:
# default multiplier
shade_transmittances[light_path] = [1]
# add custom shade transmittance
if light_path != '__static_apertures__':
shade_transmittances[light_path].append(shd_trans)
shd_trans_dict[light_path] = shd_trans
else:
shade_transmittances[light_path].append(1)
shd_trans_dict[light_path] = 1

return shade_transmittances, shd_trans_dict


def dynamic_schedule_direct_illuminance(
results: Union[str, AnnualDaylight], grids_filter: str = '*',
shade_transmittance: Union[float, dict] = 0.02,
use_states: bool = False
) -> Tuple[dict, dict]:
"""Calculate a schedule of each aperture group.

This function calculates an annual shading schedule of each aperture
group. Hour by hour it will select the least shaded aperture group
configuration, so that no more than 2% of the sensors points receive
direct illuminance of 1000 lux or more.

Args:
results: Path to results folder or a Results class object.
grids_filter: The name of a grid or a pattern to filter the grids.
Defaults to '*'.
shade_transmittance: A value to use as a multiplier in place of solar
shading. This input can be either a single value that will be used
for all aperture groups, or a dictionary where aperture groups are
keys, and the value for each key is the shade transmittance. Values
for shade transmittance must be 1 > value > 0.
Defaults to 0.02.
use_states: A boolean to note whether to use the simulated states. Set
to True to use the simulated states. The default is False which will
use the shade transmittance instead.

Returns:
Tuple: A tuple with a dictionary of the annual schedule and a
dictionary of hours where no shading configuration comply with the
2% rule.
"""
if not isinstance(results, AnnualDaylight):
results = AnnualDaylight(results)

grids_info = results._filter_grids(grids_filter=grids_filter)
schedule = occupancy_schedule_8_to_6(as_list=True)
occ_pattern = \
filter_schedule_by_hours(results.sun_up_hours, schedule=schedule)[0]
occ_mask = np.array(occ_pattern)

states_schedule = defaultdict(list)
fail_to_comply = {}
shd_trans_dict = {}

for grid_info in grids_info:
grid_count = grid_info['count']
light_paths = [lp[0] for lp in grid_info['light_path']]

shade_transmittances, shd_trans_dict = (
shade_transmittance_per_light_path(
light_paths, shade_transmittance, shd_trans_dict
)
)

if len(light_paths) > 6:
if use_states:
states_schedule, fail_to_comply = states_schedule_descending(
results, grid_info, light_paths, occ_mask,
states_schedule, fail_to_comply)
else:
states_schedule, fail_to_comply = shd_trans_schedule_descending(
results, grid_info, light_paths, shade_transmittances, occ_mask,
states_schedule, fail_to_comply)
else:
if use_states:
combinations = results._get_state_combinations(grid_info)
else:
shade_transmittances, shd_trans_dict = shade_transmittance_per_light_path(
light_paths, shade_transmittance, shd_trans_dict)
keys, values = zip(*shade_transmittances.items())
combinations = [dict(zip(keys, v)) for v in itertools.product(*values)]

array_list_combinations = []
for combination in combinations:
combination_arrays = []
for light_path, value in combination.items():
if use_states:
combination_arrays.append(
results._get_array(grid_info, light_path, state=value,
res_type='direct')
)
else:
array = results._get_array(
grid_info, light_path, res_type='direct')
if value == 1:
combination_arrays.append(array)
else:
combination_arrays.append(array * value)
combination_array = sum(combination_arrays)

combination_percentage = \
(combination_array >= 1000).sum(axis=0) / grid_count
array_list_combinations.append(combination_percentage)
array_combinations = np.array(array_list_combinations)
array_combinations[array_combinations > 0.02] = -np.inf

grid_comply = np.where(np.all(array_combinations==-np.inf, axis=0))[0]
if grid_comply.size != 0:
grid_comply = np.array(results.sun_up_hours)[grid_comply]
fail_to_comply[grid_info['name']] = \
[int(hoy) for hoy in grid_comply]

array_combinations_filter = np.apply_along_axis(
filter_array, 1, array_combinations, occ_mask
)
max_indices = array_combinations_filter.argmax(axis=0)
# select the combination for each hour
combinations = [combinations[idx] for idx in max_indices]
# merge the combinations of dicts
for combination in combinations:
for light_path, value in combination.items():
if light_path != '__static_apertures__':
states_schedule[light_path].append(value)

occupancy_hoys = schedule_to_hoys(schedule, results.sun_up_hours)

# map states to 8760 values
if use_states:
aperture_group_schedules = []
for identifier, values in states_schedule.items():
mapped_states = results.values_to_annual(
occupancy_hoys, values, results.timestep, dtype=np.int32)
aperture_group_schedules.append(
ApertureGroupSchedule(identifier, mapped_states.tolist())
)
states_schedule = \
DynamicSchedule.from_group_schedules(aperture_group_schedules)
else:
for light_path, shd_trans in states_schedule.items():
mapped_states = results.values_to_annual(
occupancy_hoys, shd_trans, results.timestep)
states_schedule[light_path] = mapped_states

return states_schedule, fail_to_comply, shd_trans_dict
Loading
Loading