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#
API reference: Losses and Uncertainty