Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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/pv_modeling/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ wrap the functions listed below. See its documentation for details.
system_models
parameters
other
windspeed
9 changes: 9 additions & 0 deletions docs/sphinx/source/reference/pv_modeling/windspeed.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.. currentmodule:: pvlib

Wind Speed
----------

.. autosummary::
:toctree: ../generated/

windspeed.wind_speed_at_height_hellmann
1 change: 1 addition & 0 deletions pvlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@
temperature,
tools,
tracking,
windspeed,
)
103 changes: 103 additions & 0 deletions pvlib/tests/test_windspeed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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)
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated


def test_wind_speed_at_height_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(
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.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,
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)
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated
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')
165 changes: 165 additions & 0 deletions pvlib/windspeed.py
Original file line number Diff line number Diff line change
@@ -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):
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated
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]_:
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated

.. math::
:label: wind speed

windspeed_h = windspeed_{href} \cdot \left( \frac{h}{h_{ref}} \right)^a
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated

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 |
+-----------+--------------------+------------------+------------------+
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated

In a report by SANDIA [3]_, this equation was experimentally tested for a
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated
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
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated
calculated wind speed is negative or a complex number.

Warning
-------
Module temperature functions often require wind speeds at a heigt of 10 m
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated
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`).
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated

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.
`source <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
"""
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated
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")
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated

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
Comment thread
IoannisSifnaios marked this conversation as resolved.
Outdated
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