Skip to content

Commit

Permalink
feat(skymodel): Added Perez Luminous Efficacy Model
Browse files Browse the repository at this point in the history
* Deleted Unnecessary Code

* feat(skymodel): Added Perez Luminous Efficacy Model

This commit adds  the Perez model for luminaious efficacy of the sky, which seems to be the state-of-the-art model for deriving illuminance values from irradiance values.  More info on the model can be found in [this paper](http://www.cuepe.ch/html/biblio/pdf/perez-ineichen%201990%20-%20modelling%20daylight%20(se).pdf) and credit is due to @hansukyang for pointing me towards a Python implementation of this model.

With this commit, the Ladybug core now possesses enough climate models to make fully-simulate-able EPWs (for both daylight and energy) with only the following:
* dry bulb temperature
* dew point temperature
* atmospheric pressure
* sky cover
* wind speed
* wind direction

* Fixing docstring of luminous efficacy

* 0.3.0

Automatically generated by python-semantic-release

* fix(deploy): use javascript semantic-release package for deployment

Using the "pure" javascript implementation of semantic-release makes it a bit more flexible to set
up custom scripts to run at different stages of the semantic release process. The packages used as
well as scripts to run at different stages are specified in the .releaserc.json file. In this case
we use standard semantic-release and add a custom deploy script on top of the normal github release
setup. The custom deploy script is the new 'deploy.sh' file which takes the new version number as an
argument to deploy the package to PyPi and build the documentation.

#125

build(pytest): add pytest-cov deps to dev-requirements.txt

build(euclid): add euclid to dev-requirements.txt

* build(requirements): cleanup dev-requirements.txt and make python2 compatible

* ci(travis): use python image to deploy with semantic-release

Seems it's easy enough to install node on a python image but the node image's python distro doesn't
play well with https.

#125

* fix(deploy): fix travis file for docs deployment

#125

* Create .nojekyll

see here: https://github.blog/2009-12-29-bypassing-jekyll-on-github-pages/

* fix(init): fix isplus assignment

* Update skymodel.py

* fix(legendparam): fix import for Python 3

* fix(init): fix conflicts

not really! GitHub being crazy. there is no conflicts.

* Clean git history (#136)

* feat(skymodel): Added Perez Luminous Efficacy Model

See  5e0a32c

* fix(travis): fix travis file for docs deployment and build
  • Loading branch information
ladybugbot authored and mostaphaRoudsari committed Apr 30, 2019
1 parent c08b730 commit 0caefbf
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 74 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
author = 'Ladybug Tools'

# The full version, including alpha/beta/rc tags
release = ''
release = version

# for example take major/minor
version = ''
Expand Down
4 changes: 2 additions & 2 deletions ladybug/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import importlib
import pkgutil

# This is a variable to check if the library is a [+] library.
setattr(sys.modules[__name__], 'isplus', False)

# find and import ladybug plugins
# this is a critical step to add additional functionalities to ladybug core library.
Expand All @@ -20,5 +22,3 @@
for key in lb_plugins:
print('Successfully imported Ladybug plugin: {}'.format(key))

# This is a variable to check if the library is a [+] library.
setattr(sys.modules[__name__], 'isplus', False)
2 changes: 0 additions & 2 deletions ladybug/_datacollectionbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
from collections import Iterable
from string import ascii_lowercase
import math
from os.path import dirname, basename, join
from importlib import import_module

import sys
if (sys.version_info >= (3, 0)):
Expand Down
4 changes: 2 additions & 2 deletions ladybug/legendparameters.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# coding=utf-8
from __future__ import division

from color import ColorRange
from listoperations import flatten, unflatten
from .color import ColorRange
from .listoperations import flatten, unflatten


class LegendParameters(object):
Expand Down
158 changes: 139 additions & 19 deletions ladybug/skymodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,128 @@ def zhang_huang_solar_split(altitudes, doys, cloud_cover, relative_humidity,
return dir_norm_rad, dif_horiz_rad


"""LUMINOUS EFFICACY OF THE SKY"""


def estimate_illuminance_from_irradiance(
altitude, ghi, dni, dhi, dew_point, rel_airmass=None):
"""Estimate sky illuminance components from irradiance components.
This function uses actual zenith rather than apparent zenith.
Note:
[1] Perez R. (1990). 'Modeling Daylight Availability and Irradiance
Components from Direct and Global Irradiance'. Solar Energy.
Vol. 44. No. 5, pp. 271-289. USA.
Args:
altitude: Solar altitude angle in degrees.
ghi: Number for Global Horizontal Irradiance in W/m2.
dni: Number for Direct Normal Irradiance in W/m2.
dhi: Number for Diffuse Horizontal Irradiance in W/m2.
dew_point: Surface dewpoint in degrees C.
rel_airmass: A number between 1 and ~38 representing the ratio of air mass
between the sun and the observer and the air mass that is directly above
the observer. Default is None, which will simply use the input solar
altitude and the get_relative_airmass function in this module with
the kastenyoung1989 model to compute this value.
Returns:
gh_ill: Value for Global Horizontal Illuminance in lux.
dn_ill: Value for Direct Normal Illuminance in lux.
dh_ill: Value for Diffuse Horizontal Illuminance in lux.
z_lum: Value for Zenith Luminance in lux.
"""
if altitude <= 0: # sun is below the horizon, return 0 for all results
return 0, 0, 0, 0

if rel_airmass is None:
rel_airmass = get_relative_airmass(altitude)
zenith = math.radians(90 - altitude)
dhi = 0.1 if dhi == 0 else dhi
kai = 1.041
eps = ((dhi + dni) / dhi + kai * zenith ** 3) / (1 + kai * zenith ** 3)
delta = dhi * rel_airmass / 1360
w = math.exp(0.08 * dew_point - 0.075)

# Perez Table 1: Discrete Sky Clearness Categories
if eps >= 1 and eps < 1.065:
e_category = 0
elif eps >= 1.065 and eps < 1.23:
e_category = 1
elif eps >= 1.23 and eps < 1.5:
e_category = 2
elif eps >= 1.5 and eps < 1.95:
e_category = 3
elif eps >= 1.95 and eps < 2.8:
e_category = 4
elif eps >= 2.8 and eps < 4.5:
e_category = 5
elif eps >= 4.5 and eps < 6.2:
e_category = 6
elif eps >= 6.2:
e_category = 7
else:
raise ValueError('Error in sky luminous efficacy calculation\n'
'eps: %f altitude: %f' % (eps, altitude))

# Perez Table 4: Luminous Efficacy
glob_lum_eff_coeff = ((96.63, -0.47, 11.50, -9.16),
(107.54, 0.79, 1.79, -1.19),
(98.73, 0.70, 4.40, -6.95),
(92.72, 0.56, 8.36, -8.31),
(86.73, 0.98, 7.10, -10.94),
(88.34, 1.39, 6.06, -7.60),
(78.63, 1.47, 4.93, -11.37),
(99.65, 1.86, -4.46, -3.15))

dir_lum_eff_coeff = ((57.20, -4.55, -2.98, 117.12),
(98.99, -3.46, -1.21, 12.38),
(109.83, -4.90, -1.71, -8.81),
(110.34, -5.84, -1.99, -4.56),
(106.36, -3.97, -1.75, -6.16),
(107.19, -1.25, -1.51, -26.73),
(105.75, 0.77, -1.26, -34.44),
(101.18, 1.58, -1.10, -8.29))

diff_lum_eff_coeff = ((97.24, -0.46, 12.00, -8.91),
(107.22, 1.15, 0.59, -3.95),
(104.97, 2.96, -5.52, -8.77),
(102.39, 5.59, -13.95, -13.90),
(100.71, 5.94, -22.75, -23.74),
(106.42, 3.83, -36.15, -28.83),
(141.88, 1.90, -53.24, -14.03),
(152.23, 0.35, -45.27, -7.98))

zen_lum_eff_coeff = ((40.86, 26.77, -29.59, -45.75),
(26.58, 14.73, 58.46, -21.25),
(19.34, 2.28, 100.00, 0.25),
(13.25, -1.39, 124.79, 15.66),
(14.47, -5.09, 160.09, 9.13),
(19.76, -3.88, 154.61, -19.21),
(28.39, -9.67, 151.58, -69.39),
(42.91, -19.62, 130.80, -164.08))

# Eq 6
a, b, c, d = glob_lum_eff_coeff[e_category]
gh_ill = ghi * (a + b * w + c * math.cos(zenith) + d * math.log(delta))

# Eq 8
a, b, c, d = dir_lum_eff_coeff[e_category]
dn_ill = max(0, dni * (a + b * w + c * math.exp(5.73 * zenith - 5) + d * delta))

# Eq 7
a, b, c, d = diff_lum_eff_coeff[e_category]
dh_ill = dhi * (a + b * w + c * math.cos(zenith) + d * math.log(delta))

# Eq 9
a, b, c, d = zen_lum_eff_coeff[e_category]
z_lum = dhi * (a + b * math.cos(zenith) + c * math.exp(-3 * zenith) + d * delta)

return gh_ill, dn_ill, dh_ill, z_lum


"""HORIZONTAL INFRARED INTENSITY + SKY TEMPERATURE MODELS"""


Expand Down Expand Up @@ -475,10 +597,10 @@ def disc(ghi, altitude, doy, pressure=101325,
This implementation limits the clearness index to 1 by default.
The original report describing the DISC model [1]_ uses the
relative airmass rather than the absolute (pressure-corrected)
airmass. However, the NREL implementation of the DISC model [2]_
uses absolute airmass. PVLib Matlab also uses the absolute airmass.
pvlib python defaults to absolute airmass, but the relative airmass
relative air mass rather than the absolute (pressure-corrected)
air mass. However, the NREL implementation of the DISC model [2]_
uses absolute air mass. PVLib Matlab also uses the absolute air mass.
pvlib python defaults to absolute air mass, but the relative airmass
can be used by supplying `pressure=None`.
Note:
Expand All @@ -498,18 +620,18 @@ def disc(ghi, altitude, doy, pressure=101325,
degrees.
doy : An integer representing the day of the year.
pressure : None or numeric, default 101325
Site pressure in Pascal. If None, relative airmass is used
instead of absolute (pressure-corrected) airmass.
Site pressure in Pascal. If None, relative air mass is used
instead of absolute (pressure-corrected) air mass.
min_sin_altitude : numeric, default 0.065
Minimum value of sin(altitude) to allow when calculating global
clearness index `kt`. Equivalent to altitude = 3.727 degrees.
min_altitude : numeric, default 87
Minimum value of altitude to allow in DNI calculation. DNI will be
set to 0 for times with altitude values smaller than `min_altitude`.
max_airmass : numeric, default 12
Maximum value of the airmass to allow in Kn calculation.
Maximum value of the air mass to allow in Kn calculation.
Default value (12) comes from range over which Kn was fit
to airmass in the original paper.
to air mass in the original paper.
Returns:
dni: The modeled direct normal irradiance
Expand Down Expand Up @@ -698,15 +820,14 @@ def clearness_index_zenith_independent(clearness_index, airmass,
def get_absolute_airmass(airmass_relative, pressure=101325.):
"""
Determine absolute (pressure corrected) airmass from relative
airmass and pressure.
airmass and atmospheirc pressure.
Gives the airmass for locations not at sea-level (i.e. not at
standard pressure). The input argument airmass_relative is the relative
airmass. The input argument pressure is the pressure (in Pascals)
air mass. The input argument pressure is the pressure (in Pascals)
at the location of interest and must be greater than 0. The
calculation for absolute airmass is
.. math::
absolute airmass = (relative airmass)*pressure/101325
calculation for absolute air mass is
`absolute airmass = (relative airmass)*pressure/101325`
Note:
[1] C. Gueymard, "Critical analysis and performance assessment of
Expand All @@ -715,13 +836,13 @@ def get_absolute_airmass(airmass_relative, pressure=101325.):
Args:
airmass_relative: numeric
The airmass at sea-level.
The air mass at sea-level.
pressure: numeric, default 101325
The site pressure in Pascal.
Returns:
airmass_absolute: numeric
Absolute (pressure corrected) airmass
Absolute (pressure corrected) air mass
"""
if airmass_relative is not None:
return airmass_relative * pressure / 101325.
Expand All @@ -734,8 +855,8 @@ def get_relative_airmass(altitude, model='kastenyoung1989'):
Gives the relative (not pressure-corrected) airmass.
Gives the airmass at sea-level when given a sun altitude angle (in
degrees). The ``model`` variable allows selection of different
airmass models (described below). If ``model`` is not included or is
degrees). The `model` variable allows selection of different
airmass models (described below). If `model` is not included or is
not valid, the default model is 'kastenyoung1989'.
Note:
Expand Down Expand Up @@ -782,8 +903,7 @@ def get_relative_airmass(altitude, model='kastenyoung1989'):
requires apparent sun altitude
Returns:
airmass_relative: numeric
Relative airmass at sea level. Will return None for any
airmass_relative: Relative airmass at sea level. Will return None for any
altitude angle smaller than 0 degrees.
"""
if altitude < 0:
Expand Down
Loading

0 comments on commit 0caefbf

Please sign in to comment.