|
9 | 9 |
|
10 | 10 | import numpy as np |
11 | 11 | import pandas as pd |
| 12 | +from windpowerlib import tools |
| 13 | +from matplotlib import pyplot as plt |
| 14 | +import os |
12 | 15 |
|
13 | 16 |
|
14 | 17 | 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, |
242 | 245 | else: |
243 | 246 | power_output = np.array(power_output) |
244 | 247 | 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 |
0 commit comments