diff --git a/docs/sphinx/source/reference/irradiance/albedo.rst b/docs/sphinx/source/reference/irradiance/albedo.rst new file mode 100644 index 0000000000..868a065d1a --- /dev/null +++ b/docs/sphinx/source/reference/irradiance/albedo.rst @@ -0,0 +1,9 @@ +.. currentmodule:: pvlib + +Albedo +------ + +.. autosummary:: + :toctree: ../generated/ + + albedo.inland_water_dvoracek diff --git a/docs/sphinx/source/reference/irradiance/index.rst b/docs/sphinx/source/reference/irradiance/index.rst index 2263a2d2c1..72064cccbc 100644 --- a/docs/sphinx/source/reference/irradiance/index.rst +++ b/docs/sphinx/source/reference/irradiance/index.rst @@ -11,3 +11,4 @@ Irradiance transposition decomposition clearness-index + albedo diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 14694d0fc4..3fa2aa41df 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -28,6 +28,9 @@ Enhancements shade perpendicular to ``axis_azimuth``. The function is applicable to both fixed-tilt and one-axis tracking systems. (:issue:`1689`, :pull:`1725`, :pull:`1962`) +* Add function :py:func:`pvlib.albedo.inland_water_dvoracek`, to calculate the + albedo for inland water bodies. + (:pull:`2079`) * Added conversion functions from spectral response ([A/W]) to quantum efficiency ([unitless]) and vice versa. The conversion functions are :py:func:`pvlib.spectrum.sr_to_qe` and :py:func:`pvlib.spectrum.qe_to_sr` @@ -55,4 +58,5 @@ Contributors * Cliff Hansen (:ghuser:`cwhanse`) * Mark Mikofski (:ghuser:`mikofski`) * Siddharth Kaul (:ghuser:`k10blogger`) +* Ioannis Sifnaios (:ghuser:`IoannisSifnaios`) * Mark Campanelli (:ghuser:`markcampanelli`) diff --git a/pvlib/__init__.py b/pvlib/__init__.py index 413af8f607..b5b07866a4 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -4,6 +4,7 @@ # list spectrum first so it's available for atmosphere & pvsystem (GH 1628) spectrum, + albedo, atmosphere, bifacial, clearsky, diff --git a/pvlib/albedo.py b/pvlib/albedo.py new file mode 100644 index 0000000000..98b920dddb --- /dev/null +++ b/pvlib/albedo.py @@ -0,0 +1,149 @@ +""" +The ``albedo`` module contains functions for modeling albedo. +""" + +from pvlib.tools import sind +import numpy as np +import pandas as pd + + +WATER_COLOR_COEFFS = { + 'clear_water_no_waves': 0.13, + 'clear_water_ripples_up_to_2.5cm': 0.16, + 'clear_water_ripples_larger_than_2.5cm_occasional_whitecaps': 0.23, + 'clear_water_frequent_whitecaps': 0.3, + 'green_water_ripples_up_to_2.5cm': 0.22, + 'muddy_water_no_waves': 0.19 +} + +WATER_ROUGHNESS_COEFFS = { + 'clear_water_no_waves': 0.29, + 'clear_water_ripples_up_to_2.5cm': 0.7, + 'clear_water_ripples_larger_than_2.5cm_occasional_whitecaps': 1.25, + 'clear_water_frequent_whitecaps': 2, + 'green_water_ripples_up_to_2.5cm': 0.7, + 'muddy_water_no_waves': 0.29 +} + + +def inland_water_dvoracek(solar_elevation, surface_condition=None, + color_coeff=None, wave_roughness_coeff=None): + r""" + Estimation of albedo for inland water bodies. + + The available surface conditions are for inland water bodies, e.g., lakes + and ponds. For ocean/open sea, see + :const:`pvlib.irradiance.SURFACE_ALBEDOS`. + + Parameters + ---------- + solar_elevation : numeric + Sun elevation angle. [degrees] + + surface_condition : string, optional + If supplied, overrides ``color_coeff`` and ``wave_roughness_coeff``. + ``surface_condition`` can be one of the following: + + * ``'clear_water_no_waves'`` + * ``'clear_water_ripples_up_to_2.5cm'`` + * ``'clear_water_ripples_larger_than_2.5cm_occasional_whitecaps'`` + * ``'clear_water_frequent_whitecaps'`` + * ``'green_water_ripples_up_to_2.5cm'`` + * ``'muddy_water_no_waves'`` + + color_coeff : float, optional + Water color coefficient. [-] + + wave_roughness_coeff : float, optional + Water wave roughness coefficient. [-] + + Returns + ------- + albedo : numeric + Albedo for inland water bodies. [-] + + Raises + ------ + ValueError + If neither of ``surface_condition`` nor a combination of + ``color_coeff`` and ``wave_roughness_coeff`` are given. + If ``surface_condition`` and any of ``color_coeff`` or + ``wave_roughness_coeff`` are given. These parameters are + mutually exclusive. + + KeyError + If ``surface_condition`` is invalid. + + Notes + ----- + The equation for calculating the albedo :math:`\rho` is given by + + .. math:: + :label: albedo + + \rho = c^{(r \cdot \sin(\alpha) + 1)} + + Inputs to the model are the water color coefficient :math:`c` [-], the + water wave roughness coefficient :math:`r` [-] and the solar elevation + :math:`\alpha` [degrees]. Parameters are provided in [1]_ , and are coded + for convenience in :data:`~pvlib.albedo.WATER_COLOR_COEFFS` and + :data:`~pvlib.albedo.WATER_ROUGHNESS_COEFFS`. The values of these + coefficients are experimentally determined. + + +------------------------+-------------------+-------------------------+ + | Surface and condition | Color coefficient | Wave roughness | + | | (:math:`c`) | coefficient (:math:`r`) | + +========================+===================+=========================+ + | Clear water, no waves | 0.13 | 0.29 | + +------------------------+-------------------+-------------------------+ + | Clear water, ripples | 0.16 | 0.70 | + | up to 2.5 cm | | | + +------------------------+-------------------+-------------------------+ + | Clear water, ripples | 0.23 | 1.25 | + | larger than 2.5 cm | | | + | (occasional whitecaps) | | | + +------------------------+-------------------+-------------------------+ + | Clear water, | 0.30 | 2.00 | + | frequent whitecaps | | | + +------------------------+-------------------+-------------------------+ + | Green water, ripples | 0.22 | 0.70 | + | up to 2.5cm | | | + +------------------------+-------------------+-------------------------+ + | Muddy water, no waves | 0.19 | 0.29 | + +------------------------+-------------------+-------------------------+ + + References + ---------- + .. [1] Dvoracek M.J., Hannabas B. (1990). "Prediction of albedo for use in + evapotranspiration and irrigation scheduling." IN: Visions of the Future + American Society of Agricultural Engineers 04-90: 692-699. + """ + + if surface_condition is not None and ( + color_coeff is None and wave_roughness_coeff is None + ): + # use surface_condition + color_coeff = WATER_COLOR_COEFFS[surface_condition] + wave_roughness_coeff = WATER_ROUGHNESS_COEFFS[surface_condition] + + elif surface_condition is None and not ( + color_coeff is None or wave_roughness_coeff is None + ): + # use provided color_coeff and wave_roughness_coeff + pass + else: + raise ValueError( + "Either a `surface_condition` has to be chosen or" + " a combination of `color_coeff` and" + " `wave_roughness_coeff`.") + + solar_elevation_positive = np.where(solar_elevation < 0, 0, + solar_elevation) + + albedo = color_coeff ** (wave_roughness_coeff * + sind(solar_elevation_positive) + 1) + + if isinstance(solar_elevation, pd.Series): + albedo = pd.Series(albedo, index=solar_elevation.index) + + return albedo diff --git a/pvlib/tests/test_albedo.py b/pvlib/tests/test_albedo.py new file mode 100644 index 0000000000..5e4c35258a --- /dev/null +++ b/pvlib/tests/test_albedo.py @@ -0,0 +1,84 @@ +import numpy as np +import pandas as pd +import pytest +from pvlib import albedo + +from .conftest import assert_series_equal +from numpy.testing import assert_allclose + + +def test_inland_water_dvoracek_default(): + result = albedo.inland_water_dvoracek(solar_elevation=90, + color_coeff=0.13, + wave_roughness_coeff=0.29) + assert_allclose(result, 0.072, 0.001) + + +def test_inland_water_dvoracek_negative_elevation(): + result = albedo.inland_water_dvoracek(solar_elevation=-60, + color_coeff=0.13, + wave_roughness_coeff=0.29) + assert_allclose(result, 0.13, 0.01) + + +def test_inland_water_dvoracek_string_surface_condition(): + result = albedo.inland_water_dvoracek(solar_elevation=90, + surface_condition='clear_water_no_waves') # noqa: E501 + assert_allclose(result, 0.072, 0.001) + + +def test_inland_water_dvoracek_ndarray(): + solar_elevs = np.array([-50, 0, 20, 60, 90]) + color_coeffs = np.array([0.1, 0.1, 0.2, 0.3, 0.4]) + roughness_coeffs = np.array([0.3, 0.3, 0.8, 1.5, 2]) + result = albedo.inland_water_dvoracek(solar_elevation=solar_elevs, + color_coeff=color_coeffs, + wave_roughness_coeff=roughness_coeffs) # noqa: E501 + expected = np.array([0.1, 0.1, 0.12875, 0.06278, 0.064]) + assert_allclose(expected, result, atol=1e-5) + + +def test_inland_water_dvoracek_series(): + times = pd.date_range(start="2015-01-01 00:00", end="2015-01-02 00:00", + freq="6h") + solar_elevs = pd.Series([-50, 0, 20, 60, 90], index=times) + color_coeffs = pd.Series([0.1, 0.1, 0.2, 0.3, 0.4], index=times) + roughness_coeffs = pd.Series([0.1, 0.3, 0.8, 1.5, 2], index=times) + result = albedo.inland_water_dvoracek(solar_elevation=solar_elevs, + color_coeff=color_coeffs, + wave_roughness_coeff=roughness_coeffs) # noqa: E501 + expected = pd.Series([0.1, 0.1, 0.12875, 0.06278, 0.064], index=times) + assert_series_equal(expected, result, atol=1e-5) + + +def test_inland_water_dvoracek_series_mix_with_array(): + times = pd.date_range(start="2015-01-01 00:00", end="2015-01-01 06:00", + freq="6h") + solar_elevs = pd.Series([45, 60], index=times) + color_coeffs = 0.13 + roughness_coeffs = 0.29 + result = albedo.inland_water_dvoracek(solar_elevation=solar_elevs, + color_coeff=color_coeffs, + wave_roughness_coeff=roughness_coeffs) # noqa: E501 + expected = pd.Series([0.08555, 0.07787], index=times) + assert_series_equal(expected, result, atol=1e-5) + + +def test_inland_water_dvoracek_invalid(): + with pytest.raises(ValueError, match='Either a `surface_condition` has to ' + 'be chosen or a combination of `color_coeff` and' + ' `wave_roughness_coeff`.'): # no surface info given + albedo.inland_water_dvoracek(solar_elevation=45) + with pytest.raises(KeyError, match='not_a_surface_type'): # invalid type + albedo.inland_water_dvoracek(solar_elevation=45, + surface_condition='not_a_surface_type') + with pytest.raises(ValueError, match='Either a `surface_condition` has to ' + 'be chosen or a combination of `color_coeff` and' + ' `wave_roughness_coeff`.'): # only one coeff given + albedo.inland_water_dvoracek(solar_elevation=45, + color_coeff=0.13) + with pytest.raises(ValueError, match='Either a `surface_condition` has to ' + 'be chosen or a combination of `color_coeff` and' + ' `wave_roughness_coeff`.'): # only one coeff given + albedo.inland_water_dvoracek(solar_elevation=45, + wave_roughness_coeff=0.29)