from __future__ import annotations
import numpy as np
from pythermalcomfort.classes_input import ScaleWindSpeedLogInputs
from pythermalcomfort.classes_return import ScaleWindSpeedLog
[docs]
def scale_wind_speed_log(
v_z1: float | list[float],
z2: float | list[float],
z1: float | list[float] = 10.0,
z0: float | list[float] = 0.01,
d: float | list[float] = 0.0,
round_output: bool = True,
) -> ScaleWindSpeedLog:
"""Scale wind speed from the reference height to a user specified height using the
logarithmic wind profile based on surface roughness length [Oke1987]_.
The logarithmic wind profile is a semi-empirical relationship that describes how
wind speed changes with height above the ground in the surface layer of the
atmosphere. This equation assumes neutral atmospheric stability and is valid for
heights typically ranging from a few meters to about 100 meters above the ground.
It is commonly used in meteorology, wind engineering, and environmental
studies to estimate wind speeds at different heights based on a known reference
height. The formula is given by:
.. math::
v(h) = v_{ref} \\times \\frac{\\ln((h - d)/z0)}{\\ln((h_{ref} - d)/z0)}
where:
- :math:`v(h)` is the wind speed at height :math:`h`
- :math:`v_{ref}` is the wind speed at the reference height :math:`h_{ref}` (commonly 10 m)
- :math:`h` is the height at which the wind speed is to be estimated
- :math:`h_{ref}` is the reference height (10 m)
- :math:`z0` is the surface roughness length, which characterizes the roughness of the terrain
- :math:`d` is the zero-plane displacement height, which accounts for flow obstruction by vegetation or buildings
.. note::
This function assumes neutral atmospheric stability conditions. For
non-neutral conditions, more complex models such as the Monin-Obukhov
similarity theory should be used.
Moreover, this function does not account for obstacles or complex terrain
effects which may require computational fluid dynamics (CFD) simulations or
measurements.
Parameters
----------
v_z1 : float or list of floats
Wind speed at the reference height z1 (default 10 m), [m/s]
z2 : float or list of floats
Height at which wind speed needs to be scaled, [m]
z1 : float or list of floats, optional
Reference height, [m]. Default is 10.0 m.
z0 : float or list of floats, optional
Surface roughness length, [m]. Default is 0.01 (open terrain). This value accounts
for the roughness of the terrain and varies based on land use source [Sharples2023]_:
- 0.005 m: Inland waterbodies (lakes, dams)
- 0.03 m: Irrigated Pasture (e.g., grassland, alfalfa)
- 0.1 m: Irrigated cropping (e.g., wheat, soybeans)
- 0.25 m: Irrigated Sugar (e.g., sugarcane, corn)
- 0.4 m: Towns, villages, agricultural land with many or high hedges, forests and very rough and uneven terrain
- 0.6 m: Large towns with tall buildings
- 1.0 m: Urban areas
- 1.6 m: Large cities with very tall buildings
d : float or list of floats, optional
Zero-plane displacement height, [m]. Default is 0.0 (no displacement).
This value accounts for the height at which the wind speed effectively becomes
zero due to obstacles like trees or buildings. Based on [Sharples2023]_:
- 0.0 m: Inland waterbodies (lakes, dams)
- 0.0 m: Irrigated Pasture (e.g., grassland, alfalfa)
- 0.5 m: Irrigated cropping (e.g., wheat, soybeans)
- 0.5 m: Irrigated Sugar (e.g., sugarcane, corn)
- 0.5 m: Urban areas
round_output : bool, optional
If True, rounds output value. If False, it does not round it. Defaults to True.
Returns
-------
ScaleWindSpeedLog
Dataclass with attribute ``v_z2`` containing the scaled wind speed(s)
at the target height z2, [m/s].
Raises
------
TypeError
If input parameters are not of valid numeric types
ValueError
If any parameter values violate physical constraints
Examples
--------
.. code-block:: python
# Scale wind speed to 2m height (default open terrain z0=0.01)
scale_wind_speed_log(v_z1=5.0, z2=2.0)
# Scale wind speed to 2m height over rough terrain
scale_wind_speed_log(v_z1=5.0, z2=2.0, z0=0.1)
# Scale multiple wind speeds to different heights
scale_wind_speed_log(v_z1=[3.0, 5.0], z2=[1.5, 2.5])
# Scale with different surface roughness for each measurement
scale_wind_speed_log(v_z1=[3.0, 5.0], z2=[1.5, 2.5], z0=[0.02, 0.1])
"""
ScaleWindSpeedLogInputs(
v_z1=v_z1,
z2=z2,
z1=z1,
z0=z0,
d=d,
)
v_z1 = np.asarray(v_z1, dtype=float)
z2 = np.asarray(z2, dtype=float)
z0 = np.asarray(z0, dtype=float)
d = np.asarray(d, dtype=float)
z1 = np.asarray(z1, dtype=float)
# Use numpy.log to support array inputs/broadcasting
with np.errstate(divide="raise", invalid="raise", over="raise", under="ignore"):
v_z2 = v_z1 * np.log((z2 - d) / z0) / np.log((z1 - d) / z0)
if round_output:
v_z2 = np.around(v_z2, 2)
# Return a dataclass instance for consistent API
return ScaleWindSpeedLog(v_z2=v_z2)