# coding=utf-8
"""Functions for adjusting MRT for radiative sky exchange (including shortwave solar).
The solarcal formulas of this module are taken from the following publications:
[1] Arens, E., T. Hoyt, X. Zhou, L. Huang, H. Zhang and S. Schiavon. 2015.
Modeling the comfort effects of short-wave solar radiation indoors.
Building and Environment, 88, 3-9. http://dx.doi.org/10.1016/j.buildenv.2014.09.004
https://escholarship.org/uc/item/89m1h2dg
[2] ASHRAE Standard 55 (2017). "Thermal Environmental Conditions for Human Occupancy".
Properties:
* SOLARCAL_SPLINES:
A dictionary with two keys: 'standing' and 'seated'.
Each value for these keys is a 2D matrix of projection factors
for human geometry. Each row refers to an degree of azimuth and each
colum refers to a degree of altitude.
"""
from __future__ import division
from ladybug.skymodel import calc_sky_temperature
from ladybug.futil import csv_to_num_matrix
import os
import math
def _load_solarcal_splines():
"""load the spline data that gets used in solarcal."""
try:
cur_dir = os.path.dirname(__file__)
solarcal_splines = {
'seated': csv_to_num_matrix(os.path.join(
cur_dir, '_mannequin', 'seatedspline.csv')),
'standing': csv_to_num_matrix(os.path.join(
cur_dir, '_mannequin', 'standingspline.csv'))}
except IOError:
solarcal_splines = {}
print('Failed to import projection factor splines from CSV.'
'\nA simpler interpolation method for Solarcal will be used.')
return solarcal_splines
SOLARCAL_SPLINES = _load_solarcal_splines()
[docs]
def outdoor_sky_heat_exch(srfs_temp, horiz_ir, diff_horiz_solar, dir_normal_solar, alt,
sky_exposure=1, fract_exposed=1, floor_reflectance=0.25,
posture='standing', sharp=135,
body_absorptivity=0.7, body_emissivity=0.95):
"""Perform a full outdoor sky radiant heat exchange.
Args:
srfs_temp: The temperature of surfaces around the person in degrees
Celsius. This includes the ground and any other surfaces
blocking the view to the sky. When the temperature of these
individual surfaces are known, the input here should be the
average temperature of the surfaces weighted by view-factor to the human.
When such individual surface temperatures are unknown, the outdoor
dry bulb temperature is typically used as a proxy.
horiz_ir: The horizontal infrared radiation intensity from the sky in W/m2.
diff_horiz_solar: Diffuse horizontal solar irradiance in W/m2.
dir_normal_solar: Direct normal solar irradiance in W/m2.
alt: The altitude of the sun in degrees [0-90].
sky_exposure: A number between 0 and 1 representing the fraction of the
sky vault in occupant’s view. Default is 1 for outdoors in an
open field.
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include the
body’s self-shading; only the shading from surroundings.
Default is 1 for a person standing in an open area.
floor_reflectance: A number between 0 and 1 the represents the
reflectance of the floor. Default is for 0.25 which is characteristic
of outdoor grass or dry bare soil.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
body_absorptivity: A number between 0 and 1 representing the average
shortwave absorptivity of the body (including clothing and skin color).
Typical clothing values - white: 0.2, khaki: 0.57, black: 0.88
Typical skin values - white: 0.57, brown: 0.65, black: 0.84
Default is 0.7 for average (brown) skin and medium clothing.
body_emissivity: A number between 0 and 1 representing the average
longwave emissivity of the body. Default is 0.95, which is almost
always the case except in rare situations of wearing metallic clothing.
Returns:
A dictionary containing results with the following keys
- s_erf : The shortwave effective radiant field (ERF) in W/m2.
- s_dmrt : The MRT delta as a result of shortwave irradiance in C.
- l_erf : The longwave effective radiant field (ERF) in W/m2.
- l_dmrt : The MRT delta as a result of longwave sky exchange in C.
- mrt: The final MRT expereinced as a result of sky heat exchange in C.
"""
# set defaults using the input parameters
fract_efficiency = 0.696 if posture == 'seated' else 0.725
# calculate the influence of shortwave irradiance
if alt >= 2:
s_flux = body_solar_flux_from_parts(diff_horiz_solar, dir_normal_solar,
alt, sharp, sky_exposure,
fract_exposed, floor_reflectance, posture)
short_erf = erf_from_body_solar_flux(s_flux, body_absorptivity, body_emissivity)
short_mrt_delta = mrt_delta_from_erf(short_erf, fract_efficiency)
else:
short_erf = 0
short_mrt_delta = 0
# calculate the influence of longwave heat exchange with the sky
long_mrt_delta = longwave_mrt_delta_from_horiz_ir(horiz_ir, srfs_temp,
sky_exposure, body_emissivity)
long_erf = erf_from_mrt_delta(long_mrt_delta, fract_efficiency)
# calculate final MRT as a result of both longwave and shortwave heat exchange
sky_adjusted_mrt = srfs_temp + short_mrt_delta + long_mrt_delta
heat_exch_result = {
's_erf': short_erf,
's_dmrt': short_mrt_delta,
'l_erf': long_erf,
'l_dmrt': long_mrt_delta,
'mrt': sky_adjusted_mrt
}
return heat_exch_result
[docs]
def indoor_sky_heat_exch(longwave_mrt, diff_horiz_solar, dir_normal_solar, alt,
sky_exposure=1, fract_exposed=1, floor_reflectance=0.25,
window_transmittance=0.4, posture='seated', sharp=135,
body_absorptivity=0.7, body_emissivity=0.95):
"""Perform a full indoor sky radiant heat exchange.
Args:
longwave_mrt: The longwave mean radiant temperature (MRT) expereinced
as a result of indoor surface temperatures in C.
diff_horiz_solar: Diffuse horizontal solar irradiance in W/m2.
dir_normal_solar: Direct normal solar irradiance in W/m2.
alt: The altitude of the sun in degrees [0-90].
sky_exposure: A number between 0 and 1 representing the fraction of the
sky vault in occupant’s view. Default is 1 for a completely glass box.
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include the
body’s self-shading; only the shading from surroundings.
Default is 1 for a person standing in an open area.
floor_reflectance: A number between 0 and 1 the represents the
reflectance of the floor. Default is for 0.25 which is characteristic
of outdoor grass or dry bare soil.
window_transmittance: A number between 0 and 1 that represents the broadband
solar transmittance of the window through which the sun is coming. Such
values tend to be slightly less than the SHGC. Values might be as low as
0.2 and could be as high as 0.85 for a single pane of glass.
Default is 0.4 assuming a double pane window with a relatively mild
low-e coating.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
body_absorptivity: A number between 0 and 1 representing the average
shortwave absorptivity of the body (including clothing and skin color).
Typical clothing values - white: 0.2, khaki: 0.57, black: 0.88
Typical skin values - white: 0.57, brown: 0.65, black: 0.84
Default is 0.7 for average (brown) skin and medium clothing.
body_emissivity: A number between 0 and 1 representing the average
longwave emissivity of the body. Default is 0.95, which is almost
always the case except in rare situations of wearing metallic clothing.
Returns:
A dictionary containing results with the following keys
- erf : The shortwave effective radiant field (ERF) in W/m2.
- dmrt : The MRT delta as a result of shortwave irradiance in C.
- mrt: The final MRT expereinced as a result of sky heat exchange in C.
"""
# set defaults using the input parameters
fract_efficiency = 0.696 if posture == 'seated' else 0.725
# calculate the influence of shortwave irradiance
if alt >= 2:
s_flux = body_solar_flux_from_parts(diff_horiz_solar, dir_normal_solar,
alt, sharp, sky_exposure,
fract_exposed, floor_reflectance, posture)
s_flux = s_flux * window_transmittance
short_erf = erf_from_body_solar_flux(s_flux, body_absorptivity, body_emissivity)
short_mrt_delta = mrt_delta_from_erf(short_erf, fract_efficiency)
else:
short_erf = 0
short_mrt_delta = 0
# calculate final MRT
sky_adjusted_mrt = longwave_mrt + short_mrt_delta
heat_exch_result = {
'erf': short_erf,
'dmrt': short_mrt_delta,
'mrt': sky_adjusted_mrt
}
return heat_exch_result
[docs]
def shortwave_from_horiz_solar(longwave_mrt, diff_horiz_solar, dir_horiz_solar, alt,
fract_exposed=1, floor_reflectance=0.25,
posture='standing', sharp=135,
body_absorptivity=0.7, body_emissivity=0.95):
"""Perform shortwave radiant heat exchange using horizontal solar components.
This is useful when building a map of MRT using the direct and diffuse
results of a Radiance study instead of the solar components directly from
an EPW or Wea file. Note that all input radiation components should already
account for the amount of sky seen and solar heat reflections off of surfaces.
Args:
longwave_mrt: The longwave mean radiant temperature (MRT) expereinced
as a result of indoor surface temperatures in C.
diff_horiz_solar: Diffuse horizontal solar irradiance in W/m2.
dir_horiz_solar: Direct horizontal solar irradiance in W/m2.
alt: The altitude of the sun in degrees [0-90].
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include the
body’s self-shading; only the shading from surroundings.
Default is 1 for a person standing in an open area.
floor_reflectance: A number between 0 and 1 the represents the
reflectance of the floor. Default is for 0.25 which is characteristic
of outdoor grass or dry bare soil.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
body_absorptivity: A number between 0 and 1 representing the average
shortwave absorptivity of the body (including clothing and skin color).
Typical clothing values - white: 0.2, khaki: 0.57, black: 0.88
Typical skin values - white: 0.57, brown: 0.65, black: 0.84
Default is 0.7 for average (brown) skin and medium clothing.
body_emissivity: A number between 0 and 1 representing the average
longwave emissivity of the body. Default is 0.95, which is almost
always the case except in rare situations of wearing metallic clothing.
Returns:
A dictionary containing results with the following keys
- erf : The shortwave effective radiant field (ERF) in W/m2.
- dmrt : The MRT delta as a result of shortwave irradiance in C.
- mrt: The final MRT expereinced as a result of sky heat exchange in C.
"""
# set defaults using the input parameters
fract_efficiency = 0.696 if posture == 'seated' else 0.725
# calculate the influence of shortwave irradiance
if alt >= 2:
s_flux = body_solar_flux_from_horiz_solar(diff_horiz_solar, dir_horiz_solar,
alt, sharp, fract_exposed,
floor_reflectance, posture)
short_erf = erf_from_body_solar_flux(s_flux, body_absorptivity, body_emissivity)
short_mrt_delta = mrt_delta_from_erf(short_erf, fract_efficiency)
else:
short_erf = 0
short_mrt_delta = 0
# calculate final MRT as a result of both longwave and shortwave heat exchange
sky_adjusted_mrt = longwave_mrt + short_mrt_delta
heat_exch_result = {
'erf': short_erf,
'dmrt': short_mrt_delta,
'mrt': sky_adjusted_mrt
}
return heat_exch_result
[docs]
def shortwave_from_horiz_components(
longwave_mrt, diff_horiz_solar, dir_horiz_solar, ref_horiz_solar, alt,
fract_exposed=1, posture='standing', sharp=135, body_absorptivity=0.7,
body_emissivity=0.95):
"""Perform shortwave radiant heat exchange using horizontal components.
This is useful when building a map of MRT using the direct, diffuse, and ground
reflected results of a Radiance study instead of the solar components directly from
an EPW or Wea file. Note that all input radiation components should already
account for the amount of sky seen and solar heat reflections off of surfaces.
Args:
longwave_mrt: The longwave mean radiant temperature (MRT) expereinced
as a result of indoor surface temperatures in C.
diff_horiz_solar: Diffuse horizontal solar irradiance in W/m2.
dir_horiz_solar: Direct horizontal solar irradiance in W/m2.
ref_horiz_solar: Ground-reflected horizontal irradiance in W/m2.
alt: The altitude of the sun in degrees [0-90].
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include the
body’s self-shading; only the shading from surroundings.
Default is 1 for a person standing in an open area.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
body_absorptivity: A number between 0 and 1 representing the average
shortwave absorptivity of the body (including clothing and skin color).
Typical clothing values - white: 0.2, khaki: 0.57, black: 0.88
Typical skin values - white: 0.57, brown: 0.65, black: 0.84
Default is 0.7 for average (brown) skin and medium clothing.
body_emissivity: A number between 0 and 1 representing the average
longwave emissivity of the body. Default is 0.95, which is almost
always the case except in rare situations of wearing metallic clothing.
Returns:
A dictionary containing results with the following keys
- erf : The shortwave effective radiant field (ERF) in W/m2.
- dmrt : The MRT delta as a result of shortwave irradiance in C.
- mrt: The final MRT expereinced as a result of sky heat exchange in C.
"""
# set defaults using the input parameters
fract_efficiency = 0.696 if posture == 'seated' else 0.725
# calculate the influence of shortwave irradiance
if alt >= 2:
s_flux = body_solar_flux_from_horiz_components(
diff_horiz_solar, dir_horiz_solar, ref_horiz_solar, alt,
sharp, fract_exposed, posture)
short_erf = erf_from_body_solar_flux(s_flux, body_absorptivity, body_emissivity)
short_mrt_delta = mrt_delta_from_erf(short_erf, fract_efficiency)
else:
short_erf = 0
short_mrt_delta = 0
# calculate final MRT as a result of both longwave and shortwave heat exchange
sky_adjusted_mrt = longwave_mrt + short_mrt_delta
heat_exch_result = {
'erf': short_erf,
'dmrt': short_mrt_delta,
'mrt': sky_adjusted_mrt
}
return heat_exch_result
[docs]
def mrt_delta_from_erf(erf, fract_efficiency=0.725, rad_trans_coeff=6.012):
"""Calculate the mean radiant temperature (MRT) delta as a result of an ERF.
Args:
erf: A number representing the effective radiant field (ERF) on the
person in W/m2.
fract_efficiency: A number representing the fraction of the body
surface exposed to radiation from the environment. This is typically
either 0.725 for a standing or supine person or 0.696 for a seated
person. Default is 0.725 for a standing person.
rad_trans_coeff: A number representing the radiant heat transfer coefficient
in (W/m2-K). Default is 6.012, which is almost always the case.
"""
return erf / (fract_efficiency * rad_trans_coeff)
[docs]
def erf_from_mrt_delta(mrt_delta, fract_efficiency=0.725, rad_trans_coeff=6.012):
"""Calculate the effective radiant field (ERF) from a MRT delta.
Args:
mrt_delta: A mean radiant temperature (MRT) delta in Kelvin or degrees Celsius.
fract_efficiency: A number representing the fraction of the body
surface exposed to radiation from the environment. This is typically
either 0.725 for a standing or supine person or 0.696 for a seated
person. Default is 0.725 for a standing person.
rad_trans_coeff: A number representing the radiant heat transfer coefficient
in (W/m2-K). Default is 6.012, which is almost always the case.
"""
return mrt_delta * fract_efficiency * rad_trans_coeff
[docs]
def longwave_mrt_delta_from_horiz_ir(horiz_ir, srfs_temp, sky_exposure=1,
body_emissivity=0.95):
"""Calculate the MRT delta as a result of longwave radiant exchange with the sky.
Note that this value is typically negative since the earth (and humans)
tend to radiate heat out to space in the longwave portion of the spectrum.
Args:
horiz_ir: A float value that represents the downwelling horizontal
infrared radiation intensity in W/m2.
srfs_temp: The temperature of surfaces around the person in degrees
Celsius. This includes the ground and any other surfaces
blocking the view to the sky. Typically, the dry bulb temperature
is used when such surface temperatures are unknown.
sky_exposure: A number between 0 and 1 representing the fraction of the
sky vault in occupant’s view. Default is 1 for outdoors in an
open field.
"""
sky_temp = calc_sky_temperature(horiz_ir, body_emissivity)
return longwave_mrt_delta_from_sky_temp(sky_temp, srfs_temp, sky_exposure)
[docs]
def longwave_mrt_delta_from_sky_temp(sky_temp, srfs_temp, sky_exposure=1):
"""Calculate the MRT delta as a result of longwave radiant exchange with the sky.
Note that this value is typically negative since the earth (and humans)
tend to radiate heat out to space in the longwave portion of the spectrum.
Args:
sky_temp: The sky temperature in degrees Celsius.
srfs_temp: The temperature of surfaces around the person in degrees
Celsius. This includes the ground and any other surfaces
blocking the view to the sky. Typically, the dry bulb temperature
is used when such surface temperatures are unknown.
sky_exposure: A number between 0 and 1 representing the fraction of the
sky vault in occupant’s view. Default is 1 for outdoors in an
open field.
"""
return 0.5 * sky_exposure * (sky_temp - srfs_temp)
[docs]
def erf_from_body_solar_flux(solar_flux, body_absorptivity=0.7, body_emissivity=0.95):
"""Calculate effective radiant field (ERF) from incident solar flux on body in W/m2.
Args:
solar_flux: A number for the average solar flux over the human body in W/m2.
body_absorptivity: A number between 0 and 1 representing the average
shortwave absorptivity of the body (including clothing and skin color).
Typical clothing values - white: 0.2, khaki: 0.57, black: 0.88
Typical skin values - white: 0.57, brown: 0.65, black: 0.84
Default is 0.7 for average (brown) skin and medium clothing.
body_emissivity: A number between 0 and 1 representing the average
longwave emissivity of the body. Default is 0.95, which is almost
always the case except in rare situations of wearing metallic clothing.
"""
return solar_flux * (body_absorptivity / body_emissivity)
[docs]
def body_solar_flux_from_parts(diff_horiz_solar, dir_normal_solar, altitude,
sharp=135, sky_exposure=1, fract_exposed=1,
floor_reflectance=0.25, posture='standing'):
"""Estimate the total solar flux on human geometry from solar components.
Args:
diff_horiz_solar: Diffuse horizontal solar irradiance in W/m2.
dir_normal_solar: Direct normal solar irradiance in W/m2.
altitude: The altitude of the sun in degrees [0-90].
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
sky_exposure: A number between 0 and 1 representing the fraction of the
sky vault in occupant’s view. Default is 1 for outdoors in an
open field.
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include the
body’s self-shading; only the shading from surroundings.
Default is 1 for a person standing in an open area.
floor_reflectance: A number between 0 and 1 the represents the
reflectance of the floor. Default is for 0.25 which is characteristic
of outdoor grass or dry bare soil.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
"""
fract_eff = 0.696 if posture == 'seated' else 0.725
glob_horiz = diff_horiz_solar + (dir_normal_solar * math.sin(math.radians(altitude)))
dir_solar = body_dir_from_dir_normal(dir_normal_solar, altitude, sharp,
posture, fract_exposed)
diff_solar = body_diff_from_diff_horiz(diff_horiz_solar, sky_exposure, fract_eff)
ref_solar = body_ref_from_glob_horiz(glob_horiz, floor_reflectance,
sky_exposure, fract_eff)
return dir_solar + diff_solar + ref_solar
[docs]
def body_solar_flux_from_horiz_solar(diff_horiz_solar, dir_horiz_solar, altitude,
sharp=135, fract_exposed=1,
floor_reflectance=0.25, posture='standing'):
"""Estimate total solar flux on human geometry from horizontal solar components.
This method is useful for cases when one wants to take the hourly results
of a spatial radiation study with Radiance and use them to build a map
of ERF or MRT delta on a person.
Args:
diff_horiz_solar: Diffuse horizontal solar irradiance in W/m2.
dir_horiz_solar: Direct horizontal solar irradiance in W/m2.
altitude: The altitude of the sun in degrees [0-90].
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include the
body’s self-shading; only the shading from surroundings.
Default is 1 for a person standing in an open area.
floor_reflectance: A number between 0 and 1 the represents the
reflectance of the floor. Default is for 0.25 which is characteristic
of outdoor grass or dry bare soil.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
"""
fract_eff = 0.696 if posture == 'seated' else 0.725
glob_horiz = diff_horiz_solar + dir_horiz_solar
dir_solar = body_dir_from_dir_horiz(dir_horiz_solar, altitude, sharp,
posture, fract_exposed)
diff_solar = body_diff_from_diff_horiz(diff_horiz_solar, 1, fract_eff)
ref_solar = body_ref_from_glob_horiz(glob_horiz, floor_reflectance, 1, fract_eff)
return dir_solar + diff_solar + ref_solar
[docs]
def body_solar_flux_from_horiz_components(
diff_horiz_solar, dir_horiz_solar, ref_horiz_solar, altitude,
sharp=135, fract_exposed=1, posture='standing'):
"""Estimate total solar flux on human geometry from horizontal components.
This method is useful for cases when one wants to take the hourly results
of a spatial radiation study with Radiance and use them to build a map
of ERF or MRT delta on a person.
Args:
diff_horiz_solar: Diffuse horizontal solar irradiance in W/m2.
dir_horiz_solar: Direct horizontal solar irradiance in W/m2.
ref_horiz_solar: Ground-reflected horizontal solar irradiance in W/m2.
altitude: The altitude of the sun in degrees [0-90].
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include the
body’s self-shading; only the shading from surroundings.
Default is 1 for a person standing in an open area.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
"""
fract_eff = 0.696 if posture == 'seated' else 0.725
glob_horiz = diff_horiz_solar + dir_horiz_solar
dir_solar = body_dir_from_dir_horiz(dir_horiz_solar, altitude, sharp,
posture, fract_exposed)
diff_solar = body_diff_from_diff_horiz(diff_horiz_solar, 1, fract_eff)
ref_solar = body_ref_from_ref_horiz(ref_horiz_solar, 1, fract_eff)
return dir_solar + diff_solar + ref_solar
[docs]
def body_diff_from_diff_horiz(diff_horiz_solar, sky_exposure=1, fract_efficiency=0.725):
"""Estimate the diffuse solar flux on human geometry from diffuse horizontal solar.
Args:
diff_horiz_solar: Diffuse horizontal solar irradiance in W/m2.
sky_exposure: A number between 0 and 1 representing the fraction of the
sky vault in occupant’s view. Default is 1 for outdoors in an
open field.
fract_efficiency: A number representing the fraction of the body
surface exposed to radiation from the environment. This is typically
either 0.725 for a standing or supine person or 0.696 for a seated
person. Default is 0.725 for a standing person.
"""
return 0.5 * sky_exposure * fract_efficiency * diff_horiz_solar
[docs]
def body_ref_from_glob_horiz(glob_horiz_solar, floor_reflectance=0.25,
sky_exposure=1, fract_efficiency=0.725):
"""Estimate floor-reflected solar flux on human geometry from global horizontal solar.
Args:
glob_horiz_solar: Global horizontal solar irradiance in W/m2.
floor_reflectance: A number between 0 and 1 the represents the
reflectance of the floor. Default is for 0.25 which is characteristic
of outdoor grass or dry bare soil.
sky_exposure: A number between 0 and 1 representing the fraction of the
sky vault in occupant’s view. Default is 1 for outdoors in an
open field.
fract_efficiency: A number representing the fraction of the body
surface exposed to radiation from the environment. This is typically
either 0.725 for a standing or supine person or 0.696 for a seated
person. Default is 0.725 for a standing person.
"""
return 0.5 * sky_exposure * fract_efficiency * glob_horiz_solar * floor_reflectance
[docs]
def body_ref_from_ref_horiz(ref_horiz_solar, sky_exposure=1, fract_efficiency=0.725):
"""Estimate floor-reflected flux on human geometry from reflected horizontal solar.
Args:
ref_horiz_solar: Ground-reflected horizontal solar irradiance in W/m2.
sky_exposure: A number between 0 and 1 representing the fraction of the
sky vault in occupant’s view. Default is 1 for outdoors in an
open field.
fract_efficiency: A number representing the fraction of the body
surface exposed to radiation from the environment. This is typically
either 0.725 for a standing or supine person or 0.696 for a seated
person. Default is 0.725 for a standing person.
"""
return 0.5 * sky_exposure * fract_efficiency * ref_horiz_solar
[docs]
def body_dir_from_dir_horiz(dir_horiz_solar, altitude, sharp=135,
posture='standing', fract_exposed=1):
"""Estimate the direct solar flux on human geometry from direct horizontal solar.
Args:
dir_horiz_solar: Direct horizontal solar irradiance in W/m2.
altitude: A number between 0 and 90 representing the altitude of the
sun in degrees.
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include
the body’s self-shading; only the shading from surroundings.
Default is 1 for a person in an open area.
"""
try:
proj_fac = get_projection_factor(altitude, sharp, posture)
except KeyError:
proj_fac = get_projection_factor_simple(altitude, sharp, posture)
dir_normal_solar = dir_horiz_solar / math.sin(math.radians(altitude))
return proj_fac * fract_exposed * dir_normal_solar
[docs]
def body_dir_from_dir_normal(dir_normal_solar, altitude, sharp=135,
posture='standing', fract_exposed=1):
"""Estimate the direct solar flux on human geometry from direct horizontal solar.
Args:
dir_normal_solar: Direct normal solar irradiance in W/m2.
altitude: A number between 0 and 90 representing the altitude of the
sun in degrees.
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
fract_exposed: A number between 0 and 1 representing the fraction of
the body exposed to direct sunlight. Note that this does not include
the body’s self-shading; only the shading from surroundings.
Default is 1 for a person in an open area.
"""
try:
proj_fac = get_projection_factor(altitude, sharp, posture)
except KeyError:
proj_fac = get_projection_factor_simple(altitude, sharp, posture)
return proj_fac * fract_exposed * dir_normal_solar
[docs]
def sharp_from_solar_and_body_azimuth(solar_azimuth, body_azimuth=0):
"""Calculate solar horizontal angle relative to front of person (SHARP).
Args:
solar_azimuth: A number between 0 and 360 representing the solar azimuth
in degrees (0=North, 90=East, 180=South, 270=West).
body_azimuth: A number between 0 and 360 representing the direction that
the human is facing in degrees (0=North, 90=East, 180=South, 270=West).
"""
angle_diff = abs(solar_azimuth - body_azimuth)
if angle_diff <= 180:
return angle_diff
else:
return 360 - angle_diff
[docs]
def get_projection_factor(altitude, sharp=135, posture='standing'):
"""Get the fraction of body surface area exposed to direct sun from solar position.
This is effectively Ap / Ad in the original Solarcal equations.
Args:
altitude: A number between 0 and 90 representing the altitude of the
sun in degrees.
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). 0 signifies sun that is
shining directly into the person's face and 180 signifies sun that
is shining at the person's back. Default is 135, assuming that a person
typically faces their side or back to the sun to avoid glare.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
"""
if posture == 'supine':
altitude, sharp = transpose_altitude_azimuth(altitude, sharp)
altitude = 1 if altitude == 0 else altitude
posture = 'standing'
try:
return SOLARCAL_SPLINES[posture][int(sharp)][int(math.ceil(altitude) - 1)]
except IndexError:
raise ValueError('altitude|azimuth {}|{} is outside of acceptable ranges'.format(
altitude, sharp))
[docs]
def get_projection_factor_simple(altitude, sharp=135, posture='standing'):
"""Get the fraction of body surface area exposed to direct sun using a simpler method.
This is effectively Ap / Ad in the original Solarcal equations.
This is a more portable version of the get_projection_area() function
since it does not rely on the large matrix of projection factors
stored externally in csv files. However, it is less precise since it
effectively interpolates over the missing parts of the matrix. So this is
only recommended for cases where such csv files are missing.
Args:
altitude: A number between 0 and 90 representing the altitude of the
sun in degrees.
sharp: A number between 0 and 180 representing the solar horizontal
angle relative to front of person (SHARP). Default is 135, assuming
a person typically faces their side or back to the sun to avoid glare.
posture: A text string indicating the posture of the body. Letters must
be lowercase. Choose from the following: "standing", "seated", "supine".
Default is "standing".
"""
if posture == 'supine':
altitude, sharp = transpose_altitude_azimuth(altitude, sharp)
posture = 'standing'
if posture == 'standing':
ap_table = ((0.254, 0.254, 0.228, 0.187, 0.149, 0.104, 0.059),
(0.248, 0.248, 0.225, 0.183, 0.145, 0.102, 0.059),
(0.239, 0.239, 0.218, 0.177, 0.138, 0.096, 0.059),
(0.225, 0.225, 0.199, 0.165, 0.127, 0.09, 0.059),
(0.205, 0.205, 0.182, 0.151, 0.116, 0.083, 0.059),
(0.183, 0.183, 0.165, 0.136, 0.109, 0.078, 0.059),
(0.167, 0.167, 0.155, 0.131, 0.107, 0.078, 0.059),
(0.175, 0.175, 0.161, 0.131, 0.111, 0.081, 0.059),
(0.199, 0.199, 0.178, 0.147, 0.12, 0.084, 0.059),
(0.22, 0.22, 0.196, 0.16, 0.126, 0.088, 0.059),
(0.238, 0.238, 0.21, 0.17, 0.133, 0.091, 0.059),
(0.249, 0.249, 0.22, 0.177, 0.138, 0.093, 0.059),
(0.252, 0.252, 0.223, 0.178, 0.138, 0.093, 0.059))
elif posture == 'seated':
ap_table = ((0.202, 0.226, 0.212, 0.211, 0.182, 0.156, 0.123),
(0.203, 0.228, 0.205, 0.2, 0.187, 0.158, 0.123),
(0.2, 0.231, 0.207, 0.202, 0.184, 0.155, 0.123),
(0.191, 0.227, 0.205, 0.201, 0.175, 0.149, 0.123),
(0.177, 0.214, 0.195, 0.192, 0.168, 0.141, 0.123),
(0.16, 0.196, 0.182, 0.181, 0.162, 0.134, 0.123),
(0.15, 0.181, 0.173, 0.17, 0.153, 0.129, 0.123),
(0.163, 0.18, 0.164, 0.158, 0.145, 0.125, 0.123),
(0.182, 0.181, 0.156, 0.145, 0.136, 0.122, 0.123),
(0.195, 0.181, 0.146, 0.134, 0.128, 0.118, 0.123),
(0.207, 0.178, 0.135, 0.121, 0.117, 0.117, 0.123),
(0.213, 0.174, 0.125, 0.109, 0.109, 0.116, 0.123),
(0.209, 0.167, 0.117, 0.106, 0.106, 0.114, 0.123))
else:
raise TypeError('Posture type {} is not recognized.'.format(posture))
def _find_span(arr, x):
# For orderd array arr, find the left index of the closest interval x falls in.
for i in range(len(arr) - 1):
if x <= arr[i+1] and x >= arr[i]:
return i
raise ValueError('altitude/azimuth {} is outside of acceptable ranges'.format(x))
alt_range = (0, 15, 30, 45, 60, 75, 90)
az_range = (0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180)
alt_i = _find_span(alt_range, altitude)
az_i = _find_span(az_range, sharp)
ap11 = ap_table[az_i][alt_i]
ap12 = ap_table[az_i][alt_i + 1]
ap21 = ap_table[az_i + 1][alt_i]
ap22 = ap_table[az_i + 1][alt_i + 1]
az1 = az_range[az_i]
az2 = az_range[az_i+1]
alt1 = alt_range[alt_i]
alt2 = alt_range[alt_i+1]
# bilinear interpolation
ap = ap11 * (az2 - sharp) * (alt2 - altitude)
ap += ap21 * (sharp - az1) * (alt2 - altitude)
ap += ap12 * (az2 - sharp) * (altitude - alt1)
ap += ap22 * (sharp - az1) * (altitude - alt1)
ap /= (az2 - az1) * (alt2 - alt1)
return ap
[docs]
def transpose_altitude_azimuth(altitude, azimuth):
"""Transpose altitude and azimuth.
This is necessary for getting correct projection factors for a supine posture
from the standing posture matrix.
"""
alt_temp = altitude
altitude = abs(90 - azimuth)
azimuth = alt_temp
return altitude, azimuth