Losses and Uncertainty#

Three steps lead from the gross annual energy production (AEP) of a wind farm to an energy yield estimate. Subtracting the wake losses gives the potential AEP; subtracting the technical losses expected in operation, such as availability and electrical losses, gives the net AEP; and quantifying the uncertainty of that estimate gives exceedance values such as the P90. The wake losses come from a flow model such as PyWAsP. For the other two steps, WindKit keeps losses and uncertainties in tables (pandas.DataFrame) that you can inspect and edit, and provides functions to combine them.

Loss Tables#

windkit.get_loss_table() returns a table of technical losses filled with DTU’s default values. Wake losses are not part of it:

loss_table = wk.get_loss_table("dtu_default")
loss_table[["loss_name", "loss_category", "loss_percentage"]]
loss_name loss_category loss_percentage
0 turbine_availability Availability 1.00
1 balance_plant_availability Availability 0.00
2 grid_availability Availability 0.50
3 electrical_operation Electrical 0.50
4 wf_consumption Electrical 0.20
5 sub_optimal_performance Turbine_performance 0.00
6 power_curve Turbine_performance 1.00
7 hysteresis Turbine_performance 0.00
8 icing Environmental 0.00
9 blade_degradation Environmental 0.25
10 other_environmental_losses Environmental 0.10
11 exposure_changes Environmental 0.00
12 load_grid_curtailments Curtailments 0.50
13 noise_visual_curtailments Curtailments 0.00
14 sector_management Operational_strategies 0.50

Each row is one loss, as a percentage of the energy production. Edit the loss_percentage column to match your project, and check the edited table with windkit.validate_loss_table().

windkit.total_loss_factor() returns the fraction of the energy that remains after all losses, which converts potential to net AEP. The losses compound: each one applies to the energy the others leave, so the factor is \(\prod_i (1 - p_i / 100)\) for loss percentages \(p_i\):

factor = wk.total_loss_factor(loss_table)
print(f"Total loss factor: {factor:.4f}")  # net_aep = potential_aep * factor
Total loss factor: 0.9554

pywasp.net_aep() applies the factor to the potential AEP PyWAsP calculates.

windkit.total_loss() returns the plain sum of the percentages instead, a common way to report losses, which is slightly larger than the compounded loss (1 - factor) * 100. windkit.loss_table_summary() prints both, along with the sum for each category:

wk.loss_table_summary(loss_table)
Loss Summary:
Total value of losses for Availability: 1.5 %
Total value of losses for Curtailments: 0.5 %
Total value of losses for Electrical: 0.7 %
Total value of losses for Environmental: 0.35 %
Total value of losses for Operational_strategies: 0.5 %
Total value of losses for Turbine_performance: 1.0 %
Total value of all losses: 4.550000000000001 %
Compounded total loss: 4.46 %

Changed in version 2.2: windkit.total_loss_factor() compounds the losses. It used to return \(1 - \sum_i p_i / 100\), which understates the remaining energy when more than one loss is non-zero, so net AEP computed from it is now slightly higher.

Uncertainty Tables#

windkit.get_uncertainty_table() returns a table of uncertainties filled with DTU’s default values. They are of two kinds:

  • wind uncertainties, as a percentage of the wind speed, such as those of the measurements, the long-term correction and the horizontal and vertical extrapolation;

  • energy uncertainties, as a percentage of the AEP, such as those of the wake model, the power curve and the availability.

uncertainty_table = wk.get_uncertainty_table("dtu_default")
uncertainty_table[["uncertainty_kind", "uncertainty_name", "uncertainty_percentage"]]
uncertainty_kind uncertainty_name uncertainty_percentage
0 wind representativeness_long_term_period 2.00
1 wind reference_data_consistency 0.50
2 wind long_term_adjustment_mcp 1.50
3 wind ws_distribution_uncertainty 0.50
4 wind on_site_data_synthesis 0.20
5 wind measurements_representativeness 0.10
6 wind iav_variability 0.21
7 wind ws_measurements_uncertainty 2.00
8 wind wd_measurements_uncertainty 1.00
9 wind other_atm_param_uncertainty 0.50
10 wind data_integrity 0.50
11 wind he_model_inputs 1.08
12 wind he_model_sensitivity 2.64
13 wind he_model_appropriateness 2.17
14 wind ve_model_uncertainty 0.80
15 wind ve_propagated_measurement_uncertainty 0.73
16 energy turbine_interaction_wake_blockage_effects 1.76
17 energy turbine 2.50
18 energy balance_of_plant 1.17
19 energy grid 0.50
20 energy electrical_efficiency 0.50
21 energy parasitic_consumption 0.00
22 energy sub_optimal_performance 0.71
23 energy generic_power_curve_adjustments 2.00
24 energy site_specific_power_curve_adjustments 0.87
25 energy high_wind_hysteresis 0.00
26 energy icing 0.01
27 energy degradation 0.25
28 energy environmental_loss_uncertainty 0.01
29 energy exposure_changes 0.00
30 energy load_curtailment 0.01
31 energy grid_curtailment 0.00
32 energy environmental_curtailment 0.01
33 energy operational_strategies 0.01

The uncertainties of each kind are treated as independent and combined as the root sum of squares. The wind uncertainty is then converted to an energy uncertainty by the sensitivity factor: the relative change in AEP for a relative change in wind speed, which depends on the wind climate and the turbines (default 1.5; pywasp.estimate_sensitivity_factor() estimates it). windkit.total_uncertainty() returns the total uncertainty along with the wind and energy parts it combines:

total, wind, energy = wk.total_uncertainty(uncertainty_table, sensitivity_factor=1.5)
print(f"Total: {total:.2f} % of AEP")
print(f"Wind: {wind:.2f} % of wind speed, energy: {energy:.2f} % of AEP")
Total: 8.71 % of AEP
Wind: 5.13 % of wind speed, energy: 4.07 % of AEP

windkit.uncertainty_table_summary() prints the contribution of every row.

Exceedance Values#

Taking the net AEP as the median (P50) of a normal distribution, windkit.total_uncertainty_factor() returns the factor that converts it into the AEP exceeded with a given probability:

for percentile in (75, 90, 99):
    px_factor = wk.total_uncertainty_factor(
        uncertainty_table, sensitivity_factor=1.5, percentile=percentile
    )
    print(f"P{percentile} = {px_factor:.3f} x net AEP")
P75 = 0.941 x net AEP
P90 = 0.888 x net AEP
P99 = 0.797 x net AEP

See Also#