Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function to estimate wind speed at different heights #2124

Merged
merged 26 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
46f7804
wind_speed_at_different_heights
IoannisSifnaios Jul 10, 2024
a15b63e
minor fixed
IoannisSifnaios Jul 10, 2024
b8450d0
fixed toc and added test
IoannisSifnaios Jul 10, 2024
91c1de3
edit math mode
IoannisSifnaios Jul 10, 2024
b611487
changed link for reference
IoannisSifnaios Jul 10, 2024
663865d
Apply suggestions from code review
IoannisSifnaios Jul 19, 2024
f165f4d
Update pvlib/tests/test_windspeed.py
IoannisSifnaios Jul 19, 2024
0cfb539
changed function name
IoannisSifnaios Jul 19, 2024
1bd9308
Update pvlib/windspeed.py
IoannisSifnaios Jul 19, 2024
9fef611
change measured height to reference
IoannisSifnaios Jul 22, 2024
773ea6b
Merge branch 'wind_speed' of https://github.com/IoannisSifnaios/pvlib…
IoannisSifnaios Jul 22, 2024
0a891a2
moved wind speed to atmosphere
IoannisSifnaios Jul 22, 2024
1927357
minor formatting fixes
IoannisSifnaios Jul 22, 2024
cd51ad3
update test parameter names
IoannisSifnaios Jul 22, 2024
6d33076
updated error message
IoannisSifnaios Jul 22, 2024
e3bd1f8
fixed error message vol.2
IoannisSifnaios Jul 22, 2024
3850388
updated tests
IoannisSifnaios Jul 22, 2024
0cfbd79
Update v0.11.1.rst
IoannisSifnaios Jul 22, 2024
aca975c
Merge branch 'main' into wind_speed
IoannisSifnaios Jul 22, 2024
07be0b7
changed nan condition
IoannisSifnaios Aug 5, 2024
2133f0c
changed function name
IoannisSifnaios Aug 5, 2024
488fb99
Apply suggestions from code review
IoannisSifnaios Aug 5, 2024
251362c
changed name (again)
IoannisSifnaios Aug 5, 2024
dbee472
Merge branch 'wind_speed' of https://github.com/IoannisSifnaios/pvlib…
IoannisSifnaios Aug 5, 2024
0809425
condition fix
IoannisSifnaios Aug 5, 2024
5cefc11
corrected the Sandia wording
IoannisSifnaios Aug 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/sphinx/source/reference/airmass_atmospheric.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ Airmass and atmospheric models
atmosphere.kasten96_lt
atmosphere.angstrom_aod_at_lambda
atmosphere.angstrom_alpha
atmosphere.windspeed_hellmann
4 changes: 3 additions & 1 deletion docs/sphinx/source/whatsnew/v0.11.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Enhancements
:py:func:`pvlib.spectrum.spectral_factor_firstsolar`.
(:issue:`2086`, :pull:`2100`)

* Added function for calculating wind speed at different heights,
:py:func:`pvlib.atmosphere.windspeed_hellmann`.
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved
(:issue:`2118`, :pull:`2124`)

Bug fixes
~~~~~~~~~
Expand Down Expand Up @@ -45,4 +48,3 @@ Contributors
* Leonardo Micheli (:ghuser:`lmicheli`)
* Echedey Luis (:ghuser:`echedey-ls`)
* Rajiv Daxini (:ghuser:`RDaxini`)

165 changes: 164 additions & 1 deletion pvlib/atmosphere.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -533,3 +534,165 @@
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,
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved
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]
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved

height_desired : float
The height at which the wind speed will be estimated. [m]
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved

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`` and 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:
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved

.. 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].
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved

It should be noted that the equation returns a value of NaN if the
calculated wind speed is negative or a complex number.
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved

Warning
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved
-------
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
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved
: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`.
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved

.. [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

Check warning on line 690 in pvlib/atmosphere.py

View check run for this annotation

Codecov / codecov/patch

pvlib/atmosphere.py#L690

Added line #L690 was not covered by tests
IoannisSifnaios marked this conversation as resolved.
Show resolved Hide resolved

# 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
71 changes: 71 additions & 0 deletions pvlib/tests/test_atmosphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,74 @@ 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.fixture
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(windspeeds_data_hellman):
# 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"],
surface_type='unstable_air_above_open_water_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(
windspeeds_data_hellman["wind_ref"].to_numpy(),
windspeeds_data_hellman["height_ref"],
windspeeds_data_hellman["height_desired"],
exponent=0.06)
assert_allclose(windspeeds_data_hellman["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"],
surface_type='unstable_air_above_open_water_surface')
assert_series_equal(windspeeds_data_hellman["wind_calc"],
result, check_names=False)


def test_windspeed_hellmann_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)
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")
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')
Loading