"""
The ``modelchain`` module contains functions and classes of the
windpowerlib. This module makes it easy to get started with the windpowerlib
and demonstrates standard ways to use the library.
SPDX-FileCopyrightText: 2019 oemof developer group <contact@oemof.org>
SPDX-License-Identifier: MIT
"""
import logging
from windpowerlib import (
wind_speed,
density,
temperature,
power_output,
tools,
data,
)
[docs]
class ModelChain(object):
r"""Model to determine the output of a wind turbine
The ModelChain class provides a standardized, high-level
interface for all of the modeling steps necessary for calculating wind
turbine power output from weather time series inputs.
Parameters
----------
power_plant : :class:`~.wind_turbine.WindTurbine`
A :class:`~.wind_turbine.WindTurbine` object representing the wind
turbine.
wind_speed_model : str
Parameter to define which model to use to calculate the wind speed at
hub height. Valid options are:
* 'logarithmic' -
See :func:`~.wind_speed.logarithmic_profile` for more information.
The parameter `obstacle_height` can be used to set the height of
obstacles in the surrounding area of the wind turbine.
* 'hellman' -
See :func:`~.wind_speed.hellman` for more information.
* 'interpolation_extrapolation' -
See :func:`~.tools.linear_interpolation_extrapolation` for more
information.
* 'log_interpolation_extrapolation' -
See :func:`~.tools.logarithmic_interpolation_extrapolation` for more
information.
Default: 'logarithmic'.
temperature_model : str
Parameter to define which model to use to calculate the temperature of
air at hub height. Valid options are:
* 'linear_gradient' -
See :func:`~.temperature.linear_gradient` for more
information.
* 'interpolation_extrapolation' -
See :func:`~.tools.linear_interpolation_extrapolation` for more
information.
Default: 'linear_gradient'.
density_model : str
Parameter to define which model to use to calculate the density of air
at hub height. Valid options are:
* 'barometric' -
See :func:`~.density.barometric` for more information.
* 'ideal_gas' -
See :func:`~.density.ideal_gas` for more information.
* 'interpolation_extrapolation' -
See :func:`~.tools.linear_interpolation_extrapolation` for more
information.
Default: 'barometric'.
power_output_model : str
Parameter to define which model to use to calculate the turbine power
output. Valid options are:
* 'power_curve' -
See :func:`~.power_output.power_curve` for more information. In order
to use the density corrected power curve to calculate the power
output set parameter `density_correction` to True.
* 'power_coefficient_curve' -
See :func:`~.power_output.power_coefficient_curve` for more
information.
Default: 'power_curve'.
density_correction : bool
This parameter is only used if the parameter `power_output_model` is
'power_curve'. For more information on this parameter see parameter
`density_correction` in :func:`~.power_output.power_curve`.
Default: False.
obstacle_height : float
This parameter is only used if the parameter `wind_speed_model` is
'logarithmic'. For more information on this parameter see parameter
`obstacle_height` in :func:`~.wind_speed.logarithmic`. Default: 0.
hellman_exp : float
This parameter is only used if the parameter `wind_speed_model` is
'hellman'. For more information on this parameter see parameter
`hellman_exponent` in :func:`~.wind_speed.hellman`. Default: None.
Attributes
----------
power_plant : :class:`~.wind_turbine.WindTurbine`
A :class:`~.wind_turbine.WindTurbine` object representing the wind
turbine.
wind_speed_model : str
Defines which model is used to calculate the wind speed at hub height.
temperature_model : str
Defines which model is used to calculate the temperature of air at hub
height.
density_model : str
Defines which model is used to calculate the density of air at hub
height.
power_output_model : str
Defines which model is used to calculate the turbine power output.
density_correction : bool
Used to set `density_correction` parameter in
:func:`~.power_output.power_curve`.
obstacle_height : float
Used to set `obstacle_height` in :func:`~.wind_speed.logarithmic`.
hellman_exp : float
Used to set `hellman_exponent` in :func:`~.wind_speed.hellman`.
power_output : :pandas:`pandas.Series<series>`
Electrical power output of the wind turbine in W.
Examples
--------
>>> from windpowerlib import modelchain
>>> from windpowerlib import wind_turbine
>>> enerconE126={
... 'hub_height': 135,
... 'rotor_diameter': 127,
... 'turbine_type': 'E-126/4200'}
>>> e126=wind_turbine.WindTurbine(**enerconE126)
>>> modelchain_data={'density_model': 'ideal_gas'}
>>> e126_mc=modelchain.ModelChain(e126, **modelchain_data)
>>> print(e126_mc.density_model)
ideal_gas
"""
[docs]
def __init__(
self,
power_plant,
wind_speed_model="logarithmic",
temperature_model="linear_gradient",
density_model="barometric",
power_output_model="power_curve",
density_correction=False,
obstacle_height=0,
hellman_exp=None,
**kwargs,
):
self.power_plant = power_plant
self.obstacle_height = obstacle_height
self.wind_speed_model = wind_speed_model
self.temperature_model = temperature_model
self.density_model = density_model
self.power_output_model = power_output_model
self.density_correction = density_correction
self.hellman_exp = hellman_exp
self.power_output = None
[docs]
def temperature_hub(self, weather_df):
r"""
Calculates the temperature of air at hub height.
The temperature is calculated using the method specified by
the parameter `temperature_model`.
Parameters
----------
weather_df : :pandas:`pandas.DataFrame<frame>`
DataFrame with time series for temperature `temperature` in K.
The columns of the DataFrame are a MultiIndex where the first level
contains the variable name (e.g. temperature) and the second level
contains the height at which it applies (e.g. 10, if it was
measured at a height of 10 m). See documentation of
:func:`ModelChain.run_model` for an example on how to create the
weather_df DataFrame.
Returns
-------
:pandas:`pandas.Series<series>` or numpy.array
Temperature of air in K at hub height.
Notes
-----
If `weather_df` contains temperatures at different heights the given
temperature(s) closest to the hub height are used.
"""
if self.power_plant.hub_height in weather_df["temperature"]:
temperature_hub = weather_df["temperature"][
self.power_plant.hub_height
]
elif self.temperature_model == "linear_gradient":
logging.debug(
"Calculating temperature using temperature " "gradient."
)
closest_height = weather_df["temperature"].columns[
min(
range(len(weather_df["temperature"].columns)),
key=lambda i: abs(
weather_df["temperature"].columns[i]
- self.power_plant.hub_height
),
)
]
temperature_hub = temperature.linear_gradient(
weather_df["temperature"][closest_height],
closest_height,
self.power_plant.hub_height,
)
elif self.temperature_model == "interpolation_extrapolation":
logging.debug(
"Calculating temperature using linear inter- or "
"extrapolation."
)
temperature_hub = tools.linear_interpolation_extrapolation(
weather_df["temperature"], self.power_plant.hub_height
)
else:
raise ValueError(
"'{0}' is an invalid value. ".format(self.temperature_model)
+ "`temperature_model` must be "
"'linear_gradient' or 'interpolation_extrapolation'."
)
return temperature_hub
[docs]
def density_hub(self, weather_df):
r"""
Calculates the density of air at hub height.
The density is calculated using the method specified by the parameter
`density_model`. Previous to the calculation of the density the
temperature at hub height is calculated using the method specified by
the parameter `temperature_model`.
Parameters
----------
weather_df : :pandas:`pandas.DataFrame<frame>`
DataFrame with time series for temperature `temperature` in K,
pressure `pressure` in Pa and/or density `density` in kg/m³,
depending on the `density_model` used.
The columns of the DataFrame are a MultiIndex where the first level
contains the variable name (e.g. temperature) and the second level
contains the height at which it applies (e.g. 10, if it was
measured at a height of 10 m). See documentation of
:func:`ModelChain.run_model` for an example on how to create the
weather_df DataFrame.
Returns
-------
:pandas:`pandas.Series<series>` or numpy.array
Density of air in kg/m³ at hub height.
Notes
-----
If `weather_df` contains data at different heights the data closest to
the hub height are used.
If `interpolation_extrapolation` is used to calculate the density at
hub height, the `weather_df` must contain at least two time series for
density.
"""
if self.density_model != "interpolation_extrapolation":
temperature_hub = self.temperature_hub(weather_df)
# Calculation of density in kg/m³ at hub height
if self.density_model == "barometric":
logging.debug(
"Calculating density using barometric height " "equation."
)
closest_height = weather_df["pressure"].columns[
min(
range(len(weather_df["pressure"].columns)),
key=lambda i: abs(
weather_df["pressure"].columns[i]
- self.power_plant.hub_height
),
)
]
density_hub = density.barometric(
weather_df["pressure"][closest_height],
closest_height,
self.power_plant.hub_height,
temperature_hub,
)
elif self.density_model == "ideal_gas":
logging.debug("Calculating density using ideal gas equation.")
closest_height = weather_df["pressure"].columns[
min(
range(len(weather_df["pressure"].columns)),
key=lambda i: abs(
weather_df["pressure"].columns[i]
- self.power_plant.hub_height
),
)
]
density_hub = density.ideal_gas(
weather_df["pressure"][closest_height],
closest_height,
self.power_plant.hub_height,
temperature_hub,
)
elif self.density_model == "interpolation_extrapolation":
logging.debug(
"Calculating density using linear inter- or " "extrapolation."
)
density_hub = tools.linear_interpolation_extrapolation(
weather_df["density"], self.power_plant.hub_height
)
else:
raise ValueError(
"'{0}' is an invalid value. ".format(self.density_model)
+ "`density_model` "
+ "must be 'barometric', 'ideal_gas' or "
+ "'interpolation_extrapolation'."
)
return density_hub
[docs]
def wind_speed_hub(self, weather_df):
r"""
Calculates the wind speed at hub height.
The method specified by the parameter `wind_speed_model` is used.
Parameters
----------
weather_df : :pandas:`pandas.DataFrame<frame>`
DataFrame with time series for wind speed `wind_speed` in m/s and
roughness length `roughness_length` in m.
The columns of the DataFrame are a MultiIndex where the first level
contains the variable name (e.g. wind_speed) and the second level
contains the height at which it applies (e.g. 10, if it was
measured at a height of 10 m). See documentation of
:func:`ModelChain.run_model` for an example on how to create the
weather_df DataFrame.
Returns
-------
:pandas:`pandas.Series<series>` or numpy.array
Wind speed in m/s at hub height.
Notes
-----
If `weather_df` contains wind speeds at different heights the given
wind speed(s) closest to the hub height are used.
"""
if self.power_plant.hub_height in weather_df["wind_speed"]:
wind_speed_hub = weather_df["wind_speed"][
self.power_plant.hub_height
]
elif self.wind_speed_model == "logarithmic":
logging.debug(
"Calculating wind speed using logarithmic wind " "profile."
)
closest_height = weather_df["wind_speed"].columns[
min(
range(len(weather_df["wind_speed"].columns)),
key=lambda i: abs(
weather_df["wind_speed"].columns[i]
- self.power_plant.hub_height
),
)
]
wind_speed_hub = wind_speed.logarithmic_profile(
weather_df["wind_speed"][closest_height],
closest_height,
self.power_plant.hub_height,
weather_df["roughness_length"].iloc[:, 0],
self.obstacle_height,
)
elif self.wind_speed_model == "hellman":
logging.debug("Calculating wind speed using hellman equation.")
closest_height = weather_df["wind_speed"].columns[
min(
range(len(weather_df["wind_speed"].columns)),
key=lambda i: abs(
weather_df["wind_speed"].columns[i]
- self.power_plant.hub_height
),
)
]
wind_speed_hub = wind_speed.hellman(
weather_df["wind_speed"][closest_height],
closest_height,
self.power_plant.hub_height,
weather_df["roughness_length"].iloc[:, 0],
self.hellman_exp,
)
elif self.wind_speed_model == "interpolation_extrapolation":
logging.debug(
"Calculating wind speed using linear inter- or "
"extrapolation."
)
wind_speed_hub = tools.linear_interpolation_extrapolation(
weather_df["wind_speed"], self.power_plant.hub_height
)
elif self.wind_speed_model == "log_interpolation_extrapolation":
logging.debug(
"Calculating wind speed using logarithmic inter- or "
"extrapolation."
)
wind_speed_hub = tools.logarithmic_interpolation_extrapolation(
weather_df["wind_speed"], self.power_plant.hub_height
)
else:
raise ValueError(
"'{0}' is an invalid value. ".format(self.wind_speed_model)
+ "`wind_speed_model` must be "
"'logarithmic', 'hellman', 'interpolation_extrapolation' "
+ "or 'log_interpolation_extrapolation'."
)
return wind_speed_hub
[docs]
def calculate_power_output(self, wind_speed_hub, density_hub):
r"""
Calculates the power output of the wind power plant.
The method specified by the parameter `power_output_model` is used.
Parameters
----------
wind_speed_hub : :pandas:`pandas.Series<series>` or numpy.array
Wind speed at hub height in m/s.
density_hub : :pandas:`pandas.Series<series>` or numpy.array
Density of air at hub height in kg/m³.
Returns
-------
:pandas:`pandas.Series<series>`
Electrical power output of the wind turbine in W.
"""
if self.power_output_model == "power_curve":
if self.power_plant.power_curve is None:
raise TypeError(
"Power curve values of {} are missing.".format(
self.power_plant
)
)
logging.debug("Calculating power output using power curve.")
return power_output.power_curve(
wind_speed_hub,
self.power_plant.power_curve["wind_speed"],
self.power_plant.power_curve["value"],
density_hub,
self.density_correction,
)
elif self.power_output_model == "power_coefficient_curve":
if self.power_plant.power_coefficient_curve is None:
raise TypeError(
"Power coefficient curve values of {} are "
"missing.".format(self.power_plant)
)
logging.debug(
"Calculating power output using power coefficient " "curve."
)
return power_output.power_coefficient_curve(
wind_speed_hub,
self.power_plant.power_coefficient_curve["wind_speed"],
self.power_plant.power_coefficient_curve["value"],
self.power_plant.rotor_diameter,
density_hub,
)
else:
raise ValueError(
"'{0}' is an invalid value. ".format(self.power_output_model)
+ "`power_output_model` must be "
+ "'power_curve' or 'power_coefficient_curve'."
)
[docs]
def run_model(self, weather_df):
r"""
Runs the model.
Parameters
----------
weather_df : :pandas:`pandas.DataFrame<frame>`
DataFrame with time series for wind speed `wind_speed` in m/s, and
roughness length `roughness_length` in m, as well as optionally
temperature `temperature` in K, pressure `pressure` in Pa and
density `density` in kg/m³ depending on `power_output_model` and
`density_model chosen`.
The columns of the DataFrame are a MultiIndex where the first level
contains the variable name (e.g. wind_speed) and the second level
contains the height at which it applies (e.g. 10, if it was
measured at a height of 10 m). See below for an example on how to
create the weather_df DataFrame.
Returns
-------
:class:`~.modelchain.ModelChain`
Examples
---------
>>> import numpy as np
>>> import pandas as pd
>>> my_weather_df = pd.DataFrame(np.random.rand(2,6),
... index=pd.date_range('1/1/2012',
... periods=2,
... freq='H'),
... columns=[np.array(['wind_speed',
... 'wind_speed',
... 'temperature',
... 'temperature',
... 'pressure',
... 'roughness_length']),
... np.array([10, 80, 10, 80,
... 10, 0])])
>>> my_weather_df.columns.get_level_values(0)[0]
'wind_speed'
"""
weather_df = data.check_weather_data(weather_df)
wind_speed_hub = self.wind_speed_hub(weather_df)
density_hub = (
None
if (
self.power_output_model == "power_curve"
and self.density_correction is False
)
else self.density_hub(weather_df)
)
self.power_output = self.calculate_power_output(
wind_speed_hub, density_hub
)
return self