Skip to content

Commit afe8054

Browse files
committed
Merge branch 'features/multi_turbine_power_curve' into features/wind_farms_and_clusters
2 parents b9c9e19 + aa209b4 commit afe8054

File tree

4 files changed

+653
-0
lines changed

4 files changed

+653
-0
lines changed

windpowerlib/power_output.py

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
import numpy as np
1111
import pandas as pd
12+
from windpowerlib import tools
13+
from matplotlib import pyplot as plt
14+
import os
1215

1316

1417
def power_coefficient_curve(wind_speed, power_coefficient_curve_wind_speeds,
@@ -242,3 +245,305 @@ def power_curve_density_correction(wind_speed, power_curve_wind_speeds,
242245
else:
243246
power_output = np.array(power_output)
244247
return power_output
248+
249+
250+
def smooth_power_curve(power_curve_wind_speeds, power_curve_values,
251+
block_width=0.5,
252+
standard_deviation_method='turbulence_intensity',
253+
mean_gauss=0, **kwargs):
254+
r"""
255+
Smoothes the input power curve values by using a gaussian distribution.
256+
257+
Parameters
258+
----------
259+
power_curve_wind_speeds : pandas.Series
260+
Wind speeds in m/s for which the power curve values are provided in
261+
`power_curve_values`.
262+
power_curve_values : pandas.Series or numpy.array
263+
Power curve values corresponding to wind speeds in
264+
`power_curve_wind_speeds`.
265+
block_width : Float
266+
Width of the moving block. Default: 0.5.
267+
standard_deviation_method : String
268+
Method for calculating the standard deviation for the gaussian
269+
distribution. Options: 'turbulence_intensity', 'Norgaard', 'Staffell'.
270+
Default: 'turbulence_intensity'.
271+
272+
Other Parameters
273+
----------------
274+
turbulence intensity : Float, optional
275+
Turbulence intensity at hub height of the wind turbine the power curve
276+
is smoothed for.
277+
278+
Returns
279+
-------
280+
smoothed_power_curve_df : pd.DataFrame
281+
Smoothed power curve. DataFrame has 'wind_speed' and
282+
'power' columns with wind speeds in m/s and the corresponding power
283+
curve value in W.
284+
285+
Notes
286+
-----
287+
The following equation is used [1]_:
288+
# TODO: add equations
289+
290+
References
291+
----------
292+
.. [1] Knorr, K.: "Modellierung von raum-zeitlichen Eigenschaften der
293+
Windenergieeinspeisung für wetterdatenbasierte
294+
Windleistungssimulationen". Universität Kassel, Diss., 2016,
295+
p. 106
296+
297+
# TODO: add references
298+
"""
299+
# Specify normalized standard deviation
300+
if standard_deviation_method == 'turbulence_intensity':
301+
if 'turbulence_intensity' in kwargs:
302+
normalized_standard_deviation = kwargs['turbulence_intensity']
303+
else:
304+
raise ValueError("Turbulence intensity must be defined for " +
305+
"using 'turbulence_intensity' as " +
306+
"`standard_deviation_method`")
307+
elif standard_deviation_method == 'Norgaard':
308+
pass # TODO add
309+
elif standard_deviation_method == 'Staffell':
310+
normalized_standard_deviation = 0.2
311+
# Initialize list for power curve values
312+
smoothed_power_curve_values = []
313+
# Step of power curve wind speeds
314+
step = power_curve_wind_speeds.iloc[-5] - power_curve_wind_speeds.iloc[-6]
315+
# Append wind speeds to `power_curve_wind_speeds` until 40 m/s
316+
while (power_curve_wind_speeds.values[-1] < 40.0):
317+
power_curve_wind_speeds = power_curve_wind_speeds.append(
318+
pd.Series(power_curve_wind_speeds.iloc[-1] + step,
319+
index=[power_curve_wind_speeds.index[-1] + 1]))
320+
power_curve_values = power_curve_values.append(
321+
pd.Series(0.0, index=[power_curve_values.index[-1] + 1]))
322+
for power_curve_wind_speed in power_curve_wind_speeds:
323+
# Create array of wind speeds for the moving block
324+
wind_speeds_block = (
325+
np.arange(-15.0, 15.0 + block_width, block_width) +
326+
power_curve_wind_speed)
327+
# Get standard deviation for gaussian filter
328+
standard_deviation = (
329+
(power_curve_wind_speed * normalized_standard_deviation + 0.6)
330+
if standard_deviation_method is 'Staffell'
331+
else power_curve_wind_speed * normalized_standard_deviation)
332+
# Get the smoothed value of the power output
333+
smoothed_value = sum(
334+
block_width * np.interp(wind_speed, power_curve_wind_speeds,
335+
power_curve_values, left=0, right=0) *
336+
tools.gaussian_distribution(
337+
power_curve_wind_speed - wind_speed,
338+
standard_deviation, mean_gauss)
339+
for wind_speed in wind_speeds_block)
340+
# Add value to list - add 0 if `smoothed_value` is nan. This occurs
341+
# because the gaussian distribution is not defined for 0.
342+
smoothed_power_curve_values.append(0 if np.isnan(smoothed_value)
343+
else smoothed_value)
344+
# Create smoothed power curve DataFrame
345+
smoothed_power_curve_df = pd.DataFrame(
346+
data=[list(power_curve_wind_speeds.values),
347+
smoothed_power_curve_values]).transpose()
348+
# Rename columns of DataFrame
349+
smoothed_power_curve_df.columns = ['wind_speed', 'power']
350+
# # Plot power curves
351+
# fig = plt.figure()
352+
# plt.plot(power_curve_wind_speeds.values, power_curve_values.values)
353+
# plt.plot(power_curve_wind_speeds.values, smoothed_power_curve_values)
354+
# fig.savefig(os.path.abspath(os.path.join(
355+
# os.path.dirname(__file__), '../Plots/power_curves',
356+
# '{0}_{1}_{2}.png'.format(kwargs['object_name'],
357+
# standard_deviation_method, block_width))))
358+
# plt.close()
359+
return smoothed_power_curve_df
360+
361+
362+
def wake_losses_to_power_curve(power_curve_wind_speeds, power_curve_values,
363+
wake_losses_method='constant_efficiency',
364+
wind_farm_efficiency=None):
365+
r"""
366+
Applies wake losses depending on the method to a power curve.
367+
368+
Parameters
369+
----------
370+
power_curve_wind_speeds : pandas.Series
371+
Wind speeds in m/s for which the power curve values are provided in
372+
`power_curve_values`.
373+
power_curve_values : pandas.Series or numpy.array
374+
Power curve values corresponding to wind speeds in
375+
`power_curve_wind_speeds`.
376+
wake_losses_method : String
377+
Defines the method for talking wake losses within the farm into
378+
consideration. Default: 'constant_efficiency'.
379+
wind_farm_efficiency : Float or pd.DataFrame or Dictionary
380+
Efficiency of the wind farm. Either constant (float) or wind efficiency
381+
curve (pd.DataFrame or Dictionary) contianing 'wind_speed' and
382+
'efficiency' columns/keys with wind speeds in m/s and the
383+
corresponding dimensionless wind farm efficiency. Default: None.
384+
385+
Returns
386+
-------
387+
power_curve_df : pd.DataFrame
388+
With wind farm efficiency reduced power curve. DataFrame power curve
389+
values in W with the corresponding wind speeds in m/s.
390+
391+
Notes
392+
-----
393+
TODO add
394+
395+
"""
396+
# Create power curve DataFrame
397+
power_curve_df = pd.DataFrame(
398+
data=[list(power_curve_wind_speeds),
399+
list(power_curve_values)]).transpose()
400+
# Rename columns of DataFrame
401+
power_curve_df.columns = ['wind_speed', 'power']
402+
if wake_losses_method == 'constant_efficiency':
403+
if not isinstance(wind_farm_efficiency, float):
404+
raise TypeError("'wind_farm_efficiency' must be float if " +
405+
"`wake_losses_method´ is '{0}'")
406+
power_curve_df['power'] = power_curve_values * wind_farm_efficiency
407+
elif wake_losses_method == 'wind_efficiency_curve':
408+
if (not isinstance(wind_farm_efficiency, dict) and
409+
not isinstance(wind_farm_efficiency, pd.DataFrame)):
410+
raise TypeError(
411+
"'wind_farm_efficiency' must be a dictionary or " +
412+
"pd.DataFrame if `wake_losses_method´ is '{0}'")
413+
df = pd.concat([power_curve_df.set_index('wind_speed'),
414+
wind_farm_efficiency.set_index('wind_speed')], axis=1)
415+
# Add by efficiency reduced power column (nan values of efficiency
416+
# are interpolated)
417+
df['reduced_power'] = df['power'] * df['efficiency'].interpolate(
418+
method='index')
419+
reduced_power = df['reduced_power'].dropna()
420+
power_curve_df = pd.DataFrame([reduced_power.index,
421+
reduced_power.values]).transpose()
422+
power_curve_df.columns = ['wind_speed', 'power']
423+
else:
424+
raise ValueError(
425+
"`wake_losses_method` is {0} but should be None, ".format(
426+
wake_losses_method) +
427+
"'constant_efficiency' or 'wind_efficiency_curve'")
428+
return power_curve_df
429+
430+
431+
def summarized_power_curve(wind_turbine_fleet, smoothing=True,
432+
density_correction=False, wake_losses_method=None,
433+
**kwargs):
434+
r"""
435+
Creates a summarized power curve for a wind turbine fleet.
436+
437+
Power curve is created by summing up all power curves. Depending on the
438+
input paramters the power cuvers are smoothed before the summation and/or
439+
a wind farm efficiency is applied after the summation.
440+
441+
Parameters
442+
----------
443+
wind_turbine_fleet : List of Dictionaries
444+
Dictionaries with the keys 'wind_turbine' (contains
445+
:class:`~.wind_turbine.WindTurbine` object) and 'number_of_turbines'
446+
(contains number of turbine type in 'wind_turbine' key).
447+
smoothing : Boolean
448+
If True the power curves will be smoothed before the summation.
449+
Default: True.
450+
density_correction : Boolean
451+
If True a density correction will be applied to the power curves
452+
before the summation. Default: False.
453+
wake_losses_method : String
454+
Defines the method for talking wake losses within the farm into
455+
consideration. Default: None.
456+
457+
Other Parameters
458+
----------------
459+
block_width : Float, optional
460+
Width of the moving block.
461+
Default in :py:func:`~.smooth_power_curve`: 0.5.
462+
standard_deviation_method : String, optional
463+
Method for calculating the standard deviation for the gaussian
464+
distribution. Options: 'turbulence_intensity', 'Norgaard', 'Staffell'.
465+
Default in :py:func:`~.smooth_power_curve`: 'turbulence_intensity'.
466+
turbulence_intensity : Float, optional
467+
Turbulence intensity at hub height of the wind turbine the power curve
468+
is smoothed for. If this parameter is not given the turbulence
469+
intensity is calculated via the `roughness_length`.
470+
roughness_length : Float, optional
471+
Roughness length. Only needed if `turbulence_intensity` is not given
472+
and `standard_deviation_method` is 'turbulence_intensity' or not given.
473+
wind_farm_efficiency : Float or pd.DataFrame or Dictionary, optional
474+
Efficiency of the wind farm. Either constant (float) or wind efficiency
475+
curve (pd.DataFrame or Dictionary) contianing 'wind_speed' and
476+
'efficiency' columns/keys with wind speeds in m/s and the
477+
corresponding dimensionless wind farm efficiency.
478+
479+
Returns
480+
-------
481+
summarized_power_curve_df : pd.DataFrame
482+
Summarized power curve. DataFrame has 'wind_speed' and
483+
'power' columns with wind speeds in m/s and the corresponding power
484+
curve value in W.
485+
486+
"""
487+
# Initialize data frame for power curve values
488+
df = pd.DataFrame()
489+
for turbine_type_dict in wind_turbine_fleet:
490+
# Start power curve
491+
power_curve = pd.DataFrame(
492+
turbine_type_dict['wind_turbine'].power_curve)
493+
if smoothing:
494+
if ('standard_deviation_method' not in kwargs or
495+
kwargs['standard_deviation_method'] ==
496+
'turbulence_intensity'):
497+
if 'turbulence_intensity' not in kwargs:
498+
if 'roughness_length' in kwargs:
499+
# Calculate turbulence intensity and write to kwargs
500+
turbulence_intensity = (
501+
tools.estimate_turbulence_intensity(
502+
turbine_type_dict['wind_turbine'].hub_height,
503+
kwargs['roughness_length']))
504+
kwargs['turbulence_intensity'] = turbulence_intensity
505+
else:
506+
raise ValueError(
507+
"`roughness_length` must be defined for using" +
508+
"'turbulence_intensity' as " +
509+
"`standard_deviation_method`")
510+
# Get smoothed power curve
511+
power_curve = smooth_power_curve(power_curve['wind_speed'],
512+
power_curve['power'], **kwargs)
513+
if density_correction:
514+
pass # TODO: add
515+
# Add power curves of all turbines of same type to data frame after
516+
# renaming columns
517+
power_curve.columns = ['wind_speed',
518+
turbine_type_dict['wind_turbine'].object_name]
519+
df = pd.concat([df, pd.DataFrame(
520+
power_curve.set_index(['wind_speed']) *
521+
turbine_type_dict['number_of_turbines'])], axis=1)
522+
# Rename back TODO: copy()
523+
power_curve.columns = ['wind_speed', 'power']
524+
# Sum up power curves of all turbine types
525+
summarized_power_curve = pd.DataFrame(
526+
sum(df[item].interpolate(method='index') for item in list(df)))
527+
summarized_power_curve.columns = ['power']
528+
# Take wake losses into consideration if `wake_losses_method` not None
529+
if wake_losses_method is None:
530+
# Create DataFrame of the above power curve data
531+
summarized_power_curve_df = pd.DataFrame(
532+
data=[list(summarized_power_curve.index),
533+
list(summarized_power_curve['power'].values)]).transpose()
534+
# Rename columns of DataFrame
535+
summarized_power_curve_df.columns = ['wind_speed', 'power']
536+
elif (wake_losses_method == 'constant_efficiency' or
537+
wake_losses_method == 'wind_efficiency_curve'):
538+
try:
539+
kwargs['wind_farm_efficiency']
540+
except KeyError:
541+
raise KeyError("'wind_farm_efficiency' must be in kwargs when " +
542+
"`wake_losses_method´ is '{0}'".format(
543+
wake_losses_method))
544+
summarized_power_curve_df = wake_losses_to_power_curve(
545+
summarized_power_curve.index,
546+
summarized_power_curve['power'].values,
547+
wake_losses_method=wake_losses_method,
548+
wind_farm_efficiency=kwargs['wind_farm_efficiency'])
549+
return summarized_power_curve_df

windpowerlib/tools.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,53 @@ def linear_interpolation_extrapolation(df, target_height):
7171
return ((df[heights_sorted[1]] - df[heights_sorted[0]]) /
7272
(heights_sorted[1] - heights_sorted[0]) *
7373
(target_height - heights_sorted[0]) + df[heights_sorted[0]])
74+
75+
76+
def gaussian_distribution(function_variable, standard_deviation, mean=0):
77+
r"""
78+
Normal distribution or gaussian distribution.
79+
80+
Parameters
81+
----------
82+
function_variable : Float
83+
Variable of the gaussian distribution.
84+
standard_deviation : Float
85+
Standard deviation of the gaussian distribution.
86+
mean : Float
87+
Defines the offset of the gaussian distribution function. Default: 0.
88+
89+
Returns
90+
-------
91+
pandas.Series or numpy.array
92+
Wind speed at hub height. Data type depends on type of `wind_speed`.
93+
94+
Notes
95+
-----
96+
The following equation is used [1]_:
97+
98+
.. math:: f(x) = \frac{1}{\sigma \sqrt{2 \pi}} exp
99+
\left[ -\frac{(x-\mu)^2}{2 \sigma^2} \right]
100+
101+
with:
102+
# TODO: add variables
103+
104+
References
105+
----------
106+
.. [1] Berendsen, H.: "A Student's Guide to Data and Error Analysis".
107+
New York, Cambridge University Press, 2011, p. 37
108+
109+
# TODO: add references
110+
111+
"""
112+
return (1 / (standard_deviation * np.sqrt(2 * np.pi)) *
113+
np.exp(-(function_variable - mean)**2 /
114+
(2 * standard_deviation**2)))
115+
116+
117+
def estimate_turbulence_intensity(height, roughness_length):
118+
"""
119+
Calculate turbulence intensity.
120+
121+
"""
122+
# TODO: Search other possibilities for TI.
123+
return 1 / (np.log(height / roughness_length))

0 commit comments

Comments
 (0)