# coding=utf-8
"""Methods for resolving MRT from Radiance and EnergyPlus output files."""
from __future__ import division
import os
import json
from ladybug.epw import EPW
from ladybug.sql import SQLiteResult
from ladybug.sunpath import Sunpath
from ladybug.datatype.energyflux import Irradiance
from ladybug.analysisperiod import AnalysisPeriod
from ladybug.header import Header
from ladybug.datacollection import HourlyContinuousCollection
from ..solarcal import sharp_from_solar_and_body_azimuth
from ..collection.solarcal import _HorizontalSolarCalMap, _HorizontalRefSolarCalMap
from ..parameter.solarcal import SolarCalParameter
[docs]def shortwave_mrt_map(
location, longwave_data, sun_up_hours, indirect_ill, direct_ill=None, ref_ill=None,
contributions=None, transmittance_contribs=None,
solarcal_par=None, indirect_is_total=False
):
"""Get MRT data collections adjusted for shortwave using Radiance .ill files.
Args:
location: A ladybug Location object to dictate the solar positions used
in the calculation.
longwave_data: An array of data collections for each point within a thermal
map. All collections must be aligned with one another. The analysis
period on the collections does not have to be annual.
sun_up_hours: File path to a sun-up-hours.txt file output by Radiance.
indirect_ill: Path to an .ill file output by Radiance containing indirect
irradiance for each longwave_data collection. Alternatively, if
indirect_is_total is set to True, this can be an .ill file with
the total irradiance, which will have the direct_ill subtracted
from it to yield the indirect illuminance.
direct_ill: Path to an .ill file output by Radiance containing direct
irradiance for each longwave_data collection. If None, all shortwave
will be assumed to be indirect. (Default: None).
ref_ill: Path to an .ill file output by Radiance containing total ground-
reflected irradiance for each longwave_data collection. If None, a
default ground reflectance of 0.25 will be assumed. (Default: None).
contributions: An optional folder containing sub-folders of irradiance
contributions from dynamic aperture groups. There should be one
sub-folder per window groups and each one should contain three .ill
files named direct.ill, indirect.ill and reflected.ill. If specified,
these will be added to the irradiance inputs before computing
shortwave MRT deltas. (Default: None).
transmittance_contribs: An optional folder containing a transmittance
schedule JSON and sub-folders of irradiance results that exclude the
shade from the calculation. There should be one sub-folder per window
groups and each one should contain three .ill files named direct.ill,
indirect.ill and reflected.ill. If specified, these will be added to
the irradiance inputs before computing shortwave MRT deltas.
solarcal_par: Optional SolarCalParameter object to account for
properties of the human geometry. (Default: None).
indirect_is_total: A boolean to note whether the indirect_ill is actually the
total irradiance, in which case the direct irradiance should be subtracted
from it to get indirect irradiance. (Default: False).
"""
# determine the analysis period and open the sun_up_hours file
a_per = longwave_data[0].header.analysis_period
is_annual, t_step, lp_yr = a_per.is_annual, a_per.timestep, a_per.is_leap_year
with open(sun_up_hours) as soh_f:
sun_indices = [int(float(h) * t_step) for h in soh_f]
# parse each of the .ill files into annual irradiance data collections
indirect = _ill_file_to_data(indirect_ill, sun_indices, t_step, lp_yr)
direct = _ill_file_to_data(direct_ill, sun_indices, t_step, lp_yr) \
if direct_ill is not None and os.path.isfile(direct_ill) else \
[_blank_ill_data(t_step, lp_yr)] * len(indirect)
ref = _ill_file_to_data(ref_ill, sun_indices, t_step, lp_yr) \
if ref_ill is not None and os.path.isfile(ref_ill) else None
# if there are dynamic contributions, then add them to the data collections
if contributions is not None and os.path.isdir(contributions):
for dyn_group in os.listdir(contributions):
# get the file paths to the contributions
group_path = os.path.join(contributions, dyn_group)
indirect_con_f = os.path.join(group_path, 'indirect.ill')
direct_con_f = os.path.join(group_path, 'direct.ill')
ref_con_f = os.path.join(group_path, 'reflected.ill')
# add the contributions to the irradiance terms
indirect_con = _ill_file_to_data(indirect_con_f, sun_indices, t_step, lp_yr)
indirect = [rad + c_rad for rad, c_rad in zip(indirect, indirect_con)]
direct_con = _ill_file_to_data(direct_con_f, sun_indices, t_step, lp_yr)
direct = [rad + c_rad for rad, c_rad in zip(direct, direct_con)]
if ref is not None and os.path.isfile(ref_con_f):
ref_con = _ill_file_to_data(ref_con_f, sun_indices, t_step, lp_yr)
ref = [rad + c_rad for rad, c_rad in zip(ref, ref_con)]
# if the analysis is not annual, apply analysis periods
if not is_annual:
indirect = [data.filter_by_analysis_period(a_per) for data in indirect]
direct = [data.filter_by_analysis_period(a_per) for data in direct]
if ref is not None:
ref = [data.filter_by_analysis_period(a_per) for data in ref]
# if there are any transmittance contributions, then compute and add them
if transmittance_contribs is not None and os.path.isdir(transmittance_contribs):
# load the JSON file with the transmittance schedules
sch_json = os.path.join(transmittance_contribs, 'schedules.json')
with open(sch_json) as json_file:
sch_dict = json.load(json_file)
shd_grps = [grp for grp in os.listdir(transmittance_contribs)
if grp != 'schedules.json']
for dyn_group in shd_grps:
t_sch_head = Header(Irradiance(), 'W/m2', a_per)
t_sch = HourlyContinuousCollection(t_sch_head, sch_dict[dyn_group])
# get the file paths to the transmittance_contribs
group_path = os.path.join(transmittance_contribs, dyn_group)
indirect_con_f = os.path.join(group_path, 'indirect.ill')
direct_con_f = os.path.join(group_path, 'direct.ill')
ref_con_f = os.path.join(group_path, 'reflected.ill')
# add the transmittance_contribs to the irradiance terms
indirect_con = _ill_file_to_data(indirect_con_f, sun_indices, t_step, lp_yr)
direct_con = _ill_file_to_data(direct_con_f, sun_indices, t_step, lp_yr)
if not is_annual:
indirect_con = \
[data.filter_by_analysis_period(a_per) for data in indirect_con]
direct_con = \
[data.filter_by_analysis_period(a_per) for data in direct_con]
indirect = [rad + ((c_rad - rad) * t_sch)
for rad, c_rad in zip(indirect, indirect_con)]
direct = [rad + ((c_rad - rad) * t_sch)
for rad, c_rad in zip(direct, direct_con)]
if ref is not None and os.path.isfile(ref_con_f):
ref_con = _ill_file_to_data(ref_con_f, sun_indices, t_step, lp_yr)
if not is_annual:
ref_con = [data.filter_by_analysis_period(a_per) for data in ref_con]
ref = [rad + ((c_rad - rad) * t_sch) for rad, c_rad in zip(ref, ref_con)]
# if need be, convert total irradiance into indirect irradiance
if indirect_is_total:
indirect = [t_rad - d_rad for t_rad, d_rad in zip(indirect, direct)]
# compute solar altitudes and sharps
body_par = SolarCalParameter() if solarcal_par is None else solarcal_par
sp = Sunpath.from_location(location)
_altitudes = []
if body_par.body_azimuth is None:
_sharps = [body_par.sharp] * len(a_per)
for t_date in a_per.datetimes:
sun = sp.calculate_sun_from_date_time(t_date)
_altitudes.append(sun.altitude)
else:
_sharps = []
for t_date in a_per.datetimes:
sun = sp.calculate_sun_from_date_time(t_date)
sharp = sharp_from_solar_and_body_azimuth(sun.azimuth, body_par.body_azimuth)
_sharps.append(sharp)
_altitudes.append(sun.altitude)
# duplicate the longwave data if there is only one data collection
if len(longwave_data) == 1:
longwave_data = [longwave_data[0]] * len(direct)
# pass all data through the solarcal collections and return MRT data collections
mrt_data = []
if ref is not None: # fully-detailed SolarCal with ground reflectance
for l_mrt, d_rad, i_rad, r_rad in zip(longwave_data, direct, indirect, ref):
scl_obj = _HorizontalRefSolarCalMap(
_altitudes, _sharps, d_rad, i_rad, r_rad, l_mrt, None, body_par)
mrt_data.append(scl_obj.mean_radiant_temperature)
else: # simpler SolarCal assuming default ground reflectance
for l_mrt, d_rad, i_rad in zip(longwave_data, direct, indirect):
scl_obj = _HorizontalSolarCalMap(
_altitudes, _sharps, d_rad, i_rad, l_mrt, None, None, body_par)
mrt_data.append(scl_obj.mean_radiant_temperature)
return mrt_data
[docs]def longwave_mrt_map(
enclosure_info, modifiers, sql, view_factors, epw, analysis_period=None):
"""Get MRT data collections adjusted for shortwave using Radiance .ill files.
Args:
enclosure_info: Path to a JSON file containing information about the radiant
enclosure that sensor points belong to.
modifiers: Path to modifiers file that aligns with the view-factors.
sql: Path to an SQLite file that was generated by EnergyPlus.
This file must contain hourly or sub-hourly results for zone comfort
variables.
view_factors: CSV of spherical view factors to the surfaces in the sql.
epw: An EPW object that will be used to specify data for any sensor outside
of any enclosure.
analysis_period: An optional AnalysisPeriod to be applied to all results.
If None, all data collections will be for the entire run period of
the sql.
"""
# load the enclosure information and modifiers list
with open(enclosure_info) as json_file:
enclosure_dict = json.load(json_file)
zone_order = [zone_id.upper() for zone_id in enclosure_dict['mapper']]
with open(modifiers) as mf:
mod_lines = mf.readlines()
srf_order = [line[:-5].upper() for line in mod_lines]
a_per = analysis_period if analysis_period is not None else AnalysisPeriod()
# load the indoor surface temperatures if they are needed
sql_obj = SQLiteResult(sql) if os.path.isfile(sql) \
and os.stat(sql).st_size != 0 else None
if enclosure_dict['has_indoor']:
assert sql_obj is not None, \
'Indoor sensors were found but no SQLite file was present.'
in_avg_outp = 'Zone Mean Radiant Temperature'
in_srf_outp = 'Surface Inside Face Temperature'
in_avg_dict = {d.header.metadata['Zone']: d for d in
sql_obj.data_collections_by_output_name(in_avg_outp)}
in_srf_dict = {d.header.metadata['Surface']: d for d in
sql_obj.data_collections_by_output_name(in_srf_outp)}
in_avg = [in_avg_dict[z] for z in zone_order]
in_srf = [in_srf_dict[s] for s in srf_order[:-3]]
if in_avg[0].header.analysis_period != a_per:
in_avg = [d.filter_by_analysis_period(a_per) for d in in_avg]
in_srf = [d.filter_by_analysis_period(a_per) for d in in_srf]
in_data = []
for zone in in_avg:
in_list = in_srf + [zone, zone, zone]
in_data.append(tuple(zip(*in_list)))
# load the EPW and outdoor surface temperatures if they are needed
if enclosure_dict['has_outdoor']:
if sql_obj is not None:
out_srf_outp = 'Surface Outside Face Temperature'
out_srf_dict = {d.header.metadata['Surface']: d for d in
sql_obj.data_collections_by_output_name(out_srf_outp)}
out_srf = [out_srf_dict[s] for s in srf_order[:-3]]
if out_srf[0].header.analysis_period != a_per:
out_srf = [d.filter_by_analysis_period(a_per) for d in out_srf]
else:
out_srf = []
epw_obj = EPW(epw)
out_avg = epw_obj.dry_bulb_temperature
out_sky = epw_obj.sky_temperature
if not a_per.is_annual:
out_avg = out_avg.filter_by_analysis_period(a_per)
out_sky = out_sky.filter_by_analysis_period(a_per)
out_data = out_srf + [out_avg, out_sky, out_avg]
out_data = tuple(zip(*out_data))
# load the view factors and perform the matrix multiplication with temperature
with open(view_factors) as csv_data_file:
vf_data = tuple(
tuple(float(val) for val in row.split(',')) for row in csv_data_file)
mrt_data = []
for sen_enc, view_facs in zip(enclosure_dict['sensor_indices'], vf_data):
if sen_enc == -1: # outdoor sensor
temp_data = out_data
else: # indoor sensor
temp_data = in_data[sen_enc]
sensor_vals = []
for t_step in temp_data:
sensor_vals.append(sum(vf * t for vf, t in zip(view_facs, t_step)))
mrt_data.append(sensor_vals)
return mrt_data
def _ill_file_to_data(ill_file, sun_indices, timestep=1, leap_yr=False):
"""Convert a list of sun-up irradiance from an .ill file into annual irradiance data.
Args:
ill_file: Path to an .ill file.
sun_indices: A list of integers for where in the total_count sun-up hours occur.
timestep: The timestep to make the data collection.
leap_yr: Boolean to note if data is for a leap year.
Return:
A list of annual HourlyContinuousCollection with irradiance data.
"""
a_period = AnalysisPeriod(timestep=timestep, is_leap_year=leap_yr)
header = Header(Irradiance(), 'W/m2', a_period)
irr_data = []
with open(ill_file) as results:
for pt_res in results:
ill_values = [float(v) for v in pt_res.split()]
pt_irr_data = _ill_values_to_data(
ill_values, sun_indices, header, timestep, leap_yr)
irr_data.append(pt_irr_data)
return irr_data
def _ill_values_to_data(ill_values, sun_indices, header, timestep=1, leap_yr=False):
"""Convert a list of sun-up irradiance from an .ill file into annual irradiance data.
Args:
ill_values: A list of raw irradiance values from an .ill file.
sun_indices: A list of integers for where in the total_count sun-up hours occur.
header: A Header object for the for the data collection.
timestep: The timestep to make the data collection.
leap_yr: Boolean to note if data is for a leap year.
Return:
An annual HourlyContinuousCollection of irradiance data.
"""
values = [0] * (8760 * timestep) if not leap_yr else [0] * (8784 * timestep)
for i, irr in zip(sun_indices, ill_values):
values[i] = irr
return HourlyContinuousCollection(header, values)
def _blank_ill_data(timestep=1, leap_yr=False):
"""Get a list of blank annual irradiance data composed of zero values.
Args:
timestep: The timestep to make the data collection.
leap_yr: Boolean to note if data is for a leap year.
Return:
A list of annual HourlyContinuousCollection with zero irradiance data.
"""
a_period = AnalysisPeriod(timestep=timestep, is_leap_year=leap_yr)
header = Header(Irradiance(), 'W/m2', a_period)
values = [0] * (8760 * timestep) if not leap_yr else [0] * (8784 * timestep)
return HourlyContinuousCollection(header, values)