Source code for openalea.astk.sky_luminance

# -*- python -*-
#
#       Copyright 2016-2025 Inria - CIRAD - INRAe
#
#       Distributed under the Cecill-C License.
#       See accompanying file LICENSE.txt or copy at
#           http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
#       WebSite : https://github.com/openalea/astk
#
#       File author(s): Christian Fournier <christian.fournier@inrae.fr>
#
# ==============================================================================
""" A collection of equation for modelling distribution of sky luminance
"""
import numpy
from openalea.astk.sky_irradiance import (
    horizontal_irradiance,
    all_weather_sky_clearness, 
    f_clear_sky,
    all_weather_sky_brightness
)
from openalea.astk.sky_map import (
    ksi_grid, 
    scale_sky, 
    sky_ni, 
    sky_lum
)


[docs] def cie_luminance_gradation(z, a=4, b=-0.7): """ function giving the dependence of the luminance of a sky element to its zenith angle CIE, 2002, Spatial distribution of daylight CIE standard general sky, CIE standard, CIE Central Bureau, Vienna z : zenith angle of the sky element (deg) a, b : coefficient for the type of sky """ def _f(z): return 1 + numpy.where(z == 90, 0, a * numpy.exp(b / numpy.cos(numpy.radians(z)))) return _f(z) / _f(0)
[docs] def cie_scattering_indicatrix(ksi, ksi_sun=0, c=10, d=-3, e=-0.45): """ function giving the dependence of the luminance to its angular distance to the sun CIE, 2002, Spatial distribution of daylight CIE standard general sky, CIE standard, CIE Central Bureau, Vienna ksi: angular distance to the sun (deg) ksi_sun: angular distance of zenith to the sun, ie zenith angle of the sun (deg) c, d, e : coefficient for the type of sky """ def _f(k): ksi_r = numpy.radians(k) return 1 + c * ( numpy.exp(d * ksi_r) - numpy.exp(d * numpy.pi / 2)) + e * numpy.power( numpy.cos(ksi_r), 2) return _f(ksi) / _f(ksi_sun)
[docs] def cie_relative_luminance(sky_zenith=None, grid=None, sun_zenith=None, sun_azimuth=None, type='soc'): """ cie relative luminance of a sky element relative to the luminance at zenith sky_zenith : zenith angle of the sky element (deg) sky_azimuth: azimuth angle of the sky element (deg) sun_zenith : zenith angle of the sun (deg) sun_azimuth: azimuth angle of the sun (deg) type is one of 'soc' (standard overcast sky), 'uoc' (uniform radiance) or 'clear_sky' (standard clear sky low turbidity) """ if sky_zenith is None and grid is None: raise ValueError('Either sky_zenith or grid should be passed') sky_azimuth = None if grid is not None: _, sky_zenith, _ = grid else: sky_zenith = numpy.array(sky_zenith) if type == 'soc': ab = {'a': 4, 'b': -0.7} elif type == 'uoc': ab = {'a': 0, 'b': -1} elif type == 'clear_sky': ab = {'a': -1, 'b': -0.32} else: raise ValueError('unknown sky_type:' + type) gradation = cie_luminance_gradation(sky_zenith, **ab) indicatrix = 1 if type == 'clear_sky': cde = {'c': 10, 'd': -3, 'e': 0.45} ksi_sun = sun_zenith ksi = ksi_grid(grid, sun_zenith, sun_azimuth) indicatrix = cie_scattering_indicatrix(ksi, ksi_sun=ksi_sun, **cde) return gradation * indicatrix
[docs] def all_weather_abcde(sun_zenith, clearness, brightness): """Parameters of the all weather sky model (Perez et al. 1993) Args: sun_zenith: zenith angle of the sun (deg) clearness: sky clearness as defined in Perez et al. (1993 brightness: sky brightness as defined in Perez et al. (1993) Returns: a tuple of 5 parameters to be used by CIE sky luminance functions Details: R. Perez, R. Seals, J. Michalsky, "All-weather model for sky luminance distribution—Preliminary configuration and validation", Solar Energy, Volume 50, Issue 3, 1993, Pages 235-245, """ def _awfit(p1, p2, p3, p4, zen, br): p1 = numpy.array(p1) p2 = numpy.array(p2) p3 = numpy.array(p3) p4 = numpy.array(p4) return p1 + p2 * zen + br * (p3 + p4 * zen) bins = [1, 1.065, 1.23, 1.5, 1.95, 2.8, 4.5, 6.2] a1 = (1.3525, -1.2219, -1.1000, -0.5484, -0.6000, -1.0156, -1.0000, -1.0500) a2 = (-0.2576, -0.7730, -0.2215, -0.6654, -0.3566, -0.3670, 0.0211, 0.0289) a3 = (-0.2690, 1.4148, 0.8952, -0.2672, -2.5000, 1.0078, 0.5025, 0.4260) a4 = (-1.4366, 1.1016, 0.0156, 0.7117, 2.3250, 1.4051, -0.5119, 0.3590) b1 = (-0.7670, -0.2054, 0.2782, 0.7234, 0.2937, 0.2875, -0.3000, -0.3250) b2 = (0.0007, 0.0367, -0.1812, -0.6219, 0.0496, -0.5328, 0.1922, 0.1156) b3 = (1.2734, -3.9128, -4.5000, -5.6812, -5.6812, -3.8500, 0.7023, 0.7781) b4 = (-0.1233, 0.9156, 1.1766, 2.6297, 1.8415, 3.3750, -1.6317, 0.0025) c1 = (2.8000, 6.9750, 24.7219, 33.3389, 21.0000, 14.0000, 19.0000, 31.0625) c2 = ( 0.6004, 0.1774, -13.0812, -18.3000, -4.7656, -0.9999, -5.0000, -14.5000) c3 = ( 1.2375, 6.4477, -37.7000, -62.2500, -21.5906, -7.1406, 1.2438, -46.1148) c4 = (1.0000, -0.1239, 34.8438, 52.0781, 7.2492, 7.5469, -1.9094, 55.3750) d1 = (1.8734, -1.5798, -5.0000, -3.5000, -3.5000, -3.4000, -4.0000, -7.2312) d2 = (0.6297, -0.5081, 1.5218, 0.0016, -0.1554, -0.1078, 0.0250, 0.4050) d3 = (0.9738, -1.7812, 3.9229, 1.1477, 1.4062, -1.0750, 0.3844, 13.3500) d4 = (0.2809, 0.1080, -2.6204, 0.1062, 0.3988, 1.5702, 0.2656, 0.6234) e1 = (0.0356, 0.2624, -0.0156, 0.4659, 0.0032, -0.0672, 1.0468, 1.5000) e2 = (-0.1246, 0.0672, 0.1597, -0.3296, 0.0766, 0.4016, -0.3788, -0.6426) e3 = (-0.5718, -0.2190, 0.4199, -0.0876, -0.0656, 0.3017, -2.4517, 1.8564) e4 = (0.9938, -0.4285, -0.5562, -0.0329, -0.1294, -0.4844, 1.4656, 0.5636) index = max(0, numpy.searchsorted(bins, clearness) - 1) z = numpy.radians(sun_zenith) a = _awfit(a1, a2, a3, a4, z, brightness)[index] b = _awfit(b1, b2, b3, b4, z, brightness)[index] c = _awfit(c1, c2, c3, c4, z, brightness)[index] d = _awfit(d1, d2, d3, d4, z, brightness)[index] e = _awfit(e1, e2, e3, e4, z, brightness)[index] if clearness <= 1.065: c = numpy.exp(numpy.power(brightness * (c1[0] + c2[0] * z), c3[0])) - \ c4[0] d = -numpy.exp(brightness * (d1[0] + d2[0] * z)) + d3[0] + d4[ 0] * brightness return a, b, c, d, e
[docs] def all_weather_relative_luminance(grid, sun_zenith, sun_azimuth, clearness, brightness): """All weather relative luminance of a sky element relative to the luminance at zenith Args: grid: a (azimuth, zenith, az_c, z_c) tuple, such as returned by astk.sky_grid sun_zenith : zenith angle of the sun (deg) sun_azimuth: azimuth angle of the sun (deg) clearness: sky clearness as defined in Perez et al. (1993 brightness: sky brightness as defined in Perez et al. (1993) Details: R. Perez, R. Seals, J. Michalsky, "All-weather model for sky luminance distribution—Preliminary configuration and validation", Solar Energy, Volume 50, Issue 3, 1993, Pages 235-245, """ a, b, c, d, e = all_weather_abcde(sun_zenith, clearness, brightness) _, sky_zenith, _ = grid gradation = cie_luminance_gradation(sky_zenith, a=a, b=b) ksi_sun = sun_zenith ksi = ksi_grid(grid, sun_zenith, sun_azimuth) indicatrix = cie_scattering_indicatrix(ksi, ksi_sun=ksi_sun, c=c, d=d, e=e) return gradation * indicatrix
[docs] def sky_luminance(grid, sky_type='soc', sky_irradiance=None, scale=None, sun_in_sky=False): """Sun and sky luminance as a function of sky type and sky_irradiance Args: grid: a (azimuth, zenith, az_c, z_c, sr_c) tuple of sky coordinates, such as returned by astk.sky_map.sky_grid sky_type (str): sky type (see details below), one of ('soc', 'uoc', 'clear_sky', 'sun_soc', 'blended', 'all_weather'). sky_irradiance: a datetime indexed dataframe specifying sky irradiances for the period , such as returned by astk.meteorology.sky_irradiance.sky_irradiance. Needed for all sky_types except 'uoc' and 'soc' scale (str): How should sun/sky luminance be scaled ? If None (default) luminance are scaled so that sun+sky horizontal irradiance equals one. Other options are: - 'ghi': sun+sky horizontal flux equals mean ghi (W.m-2.s-1) - 'ppfd' : sun+sky horizontal flux equals mean PPFD (micromolPAR.m-2.s-1) - 'global': sun+sky horizontal flux equals time-integrated global irradiance (MJ.m-2) - 'par': sun+sky horizontal flux equals time-integrated PPFD (molPAR.m-2) sun_in_sky: Should the sun be added to the sky ? If True, sky luminance is set to sun luminance in the sun region, and sun luminance list is emptied. Ignored for sky types 'uoc' and 'soc'. Returns: sun, sky : a (sun_elevation, sun_azimuth, sun_luminance), sky_luminance tuple defining sun luminance over the period and a sky luminance gridded array Details: sky_type refer to different sky models: - 'clear_sky', 'uoc' and 'soc' are CIE sky types defined in [1]. For all these types, only relative sky luminance are returned (sun source list is empty), and total sky horizontal irradiance equals one. - 'sun_soc' refers to the approach defined in [2] where direct normal irradiance comes from the sun, and diffuse horizontal irradiance comes from an CIE soc sky luminance distribution - 'blended' refers to the sky blending approach defined in [3], where direct normal irradiance comes from the sun, and diffuse horizontal irradiance comes from a blending of CIE clear-sky and CIE soc sky luminance distribution, that depends on sky clearness index - 'all_weather' refers to the approach defined in [4], where direct normal irradiance comes from the sun, and diffuse horizontal irradiance comes from a luminance distribution that depends on sky clearness and sky brightness [1] CIE, 2002, Spatial distribution of daylight CIE standard general sky, CIE standard, CIE Central Bureau, Vienna [2] Sinoquet H. & Bonhomme R. (1992) Modeling radiative transfer in mixed and row intercropping systems. Agricultural and Forest Meteorology 62, 219–240 [3] J. Mardaljevic. Daylight Simulation: Validation, Sky Models and Daylight Coefficients. PhD thesis, De Montfort University, Leicester, UK, 2000. p193,eq. 5-10 [4] R. Perez, R. Seals, J. Michalsky, "All-weather model for sky luminance distribution—Preliminary configuration and validation", Solar Energy, Volume 50, Issue 3, 1993, Pages 235-245 """ if sky_type not in ('soc', 'uoc'): if sky_irradiance is None: raise ValueError('sky_irradiance is required for this type of sky') sun = [] if sky_type in ('soc', 'uoc'): sky = scale_sky(grid, cie_relative_luminance(grid=grid, type=sky_type)) if sky_irradiance is None: return [], sky else: sky = numpy.zeros_like(grid[0]) if sun_in_sky: hi_sum = sky_irradiance.ghi.sum() else: hi_sum = sky_irradiance.dhi.sum() if sky_type in ('blended', 'sun_soc'): soc = scale_sky(grid, cie_relative_luminance(grid=grid, type='soc')) for row in sky_irradiance.itertuples(): if sky_type in ('clear_sky', 'blended'): cs = scale_sky(grid, cie_relative_luminance(grid=grid, sun_zenith=row.zenith, sun_azimuth=row.azimuth, type='clear_sky')) if sky_type == 'clear_sky': _lum = cs.copy() elif sky_type == 'sun_soc': _lum = soc.copy() elif sky_type == 'blended': epsilon = all_weather_sky_clearness(row.dni, row.dhi, row.zenith) f_clear = f_clear_sky(epsilon) _lum = f_clear * cs + (1 - f_clear) * soc elif sky_type == 'all_weather': brightness = all_weather_sky_brightness(row.Index, row.dhi, row.zenith) clearness = all_weather_sky_clearness(row.dni, row.dhi, row.zenith) _lum = scale_sky(grid, all_weather_relative_luminance(grid, sun_zenith=row.zenith, sun_azimuth=row.azimuth, brightness=brightness, clearness=clearness)) else: raise ValueError('undefined sky type: ' + sky_type) if row.dni > 0: sun.append((90 - row.zenith, row.azimuth, row.dni)) if sun_in_sky: ksi_sun = ksi_grid(grid, sun_zenith=row.zenith, sun_azimuth=row.azimuth) sun_index = numpy.unravel_index(numpy.argmin(ksi_sun), ksi_sun.shape) _lum = sky_ni(grid, scale_sky(grid, _lum, row.dhi)) _lum[sun_index] = max(row.dni, _lum[sun_index]) _lum = scale_sky(grid, sky_lum(grid, _lum), row.ghi / hi_sum) else: _lum *= row.dhi / hi_sum sky += _lum sky = scale_sky(grid, sky) if sky_type == 'clear_sky' or sun_in_sky: sun = [] sc = 1 if scale is None: pass elif sky_irradiance is None: raise ValueError('Cannot compute scaling to ' + scale + ' without sky_irradiance') elif scale == 'ghi': sc = sky_irradiance.ghi.mean() elif scale == 'ppfd': sc = sky_irradiance.ppfd.mean() elif scale == 'global': sc = sky_irradiance.ghi.sum() * 3600 / 1e6 elif scale == 'par': sc = sky_irradiance.ppfd.sum() * 3600 / 1e6 else: raise ValueError('undefined scale: ' + scale + '. Should be None or one of ghi, ppfd, global or par') # scale if len(sun) > 0: sun_el, sun_az, sun_lum = list(map(numpy.array, zip(*sun))) sun_hi = sum(horizontal_irradiance(sun_lum, sun_el)) sun_lum /= sun_hi sun_lum *= sun_hi / sky_irradiance.ghi.sum() sky *= (1 - sun_hi / sky_irradiance.ghi.sum()) sun_lum *= sc sun = list(zip(sun_el, sun_az, sun_lum)) sky *= sc return sun, sky