Example: Optimize yaw and compare AEP in parallel#
"""Example: Optimize yaw and compare AEP in parallel
This example demonstrates how to perform a yaw optimization and evaluate the performance
over a full wind rose. The example repeats the steps in 04 except using parallel
optimization and evaluation.
Note that constraints on parallelized operations mean that some syntax is different and
not all operations are possible. Also, rather passing the ParallelFlorisModel
object to a YawOptimizationSR object, the optimization is performed
directly by member functions
"""
from time import perf_counter as timerpc
import numpy as np
from floris import (
ParFlorisModel,
TimeSeries,
WindRose,
)
from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR
# When using parallel optimization it is important the "root" script include this
# if __name__ == "__main__": block to avoid problems
if __name__ == "__main__":
# Load the wind rose from csv
wind_rose = WindRose.read_csv_long(
"../inputs/wind_rose.csv", wd_col="wd", ws_col="ws", freq_col="freq_val",
ti_col_or_value=0.06
)
# Load FLORIS as a parallel model
max_workers = 16
pfmodel = ParFlorisModel(
"../inputs/gch.yaml",
max_workers=max_workers,
n_wind_condition_splits=max_workers,
interface="pathos",
print_timings=True,
)
# Specify wind farm layout and update in the floris object
N = 2 # number of turbines per row and per column
X, Y = np.meshgrid(
5.0 * pfmodel.core.farm.rotor_diameters_sorted[0][0] * np.arange(0, N, 1),
5.0 * pfmodel.core.farm.rotor_diameters_sorted[0][0] * np.arange(0, N, 1),
)
pfmodel.set(layout_x=X.flatten(), layout_y=Y.flatten())
# Get the number of turbines
n_turbines = len(pfmodel.layout_x)
# Optimize the yaw angles. This could be done for every wind direction and wind speed
# but in practice it is much faster to optimize only for one speed and infer the rest
# using a rule of thumb
time_series = TimeSeries(
wind_directions=wind_rose.wind_directions, wind_speeds=8.0, turbulence_intensities=0.06
)
pfmodel.set(wind_data=time_series)
start_time = timerpc()
yaw_opt = YawOptimizationSR(
fmodel=pfmodel,
minimum_yaw_angle=0.0, # Allowable yaw angles lower bound
maximum_yaw_angle=20.0, # Allowable yaw angles upper bound
Ny_passes=[5, 4],
exclude_downstream_turbines=True,
)
df_opt = yaw_opt.optimize()
end_time = timerpc()
t_tot = end_time - start_time
print("Optimization finished in {:.2f} seconds.".format(t_tot))
# Calculate the AEP in the baseline case, using the parallel interface
pfmodel.set(wind_data=wind_rose)
pfmodel.run()
aep_baseline = pfmodel.get_farm_AEP()
# Now need to apply the optimal yaw angles to the wind rose to get the optimized AEP
# do this by applying a rule of thumb where the optimal yaw is applied between 6 and 12 m/s
# and ramped down to 0 above and below this range
# Grab wind speeds and wind directions from the fmodel. Note that we do this because the
# yaw angles will need to be n_findex long, and accounting for the fact that some wind
# directions and wind speeds may not be present in the wind rose (0 frequency) and aren't
# included in the fmodel
# TODO: add operation wind rose to example, once built
wind_directions = pfmodel.wind_directions
wind_speeds = pfmodel.wind_speeds
n_findex = pfmodel.n_findex
# Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds
yaw_angles_opt = np.vstack(df_opt["yaw_angles_opt"])
yaw_angles_wind_rose = np.zeros((n_findex, n_turbines))
for i in range(n_findex):
wind_speed = wind_speeds[i]
wind_direction = wind_directions[i]
# Interpolate the optimal yaw angles for this wind direction from df_opt
id_opt = df_opt["wind_direction"] == wind_direction
yaw_opt_full = np.array(df_opt.loc[id_opt, "yaw_angles_opt"])[0]
# Now decide what to do for different wind speeds
if (wind_speed < 4.0) | (wind_speed > 14.0):
yaw_opt = np.zeros(n_turbines) # do nothing for very low/high speeds
elif wind_speed < 6.0:
yaw_opt = yaw_opt_full * (6.0 - wind_speed) / 2.0 # Linear ramp up
elif wind_speed > 12.0:
yaw_opt = yaw_opt_full * (14.0 - wind_speed) / 2.0 # Linear ramp down
else:
yaw_opt = yaw_opt_full # Apply full offsets between 6.0 and 12.0 m/s
# Save to collective array
yaw_angles_wind_rose[i, :] = yaw_opt
# Now apply the optimal yaw angles and get the AEP
pfmodel.set(yaw_angles=yaw_angles_wind_rose)
pfmodel.run()
aep_opt = pfmodel.get_farm_AEP()
aep_uplift = 100.0 * (aep_opt / aep_baseline - 1)
print("Baseline AEP: {:.2f} GWh.".format(aep_baseline/1E9))
print("Optimal AEP: {:.2f} GWh.".format(aep_opt/1E9))
print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift))
import warnings
warnings.filterwarnings('ignore')
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
===============================================================================
Total time spent for parallel calculation (16 workers): 1.130 s
Time spent in parallel preprocessing: 0.003 s
Time spent in parallel loop execution: 1.126 s.
Time spent in parallel postprocessing: 0.001 s
[Serial Refine] Processing pass=0, turbine_depth=0 (0.0%)
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
===============================================================================
Total time spent for parallel calculation (16 workers): 0.941 s
Time spent in parallel preprocessing: 0.007 s
Time spent in parallel loop execution: 0.933 s.
Time spent in parallel postprocessing: 0.001 s
[Serial Refine] Processing pass=0, turbine_depth=1 (12.5%)
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
===============================================================================
Total time spent for parallel calculation (16 workers): 0.959 s
Time spent in parallel preprocessing: 0.006 s
Time spent in parallel loop execution: 0.952 s.
Time spent in parallel postprocessing: 0.001 s
[Serial Refine] Processing pass=0, turbine_depth=2 (25.0%)
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
===============================================================================
Total time spent for parallel calculation (16 workers): 0.971 s
Time spent in parallel preprocessing: 0.006 s
Time spent in parallel loop execution: 0.964 s.
Time spent in parallel postprocessing: 0.001 s
[Serial Refine] Processing pass=0, turbine_depth=3 (37.5%)
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
===============================================================================
Total time spent for parallel calculation (16 workers): 0.972 s
Time spent in parallel preprocessing: 0.006 s
Time spent in parallel loop execution: 0.965 s.
Time spent in parallel postprocessing: 0.001 s
[Serial Refine] Processing pass=1, turbine_depth=0 (50.0%)
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
===============================================================================
Total time spent for parallel calculation (16 workers): 0.942 s
Time spent in parallel preprocessing: 0.005 s
Time spent in parallel loop execution: 0.936 s.
Time spent in parallel postprocessing: 0.001 s
[Serial Refine] Processing pass=1, turbine_depth=1 (62.5%)
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
===============================================================================
Total time spent for parallel calculation (16 workers): 0.955 s
Time spent in parallel preprocessing: 0.005 s
Time spent in parallel loop execution: 0.949 s.
Time spent in parallel postprocessing: 0.001 s
[Serial Refine] Processing pass=1, turbine_depth=2 (75.0%)
floris.par_floris_model.ParFlorisModel WARNING Deleting stored wind_data information.
===============================================================================
Total time spent for parallel calculation (16 workers): 0.954 s
Time spent in parallel preprocessing: 0.005 s
Time spent in parallel loop execution: 0.948 s.
Time spent in parallel postprocessing: 0.001 s
[Serial Refine] Processing pass=1, turbine_depth=3 (87.5%)
===============================================================================
Total time spent for parallel calculation (16 workers): 0.965 s
Time spent in parallel preprocessing: 0.005 s
Time spent in parallel loop execution: 0.958 s.
Time spent in parallel postprocessing: 0.001 s
Optimization finished in 9.38 seconds.
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:498: RuntimeWarning: invalid value encountered in divide
I_total = np.sqrt((2 / 3) * k_total) / average_u_i
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:328: RuntimeWarning: invalid value encountered in divide
val = 2 * (avg_v - v_core) / (v_top + v_bottom)
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:163: RuntimeWarning: invalid value encountered in divide
C0 = 1 - u0 / freestream_velocity
/home/runner/work/floris/floris/floris/core/wake_velocity/gauss.py:80: RuntimeWarning: invalid value encountered in divide
sigma_z0 = rotor_diameter_i * 0.5 * np.sqrt(uR / (u_initial + u0))
===============================================================================
Total time spent for parallel calculation (16 workers): 1.166 s
Time spent in parallel preprocessing: 0.023 s
Time spent in parallel loop execution: 1.142 s.
Time spent in parallel postprocessing: 0.002 s
/home/runner/work/floris/floris/floris/core/wake_deflection/gauss.py:498: RuntimeWarning: invalid value encountered in divide
I_total = np.sqrt((2 / 3) * k_total) / average_u_i
===============================================================================
Total time spent for parallel calculation (16 workers): 1.115 s
Time spent in parallel preprocessing: 0.022 s
Time spent in parallel loop execution: 1.091 s.
Time spent in parallel postprocessing: 0.002 s
Baseline AEP: 75.48 GWh.
Optimal AEP: 76.18 GWh.
Relative AEP uplift by wake steering: 0.926 %.