Wake steering¶
This example demonstrates how to optimize the yaw angles of wind turbine rotors, such that the wakes are steered away from downwind turbines.
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import foxes
import foxes.variables as FV
from iwopy.interfaces.pymoo import Optimizer_pymoo
from foxes_opt.problems import OptFarmVars
from foxes_opt.objectives import MaxFarmPower
/home/runner/work/foxes-opt/foxes-opt/.venv/lib/python3.10/site-packages/foxes/core/engine.py:4: TqdmExperimentalWarning: Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)
from tqdm.autonotebook import tqdm
Let’s create the foxes engine, which will be used for all calculations:
engine = foxes.Engine.new(
"process",
chunk_size_states=500,
chunk_size_points=5000,
verbosity=0,
)
In this example we are looking at a small 3 x 3 regualar wind farm with NREL5MW turbines:
farm = foxes.WindFarm()
foxes.input.farm_layout.add_grid(
farm,
xy_base=np.array([500.0, 500.0]),
step_vectors=np.array([[1300.0, 0], [200, 600.0]]),
steps=(3, 3),
turbine_models=["opt_yawm", "yawm2yaw", "NREL5MW"],
verbosity=0,
)
ax = foxes.output.FarmLayoutOutput(farm).get_figure()
plt.show()
plt.close(ax.get_figure())
Notice how the name of the optimization model opt_yawm, which will be defined shortly, appears in the list of turbine models. The idea is as follows:
First, the optimizer sets the
FV.YAWMvariable, representing yaw misalignment in degrees, i.e., a delta yaw value from greedy conditionsThen, the model
yawm2yawtranslates this into the absolute yaw value, i.e., the absolute turbine axis orientation, expressed in degreesThis setting is then used for thrust and power calculations, by the turbine type model
NREL5MW.
We are considering sinlge-state uniform inflow conditions:
states = foxes.input.states.SingleStateStates(ws=9, wd=270, ti=0.06, rho=1.225)
The algorithm is defined next, including a TI wake model and the Bastankhah2016 wake model. Also notice the wake frame choice yawed, which realizes the wake bending for yawed conditions:
algo = foxes.algorithms.Downwind(
farm,
states,
rotor_model="grid25",
wake_models=["IECTI2019k_quadratic_ambka02", "Bastankhah2014_vector_ambka04"],
wake_deflection="Jimenez",
verbosity=0,
)
The optimization problem is power maximization by finding the optimal value of the variable FV.YAWM for each turbine:
problem = OptFarmVars("opt_yawm", algo)
problem.add_var(FV.YAWM, float, 0.0, -40.0, 40.0, level="turbine")
problem.add_objective(MaxFarmPower(problem))
problem.initialize()
Problem 'opt_yawm': Optimization variable list
name var type index level state turbine sel_turbine init \
0 YAWM_0000 YAWM float 0 turbine -1 0 0 0.0
1 YAWM_0001 YAWM float 1 turbine -1 1 1 0.0
2 YAWM_0002 YAWM float 2 turbine -1 2 2 0.0
3 YAWM_0003 YAWM float 3 turbine -1 3 3 0.0
4 YAWM_0004 YAWM float 4 turbine -1 4 4 0.0
5 YAWM_0005 YAWM float 5 turbine -1 5 5 0.0
6 YAWM_0006 YAWM float 6 turbine -1 6 6 0.0
7 YAWM_0007 YAWM float 7 turbine -1 7 7 0.0
8 YAWM_0008 YAWM float 8 turbine -1 8 8 0.0
min max pre_rotor model_key
0 -40.0 40.0 False opt_yawm
1 -40.0 40.0 False opt_yawm
2 -40.0 40.0 False opt_yawm
3 -40.0 40.0 False opt_yawm
4 -40.0 40.0 False opt_yawm
5 -40.0 40.0 False opt_yawm
6 -40.0 40.0 False opt_yawm
7 -40.0 40.0 False opt_yawm
8 -40.0 40.0 False opt_yawm
Problem 'opt_yawm' (OptFarmVars): Initializing
----------------------------------------------
n_vars_int : 0
n_vars_float: 9
----------------------------------------------
n_objectives: 1
n_obj_cmptns: 1
----------------------------------------------
n_constraints: 0
n_con_cmptns: 0
----------------------------------------------
Next, we setup the solver:
solver = Optimizer_pymoo(
problem,
problem_pars=dict(vectorize=True),
algo_pars=dict(type="GA", pop_size=100, seed=42),
setup_pars=dict(),
term_pars=("n_gen", 100),
)
solver.initialize()
solver.print_info()
Loading pymoo
pymoo successfully loaded
Initializing Optimizer_pymoo
Selecting sampling: float_random (FloatRandomSampling)
Selecting algorithm: GA (GA)
Problem:
--------
vectorize: True
Algorithm:
----------
type: GA
pop_size: 100
seed: 42
Termination:
------------
n_gen: 100
Now everything is setup, and we can solve the problem:
with engine:
results = solver.solve()
solver.finalize(results)
=================================================
n_gen | n_eval | f_avg | f_min
=================================================
1 | 100 | -2.894092E-01 | -3.392528E-01
2 | 200 | -3.139203E-01 | -3.392528E-01
3 | 300 | -3.265914E-01 | -3.405948E-01
4 | 400 | -3.346273E-01 | -3.584494E-01
5 | 500 | -3.411709E-01 | -3.585472E-01
6 | 600 | -3.461708E-01 | -3.585472E-01
7 | 700 | -3.503315E-01 | -3.585472E-01
8 | 800 | -3.535017E-01 | -3.585472E-01
9 | 900 | -3.559392E-01 | -3.593399E-01
10 | 1000 | -3.574720E-01 | -3.597935E-01
11 | 1100 | -3.583098E-01 | -3.603311E-01
12 | 1200 | -3.591337E-01 | -3.610024E-01
13 | 1300 | -3.597703E-01 | -3.616003E-01
14 | 1400 | -3.603916E-01 | -3.620144E-01
15 | 1500 | -3.608891E-01 | -3.620728E-01
16 | 1600 | -3.613228E-01 | -3.621632E-01
17 | 1700 | -3.616381E-01 | -3.622094E-01
18 | 1800 | -3.618580E-01 | -3.622279E-01
19 | 1900 | -3.620020E-01 | -3.622863E-01
20 | 2000 | -3.621277E-01 | -3.624825E-01
21 | 2100 | -3.622280E-01 | -3.625173E-01
22 | 2200 | -3.622978E-01 | -3.625173E-01
23 | 2300 | -3.623588E-01 | -3.625494E-01
24 | 2400 | -3.624133E-01 | -3.625519E-01
25 | 2500 | -3.624545E-01 | -3.626254E-01
26 | 2600 | -3.624911E-01 | -3.626301E-01
27 | 2700 | -3.625166E-01 | -3.626301E-01
28 | 2800 | -3.625482E-01 | -3.626493E-01
29 | 2900 | -3.625685E-01 | -3.626493E-01
30 | 3000 | -3.625929E-01 | -3.626493E-01
31 | 3100 | -3.626097E-01 | -3.626493E-01
32 | 3200 | -3.626247E-01 | -3.626709E-01
33 | 3300 | -3.626336E-01 | -3.626709E-01
34 | 3400 | -3.626413E-01 | -3.626709E-01
35 | 3500 | -3.626480E-01 | -3.626769E-01
36 | 3600 | -3.626546E-01 | -3.626769E-01
37 | 3700 | -3.626597E-01 | -3.626769E-01
38 | 3800 | -3.626639E-01 | -3.626773E-01
39 | 3900 | -3.626671E-01 | -3.626773E-01
40 | 4000 | -3.626711E-01 | -3.626795E-01
41 | 4100 | -3.626739E-01 | -3.626809E-01
42 | 4200 | -3.626760E-01 | -3.626830E-01
43 | 4300 | -3.626778E-01 | -3.626835E-01
44 | 4400 | -3.626791E-01 | -3.626850E-01
45 | 4500 | -3.626801E-01 | -3.626850E-01
46 | 4600 | -3.626813E-01 | -3.626875E-01
47 | 4700 | -3.626825E-01 | -3.626875E-01
48 | 4800 | -3.626838E-01 | -3.626877E-01
49 | 4900 | -3.626849E-01 | -3.626877E-01
50 | 5000 | -3.626859E-01 | -3.626891E-01
51 | 5100 | -3.626868E-01 | -3.626918E-01
52 | 5200 | -3.626878E-01 | -3.626922E-01
53 | 5300 | -3.626888E-01 | -3.626925E-01
54 | 5400 | -3.626900E-01 | -3.626954E-01
55 | 5500 | -3.626913E-01 | -3.626954E-01
56 | 5600 | -3.626922E-01 | -3.626954E-01
57 | 5700 | -3.626932E-01 | -3.626956E-01
58 | 5800 | -3.626941E-01 | -3.626956E-01
59 | 5900 | -3.626946E-01 | -3.626956E-01
60 | 6000 | -3.626949E-01 | -3.626957E-01
61 | 6100 | -3.626951E-01 | -3.626957E-01
62 | 6200 | -3.626953E-01 | -3.626957E-01
63 | 6300 | -3.626955E-01 | -3.626959E-01
64 | 6400 | -3.626956E-01 | -3.626959E-01
65 | 6500 | -3.626956E-01 | -3.626959E-01
66 | 6600 | -3.626957E-01 | -3.626959E-01
67 | 6700 | -3.626957E-01 | -3.626959E-01
68 | 6800 | -3.626958E-01 | -3.626960E-01
69 | 6900 | -3.626958E-01 | -3.626960E-01
70 | 7000 | -3.626959E-01 | -3.626960E-01
71 | 7100 | -3.626959E-01 | -3.626960E-01
72 | 7200 | -3.626960E-01 | -3.626966E-01
73 | 7300 | -3.626960E-01 | -3.626966E-01
74 | 7400 | -3.626960E-01 | -3.626966E-01
75 | 7500 | -3.626961E-01 | -3.626966E-01
76 | 7600 | -3.626961E-01 | -3.626967E-01
77 | 7700 | -3.626962E-01 | -3.626967E-01
78 | 7800 | -3.626963E-01 | -3.626967E-01
79 | 7900 | -3.626964E-01 | -3.626967E-01
80 | 8000 | -3.626966E-01 | -3.626967E-01
81 | 8100 | -3.626966E-01 | -3.626967E-01
82 | 8200 | -3.626967E-01 | -3.626967E-01
83 | 8300 | -3.626967E-01 | -3.626967E-01
84 | 8400 | -3.626967E-01 | -3.626967E-01
85 | 8500 | -3.626967E-01 | -3.626967E-01
86 | 8600 | -3.626967E-01 | -3.626967E-01
87 | 8700 | -3.626967E-01 | -3.626967E-01
88 | 8800 | -3.626967E-01 | -3.626967E-01
89 | 8900 | -3.626967E-01 | -3.626967E-01
90 | 9000 | -3.626967E-01 | -3.626967E-01
91 | 9100 | -3.626967E-01 | -3.626968E-01
92 | 9200 | -3.626967E-01 | -3.626968E-01
93 | 9300 | -3.626967E-01 | -3.626968E-01
94 | 9400 | -3.626967E-01 | -3.626968E-01
95 | 9500 | -3.626967E-01 | -3.626968E-01
96 | 9600 | -3.626967E-01 | -3.626968E-01
97 | 9700 | -3.626968E-01 | -3.626968E-01
98 | 9800 | -3.626968E-01 | -3.626968E-01
99 | 9900 | -3.626968E-01 | -3.626968E-01
100 | 10000 | -3.626968E-01 | -3.626968E-01
Optimizer_pymoo: Optimization run finished
Success: True
Best maximize_power = 16321.354958132983
print()
print(results)
fr = results.problem_results.to_dataframe()
fr[[FV.X, FV.Y, FV.AMB_WD, FV.YAWM, FV.TI, FV.REWS, FV.P]]
Results problem 'opt_yawm':
----------------------------
Float variables:
0: YAWM_0000 = 2.293505e+01
1: YAWM_0001 = 1.476572e+01
2: YAWM_0002 = 1.474903e+01
3: YAWM_0003 = 1.088119e+01
4: YAWM_0004 = 1.155909e+01
5: YAWM_0005 = 1.152734e+01
6: YAWM_0006 = -1.167784e+00
7: YAWM_0007 = -1.122612e+00
8: YAWM_0008 = -1.102372e+00
----------------------------
Objectives:
0: maximize_power = 1.632135e+04
----------------------------
Success: True
----------------------------
| X | Y | AMB_WD | YAWM | TI | REWS | P | ||
|---|---|---|---|---|---|---|---|---|
| state | turbine | |||||||
| 0 | 0 | 500.0 | 500.0 | 270.0 | 22.935052 | 0.060000 | 9.000000 | 2180.216367 |
| 1 | 700.0 | 1100.0 | 270.0 | 14.765724 | 0.060000 | 8.702784 | 2160.960596 | |
| 2 | 900.0 | 1700.0 | 270.0 | 14.749031 | 0.060000 | 8.703452 | 2161.755846 | |
| 3 | 1800.0 | 500.0 | 270.0 | 10.881192 | 0.068637 | 8.010250 | 1724.208759 | |
| 4 | 2000.0 | 1100.0 | 270.0 | 11.559094 | 0.080826 | 7.734368 | 1558.379830 | |
| 5 | 2200.0 | 1700.0 | 270.0 | 11.527340 | 0.080863 | 7.734593 | 1558.825575 | |
| 6 | 3100.0 | 500.0 | 270.0 | -1.167784 | 0.091396 | 7.942026 | 1736.645166 | |
| 7 | 3300.0 | 1100.0 | 270.0 | -1.122612 | 0.092882 | 7.743524 | 1620.799686 | |
| 8 | 3500.0 | 1700.0 | 270.0 | -1.102372 | 0.092974 | 7.741373 | 1619.563135 |
Finally, we can visualize the result by looking at the flow field:
with engine:
o = foxes.output.FlowPlots2D(algo, results.problem_results)
plot_data = o.get_mean_data_xy("WS", resolution=10, xmax=5000)
fig = o.get_mean_fig_xy(plot_data)
plt.show()
Clearly the turbines are trying to avoid hitting downwind turbines with their wakes.