Skip to content

Commit 1c2e603

Browse files
committed
Merge branch 'features/wind_farms_and_clusters' into dev
2 parents 44a7f75 + d24bb3f commit 1c2e603

File tree

7 files changed

+230
-240
lines changed

7 files changed

+230
-240
lines changed

windpowerlib/data/wind_efficiency_curves.csv

Lines changed: 52 additions & 167 deletions
Large diffs are not rendered by default.

windpowerlib/modelchain.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,8 @@ def wind_speed_hub(self, weather_df):
310310
else:
311311
raise ValueError("'{0}' is an invalid value. ".format(
312312
self.wind_speed_model) + "`wind_speed_model` must be "
313-
"'logarithmic', 'hellman' or "
314-
"'interpolation_extrapolation'.")
313+
"'logarithmic', 'hellman', 'interpolation_extrapolation' " +
314+
"or 'log_interpolation_extrapolation'.")
315315
return wind_speed_hub
316316

317317
def turbine_power_output(self, wind_speed_hub, density_hub):

windpowerlib/power_curves.py

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
import numpy as np
1111
import pandas as pd
1212
from windpowerlib import tools
13+
import os
1314

1415

1516
def smooth_power_curve(power_curve_wind_speeds, power_curve_values,
16-
block_width=0.5,
17+
block_width=0.5, wind_speed_range=15.0,
1718
standard_deviation_method='turbulence_intensity',
1819
mean_gauss=0, **kwargs):
19-
# TODO: All functions in this module have to work without pandas
2020
r"""
2121
Smoothes the input power curve values by using a gaussian distribution.
2222
@@ -32,7 +32,7 @@ def smooth_power_curve(power_curve_wind_speeds, power_curve_values,
3232
Width of the moving block. Default: 0.5.
3333
standard_deviation_method : String
3434
Method for calculating the standard deviation for the gaussian
35-
distribution. Options: 'turbulence_intensity', 'Norgaard', 'Staffell'.
35+
distribution. Options: 'turbulence_intensity', 'Norgaard', 'Staffell_Pfenninger'.
3636
Default: 'turbulence_intensity'.
3737
3838
Other Parameters
@@ -70,30 +70,28 @@ def smooth_power_curve(power_curve_wind_speeds, power_curve_values,
7070
raise ValueError("Turbulence intensity must be defined for " +
7171
"using 'turbulence_intensity' as " +
7272
"`standard_deviation_method`")
73-
elif standard_deviation_method == 'Norgaard':
74-
pass # TODO add
75-
elif standard_deviation_method == 'Staffell':
73+
elif standard_deviation_method == 'Staffell_Pfenninger':
7674
normalized_standard_deviation = 0.2
7775
# Initialize list for power curve values
7876
smoothed_power_curve_values = []
79-
# Step of power curve wind speeds
80-
step = power_curve_wind_speeds.iloc[-5] - power_curve_wind_speeds.iloc[-6]
81-
# Append wind speeds to `power_curve_wind_speeds` until 40 m/s
82-
while (power_curve_wind_speeds.values[-1] < 40.0):
77+
# Append wind speeds to `power_curve_wind_speeds` until last value + range
78+
maximum_value = power_curve_wind_speeds.values[-1] + wind_speed_range
79+
while (power_curve_wind_speeds.values[-1] < maximum_value):
8380
power_curve_wind_speeds = power_curve_wind_speeds.append(
84-
pd.Series(power_curve_wind_speeds.iloc[-1] + step,
81+
pd.Series(power_curve_wind_speeds.iloc[-1] + 0.5,
8582
index=[power_curve_wind_speeds.index[-1] + 1]))
8683
power_curve_values = power_curve_values.append(
8784
pd.Series(0.0, index=[power_curve_values.index[-1] + 1]))
8885
for power_curve_wind_speed in power_curve_wind_speeds:
8986
# Create array of wind speeds for the moving block
90-
wind_speeds_block = (
91-
np.arange(-15.0, 15.0 + block_width, block_width) +
92-
power_curve_wind_speed)
87+
wind_speeds_block = (np.arange(-wind_speed_range,
88+
wind_speed_range + block_width,
89+
block_width) +
90+
power_curve_wind_speed)
9391
# Get standard deviation for gaussian filter
9492
standard_deviation = (
9593
(power_curve_wind_speed * normalized_standard_deviation + 0.6)
96-
if standard_deviation_method is 'Staffell'
94+
if standard_deviation_method is 'Staffell_Pfenninger'
9795
else power_curve_wind_speed * normalized_standard_deviation)
9896
# Get the smoothed value of the power output
9997
smoothed_value = sum(
@@ -113,20 +111,20 @@ def smooth_power_curve(power_curve_wind_speeds, power_curve_values,
113111
smoothed_power_curve_values]).transpose()
114112
# Rename columns of DataFrame
115113
smoothed_power_curve_df.columns = ['wind_speed', 'power']
116-
# # Plot power curves
117-
# fig = plt.figure()
118-
# plt.plot(power_curve_wind_speeds.values, power_curve_values.values)
119-
# plt.plot(power_curve_wind_speeds.values, smoothed_power_curve_values)
120-
# fig.savefig(os.path.abspath(os.path.join(
121-
# os.path.dirname(__file__), '../Plots/power_curves',
122-
# '{0}_{1}_{2}.png'.format(kwargs['object_name'],
123-
# standard_deviation_method, block_width))))
124-
# plt.close() # TODO: delete plot later
114+
# # Plot power curves
115+
# fig = plt.figure()
116+
# plt.plot(power_curve_wind_speeds.values, power_curve_values.values)
117+
# plt.plot(power_curve_wind_speeds.values, smoothed_power_curve_values)
118+
# fig.savefig(os.path.abspath(os.path.join(
119+
# os.path.dirname(__file__), '../Plots/power_curves',
120+
# '{0}_{1}_{2}.png'.format(kwargs['object_name'],
121+
# standard_deviation_method, block_width))))
122+
# plt.close() # TODO: delete plot later
125123
return smoothed_power_curve_df
126124

127125

128126
def wake_losses_to_power_curve(power_curve_wind_speeds, power_curve_values,
129-
wake_losses_method='constant_efficiency',
127+
wake_losses_method='wind_efficiency_curve',
130128
wind_farm_efficiency=None):
131129
r"""
132130
Applies wake losses depending on the method to a power curve.
@@ -141,7 +139,8 @@ def wake_losses_to_power_curve(power_curve_wind_speeds, power_curve_values,
141139
`power_curve_wind_speeds`.
142140
wake_losses_method : String
143141
Defines the method for talking wake losses within the farm into
144-
consideration. Default: 'constant_efficiency'.
142+
consideration. Options: 'wind_efficiency_curve', 'constant_efficiency'.
143+
Default: 'wind_efficiency_curve'.
145144
wind_farm_efficiency : Float or pd.DataFrame or Dictionary
146145
Efficiency of the wind farm. Either constant (float) or wind efficiency
147146
curve (pd.DataFrame or Dictionary) contianing 'wind_speed' and
@@ -151,8 +150,9 @@ def wake_losses_to_power_curve(power_curve_wind_speeds, power_curve_values,
151150
Returns
152151
-------
153152
power_curve_df : pd.DataFrame
154-
With wind farm efficiency reduced power curve. DataFrame power curve
155-
values in W with the corresponding wind speeds in m/s.
153+
With wind farm efficiency reduced power curve. DataFrame has
154+
'wind_speed' and 'power' columns with wind speeds in m/s and the
155+
corresponding power curve value in W.
156156
157157
Notes
158158
-----
@@ -188,7 +188,85 @@ def wake_losses_to_power_curve(power_curve_wind_speeds, power_curve_values,
188188
power_curve_df.columns = ['wind_speed', 'power']
189189
else:
190190
raise ValueError(
191-
"`wake_losses_method` is {0} but should be None, ".format(
191+
"`wake_losses_method` is {0} but should be ".format(
192192
wake_losses_method) +
193193
"'constant_efficiency' or 'wind_efficiency_curve'")
194194
return power_curve_df
195+
196+
197+
def density_correct_power_curve(density, power_curve_wind_speeds,
198+
power_curve_values):
199+
r"""
200+
Applies a density correction to a power curve.
201+
202+
As site specific density a mean density has to be given as parameter.
203+
204+
Parameters
205+
----------
206+
density : float
207+
Mean density of air at hub height in kg/m³.
208+
power_curve_wind_speeds : pandas.Series or numpy.array
209+
Wind speeds in m/s for which the power curve values are provided in
210+
`power_curve_values`.
211+
power_curve_values : pandas.Series or numpy.array
212+
Power curve values corresponding to wind speeds in
213+
`power_curve_wind_speeds`.
214+
215+
Returns
216+
-------
217+
power_curve_df : pd.DataFrame
218+
Density corrected power curve. DataFrame has 'wind_speed' and 'power'
219+
columns with wind speeds in m/s and the corresponding power curve
220+
value in W.
221+
222+
Notes
223+
-----
224+
The following equation is used for the site specific power curve wind
225+
speeds [1]_, [2]_, [3]_:
226+
227+
.. math:: v_{site}=v_{std}\cdot\left(\frac{\rho_0}
228+
{\rho_{site}}\right)^{p(v)}
229+
230+
with:
231+
.. math:: p=\begin{cases}
232+
\frac{1}{3} & v_{std} \leq 7.5\text{ m/s}\\
233+
\frac{1}{15}\cdot v_{std}-\frac{1}{6} & 7.5
234+
\text{ m/s}<v_{std}<12.5\text{ m/s}\\
235+
\frac{2}{3} & \geq 12.5 \text{ m/s}
236+
\end{cases},
237+
v: wind speed [m/s], :math:`\rho`: density [kg/m³]
238+
239+
:math:`v_{std}` is the standard wind speed in the power curve
240+
(:math:`v_{std}`, :math:`P_{std}`),
241+
:math:`v_{site}` is the density corrected wind speed for the power curve
242+
(:math:`v_{site}`, :math:`P_{std}`),
243+
:math:`\rho_0` is the ambient density (1.225 kg/m³)
244+
and :math:`\rho_{site}` the density at site conditions (and hub height).
245+
246+
It is assumed that the power output for wind speeds above the maximum
247+
and below the minimum wind speed given in the power curve is zero.
248+
249+
References
250+
----------
251+
.. [1] Svenningsen, L.: "Power Curve Air Density Correction And Other
252+
Power Curve Options in WindPRO". 1st edition, Aalborg,
253+
EMD International A/S , 2010, p. 4
254+
.. [2] Svenningsen, L.: "Proposal of an Improved Power Curve Correction".
255+
EMD International A/S , 2010
256+
.. [3] Biank, M.: "Methodology, Implementation and Validation of a
257+
Variable Scale Simulation Model for Windpower based on the
258+
Georeferenced Installation Register of Germany". Master's Thesis
259+
at Reiner Lemoine Institute, 2014, p. 13
260+
261+
"""
262+
if density is None:
263+
raise TypeError("`density` is None. For the calculation with a " +
264+
"density corrected power curve mean density at hub " +
265+
"height is needed.")
266+
corrected_power_curve_wind_speeds = (
267+
power_curve_wind_speeds * (1.225 / density) ** (
268+
np.interp(power_curve_wind_speeds, [7.5, 12.5], [1/3, 2/3])))
269+
power_curve_df = pd.DataFrame([corrected_power_curve_wind_speeds,
270+
power_curve_values]).transpose()
271+
power_curve_df.columns = ['wind_speed', 'power']
272+
return power_curve_df

windpowerlib/power_output.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ def power_curve_density_correction(wind_speed, power_curve_wind_speeds,
188188
189189
Notes
190190
-----
191-
The following equation is used for the wind speed at site
192-
[1]_, [2]_, [3]_:
191+
The following equation is used for the site specific power curve wind
192+
speeds [1]_, [2]_, [3]_:
193193
194194
.. math:: v_{site}=v_{std}\cdot\left(\frac{\rho_0}
195195
{\rho_{site}}\right)^{p(v)}

windpowerlib/turbine_cluster_modelchain.py

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class TurbineClusterModelChain(object):
2727
before the summation. Default: False.
2828
wake_losses_method : String
2929
Defines the method for talking wake losses within the farm into
30-
consideration. Default: 'constant_efficiency'.
30+
consideration. Options: 'wind_efficiency_curve', 'constant_efficiency'
31+
or None. Default: 'wind_efficiency_curve'.
3132
smoothing : Boolean
3233
If True the power curves will be smoothed before the summation.
3334
Default: True.
@@ -36,7 +37,8 @@ class TurbineClusterModelChain(object):
3637
Default in :py:func:`~.power_curves.smooth_power_curve`: 0.5.
3738
standard_deviation_method : String, optional
3839
Method for calculating the standard deviation for the gaussian
39-
distribution. Options: 'turbulence_intensity', 'Norgaard', 'Staffell'.
40+
distribution. Options: 'turbulence_intensity', 'Norgaard',
41+
'Staffell_Pfenninger'.
4042
Default in :py:func:`~.power_curves.smooth_power_curve`:
4143
'turbulence_intensity'.
4244
density_correction_order : String
@@ -61,7 +63,8 @@ class TurbineClusterModelChain(object):
6163
before the summation. Default: False.
6264
wake_losses_method : String
6365
Defines the method for talking wake losses within the farm into
64-
consideration. Default: 'constant_efficiency'.
66+
consideration. Options: 'wind_efficiency_curve', 'constant_efficiency'
67+
or None. Default: 'wind_efficiency_curve'.
6568
smoothing : Boolean
6669
If True the power curves will be smoothed before the summation.
6770
Default: True.
@@ -70,7 +73,8 @@ class TurbineClusterModelChain(object):
7073
Default in :py:func:`~.power_curves.smooth_power_curve`: 0.5.
7174
standard_deviation_method : String, optional
7275
Method for calculating the standard deviation for the gaussian
73-
distribution. Options: 'turbulence_intensity', 'Norgaard', 'Staffell'.
76+
distribution. Options: 'turbulence_intensity', 'Norgaard',
77+
'Staffell_Pfenninger'.
7478
Default in :py:func:`~.power_curves.smooth_power_curve`:
7579
'turbulence_intensity'.
7680
power_output : pandas.Series
@@ -88,7 +92,7 @@ class TurbineClusterModelChain(object):
8892
8993
"""
9094
def __init__(self, wind_object, density_correction=False,
91-
wake_losses_method='constant_efficiency', smoothing=True,
95+
wake_losses_method='wind_efficiency_curve', smoothing=True,
9296
block_width=0.5,
9397
standard_deviation_method='turbulence_intensity',
9498
density_correction_order='wind_farm_power_curves',
@@ -114,9 +118,6 @@ def __init__(self, wind_object, density_correction=False,
114118
"cluster but `wind_object` is an object of the " +
115119
"class WindFarm.")
116120

117-
# TODO: if a wind turbine of wind farm does not have a power curve but a cp curve:
118-
# calculate power curve from cp curve
119-
120121
def wind_farm_power_curve(self, wind_farm, **kwargs):
121122
r"""
122123
Caluclates the power curve of a wind farm.
@@ -125,6 +126,20 @@ def wind_farm_power_curve(self, wind_farm, **kwargs):
125126
and/or density corrected before or after the summation and/or a wind
126127
farm efficiency is applied after the summation. TODO: check entry
127128
129+
Parameters
130+
----------
131+
wind_farm : WindFarm
132+
A :class:`~.wind_farm.WindFarm` object representing the wind farm.
133+
134+
# density_df : pandas.DataFrame
135+
# DataFrame with time series for density `density` in kg/m³.
136+
# The columns of the DataFrame are a MultiIndex where the first level
137+
# contains the variable name (density) and the second level
138+
# contains the height at which it applies (e.g. 10, if it was
139+
# measured at a height of 10 m).
140+
# See :py:func:`~.run_model()` for an example on how to
141+
# create the density_df DataFrame.
142+
128143
Other Parameters
129144
----------------
130145
roughness_length : Float, optional.
@@ -157,11 +172,12 @@ def wind_farm_power_curve(self, wind_farm, **kwargs):
157172
raise ValueError(
158173
"`roughness_length` must be defined for using " +
159174
"'turbulence_intensity' as " +
160-
"`standard_deviation_method`")
161-
if self. density_correction:
162-
pass # TODO: restrictions (density needed)
175+
"`standard_deviation_method` if " +
176+
"`turbulence_intensity` is not given")
177+
# if self. density_correction:
178+
# pass # TODO: restrictions (density needed)
163179
if self.wake_losses_method is not None:
164-
if self.wind_object.efficiency is None: # TODO if not...
180+
if wind_farm.efficiency is None:
165181
raise KeyError(
166182
"wind_farm_efficiency is needed if " +
167183
"`wake_losses_method´ is '{0}', but ".format(
@@ -173,13 +189,17 @@ def wind_farm_power_curve(self, wind_farm, **kwargs):
173189
power_curve = pd.DataFrame(
174190
turbine_type_dict['wind_turbine'].power_curve)
175191
# Editions to power curve before the summation
176-
if (self.density_correction and
177-
self.density_correction_order == 'turbine_power_curves'):
178-
pass # TODO: add density correction
192+
# if (self.density_correction and
193+
# self.density_correction_order == 'turbine_power_curves'):
194+
# power_curve = power_curves.density_correct_power_curve(
195+
# density_df, power_curve['wind_speed'], # Note: density at hub height has to be passed
196+
# power_curve['power'])
179197
if (self.smoothing and
180198
self.smoothing_order == 'turbine_power_curves'):
181199
power_curve = power_curves.smooth_power_curve(
182-
power_curve['wind_speed'], power_curve['power'], **kwargs)
200+
power_curve['wind_speed'], power_curve['power'],
201+
standard_deviation_method=self.standard_deviation_method,
202+
**kwargs)
183203
# Add power curves of all turbines of same type to data frame after
184204
# renaming columns
185205
power_curve.columns = ['wind_speed', turbine_type_dict[
@@ -199,14 +219,19 @@ def wind_farm_power_curve(self, wind_farm, **kwargs):
199219
list(summarized_power_curve['power'].values)]).transpose()
200220
summarized_power_curve_df.columns = ['wind_speed', 'power']
201221
# Editions to power curve after the summation
202-
if (self.density_correction and
203-
self.density_correction_order == 'wind_farm_power_curves'):
204-
pass # TODO: add density correction
222+
# if (self.density_correction and
223+
# self.density_correction_order == 'wind_farm_power_curves'):
224+
# summarized_power_curve_df = (
225+
# power_curves.density_correct_power_curve(
226+
# density_df, summarized_power_curve_df['wind_speed'],
227+
# summarized_power_curve_df['power'])) # Note: density at hub height has to be passed
205228
if (self.smoothing and
206229
self.smoothing_order == 'wind_farm_power_curves'):
207230
summarized_power_curve_df = power_curves.smooth_power_curve(
208231
summarized_power_curve_df['wind_speed'],
209-
summarized_power_curve_df['power'], **kwargs)
232+
summarized_power_curve_df['power'],
233+
standard_deviation_method=self.standard_deviation_method,
234+
**kwargs)
210235
if (self.wake_losses_method == 'constant_efficiency' or
211236
self.wake_losses_method == 'wind_efficiency_curve'):
212237
summarized_power_curve_df = (
@@ -406,8 +431,12 @@ def run_model(self, weather_df, **kwargs):
406431
modelchain_data = self.get_modelchain_data(**kwargs)
407432
# Density correction to cluster power curve # TODO test
408433
if (self.density_correction and
409-
self.density_correction_order == 'cluster_power_curve'):
434+
self.density_correction_order == 'wind_farm_power_curves'):
435+
# Note actually for 'cluster_power_curve'. however, probably the
436+
# density correcton will always be done at the end
410437
modelchain_data['density_correction'] = True
438+
else:
439+
modelchain_data['density_correction'] = False
411440
# Run modelchain
412441
mc = modelchain.ModelChain(
413442
self.wind_object, **modelchain_data).run_model(weather_df)

0 commit comments

Comments
 (0)