From 46f78042e34a857bf9230d97fe1efd8ffb9cd803 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Wed, 10 Jul 2024 17:55:00 +0200 Subject: [PATCH 01/23] wind_speed_at_different_heights --- .../source/reference/pv_modeling/index.rst | 1 + .../reference/pv_modeling/windspeed.rst | 9 + pvlib/__init__.py | 1 + pvlib/tests/test_windspeed.py | 94 ++++++++++ pvlib/windspeed.py | 165 ++++++++++++++++++ 5 files changed, 270 insertions(+) create mode 100644 docs/sphinx/source/reference/pv_modeling/windspeed.rst create mode 100644 pvlib/tests/test_windspeed.py create mode 100644 pvlib/windspeed.py diff --git a/docs/sphinx/source/reference/pv_modeling/index.rst b/docs/sphinx/source/reference/pv_modeling/index.rst index 3941672c8f..d7f8c16167 100644 --- a/docs/sphinx/source/reference/pv_modeling/index.rst +++ b/docs/sphinx/source/reference/pv_modeling/index.rst @@ -25,3 +25,4 @@ wrap the functions listed below. See its documentation for details. system_models parameters other + windspeed diff --git a/docs/sphinx/source/reference/pv_modeling/windspeed.rst b/docs/sphinx/source/reference/pv_modeling/windspeed.rst new file mode 100644 index 0000000000..c23450f97c --- /dev/null +++ b/docs/sphinx/source/reference/pv_modeling/windspeed.rst @@ -0,0 +1,9 @@ +.. currentmodule:: pvlib + +Wind Speed +---------- + +.. autosummary:: + :toctree: ../generated/ + + windspeed.wind_speed_at_height diff --git a/pvlib/__init__.py b/pvlib/__init__.py index b5b07866a4..c2ec67a3ee 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -27,4 +27,5 @@ temperature, tools, tracking, + windspeed, ) diff --git a/pvlib/tests/test_windspeed.py b/pvlib/tests/test_windspeed.py new file mode 100644 index 0000000000..ab94538f9e --- /dev/null +++ b/pvlib/tests/test_windspeed.py @@ -0,0 +1,94 @@ +import numpy as np +import pandas as pd +import pytest +from pvlib import windspeed + +from .conftest import assert_series_equal +from numpy.testing import assert_allclose + + +@pytest.mark.parametrize( + 'wind_speed_measured,height_measured,height_desired,wind_speed_calc', + [ + (10, -2, 5, np.nan), + (-10, 2, 5, np.nan), + (5, 4, 5, 5.067393209486324), + (7, 6, 10, 7.2178684911195905), + (10, 8, 20, 10.565167835216586), + (12, 10, 30, 12.817653329393977),]) +def test_wind_speed_at_height_hellmann(wind_speed_measured, + height_measured, + height_desired, + wind_speed_calc): + result = windspeed.wind_speed_at_height_hellmann( + wind_speed_measured, + height_measured, + height_desired, + surface_type='unstable_air_above_open_water_surface') + assert_allclose(result, wind_speed_calc) + + +@pytest.fixture +def times(): + return pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00", + freq="1h") + + +@pytest.fixture +def wind_speeds_measured(times): + return pd.Series([10, -10, 5, 7, 10, 12], index=times) + + +@pytest.fixture +def heights_measured(times): + return np.array([-2, 2, 4, 6, 8, 10]) + + +@pytest.fixture +def heights_desired(): + return np.array([5, 5, 5, 10, 20, 30]) + + +@pytest.fixture +def wind_speeds_calc(times): + return pd.Series([np.nan, np.nan, 5.067393209486324, 7.2178684911195905, + 10.565167835216586, 12.817653329393977], index=times) + + +def test_wind_speed_at_height_hellmann_ndarray(wind_speeds_measured, + heights_measured, + heights_desired, + wind_speeds_calc): + result = windspeed.wind_speed_at_height_hellmann( + wind_speeds_measured.to_numpy(), + heights_measured, + heights_desired, + surface_type='unstable_air_above_open_water_surface') + assert_allclose(wind_speeds_calc.to_numpy(), result) + + +def test_wind_speed_at_height_hellmann_series(wind_speeds_measured, + heights_measured, + heights_desired, + wind_speeds_calc): + result = windspeed.wind_speed_at_height_hellmann( + wind_speeds_measured, + heights_measured, + heights_desired, + surface_type='unstable_air_above_open_water_surface') + assert_series_equal(wind_speeds_calc, result) + + +def test_wind_speed_at_height_hellmann_invalid(): + with pytest.raises(ValueError, match='Either a `surface_type` has to be ' + 'chosen or an exponent'): + # no exponent or surface_type given + windspeed.wind_speed_at_height_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10) + with pytest.raises(KeyError, match='not_an_exponent'): + # invalid surface_type + windspeed.wind_speed_at_height_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10, + surface_type='not_an_exponent') diff --git a/pvlib/windspeed.py b/pvlib/windspeed.py new file mode 100644 index 0000000000..fc0303107d --- /dev/null +++ b/pvlib/windspeed.py @@ -0,0 +1,165 @@ +"""The ``windspeed`` module contains functions for calculating wind speed.""" + +import numpy as np +import pandas as pd + + +# Values of the Hellmann exponent +HELLMANN_SURFACE_EXPONENTS = { + 'unstable_air_above_open_water_surface': 0.06, + 'neutral_air_above_open_water_surface': 0.10, + 'stable_air_above_open_water_surface': 0.27, + 'unstable_air_above_flat_open_coast': 0.11, + 'neutral_air_above_flat_open_coast': 0.16, + 'stable_air_above_flat_open_coast': 0.40, + 'unstable_air_above_human_inhabited_areas': 0.27, + 'neutral_air_above_human_inhabited_areas': 0.34, + 'stable_air_above_human_inhabited_areas': 0.60, +} + + +def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, + height_desired, exponent=None, + surface_type=None): + r""" + Estimate wind speed for different heights. + + The model is based on the power law equation by Hellmann [1]_, [2]_. + + Parameters + ---------- + wind_speed_measured : numeric + Measured wind speed. [m/s] + + height_measured : float + The height at which the wind speed is measured. [m] + + height_desired : float + The height at which the wind speed will be estimated. [m] + + exponent : float, optional + Exponent based on the surface type. [-] + + surface_type : string, optional + If supplied, overrides ``exponent``. Can be one of the following + (see [1]_): + + * ``'unstable_air_above_open_water_surface'`` + * ``'neutral_air_above_open_water_surface'`` + * ``'stable_air_above_open_water_surface'`` + * ``'unstable_air_above_flat_open_coast'`` + * ``'neutral_air_above_flat_open_coast'`` + * ``'stable_air_above_flat_open_coast'`` + * ``'unstable_air_above_human_inhabited_areas'`` + * ``'neutral_air_above_human_inhabited_areas'`` + * ``'stable_air_above_human_inhabited_areas'`` + + Returns + ------- + wind_speed : numeric + Adjusted wind speed for the desired height. [m/s] + + Raises + ------ + ValueError + If neither of ``exponent`` nor a ``surface_type`` is given. + If both ``exponent`` nor a ``surface_type`` is given. These parameters + are mutually exclusive. + + KeyError + If the specified ``surface_type`` is invalid. + + Notes + ----- + The equation for calculating the wind speed at a height of :math:`h` is + given by the following power law equation [1]_, [2]_: + + .. math:: + :label: wind speed + + \windspeed_h = windspeed_{href} \cdot (\frac{h}{h_{ref}})^a + + where :math:`h` [m] is the height at which we would like to calculate the + wind speed, :math:`h_{ref}` [m] is the reference height at which the wind + speed is known, and :math:`windspeed_h` [m/s] and :math:`windspeed_{href}` + [m/s] are the corresponding wind speeds at these heights. :math:`a` is a + value that depends on the surface type. Some values found in the literature + [1]_ for :math:`a` are the following: + + .. table:: Values for the Hellmann-exponent + + +-----------+--------------------+------------------+------------------+ + | Stability | Open water surface | Flat, open coast | Cities, villages | + +===========+====================+==================+==================+ + | Unstable | 0.06 | 0.10 | 0.27 | + +-----------+--------------------+------------------+------------------+ + | Neutral | 0.11 | 0.16 | 0.40 | + +-----------+--------------------+------------------+------------------+ + | Stable | 0.27 | 0.34 | 0.60 | + +-----------+--------------------+------------------+------------------+ + + In a report by SANDIA [3]_, this equation was experimentally tested for a + height of 30 ft (9.144 m) and the following coefficients were recommended: + :math:`h_{ref} = 9.144` [m], :math:`a = 0.219` [-], and + :math:`windspeed_{href}` is the wind speed at a height of 9.144 [m]. + + It should be noted that the equation retuns a value of NaN if the + calculated wind speed is negative or a complex number. + + Warning + ------- + Module temperature functions often require wind speeds at a heigt of 10 m + and not the wind speed at the module height. + + For example, the following temperature functions require the input wind + speed to be 10 m: :py:func:`~pvlib.temperature.sapm_cell`, + :py:func:`~pvlib.temperature.sapm_module`, and + :py:func:`~pvlib.temperature.generic_linear`, whereas the + :py:func:`~pvlib.temperature.fuentes` model requires wind speed at 9.144 m. + + Additionally, the heat loss coefficients of some models have been developed + for wind speed measurements at 10 m (e.g., + :py:func:`~pvlib.temperature.pvsyst_cell`, + :py:func:`~pvlib.temperature.faiman`, and + :py:func:`~pvlib.temperature.faiman_rad`). + + References + ---------- + .. [1] Kaltschmitt M., Streicher W., Wiese A. (2007). "Renewable Energy: + Technology, Economics and Environment." Springer, + :doi:`10.1007/3-540-70949-5`. + + .. [2] Hellmann G. (1915). "Über die Bewegung der Luft in den untersten + Schichten der Atmosphäre." Meteorologische Zeitschrift, 32 + + .. [3] Menicucci D.F., Hall I.J. (1985). "Estimating wind speed as a + function of height above ground: An analysis of data obtained at the + southwest residential experiment station, Las Cruses, New Mexico." + SAND84-2530, Sandia National Laboratories. + `link `_ + """ + if surface_type is not None and exponent is None: + # use the Hellmann exponent from dictionary + exponent = HELLMANN_SURFACE_EXPONENTS[surface_type] + elif surface_type is None and exponent is not None: + # use the provided exponent + pass + else: + raise ValueError( + "Either a `surface_type` has to be chosen or an exponent") + + wind_speed = wind_speed_measured * ( + (height_desired / height_measured) ** exponent) + + # if the provided height is negative the calculated wind speed is complex + # so a NaN value is returned + if isinstance(wind_speed, complex): + wind_speed = np.nan + + # if wind speed is nagative return NaN + wind_speed = np.where(wind_speed < 0, np.nan, wind_speed) + + if isinstance(wind_speed_measured, pd.Series): + wind_speed = pd.Series(wind_speed, index=wind_speed_measured.index) + + return wind_speed From a15b63e7c2cbc0100051a6e36e49ec5a8b9f8167 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Wed, 10 Jul 2024 18:02:18 +0200 Subject: [PATCH 02/23] minor fixed --- pvlib/tests/test_windspeed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_windspeed.py b/pvlib/tests/test_windspeed.py index ab94538f9e..2faba7fda3 100644 --- a/pvlib/tests/test_windspeed.py +++ b/pvlib/tests/test_windspeed.py @@ -15,7 +15,7 @@ (5, 4, 5, 5.067393209486324), (7, 6, 10, 7.2178684911195905), (10, 8, 20, 10.565167835216586), - (12, 10, 30, 12.817653329393977),]) + (12, 10, 30, 12.817653329393977)]) def test_wind_speed_at_height_hellmann(wind_speed_measured, height_measured, height_desired, From b8450d0b9893335db103897cd459f9d38b17350c Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Wed, 10 Jul 2024 18:15:12 +0200 Subject: [PATCH 03/23] fixed toc and added test --- .../source/reference/pv_modeling/windspeed.rst | 2 +- pvlib/tests/test_windspeed.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/source/reference/pv_modeling/windspeed.rst b/docs/sphinx/source/reference/pv_modeling/windspeed.rst index c23450f97c..0ed2f41771 100644 --- a/docs/sphinx/source/reference/pv_modeling/windspeed.rst +++ b/docs/sphinx/source/reference/pv_modeling/windspeed.rst @@ -6,4 +6,4 @@ Wind Speed .. autosummary:: :toctree: ../generated/ - windspeed.wind_speed_at_height + windspeed.wind_speed_at_height_hellmann diff --git a/pvlib/tests/test_windspeed.py b/pvlib/tests/test_windspeed.py index 2faba7fda3..38a74ab434 100644 --- a/pvlib/tests/test_windspeed.py +++ b/pvlib/tests/test_windspeed.py @@ -59,12 +59,21 @@ def test_wind_speed_at_height_hellmann_ndarray(wind_speeds_measured, heights_measured, heights_desired, wind_speeds_calc): - result = windspeed.wind_speed_at_height_hellmann( + # test wind speed estimation by passing in surface_type + result_surface = windspeed.wind_speed_at_height_hellmann( wind_speeds_measured.to_numpy(), heights_measured, heights_desired, surface_type='unstable_air_above_open_water_surface') - assert_allclose(wind_speeds_calc.to_numpy(), result) + assert_allclose(wind_speeds_calc.to_numpy(), result_surface) + # test wind speed estimation by passing in the exponent corresponding + # to the surface_type above + result_exponent = windspeed.wind_speed_at_height_hellmann( + wind_speeds_measured.to_numpy(), + heights_measured, + heights_desired, + exponent=0.06) + assert_allclose(wind_speeds_calc.to_numpy(), result_exponent) def test_wind_speed_at_height_hellmann_series(wind_speeds_measured, From 91c1de31d8e58c65b0087857d7697164479855d7 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Wed, 10 Jul 2024 18:26:04 +0200 Subject: [PATCH 04/23] edit math mode --- pvlib/windspeed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/windspeed.py b/pvlib/windspeed.py index fc0303107d..884b16d1f8 100644 --- a/pvlib/windspeed.py +++ b/pvlib/windspeed.py @@ -77,7 +77,7 @@ def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, .. math:: :label: wind speed - \windspeed_h = windspeed_{href} \cdot (\frac{h}{h_{ref}})^a + windspeed_h = windspeed_{href} \cdot \left( \frac{h}{h_{ref}} \right)^a where :math:`h` [m] is the height at which we would like to calculate the wind speed, :math:`h_{ref}` [m] is the reference height at which the wind From b611487acdeebe3641ed3ed386afcca52c177aa2 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Wed, 10 Jul 2024 18:28:50 +0200 Subject: [PATCH 05/23] changed link for reference --- pvlib/windspeed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/windspeed.py b/pvlib/windspeed.py index 884b16d1f8..632220fe30 100644 --- a/pvlib/windspeed.py +++ b/pvlib/windspeed.py @@ -136,7 +136,7 @@ def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, function of height above ground: An analysis of data obtained at the southwest residential experiment station, Las Cruses, New Mexico." SAND84-2530, Sandia National Laboratories. - `link `_ + `source `_ # noqa:E501 """ if surface_type is not None and exponent is None: # use the Hellmann exponent from dictionary From 663865d358051fd94218c2a3677318ebe0dff90b Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios <88548539+IoannisSifnaios@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:48:58 +0300 Subject: [PATCH 06/23] Apply suggestions from code review Co-authored-by: RDaxini <143435106+RDaxini@users.noreply.github.com> Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com> --- pvlib/windspeed.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pvlib/windspeed.py b/pvlib/windspeed.py index 632220fe30..3a13f8141e 100644 --- a/pvlib/windspeed.py +++ b/pvlib/windspeed.py @@ -72,7 +72,7 @@ def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, Notes ----- The equation for calculating the wind speed at a height of :math:`h` is - given by the following power law equation [1]_, [2]_: + given by the following power law equation [1]_[2]_: .. math:: :label: wind speed @@ -98,17 +98,17 @@ def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, | Stable | 0.27 | 0.34 | 0.60 | +-----------+--------------------+------------------+------------------+ - In a report by SANDIA [3]_, this equation was experimentally tested for a + In a report by Sandia [3]_, this equation was experimentally tested for a height of 30 ft (9.144 m) and the following coefficients were recommended: :math:`h_{ref} = 9.144` [m], :math:`a = 0.219` [-], and :math:`windspeed_{href}` is the wind speed at a height of 9.144 [m]. - It should be noted that the equation retuns a value of NaN if the + It should be noted that the equation returns a value of NaN if the calculated wind speed is negative or a complex number. Warning ------- - Module temperature functions often require wind speeds at a heigt of 10 m + Module temperature functions often require wind speeds at a height of 10 m and not the wind speed at the module height. For example, the following temperature functions require the input wind @@ -136,8 +136,8 @@ def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, function of height above ground: An analysis of data obtained at the southwest residential experiment station, Las Cruses, New Mexico." SAND84-2530, Sandia National Laboratories. - `source `_ # noqa:E501 - """ + Accessed at https://web.archive.org/web/20230418202422/https://www2.jpl.nasa.gov/adv_tech/photovol/2016CTR/SNL%20-%20Est%20Wind%20Speed%20vs%20Height_1985.pdf + """ # noqa:E501 if surface_type is not None and exponent is None: # use the Hellmann exponent from dictionary exponent = HELLMANN_SURFACE_EXPONENTS[surface_type] @@ -146,7 +146,7 @@ def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, pass else: raise ValueError( - "Either a `surface_type` has to be chosen or an exponent") + "Either a 'surface_type' or an 'exponent' parameter must be given") wind_speed = wind_speed_measured * ( (height_desired / height_measured) ** exponent) @@ -156,7 +156,7 @@ def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, if isinstance(wind_speed, complex): wind_speed = np.nan - # if wind speed is nagative return NaN + # if wind speed is negative return NaN wind_speed = np.where(wind_speed < 0, np.nan, wind_speed) if isinstance(wind_speed_measured, pd.Series): From f165f4d3afb274a932415761ca0fb78d357e6a70 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios <88548539+IoannisSifnaios@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:29:52 +0300 Subject: [PATCH 07/23] Update pvlib/tests/test_windspeed.py Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com> --- pvlib/tests/test_windspeed.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pvlib/tests/test_windspeed.py b/pvlib/tests/test_windspeed.py index 38a74ab434..cc15a530f1 100644 --- a/pvlib/tests/test_windspeed.py +++ b/pvlib/tests/test_windspeed.py @@ -95,6 +95,14 @@ def test_wind_speed_at_height_hellmann_invalid(): windspeed.wind_speed_at_height_hellmann(wind_speed_measured=10, height_measured=5, height_desired=10) + with pytest.raises(ValueError, match='Either a `surface_type` has to be ' + 'chosen or an exponent'): + # no exponent or surface_type given + windspeed.wind_speed_at_height_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10, + exponent=1.2, + surface_type="surf") with pytest.raises(KeyError, match='not_an_exponent'): # invalid surface_type windspeed.wind_speed_at_height_hellmann(wind_speed_measured=10, From 0cfb5390d2a945e00328ec834fb9ef492484c7b7 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Fri, 19 Jul 2024 15:42:00 +0300 Subject: [PATCH 08/23] changed function name --- pvlib/tests/test_windspeed.py | 58 +++++++++++++++++------------------ pvlib/windspeed.py | 6 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/pvlib/tests/test_windspeed.py b/pvlib/tests/test_windspeed.py index cc15a530f1..35b06bb8c2 100644 --- a/pvlib/tests/test_windspeed.py +++ b/pvlib/tests/test_windspeed.py @@ -16,11 +16,11 @@ (7, 6, 10, 7.2178684911195905), (10, 8, 20, 10.565167835216586), (12, 10, 30, 12.817653329393977)]) -def test_wind_speed_at_height_hellmann(wind_speed_measured, - height_measured, - height_desired, - wind_speed_calc): - result = windspeed.wind_speed_at_height_hellmann( +def test_windspeed_hellmann(wind_speed_measured, + height_measured, + height_desired, + wind_speed_calc): + result = windspeed.windspeed_hellmann( wind_speed_measured, height_measured, height_desired, @@ -55,12 +55,12 @@ def wind_speeds_calc(times): 10.565167835216586, 12.817653329393977], index=times) -def test_wind_speed_at_height_hellmann_ndarray(wind_speeds_measured, - heights_measured, - heights_desired, - wind_speeds_calc): +def test_windspeed_hellmann_ndarray(wind_speeds_measured, + heights_measured, + heights_desired, + wind_speeds_calc): # test wind speed estimation by passing in surface_type - result_surface = windspeed.wind_speed_at_height_hellmann( + result_surface = windspeed.windspeed_hellmann( wind_speeds_measured.to_numpy(), heights_measured, heights_desired, @@ -68,7 +68,7 @@ def test_wind_speed_at_height_hellmann_ndarray(wind_speeds_measured, assert_allclose(wind_speeds_calc.to_numpy(), result_surface) # test wind speed estimation by passing in the exponent corresponding # to the surface_type above - result_exponent = windspeed.wind_speed_at_height_hellmann( + result_exponent = windspeed.windspeed_hellmann( wind_speeds_measured.to_numpy(), heights_measured, heights_desired, @@ -76,11 +76,11 @@ def test_wind_speed_at_height_hellmann_ndarray(wind_speeds_measured, assert_allclose(wind_speeds_calc.to_numpy(), result_exponent) -def test_wind_speed_at_height_hellmann_series(wind_speeds_measured, - heights_measured, - heights_desired, - wind_speeds_calc): - result = windspeed.wind_speed_at_height_hellmann( +def test_windspeed_hellmann_series(wind_speeds_measured, + heights_measured, + heights_desired, + wind_speeds_calc): + result = windspeed.windspeed_hellmann( wind_speeds_measured, heights_measured, heights_desired, @@ -88,24 +88,24 @@ def test_wind_speed_at_height_hellmann_series(wind_speeds_measured, assert_series_equal(wind_speeds_calc, result) -def test_wind_speed_at_height_hellmann_invalid(): +def test_windspeed_hellmann_invalid(): with pytest.raises(ValueError, match='Either a `surface_type` has to be ' 'chosen or an exponent'): # no exponent or surface_type given - windspeed.wind_speed_at_height_hellmann(wind_speed_measured=10, - height_measured=5, - height_desired=10) + windspeed.windspeed_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10) with pytest.raises(ValueError, match='Either a `surface_type` has to be ' 'chosen or an exponent'): # no exponent or surface_type given - windspeed.wind_speed_at_height_hellmann(wind_speed_measured=10, - height_measured=5, - height_desired=10, - exponent=1.2, - surface_type="surf") + windspeed.windspeed_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10, + exponent=1.2, + surface_type="surf") with pytest.raises(KeyError, match='not_an_exponent'): # invalid surface_type - windspeed.wind_speed_at_height_hellmann(wind_speed_measured=10, - height_measured=5, - height_desired=10, - surface_type='not_an_exponent') + windspeed.windspeed_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10, + surface_type='not_an_exponent') diff --git a/pvlib/windspeed.py b/pvlib/windspeed.py index 3a13f8141e..f0cb503611 100644 --- a/pvlib/windspeed.py +++ b/pvlib/windspeed.py @@ -18,9 +18,9 @@ } -def wind_speed_at_height_hellmann(wind_speed_measured, height_measured, - height_desired, exponent=None, - surface_type=None): +def windspeed_hellmann(wind_speed_measured, height_measured, + height_desired, exponent=None, + surface_type=None): r""" Estimate wind speed for different heights. From 1bd9308ae24534c85f763c213b8e69e111829047 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios <88548539+IoannisSifnaios@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:23:30 +0300 Subject: [PATCH 09/23] Update pvlib/windspeed.py Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com> --- pvlib/windspeed.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pvlib/windspeed.py b/pvlib/windspeed.py index f0cb503611..808efdeae2 100644 --- a/pvlib/windspeed.py +++ b/pvlib/windspeed.py @@ -88,15 +88,15 @@ def windspeed_hellmann(wind_speed_measured, height_measured, .. table:: Values for the Hellmann-exponent - +-----------+--------------------+------------------+------------------+ - | Stability | Open water surface | Flat, open coast | Cities, villages | - +===========+====================+==================+==================+ - | Unstable | 0.06 | 0.10 | 0.27 | - +-----------+--------------------+------------------+------------------+ - | Neutral | 0.11 | 0.16 | 0.40 | - +-----------+--------------------+------------------+------------------+ - | Stable | 0.27 | 0.34 | 0.60 | - +-----------+--------------------+------------------+------------------+ + +-----------+--------------------+------------------+------------------+ + | Stability | Open water surface | Flat, open coast | Cities, villages | + +===========+====================+==================+==================+ + | Unstable | 0.06 | 0.10 | 0.27 | + +-----------+--------------------+------------------+------------------+ + | Neutral | 0.11 | 0.16 | 0.40 | + +-----------+--------------------+------------------+------------------+ + | Stable | 0.27 | 0.34 | 0.60 | + +-----------+--------------------+------------------+------------------+ In a report by Sandia [3]_, this equation was experimentally tested for a height of 30 ft (9.144 m) and the following coefficients were recommended: From 9fef61115d1437c9cc2d2095944d28872eba627a Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 22 Jul 2024 18:41:15 +0200 Subject: [PATCH 10/23] change measured height to reference --- pvlib/windspeed.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pvlib/windspeed.py b/pvlib/windspeed.py index f0cb503611..9c84a8c0e1 100644 --- a/pvlib/windspeed.py +++ b/pvlib/windspeed.py @@ -18,7 +18,7 @@ } -def windspeed_hellmann(wind_speed_measured, height_measured, +def windspeed_hellmann(wind_speed_reference, height_reference, height_desired, exponent=None, surface_type=None): r""" @@ -28,10 +28,10 @@ def windspeed_hellmann(wind_speed_measured, height_measured, Parameters ---------- - wind_speed_measured : numeric + wind_speed_reference : numeric Measured wind speed. [m/s] - height_measured : float + height_reference : float The height at which the wind speed is measured. [m] height_desired : float @@ -77,11 +77,11 @@ def windspeed_hellmann(wind_speed_measured, height_measured, .. math:: :label: wind speed - windspeed_h = windspeed_{href} \cdot \left( \frac{h}{h_{ref}} \right)^a + U_{w,h} = U_{w,ref} \cdot \left( \frac{h}{h_{ref}} \right)^a where :math:`h` [m] is the height at which we would like to calculate the wind speed, :math:`h_{ref}` [m] is the reference height at which the wind - speed is known, and :math:`windspeed_h` [m/s] and :math:`windspeed_{href}` + speed is known, and :math:`U_{w,h}` [m/s] and :math:`U_{w,ref}` [m/s] are the corresponding wind speeds at these heights. :math:`a` is a value that depends on the surface type. Some values found in the literature [1]_ for :math:`a` are the following: @@ -136,7 +136,8 @@ def windspeed_hellmann(wind_speed_measured, height_measured, function of height above ground: An analysis of data obtained at the southwest residential experiment station, Las Cruses, New Mexico." SAND84-2530, Sandia National Laboratories. - Accessed at https://web.archive.org/web/20230418202422/https://www2.jpl.nasa.gov/adv_tech/photovol/2016CTR/SNL%20-%20Est%20Wind%20Speed%20vs%20Height_1985.pdf + Accessed at + https://web.archive.org/web/20230418202422/https://www2.jpl.nasa.gov/adv_tech/photovol/2016CTR/SNL%20-%20Est%20Wind%20Speed%20vs%20Height_1985.pdf """ # noqa:E501 if surface_type is not None and exponent is None: # use the Hellmann exponent from dictionary @@ -148,8 +149,8 @@ def windspeed_hellmann(wind_speed_measured, height_measured, raise ValueError( "Either a 'surface_type' or an 'exponent' parameter must be given") - wind_speed = wind_speed_measured * ( - (height_desired / height_measured) ** exponent) + wind_speed = wind_speed_reference * ( + (height_desired / height_reference) ** exponent) # if the provided height is negative the calculated wind speed is complex # so a NaN value is returned @@ -159,7 +160,7 @@ def windspeed_hellmann(wind_speed_measured, height_measured, # if wind speed is negative return NaN wind_speed = np.where(wind_speed < 0, np.nan, wind_speed) - if isinstance(wind_speed_measured, pd.Series): - wind_speed = pd.Series(wind_speed, index=wind_speed_measured.index) + if isinstance(wind_speed_reference, pd.Series): + wind_speed = pd.Series(wind_speed, index=wind_speed_reference.index) return wind_speed From 0a891a2b9a533334187729ad59e09ef6eae728ec Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 22 Jul 2024 18:57:53 +0200 Subject: [PATCH 11/23] moved wind speed to atmosphere --- .../source/reference/airmass_atmospheric.rst | 1 + .../source/reference/pv_modeling/index.rst | 1 - .../reference/pv_modeling/windspeed.rst | 9 - pvlib/__init__.py | 1 - pvlib/atmosphere.py | 165 ++++++++++++++++- pvlib/tests/test_atmosphere.py | 104 +++++++++++ pvlib/tests/test_windspeed.py | 111 ------------ pvlib/windspeed.py | 166 ------------------ 8 files changed, 269 insertions(+), 289 deletions(-) delete mode 100644 docs/sphinx/source/reference/pv_modeling/windspeed.rst delete mode 100644 pvlib/tests/test_windspeed.py delete mode 100644 pvlib/windspeed.py diff --git a/docs/sphinx/source/reference/airmass_atmospheric.rst b/docs/sphinx/source/reference/airmass_atmospheric.rst index fbd33a5f28..945cdccc3a 100644 --- a/docs/sphinx/source/reference/airmass_atmospheric.rst +++ b/docs/sphinx/source/reference/airmass_atmospheric.rst @@ -17,3 +17,4 @@ Airmass and atmospheric models atmosphere.kasten96_lt atmosphere.angstrom_aod_at_lambda atmosphere.angstrom_alpha + atmosphere.windspeed_hellmann diff --git a/docs/sphinx/source/reference/pv_modeling/index.rst b/docs/sphinx/source/reference/pv_modeling/index.rst index d7f8c16167..3941672c8f 100644 --- a/docs/sphinx/source/reference/pv_modeling/index.rst +++ b/docs/sphinx/source/reference/pv_modeling/index.rst @@ -25,4 +25,3 @@ wrap the functions listed below. See its documentation for details. system_models parameters other - windspeed diff --git a/docs/sphinx/source/reference/pv_modeling/windspeed.rst b/docs/sphinx/source/reference/pv_modeling/windspeed.rst deleted file mode 100644 index 0ed2f41771..0000000000 --- a/docs/sphinx/source/reference/pv_modeling/windspeed.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. currentmodule:: pvlib - -Wind Speed ----------- - -.. autosummary:: - :toctree: ../generated/ - - windspeed.wind_speed_at_height_hellmann diff --git a/pvlib/__init__.py b/pvlib/__init__.py index c2ec67a3ee..b5b07866a4 100644 --- a/pvlib/__init__.py +++ b/pvlib/__init__.py @@ -27,5 +27,4 @@ temperature, tools, tracking, - windspeed, ) diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index 08e40b9fc2..f746e0ff90 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -1,6 +1,7 @@ """ The ``atmosphere`` module contains methods to calculate relative and -absolute airmass and to determine pressure from altitude or vice versa. +absolute airmass, determine pressure from altitude or vice versa, and wind +speed at different heights. """ import numpy as np @@ -533,3 +534,165 @@ def angstrom_alpha(aod1, lambda1, aod2, lambda2): pvlib.atmosphere.angstrom_aod_at_lambda """ return - np.log(aod1 / aod2) / np.log(lambda1 / lambda2) + + +# Values of the Hellmann exponent +HELLMANN_SURFACE_EXPONENTS = { + 'unstable_air_above_open_water_surface': 0.06, + 'neutral_air_above_open_water_surface': 0.10, + 'stable_air_above_open_water_surface': 0.27, + 'unstable_air_above_flat_open_coast': 0.11, + 'neutral_air_above_flat_open_coast': 0.16, + 'stable_air_above_flat_open_coast': 0.40, + 'unstable_air_above_human_inhabited_areas': 0.27, + 'neutral_air_above_human_inhabited_areas': 0.34, + 'stable_air_above_human_inhabited_areas': 0.60, +} + + +def windspeed_hellmann(wind_speed_reference, height_reference, + height_desired, exponent=None, + surface_type=None): + r""" + Estimate wind speed for different heights. + + The model is based on the power law equation by Hellmann [1]_[2]_. + + Parameters + ---------- + wind_speed_reference : numeric + Measured wind speed. [m/s] + + height_reference : float + The height at which the wind speed is measured. [m] + + height_desired : float + The height at which the wind speed will be estimated. [m] + + exponent : float, optional + Exponent based on the surface type. [-] + + surface_type : string, optional + If supplied, overrides ``exponent``. Can be one of the following + (see [1]_): + + * ``'unstable_air_above_open_water_surface'`` + * ``'neutral_air_above_open_water_surface'`` + * ``'stable_air_above_open_water_surface'`` + * ``'unstable_air_above_flat_open_coast'`` + * ``'neutral_air_above_flat_open_coast'`` + * ``'stable_air_above_flat_open_coast'`` + * ``'unstable_air_above_human_inhabited_areas'`` + * ``'neutral_air_above_human_inhabited_areas'`` + * ``'stable_air_above_human_inhabited_areas'`` + + Returns + ------- + wind_speed : numeric + Adjusted wind speed for the desired height. [m/s] + + Raises + ------ + ValueError + If neither of ``exponent`` nor a ``surface_type`` is given. + If both ``exponent`` nor a ``surface_type`` is given. These parameters + are mutually exclusive. + + KeyError + If the specified ``surface_type`` is invalid. + + Notes + ----- + The equation for calculating the wind speed at a height of :math:`h` is + given by the following power law equation [1]_[2]_: + + .. math:: + :label: wind speed + + U_{w,h} = U_{w,ref} \cdot \left( \frac{h}{h_{ref}} \right)^a + + where :math:`h` [m] is the height at which we would like to calculate the + wind speed, :math:`h_{ref}` [m] is the reference height at which the wind + speed is known, and :math:`U_{w,h}` [m/s] and :math:`U_{w,ref}` + [m/s] are the corresponding wind speeds at these heights. :math:`a` is a + value that depends on the surface type. Some values found in the literature + [1]_ for :math:`a` are the following: + + .. table:: Values for the Hellmann-exponent + + +-----------+--------------------+------------------+------------------+ + | Stability | Open water surface | Flat, open coast | Cities, villages | + +===========+====================+==================+==================+ + | Unstable | 0.06 | 0.10 | 0.27 | + +-----------+--------------------+------------------+------------------+ + | Neutral | 0.11 | 0.16 | 0.40 | + +-----------+--------------------+------------------+------------------+ + | Stable | 0.27 | 0.34 | 0.60 | + +-----------+--------------------+------------------+------------------+ + + In a report by Sandia [3]_, this equation was experimentally tested for a + height of 30 ft (9.144 m) and the following coefficients were recommended: + :math:`h_{ref} = 9.144` [m], :math:`a = 0.219` [-], and + :math:`windspeed_{href}` is the wind speed at a height of 9.144 [m]. + + It should be noted that the equation returns a value of NaN if the + calculated wind speed is negative or a complex number. + + Warning + ------- + Module temperature functions often require wind speeds at a height of 10 m + and not the wind speed at the module height. + + For example, the following temperature functions require the input wind + speed to be 10 m: :py:func:`~pvlib.temperature.sapm_cell`, + :py:func:`~pvlib.temperature.sapm_module`, and + :py:func:`~pvlib.temperature.generic_linear`, whereas the + :py:func:`~pvlib.temperature.fuentes` model requires wind speed at 9.144 m. + + Additionally, the heat loss coefficients of some models have been developed + for wind speed measurements at 10 m (e.g., + :py:func:`~pvlib.temperature.pvsyst_cell`, + :py:func:`~pvlib.temperature.faiman`, and + :py:func:`~pvlib.temperature.faiman_rad`). + + References + ---------- + .. [1] Kaltschmitt M., Streicher W., Wiese A. (2007). "Renewable Energy: + Technology, Economics and Environment." Springer, + :doi:`10.1007/3-540-70949-5`. + + .. [2] Hellmann G. (1915). "Über die Bewegung der Luft in den untersten + Schichten der Atmosphäre." Meteorologische Zeitschrift, 32 + + .. [3] Menicucci D.F., Hall I.J. (1985). "Estimating wind speed as a + function of height above ground: An analysis of data obtained at the + southwest residential experiment station, Las Cruses, New Mexico." + SAND84-2530, Sandia National Laboratories. + Accessed at + https://web.archive.org/web/20230418202422/https://www2.jpl.nasa.gov/adv_tech/photovol/2016CTR/SNL%20-%20Est%20Wind%20Speed%20vs%20Height_1985.pdf + """ # noqa:E501 + if surface_type is not None and exponent is None: + # use the Hellmann exponent from dictionary + exponent = HELLMANN_SURFACE_EXPONENTS[surface_type] + elif surface_type is None and exponent is not None: + # use the provided exponent + pass + else: + raise ValueError( + "Either a 'surface_type' or an 'exponent' parameter must be given") + + wind_speed = wind_speed_reference * ( + (height_desired / height_reference) ** exponent) + + # if the provided height is negative the calculated wind speed is complex + # so a NaN value is returned + if isinstance(wind_speed, complex): + wind_speed = np.nan + + # if wind speed is negative return NaN + wind_speed = np.where(wind_speed < 0, np.nan, wind_speed) + + if isinstance(wind_speed_reference, pd.Series): + wind_speed = pd.Series(wind_speed, index=wind_speed_reference.index) + + return wind_speed diff --git a/pvlib/tests/test_atmosphere.py b/pvlib/tests/test_atmosphere.py index 46db622ee5..8fb4692755 100644 --- a/pvlib/tests/test_atmosphere.py +++ b/pvlib/tests/test_atmosphere.py @@ -131,3 +131,107 @@ def test_bird_hulstrom80_aod_bb(): aod380, aod500 = 0.22072480948195175, 0.1614279181106312 bird_hulstrom = atmosphere.bird_hulstrom80_aod_bb(aod380, aod500) assert np.isclose(0.11738229553812768, bird_hulstrom) + + +@pytest.mark.parametrize( + 'wind_speed_measured,height_measured,height_desired,wind_speed_calc', + [ + (10, -2, 5, np.nan), + (-10, 2, 5, np.nan), + (5, 4, 5, 5.067393209486324), + (7, 6, 10, 7.2178684911195905), + (10, 8, 20, 10.565167835216586), + (12, 10, 30, 12.817653329393977)]) +def test_windspeed_hellmann(wind_speed_measured, + height_measured, + height_desired, + wind_speed_calc): + result = atmosphere.windspeed_hellmann( + wind_speed_measured, + height_measured, + height_desired, + surface_type='unstable_air_above_open_water_surface') + assert_allclose(result, wind_speed_calc) + + +@pytest.fixture +def times(): + return pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00", + freq="1h") + + +@pytest.fixture +def wind_speeds_measured(times): + return pd.Series([10, -10, 5, 7, 10, 12], index=times) + + +@pytest.fixture +def heights_measured(times): + return np.array([-2, 2, 4, 6, 8, 10]) + + +@pytest.fixture +def heights_desired(): + return np.array([5, 5, 5, 10, 20, 30]) + + +@pytest.fixture +def wind_speeds_calc(times): + return pd.Series([np.nan, np.nan, 5.067393209486324, 7.2178684911195905, + 10.565167835216586, 12.817653329393977], index=times) + + +def test_windspeed_hellmann_ndarray(wind_speeds_measured, + heights_measured, + heights_desired, + wind_speeds_calc): + # test wind speed estimation by passing in surface_type + result_surface = atmosphere.windspeed_hellmann( + wind_speeds_measured.to_numpy(), + heights_measured, + heights_desired, + surface_type='unstable_air_above_open_water_surface') + assert_allclose(wind_speeds_calc.to_numpy(), result_surface) + # test wind speed estimation by passing in the exponent corresponding + # to the surface_type above + result_exponent = atmosphere.windspeed_hellmann( + wind_speeds_measured.to_numpy(), + heights_measured, + heights_desired, + exponent=0.06) + assert_allclose(wind_speeds_calc.to_numpy(), result_exponent) + + +def test_windspeed_hellmann_series(wind_speeds_measured, + heights_measured, + heights_desired, + wind_speeds_calc): + result = atmosphere.windspeed_hellmann( + wind_speeds_measured, + heights_measured, + heights_desired, + surface_type='unstable_air_above_open_water_surface') + assert_series_equal(wind_speeds_calc, result) + + +def test_windspeed_hellmann_invalid(): + with pytest.raises(ValueError, match='Either a `surface_type` has to be ' + 'chosen or an exponent'): + # no exponent or surface_type given + atmosphere.windspeed_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10) + with pytest.raises(ValueError, match='Either a `surface_type` has to be ' + 'chosen or an exponent'): + # no exponent or surface_type given + atmosphere.windspeed_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10, + exponent=1.2, + surface_type="surf") + with pytest.raises(KeyError, match='not_an_exponent'): + # invalid surface_type + atmosphere.windspeed_hellmann(wind_speed_measured=10, + height_measured=5, + height_desired=10, + surface_type='not_an_exponent') diff --git a/pvlib/tests/test_windspeed.py b/pvlib/tests/test_windspeed.py deleted file mode 100644 index 35b06bb8c2..0000000000 --- a/pvlib/tests/test_windspeed.py +++ /dev/null @@ -1,111 +0,0 @@ -import numpy as np -import pandas as pd -import pytest -from pvlib import windspeed - -from .conftest import assert_series_equal -from numpy.testing import assert_allclose - - -@pytest.mark.parametrize( - 'wind_speed_measured,height_measured,height_desired,wind_speed_calc', - [ - (10, -2, 5, np.nan), - (-10, 2, 5, np.nan), - (5, 4, 5, 5.067393209486324), - (7, 6, 10, 7.2178684911195905), - (10, 8, 20, 10.565167835216586), - (12, 10, 30, 12.817653329393977)]) -def test_windspeed_hellmann(wind_speed_measured, - height_measured, - height_desired, - wind_speed_calc): - result = windspeed.windspeed_hellmann( - wind_speed_measured, - height_measured, - height_desired, - surface_type='unstable_air_above_open_water_surface') - assert_allclose(result, wind_speed_calc) - - -@pytest.fixture -def times(): - return pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00", - freq="1h") - - -@pytest.fixture -def wind_speeds_measured(times): - return pd.Series([10, -10, 5, 7, 10, 12], index=times) - - -@pytest.fixture -def heights_measured(times): - return np.array([-2, 2, 4, 6, 8, 10]) - - -@pytest.fixture -def heights_desired(): - return np.array([5, 5, 5, 10, 20, 30]) - - -@pytest.fixture -def wind_speeds_calc(times): - return pd.Series([np.nan, np.nan, 5.067393209486324, 7.2178684911195905, - 10.565167835216586, 12.817653329393977], index=times) - - -def test_windspeed_hellmann_ndarray(wind_speeds_measured, - heights_measured, - heights_desired, - wind_speeds_calc): - # test wind speed estimation by passing in surface_type - result_surface = windspeed.windspeed_hellmann( - wind_speeds_measured.to_numpy(), - heights_measured, - heights_desired, - surface_type='unstable_air_above_open_water_surface') - assert_allclose(wind_speeds_calc.to_numpy(), result_surface) - # test wind speed estimation by passing in the exponent corresponding - # to the surface_type above - result_exponent = windspeed.windspeed_hellmann( - wind_speeds_measured.to_numpy(), - heights_measured, - heights_desired, - exponent=0.06) - assert_allclose(wind_speeds_calc.to_numpy(), result_exponent) - - -def test_windspeed_hellmann_series(wind_speeds_measured, - heights_measured, - heights_desired, - wind_speeds_calc): - result = windspeed.windspeed_hellmann( - wind_speeds_measured, - heights_measured, - heights_desired, - surface_type='unstable_air_above_open_water_surface') - assert_series_equal(wind_speeds_calc, result) - - -def test_windspeed_hellmann_invalid(): - with pytest.raises(ValueError, match='Either a `surface_type` has to be ' - 'chosen or an exponent'): - # no exponent or surface_type given - windspeed.windspeed_hellmann(wind_speed_measured=10, - height_measured=5, - height_desired=10) - with pytest.raises(ValueError, match='Either a `surface_type` has to be ' - 'chosen or an exponent'): - # no exponent or surface_type given - windspeed.windspeed_hellmann(wind_speed_measured=10, - height_measured=5, - height_desired=10, - exponent=1.2, - surface_type="surf") - with pytest.raises(KeyError, match='not_an_exponent'): - # invalid surface_type - windspeed.windspeed_hellmann(wind_speed_measured=10, - height_measured=5, - height_desired=10, - surface_type='not_an_exponent') diff --git a/pvlib/windspeed.py b/pvlib/windspeed.py deleted file mode 100644 index d326f91337..0000000000 --- a/pvlib/windspeed.py +++ /dev/null @@ -1,166 +0,0 @@ -"""The ``windspeed`` module contains functions for calculating wind speed.""" - -import numpy as np -import pandas as pd - - -# Values of the Hellmann exponent -HELLMANN_SURFACE_EXPONENTS = { - 'unstable_air_above_open_water_surface': 0.06, - 'neutral_air_above_open_water_surface': 0.10, - 'stable_air_above_open_water_surface': 0.27, - 'unstable_air_above_flat_open_coast': 0.11, - 'neutral_air_above_flat_open_coast': 0.16, - 'stable_air_above_flat_open_coast': 0.40, - 'unstable_air_above_human_inhabited_areas': 0.27, - 'neutral_air_above_human_inhabited_areas': 0.34, - 'stable_air_above_human_inhabited_areas': 0.60, -} - - -def windspeed_hellmann(wind_speed_reference, height_reference, - height_desired, exponent=None, - surface_type=None): - r""" - Estimate wind speed for different heights. - - The model is based on the power law equation by Hellmann [1]_, [2]_. - - Parameters - ---------- - wind_speed_reference : numeric - Measured wind speed. [m/s] - - height_reference : float - The height at which the wind speed is measured. [m] - - height_desired : float - The height at which the wind speed will be estimated. [m] - - exponent : float, optional - Exponent based on the surface type. [-] - - surface_type : string, optional - If supplied, overrides ``exponent``. Can be one of the following - (see [1]_): - - * ``'unstable_air_above_open_water_surface'`` - * ``'neutral_air_above_open_water_surface'`` - * ``'stable_air_above_open_water_surface'`` - * ``'unstable_air_above_flat_open_coast'`` - * ``'neutral_air_above_flat_open_coast'`` - * ``'stable_air_above_flat_open_coast'`` - * ``'unstable_air_above_human_inhabited_areas'`` - * ``'neutral_air_above_human_inhabited_areas'`` - * ``'stable_air_above_human_inhabited_areas'`` - - Returns - ------- - wind_speed : numeric - Adjusted wind speed for the desired height. [m/s] - - Raises - ------ - ValueError - If neither of ``exponent`` nor a ``surface_type`` is given. - If both ``exponent`` nor a ``surface_type`` is given. These parameters - are mutually exclusive. - - KeyError - If the specified ``surface_type`` is invalid. - - Notes - ----- - The equation for calculating the wind speed at a height of :math:`h` is - given by the following power law equation [1]_[2]_: - - .. math:: - :label: wind speed - - U_{w,h} = U_{w,ref} \cdot \left( \frac{h}{h_{ref}} \right)^a - - where :math:`h` [m] is the height at which we would like to calculate the - wind speed, :math:`h_{ref}` [m] is the reference height at which the wind - speed is known, and :math:`U_{w,h}` [m/s] and :math:`U_{w,ref}` - [m/s] are the corresponding wind speeds at these heights. :math:`a` is a - value that depends on the surface type. Some values found in the literature - [1]_ for :math:`a` are the following: - - .. table:: Values for the Hellmann-exponent - - +-----------+--------------------+------------------+------------------+ - | Stability | Open water surface | Flat, open coast | Cities, villages | - +===========+====================+==================+==================+ - | Unstable | 0.06 | 0.10 | 0.27 | - +-----------+--------------------+------------------+------------------+ - | Neutral | 0.11 | 0.16 | 0.40 | - +-----------+--------------------+------------------+------------------+ - | Stable | 0.27 | 0.34 | 0.60 | - +-----------+--------------------+------------------+------------------+ - - In a report by Sandia [3]_, this equation was experimentally tested for a - height of 30 ft (9.144 m) and the following coefficients were recommended: - :math:`h_{ref} = 9.144` [m], :math:`a = 0.219` [-], and - :math:`windspeed_{href}` is the wind speed at a height of 9.144 [m]. - - It should be noted that the equation returns a value of NaN if the - calculated wind speed is negative or a complex number. - - Warning - ------- - Module temperature functions often require wind speeds at a height of 10 m - and not the wind speed at the module height. - - For example, the following temperature functions require the input wind - speed to be 10 m: :py:func:`~pvlib.temperature.sapm_cell`, - :py:func:`~pvlib.temperature.sapm_module`, and - :py:func:`~pvlib.temperature.generic_linear`, whereas the - :py:func:`~pvlib.temperature.fuentes` model requires wind speed at 9.144 m. - - Additionally, the heat loss coefficients of some models have been developed - for wind speed measurements at 10 m (e.g., - :py:func:`~pvlib.temperature.pvsyst_cell`, - :py:func:`~pvlib.temperature.faiman`, and - :py:func:`~pvlib.temperature.faiman_rad`). - - References - ---------- - .. [1] Kaltschmitt M., Streicher W., Wiese A. (2007). "Renewable Energy: - Technology, Economics and Environment." Springer, - :doi:`10.1007/3-540-70949-5`. - - .. [2] Hellmann G. (1915). "Über die Bewegung der Luft in den untersten - Schichten der Atmosphäre." Meteorologische Zeitschrift, 32 - - .. [3] Menicucci D.F., Hall I.J. (1985). "Estimating wind speed as a - function of height above ground: An analysis of data obtained at the - southwest residential experiment station, Las Cruses, New Mexico." - SAND84-2530, Sandia National Laboratories. - Accessed at - https://web.archive.org/web/20230418202422/https://www2.jpl.nasa.gov/adv_tech/photovol/2016CTR/SNL%20-%20Est%20Wind%20Speed%20vs%20Height_1985.pdf - """ # noqa:E501 - if surface_type is not None and exponent is None: - # use the Hellmann exponent from dictionary - exponent = HELLMANN_SURFACE_EXPONENTS[surface_type] - elif surface_type is None and exponent is not None: - # use the provided exponent - pass - else: - raise ValueError( - "Either a 'surface_type' or an 'exponent' parameter must be given") - - wind_speed = wind_speed_reference * ( - (height_desired / height_reference) ** exponent) - - # if the provided height is negative the calculated wind speed is complex - # so a NaN value is returned - if isinstance(wind_speed, complex): - wind_speed = np.nan - - # if wind speed is negative return NaN - wind_speed = np.where(wind_speed < 0, np.nan, wind_speed) - - if isinstance(wind_speed_reference, pd.Series): - wind_speed = pd.Series(wind_speed, index=wind_speed_reference.index) - - return wind_speed From 1927357cb951e3a5fd0050d7319a233b7208dfe2 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 22 Jul 2024 19:06:22 +0200 Subject: [PATCH 12/23] minor formatting fixes --- pvlib/atmosphere.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index f746e0ff90..f76f21f0c6 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -556,7 +556,7 @@ def windspeed_hellmann(wind_speed_reference, height_reference, r""" Estimate wind speed for different heights. - The model is based on the power law equation by Hellmann [1]_[2]_. + The model is based on the power law equation by Hellmann [1]_ [2]_. Parameters ---------- @@ -604,7 +604,7 @@ def windspeed_hellmann(wind_speed_reference, height_reference, Notes ----- The equation for calculating the wind speed at a height of :math:`h` is - given by the following power law equation [1]_[2]_: + given by the following power law equation [1]_ [2]_: .. math:: :label: wind speed @@ -668,7 +668,7 @@ def windspeed_hellmann(wind_speed_reference, height_reference, function of height above ground: An analysis of data obtained at the southwest residential experiment station, Las Cruses, New Mexico." SAND84-2530, Sandia National Laboratories. - Accessed at + Accessed at: https://web.archive.org/web/20230418202422/https://www2.jpl.nasa.gov/adv_tech/photovol/2016CTR/SNL%20-%20Est%20Wind%20Speed%20vs%20Height_1985.pdf """ # noqa:E501 if surface_type is not None and exponent is None: From cd51ad3104cb9a246abc6c45ab3e1a4db42fa310 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 22 Jul 2024 19:09:04 +0200 Subject: [PATCH 13/23] update test parameter names --- pvlib/tests/test_atmosphere.py | 46 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/pvlib/tests/test_atmosphere.py b/pvlib/tests/test_atmosphere.py index 8fb4692755..d79dc048fd 100644 --- a/pvlib/tests/test_atmosphere.py +++ b/pvlib/tests/test_atmosphere.py @@ -134,7 +134,7 @@ def test_bird_hulstrom80_aod_bb(): @pytest.mark.parametrize( - 'wind_speed_measured,height_measured,height_desired,wind_speed_calc', + 'wind_speed_reference,height_reference,height_desired,wind_speed_calc', [ (10, -2, 5, np.nan), (-10, 2, 5, np.nan), @@ -142,13 +142,13 @@ def test_bird_hulstrom80_aod_bb(): (7, 6, 10, 7.2178684911195905), (10, 8, 20, 10.565167835216586), (12, 10, 30, 12.817653329393977)]) -def test_windspeed_hellmann(wind_speed_measured, - height_measured, +def test_windspeed_hellmann(wind_speed_reference, + height_reference, height_desired, wind_speed_calc): result = atmosphere.windspeed_hellmann( - wind_speed_measured, - height_measured, + wind_speed_reference, + height_reference, height_desired, surface_type='unstable_air_above_open_water_surface') assert_allclose(result, wind_speed_calc) @@ -161,12 +161,12 @@ def times(): @pytest.fixture -def wind_speeds_measured(times): +def wind_speeds_reference(times): return pd.Series([10, -10, 5, 7, 10, 12], index=times) @pytest.fixture -def heights_measured(times): +def heights_reference(times): return np.array([-2, 2, 4, 6, 8, 10]) @@ -181,34 +181,34 @@ def wind_speeds_calc(times): 10.565167835216586, 12.817653329393977], index=times) -def test_windspeed_hellmann_ndarray(wind_speeds_measured, - heights_measured, +def test_windspeed_hellmann_ndarray(wind_speeds_reference, + heights_reference, heights_desired, wind_speeds_calc): # test wind speed estimation by passing in surface_type result_surface = atmosphere.windspeed_hellmann( - wind_speeds_measured.to_numpy(), - heights_measured, + wind_speeds_reference.to_numpy(), + heights_reference, heights_desired, surface_type='unstable_air_above_open_water_surface') assert_allclose(wind_speeds_calc.to_numpy(), result_surface) # test wind speed estimation by passing in the exponent corresponding # to the surface_type above result_exponent = atmosphere.windspeed_hellmann( - wind_speeds_measured.to_numpy(), - heights_measured, + wind_speeds_reference.to_numpy(), + heights_reference, heights_desired, exponent=0.06) assert_allclose(wind_speeds_calc.to_numpy(), result_exponent) -def test_windspeed_hellmann_series(wind_speeds_measured, - heights_measured, +def test_windspeed_hellmann_series(wind_speeds_reference, + heights_reference, heights_desired, wind_speeds_calc): result = atmosphere.windspeed_hellmann( - wind_speeds_measured, - heights_measured, + wind_speeds_reference, + heights_reference, heights_desired, surface_type='unstable_air_above_open_water_surface') assert_series_equal(wind_speeds_calc, result) @@ -218,20 +218,20 @@ def test_windspeed_hellmann_invalid(): with pytest.raises(ValueError, match='Either a `surface_type` has to be ' 'chosen or an exponent'): # no exponent or surface_type given - atmosphere.windspeed_hellmann(wind_speed_measured=10, - height_measured=5, + atmosphere.windspeed_hellmann(wind_speed_reference=10, + height_reference=5, height_desired=10) with pytest.raises(ValueError, match='Either a `surface_type` has to be ' 'chosen or an exponent'): # no exponent or surface_type given - atmosphere.windspeed_hellmann(wind_speed_measured=10, - height_measured=5, + atmosphere.windspeed_hellmann(wind_speed_reference=10, + height_reference=5, height_desired=10, exponent=1.2, surface_type="surf") with pytest.raises(KeyError, match='not_an_exponent'): # invalid surface_type - atmosphere.windspeed_hellmann(wind_speed_measured=10, - height_measured=5, + atmosphere.windspeed_hellmann(wind_speed_reference=10, + height_reference=5, height_desired=10, surface_type='not_an_exponent') From 6d330767170c994fd7eb3901faecb2f4d831c70c Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 22 Jul 2024 19:20:39 +0200 Subject: [PATCH 14/23] updated error message --- pvlib/tests/test_atmosphere.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/tests/test_atmosphere.py b/pvlib/tests/test_atmosphere.py index d79dc048fd..ba1a67a213 100644 --- a/pvlib/tests/test_atmosphere.py +++ b/pvlib/tests/test_atmosphere.py @@ -215,14 +215,14 @@ def test_windspeed_hellmann_series(wind_speeds_reference, def test_windspeed_hellmann_invalid(): - with pytest.raises(ValueError, match='Either a `surface_type` has to be ' - 'chosen or an exponent'): + with pytest.raises(ValueError, match="Either a 'surface_type' or an" + "'exponent' parameter must be given"): # no exponent or surface_type given atmosphere.windspeed_hellmann(wind_speed_reference=10, height_reference=5, height_desired=10) - with pytest.raises(ValueError, match='Either a `surface_type` has to be ' - 'chosen or an exponent'): + with pytest.raises(ValueError, match="Either a 'surface_type' or an" + "'exponent' parameter must be given"): # no exponent or surface_type given atmosphere.windspeed_hellmann(wind_speed_reference=10, height_reference=5, From e3bd1f8fbb6dfb303ef1534fcba4c8fb6dca4ae0 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 22 Jul 2024 19:21:47 +0200 Subject: [PATCH 15/23] fixed error message vol.2 --- pvlib/tests/test_atmosphere.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_atmosphere.py b/pvlib/tests/test_atmosphere.py index ba1a67a213..a282a4c655 100644 --- a/pvlib/tests/test_atmosphere.py +++ b/pvlib/tests/test_atmosphere.py @@ -215,13 +215,13 @@ def test_windspeed_hellmann_series(wind_speeds_reference, def test_windspeed_hellmann_invalid(): - with pytest.raises(ValueError, match="Either a 'surface_type' or an" + with pytest.raises(ValueError, match="Either a 'surface_type' or an " "'exponent' parameter must be given"): # no exponent or surface_type given atmosphere.windspeed_hellmann(wind_speed_reference=10, height_reference=5, height_desired=10) - with pytest.raises(ValueError, match="Either a 'surface_type' or an" + with pytest.raises(ValueError, match="Either a 'surface_type' or an " "'exponent' parameter must be given"): # no exponent or surface_type given atmosphere.windspeed_hellmann(wind_speed_reference=10, From 3850388b3996d7782ed574dc29067ac7c3b0d714 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 22 Jul 2024 19:47:42 +0200 Subject: [PATCH 16/23] updated tests --- pvlib/atmosphere.py | 2 +- pvlib/tests/test_atmosphere.py | 97 +++++++++++----------------------- 2 files changed, 33 insertions(+), 66 deletions(-) diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index f76f21f0c6..5459a94c0a 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -595,7 +595,7 @@ def windspeed_hellmann(wind_speed_reference, height_reference, ------ ValueError If neither of ``exponent`` nor a ``surface_type`` is given. - If both ``exponent`` nor a ``surface_type`` is given. These parameters + If both ``exponent`` and a ``surface_type`` is given. These parameters are mutually exclusive. KeyError diff --git a/pvlib/tests/test_atmosphere.py b/pvlib/tests/test_atmosphere.py index a282a4c655..24220e787c 100644 --- a/pvlib/tests/test_atmosphere.py +++ b/pvlib/tests/test_atmosphere.py @@ -133,85 +133,52 @@ def test_bird_hulstrom80_aod_bb(): assert np.isclose(0.11738229553812768, bird_hulstrom) -@pytest.mark.parametrize( - 'wind_speed_reference,height_reference,height_desired,wind_speed_calc', - [ - (10, -2, 5, np.nan), - (-10, 2, 5, np.nan), - (5, 4, 5, 5.067393209486324), - (7, 6, 10, 7.2178684911195905), - (10, 8, 20, 10.565167835216586), - (12, 10, 30, 12.817653329393977)]) -def test_windspeed_hellmann(wind_speed_reference, - height_reference, - height_desired, - wind_speed_calc): - result = atmosphere.windspeed_hellmann( - wind_speed_reference, - height_reference, - height_desired, - surface_type='unstable_air_above_open_water_surface') - assert_allclose(result, wind_speed_calc) - - @pytest.fixture -def times(): - return pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00", - freq="1h") - - -@pytest.fixture -def wind_speeds_reference(times): - return pd.Series([10, -10, 5, 7, 10, 12], index=times) - - -@pytest.fixture -def heights_reference(times): - return np.array([-2, 2, 4, 6, 8, 10]) - - -@pytest.fixture -def heights_desired(): - return np.array([5, 5, 5, 10, 20, 30]) - - -@pytest.fixture -def wind_speeds_calc(times): - return pd.Series([np.nan, np.nan, 5.067393209486324, 7.2178684911195905, - 10.565167835216586, 12.817653329393977], index=times) +def windspeeds_data_hellman(): + data = pd.DataFrame( + index=pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00", + freq="1h"), + columns=["wind_ref", "height_ref", "height_desired", "wind_calc"], + data=[ + (10, -2, 5, np.nan), + (-10, 2, 5, np.nan), + (5, 4, 5, 5.067393209486324), + (7, 6, 10, 7.2178684911195905), + (10, 8, 20, 10.565167835216586), + (12, 10, 30, 12.817653329393977) + ] + ) + return data -def test_windspeed_hellmann_ndarray(wind_speeds_reference, - heights_reference, - heights_desired, - wind_speeds_calc): +def test_windspeed_hellmann_ndarray(windspeeds_data_hellman): # test wind speed estimation by passing in surface_type result_surface = atmosphere.windspeed_hellmann( - wind_speeds_reference.to_numpy(), - heights_reference, - heights_desired, + windspeeds_data_hellman["wind_ref"].to_numpy(), + windspeeds_data_hellman["height_ref"], + windspeeds_data_hellman["height_desired"], surface_type='unstable_air_above_open_water_surface') - assert_allclose(wind_speeds_calc.to_numpy(), result_surface) + assert_allclose(windspeeds_data_hellman["wind_calc"].to_numpy(), + result_surface) # test wind speed estimation by passing in the exponent corresponding # to the surface_type above result_exponent = atmosphere.windspeed_hellmann( - wind_speeds_reference.to_numpy(), - heights_reference, - heights_desired, + windspeeds_data_hellman["wind_ref"].to_numpy(), + windspeeds_data_hellman["height_ref"], + windspeeds_data_hellman["height_desired"], exponent=0.06) - assert_allclose(wind_speeds_calc.to_numpy(), result_exponent) + assert_allclose(windspeeds_data_hellman["wind_calc"].to_numpy(), + result_exponent) -def test_windspeed_hellmann_series(wind_speeds_reference, - heights_reference, - heights_desired, - wind_speeds_calc): +def test_windspeed_hellmann_series(windspeeds_data_hellman): result = atmosphere.windspeed_hellmann( - wind_speeds_reference, - heights_reference, - heights_desired, + windspeeds_data_hellman["wind_ref"], + windspeeds_data_hellman["height_ref"], + windspeeds_data_hellman["height_desired"], surface_type='unstable_air_above_open_water_surface') - assert_series_equal(wind_speeds_calc, result) + assert_series_equal(windspeeds_data_hellman["wind_calc"], + result, check_names=False) def test_windspeed_hellmann_invalid(): From 0cfbd79b5b37d7bdca69a64d52903a8f52c51989 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 22 Jul 2024 20:09:02 +0200 Subject: [PATCH 17/23] Update v0.11.1.rst --- docs/sphinx/source/whatsnew/v0.11.1.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.11.1.rst b/docs/sphinx/source/whatsnew/v0.11.1.rst index 1e839c596c..c154301354 100644 --- a/docs/sphinx/source/whatsnew/v0.11.1.rst +++ b/docs/sphinx/source/whatsnew/v0.11.1.rst @@ -14,6 +14,9 @@ Enhancements modules, :py:func:`pvlib.bifacial.power_mismatch_deline`. (:issue:`2045`, :pull:`2046`) +* Added function for calculating wind speed at different heights, + :py:func:`pvlib.atmosphere.windspeed_hellmann`. + (:issue:`2118`, :pull:`2124`) Bug fixes ~~~~~~~~~ @@ -34,3 +37,5 @@ Requirements Contributors ~~~~~~~~~~~~ * Echedey Luis (:ghuser:`echedey-ls`) +* Ioannis Sifnaios (:ghuser:`IoannisSifnaios`) + From 07be0b73f58fc7bd3abcf47f737827e399a7c720 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 5 Aug 2024 17:19:43 +0200 Subject: [PATCH 18/23] changed nan condition --- pvlib/atmosphere.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index 5459a94c0a..bd8c02c11e 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -684,13 +684,9 @@ def windspeed_hellmann(wind_speed_reference, height_reference, wind_speed = wind_speed_reference * ( (height_desired / height_reference) ** exponent) - # if the provided height is negative the calculated wind speed is complex - # so a NaN value is returned - if isinstance(wind_speed, complex): - wind_speed = np.nan - - # if wind speed is negative return NaN - wind_speed = np.where(wind_speed < 0, np.nan, wind_speed) + # if wind speed is negative or complex return NaN + wind_speed = np.where(np.iscomplex(wind_speed) | wind_speed < 0, + np.nan, wind_speed) if isinstance(wind_speed_reference, pd.Series): wind_speed = pd.Series(wind_speed, index=wind_speed_reference.index) From 2133f0cf486811f2f50f5b3147b26e085460b3d0 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 5 Aug 2024 17:40:14 +0200 Subject: [PATCH 19/23] changed function name --- .../source/reference/airmass_atmospheric.rst | 2 +- pvlib/atmosphere.py | 37 +++++------ pvlib/tests/test_atmosphere.py | 62 +++++++++---------- 3 files changed, 49 insertions(+), 52 deletions(-) diff --git a/docs/sphinx/source/reference/airmass_atmospheric.rst b/docs/sphinx/source/reference/airmass_atmospheric.rst index 945cdccc3a..be3a85ae04 100644 --- a/docs/sphinx/source/reference/airmass_atmospheric.rst +++ b/docs/sphinx/source/reference/airmass_atmospheric.rst @@ -17,4 +17,4 @@ Airmass and atmospheric models atmosphere.kasten96_lt atmosphere.angstrom_aod_at_lambda atmosphere.angstrom_alpha - atmosphere.windspeed_hellmann + atmosphere.windspeed_power_law diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index bd8c02c11e..59c80c0920 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -550,9 +550,9 @@ def angstrom_alpha(aod1, lambda1, aod2, lambda2): } -def windspeed_hellmann(wind_speed_reference, height_reference, - height_desired, exponent=None, - surface_type=None): +def windspeed_power_law(wind_speed_reference, height_reference, + height_desired, exponent=None, + surface_type=None): r""" Estimate wind speed for different heights. @@ -603,6 +603,20 @@ def windspeed_hellmann(wind_speed_reference, height_reference, Notes ----- + Module temperature functions often require wind speeds at a height of 10 m + and not the wind speed at the module height. + + For example, the following temperature functions require the input wind + speed to be 10 m: :py:func:`~pvlib.temperature.sapm_cell`, and + :py:func:`~pvlib.temperature.sapm_module` whereas the + :py:func:`~pvlib.temperature.fuentes` model requires wind speed at 9.144 m. + + Additionally, the heat loss coefficients of some models have been developed + for wind speed measurements at 10 m (e.g., + :py:func:`~pvlib.temperature.pvsyst_cell`, + :py:func:`~pvlib.temperature.faiman`, and + :py:func:`~pvlib.temperature.faiman_rad`). + The equation for calculating the wind speed at a height of :math:`h` is given by the following power law equation [1]_ [2]_: @@ -638,23 +652,6 @@ def windspeed_hellmann(wind_speed_reference, height_reference, It should be noted that the equation returns a value of NaN if the calculated wind speed is negative or a complex number. - Warning - ------- - Module temperature functions often require wind speeds at a height of 10 m - and not the wind speed at the module height. - - For example, the following temperature functions require the input wind - speed to be 10 m: :py:func:`~pvlib.temperature.sapm_cell`, - :py:func:`~pvlib.temperature.sapm_module`, and - :py:func:`~pvlib.temperature.generic_linear`, whereas the - :py:func:`~pvlib.temperature.fuentes` model requires wind speed at 9.144 m. - - Additionally, the heat loss coefficients of some models have been developed - for wind speed measurements at 10 m (e.g., - :py:func:`~pvlib.temperature.pvsyst_cell`, - :py:func:`~pvlib.temperature.faiman`, and - :py:func:`~pvlib.temperature.faiman_rad`). - References ---------- .. [1] Kaltschmitt M., Streicher W., Wiese A. (2007). "Renewable Energy: diff --git a/pvlib/tests/test_atmosphere.py b/pvlib/tests/test_atmosphere.py index 24220e787c..e672d1740e 100644 --- a/pvlib/tests/test_atmosphere.py +++ b/pvlib/tests/test_atmosphere.py @@ -134,7 +134,7 @@ def test_bird_hulstrom80_aod_bb(): @pytest.fixture -def windspeeds_data_hellman(): +def windspeeds_data_power_law(): data = pd.DataFrame( index=pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00", freq="1h"), @@ -151,54 +151,54 @@ def windspeeds_data_hellman(): return data -def test_windspeed_hellmann_ndarray(windspeeds_data_hellman): +def test_windspeed_power_law_ndarray(windspeeds_data_power_law): # test wind speed estimation by passing in surface_type - result_surface = atmosphere.windspeed_hellmann( - windspeeds_data_hellman["wind_ref"].to_numpy(), - windspeeds_data_hellman["height_ref"], - windspeeds_data_hellman["height_desired"], + result_surface = atmosphere.windspeed_power_law( + windspeeds_data_power_law["wind_ref"].to_numpy(), + windspeeds_data_power_law["height_ref"], + windspeeds_data_power_law["height_desired"], surface_type='unstable_air_above_open_water_surface') - assert_allclose(windspeeds_data_hellman["wind_calc"].to_numpy(), + assert_allclose(windspeeds_data_power_law["wind_calc"].to_numpy(), result_surface) # test wind speed estimation by passing in the exponent corresponding # to the surface_type above - result_exponent = atmosphere.windspeed_hellmann( - windspeeds_data_hellman["wind_ref"].to_numpy(), - windspeeds_data_hellman["height_ref"], - windspeeds_data_hellman["height_desired"], + result_exponent = atmosphere.windspeed_power_law( + windspeeds_data_power_law["wind_ref"].to_numpy(), + windspeeds_data_power_law["height_ref"], + windspeeds_data_power_law["height_desired"], exponent=0.06) - assert_allclose(windspeeds_data_hellman["wind_calc"].to_numpy(), + assert_allclose(windspeeds_data_power_law["wind_calc"].to_numpy(), result_exponent) -def test_windspeed_hellmann_series(windspeeds_data_hellman): - result = atmosphere.windspeed_hellmann( - windspeeds_data_hellman["wind_ref"], - windspeeds_data_hellman["height_ref"], - windspeeds_data_hellman["height_desired"], +def test_windspeed_power_law_series(windspeeds_data_power_law): + result = atmosphere.windspeed_power_law( + windspeeds_data_power_law["wind_ref"], + windspeeds_data_power_law["height_ref"], + windspeeds_data_power_law["height_desired"], surface_type='unstable_air_above_open_water_surface') - assert_series_equal(windspeeds_data_hellman["wind_calc"], + assert_series_equal(windspeeds_data_power_law["wind_calc"], result, check_names=False) -def test_windspeed_hellmann_invalid(): +def test_windspeed_power_law_invalid(): with pytest.raises(ValueError, match="Either a 'surface_type' or an " "'exponent' parameter must be given"): # no exponent or surface_type given - atmosphere.windspeed_hellmann(wind_speed_reference=10, - height_reference=5, - height_desired=10) + atmosphere.windspeed_power_law(wind_speed_reference=10, + height_reference=5, + height_desired=10) with pytest.raises(ValueError, match="Either a 'surface_type' or an " "'exponent' parameter must be given"): # no exponent or surface_type given - atmosphere.windspeed_hellmann(wind_speed_reference=10, - height_reference=5, - height_desired=10, - exponent=1.2, - surface_type="surf") + atmosphere.windspeed_power_law(wind_speed_reference=10, + height_reference=5, + height_desired=10, + exponent=1.2, + surface_type="surf") with pytest.raises(KeyError, match='not_an_exponent'): # invalid surface_type - atmosphere.windspeed_hellmann(wind_speed_reference=10, - height_reference=5, - height_desired=10, - surface_type='not_an_exponent') + atmosphere.windspeed_power_law(wind_speed_reference=10, + height_reference=5, + height_desired=10, + surface_type='not_an_exponent') From 488fb99e3b7be91277cd305f56a08409adc50d17 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios <88548539+IoannisSifnaios@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:42:14 +0200 Subject: [PATCH 20/23] Apply suggestions from code review Co-authored-by: Cliff Hansen Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- pvlib/atmosphere.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index 59c80c0920..4a5fdae933 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -564,10 +564,10 @@ def windspeed_power_law(wind_speed_reference, height_reference, Measured wind speed. [m/s] height_reference : float - The height at which the wind speed is measured. [m] + The height above ground at which the wind speed is measured. [m] height_desired : float - The height at which the wind speed will be estimated. [m] + The height above ground at which the wind speed will be estimated. [m] exponent : float, optional Exponent based on the surface type. [-] @@ -623,14 +623,14 @@ def windspeed_power_law(wind_speed_reference, height_reference, .. math:: :label: wind speed - U_{w,h} = U_{w,ref} \cdot \left( \frac{h}{h_{ref}} \right)^a + WS_{h} = WS_{ref} \cdot \left( \frac{h}{h_{ref}} \right)^a where :math:`h` [m] is the height at which we would like to calculate the wind speed, :math:`h_{ref}` [m] is the reference height at which the wind - speed is known, and :math:`U_{w,h}` [m/s] and :math:`U_{w,ref}` - [m/s] are the corresponding wind speeds at these heights. :math:`a` is a - value that depends on the surface type. Some values found in the literature - [1]_ for :math:`a` are the following: + speed is known, and :math:`WS_{h}` [m/s] and :math:`WS_{ref}` + [m/s] are the corresponding wind speeds at these heights. The exponent + :math:`a` [unitless] depends on the surface type. Some values found in the + literature [1]_ for :math:`a` are: .. table:: Values for the Hellmann-exponent @@ -644,13 +644,12 @@ def windspeed_power_law(wind_speed_reference, height_reference, | Stable | 0.27 | 0.34 | 0.60 | +-----------+--------------------+------------------+------------------+ - In a report by Sandia [3]_, this equation was experimentally tested for a - height of 30 ft (9.144 m) and the following coefficients were recommended: - :math:`h_{ref} = 9.144` [m], :math:`a = 0.219` [-], and - :math:`windspeed_{href}` is the wind speed at a height of 9.144 [m]. + In a report by Sandia [3]_, the equation was experimentally tested for a + height of 30 ft (:math:`h_{ref} = 9.144` [m]) and a coefficient of + :math:`a = 0.219` [-] was recommended. It should be noted that the equation returns a value of NaN if the - calculated wind speed is negative or a complex number. + reference heights or wind speed are negative. References ---------- From 251362c93654c116d838b079a1239416f03a7a7d Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 5 Aug 2024 17:52:47 +0200 Subject: [PATCH 21/23] changed name (again) --- .../source/reference/airmass_atmospheric.rst | 2 +- docs/sphinx/source/whatsnew/v0.11.1.rst | 2 +- pvlib/atmosphere.py | 8 +-- pvlib/tests/test_atmosphere.py | 62 +++++++++---------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/sphinx/source/reference/airmass_atmospheric.rst b/docs/sphinx/source/reference/airmass_atmospheric.rst index be3a85ae04..384c345aec 100644 --- a/docs/sphinx/source/reference/airmass_atmospheric.rst +++ b/docs/sphinx/source/reference/airmass_atmospheric.rst @@ -17,4 +17,4 @@ Airmass and atmospheric models atmosphere.kasten96_lt atmosphere.angstrom_aod_at_lambda atmosphere.angstrom_alpha - atmosphere.windspeed_power_law + atmosphere.windspeed_powerlaw diff --git a/docs/sphinx/source/whatsnew/v0.11.1.rst b/docs/sphinx/source/whatsnew/v0.11.1.rst index b91222dbcc..3197882d93 100644 --- a/docs/sphinx/source/whatsnew/v0.11.1.rst +++ b/docs/sphinx/source/whatsnew/v0.11.1.rst @@ -18,7 +18,7 @@ Enhancements (:issue:`2086`, :pull:`2100`) * Added function for calculating wind speed at different heights, - :py:func:`pvlib.atmosphere.windspeed_hellmann`. + :py:func:`pvlib.atmosphere.windspeed_powerlaw`. (:issue:`2118`, :pull:`2124`) Bug fixes diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index 59c80c0920..a3c8d6207d 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -550,9 +550,9 @@ def angstrom_alpha(aod1, lambda1, aod2, lambda2): } -def windspeed_power_law(wind_speed_reference, height_reference, - height_desired, exponent=None, - surface_type=None): +def windspeed_powerlaw(wind_speed_reference, height_reference, + height_desired, exponent=None, + surface_type=None): r""" Estimate wind speed for different heights. @@ -682,7 +682,7 @@ def windspeed_power_law(wind_speed_reference, height_reference, (height_desired / height_reference) ** exponent) # if wind speed is negative or complex return NaN - wind_speed = np.where(np.iscomplex(wind_speed) | wind_speed < 0, + wind_speed = np.where(np.iscomplex(wind_speed) or wind_speed < 0, np.nan, wind_speed) if isinstance(wind_speed_reference, pd.Series): diff --git a/pvlib/tests/test_atmosphere.py b/pvlib/tests/test_atmosphere.py index e672d1740e..e12a41dc6d 100644 --- a/pvlib/tests/test_atmosphere.py +++ b/pvlib/tests/test_atmosphere.py @@ -134,7 +134,7 @@ def test_bird_hulstrom80_aod_bb(): @pytest.fixture -def windspeeds_data_power_law(): +def windspeeds_data_powerlaw(): data = pd.DataFrame( index=pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00", freq="1h"), @@ -151,54 +151,54 @@ def windspeeds_data_power_law(): return data -def test_windspeed_power_law_ndarray(windspeeds_data_power_law): +def test_windspeed_powerlaw_ndarray(windspeeds_data_powerlaw): # test wind speed estimation by passing in surface_type - result_surface = atmosphere.windspeed_power_law( - windspeeds_data_power_law["wind_ref"].to_numpy(), - windspeeds_data_power_law["height_ref"], - windspeeds_data_power_law["height_desired"], + result_surface = atmosphere.windspeed_powerlaw( + windspeeds_data_powerlaw["wind_ref"].to_numpy(), + windspeeds_data_powerlaw["height_ref"], + windspeeds_data_powerlaw["height_desired"], surface_type='unstable_air_above_open_water_surface') - assert_allclose(windspeeds_data_power_law["wind_calc"].to_numpy(), + assert_allclose(windspeeds_data_powerlaw["wind_calc"].to_numpy(), result_surface) # test wind speed estimation by passing in the exponent corresponding # to the surface_type above - result_exponent = atmosphere.windspeed_power_law( - windspeeds_data_power_law["wind_ref"].to_numpy(), - windspeeds_data_power_law["height_ref"], - windspeeds_data_power_law["height_desired"], + result_exponent = atmosphere.windspeed_powerlaw( + windspeeds_data_powerlaw["wind_ref"].to_numpy(), + windspeeds_data_powerlaw["height_ref"], + windspeeds_data_powerlaw["height_desired"], exponent=0.06) - assert_allclose(windspeeds_data_power_law["wind_calc"].to_numpy(), + assert_allclose(windspeeds_data_powerlaw["wind_calc"].to_numpy(), result_exponent) -def test_windspeed_power_law_series(windspeeds_data_power_law): - result = atmosphere.windspeed_power_law( - windspeeds_data_power_law["wind_ref"], - windspeeds_data_power_law["height_ref"], - windspeeds_data_power_law["height_desired"], +def test_windspeed_powerlaw_series(windspeeds_data_powerlaw): + result = atmosphere.windspeed_powerlaw( + windspeeds_data_powerlaw["wind_ref"], + windspeeds_data_powerlaw["height_ref"], + windspeeds_data_powerlaw["height_desired"], surface_type='unstable_air_above_open_water_surface') - assert_series_equal(windspeeds_data_power_law["wind_calc"], + assert_series_equal(windspeeds_data_powerlaw["wind_calc"], result, check_names=False) -def test_windspeed_power_law_invalid(): +def test_windspeed_powerlaw_invalid(): with pytest.raises(ValueError, match="Either a 'surface_type' or an " "'exponent' parameter must be given"): # no exponent or surface_type given - atmosphere.windspeed_power_law(wind_speed_reference=10, - height_reference=5, - height_desired=10) + atmosphere.windspeed_powerlaw(wind_speed_reference=10, + height_reference=5, + height_desired=10) with pytest.raises(ValueError, match="Either a 'surface_type' or an " "'exponent' parameter must be given"): # no exponent or surface_type given - atmosphere.windspeed_power_law(wind_speed_reference=10, - height_reference=5, - height_desired=10, - exponent=1.2, - surface_type="surf") + atmosphere.windspeed_powerlaw(wind_speed_reference=10, + height_reference=5, + height_desired=10, + exponent=1.2, + surface_type="surf") with pytest.raises(KeyError, match='not_an_exponent'): # invalid surface_type - atmosphere.windspeed_power_law(wind_speed_reference=10, - height_reference=5, - height_desired=10, - surface_type='not_an_exponent') + atmosphere.windspeed_powerlaw(wind_speed_reference=10, + height_reference=5, + height_desired=10, + surface_type='not_an_exponent') From 08094256eb8a875559b57a507ee063f1f17979c3 Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 5 Aug 2024 18:07:23 +0200 Subject: [PATCH 22/23] condition fix --- pvlib/atmosphere.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index 269b35951d..7cf5cc6834 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -681,7 +681,7 @@ def windspeed_powerlaw(wind_speed_reference, height_reference, (height_desired / height_reference) ** exponent) # if wind speed is negative or complex return NaN - wind_speed = np.where(np.iscomplex(wind_speed) or wind_speed < 0, + wind_speed = np.where(np.iscomplex(wind_speed) | (wind_speed < 0), np.nan, wind_speed) if isinstance(wind_speed_reference, pd.Series): From 5cefc1104d4324f0b7a7899878854fd3d6db9d1d Mon Sep 17 00:00:00 2001 From: Ioannis Sifnaios Date: Mon, 5 Aug 2024 18:21:34 +0200 Subject: [PATCH 23/23] corrected the Sandia wording --- pvlib/atmosphere.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pvlib/atmosphere.py b/pvlib/atmosphere.py index 7cf5cc6834..5b7ffff4ee 100644 --- a/pvlib/atmosphere.py +++ b/pvlib/atmosphere.py @@ -570,7 +570,7 @@ def windspeed_powerlaw(wind_speed_reference, height_reference, The height above ground at which the wind speed will be estimated. [m] exponent : float, optional - Exponent based on the surface type. [-] + Exponent based on the surface type. [unitless] surface_type : string, optional If supplied, overrides ``exponent``. Can be one of the following @@ -645,8 +645,9 @@ def windspeed_powerlaw(wind_speed_reference, height_reference, +-----------+--------------------+------------------+------------------+ In a report by Sandia [3]_, the equation was experimentally tested for a - height of 30 ft (:math:`h_{ref} = 9.144` [m]) and a coefficient of - :math:`a = 0.219` [-] was recommended. + height of 30 ft (:math:`h_{ref} = 9.144` [m]) at their test site in + Albuquerque for a period of six weeks where a coefficient of + :math:`a = 0.219` was calculated. It should be noted that the equation returns a value of NaN if the reference heights or wind speed are negative.