Yawed rotor wakes

This example demonstrates wake modelling for yawed wind turbine rotors, following Bastankhah & Porte-Agel. Let’s start by importing the required packages:

In [1]:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

import foxes
import foxes.variables as FV

Our aim in this example is to model two turbines that are located along the x axis, and uniform wind from west. Three states should be computed, with identical wind conditions, in a setup where the first turbine is yawed by 30, 0, -30 degrees, respectively.

We can realize these wind conditions with the StatesTable class, using a pandas.DataFrame object as data as source:

In [2]:
sdata = pd.DataFrame(index=range(3))
sdata[FV.WS] = 8.0
sdata[FV.WD] = 270.0
sdata
Out[2]:
WS WD
0 8.0 270.0
1 8.0 270.0
2 8.0 270.0
In [3]:
states = foxes.input.states.StatesTable(
    data_source=sdata,
    output_vars=[FV.WS, FV.WD, FV.TI, FV.RHO],
    fixed_vars={FV.RHO: 1.225, FV.TI: 0.05},
)

For each of these 3 states, and for each of the two turbines, we wish to set the yaw misalignment variable FV.YAWM. The following numpy.ndarray defines these angles, with shape (3, 2) representing (FC.STATE, FC.TURBINE) dimensions:

In [4]:
yawm = np.array([[30.0, 0.0], [0.0, 0.0], [-30.0, 0.0]])
yawm
Out[4]:
array([[ 30.,   0.],
       [  0.,   0.],
       [-30.,   0.]])

For transferring these data to the variable FV.YAWM, we invoke the turbine model SetFarmVars, by adding it to the turbine_models section of the ModelBook:

In [5]:
mbook = foxes.ModelBook()
mbook.turbine_models["set_yawm"] = foxes.models.turbine_models.SetFarmVars()
mbook.turbine_models["set_yawm"].add_var(FV.YAWM, yawm)

Notice that this models appears together with the model yawm2yaw, which exists by default in the ModelBook:

In [6]:
farm = foxes.WindFarm()
farm.add_turbine(
    foxes.Turbine(
        xy=[0.0, 0.0],
        turbine_models=["set_yawm", "yawm2yaw", "NREL5MW", "kTI_05"],
    )
)
farm.add_turbine(
    foxes.Turbine(
        xy=[1000.0, 0.0],
        turbine_models=["set_yawm", "yawm2yaw", "NREL5MW", "kTI_05"],
    )
)
Turbine 0, T0: xy=(0.00, 0.00), set_yawm, yawm2yaw, NREL5MW, kTI_05
Turbine 1, T1: xy=(1000.00, 0.00), set_yawm, yawm2yaw, NREL5MW, kTI_05

The yawm2yaw updates the FV.YAW variable by adding FV.YAWM to the wind direction, i.e., the yaw misalignment defines the absolute rotor yaw in this case (in cases where the latter is set, you may want to include the inverse model yaw2yawm instead).

We can now create an Algorithm object and calculate the farm results. We combine the yaw-sensitive wake model PorteAgel_linear (c.f. reference) with the wake frame called yawed, which is also based on the same publication:

In [7]:
algo = foxes.algorithms.Downwind(
    farm,
    states,
    rotor_model="centre",
    wake_models=["Bastankhah2016_linear", "IECTI2019_max"],
    wake_frame="yawed",
    mbook=mbook,
    verbosity=0,
)
In [8]:
farm_results = algo.calc_farm()
Selecting 'DefaultEngine(n_procs=16, chunk_size_states=None, chunk_size_points=None)'
DefaultEngine: Selecting engine 'single'
SingleChunkEngine: Calculating 3 states for 2 turbines
SingleChunkEngine: Running single chunk calculation for 3 states

Once the wind farm results are ready we can continue and plot the wind field:

In [9]:
o = foxes.output.FlowPlots2D(algo, farm_results)
g = o.gen_states_fig_xy(FV.WS, resolution=10, xmin=-100, xmax=3000, verbosity=0)
for fig in g:
    plt.show()
DefaultEngine: Selecting engine 'process'
ProcessEngine: Calculating data at 31411 points for 3 states
ProcessEngine: Computing 48 chunks using 16 processes
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 48/48 [00:00<00:00, 19074.05it/s]
../_images/notebooks_yawed_wake_16_2.png
../_images/notebooks_yawed_wake_16_3.png
../_images/notebooks_yawed_wake_16_4.png

As well as some vertical cross sections some distance downstream:

In [10]:
o = foxes.output.FlowPlots2D(algo, farm_results)
g = o.gen_states_fig_yz(
    FV.WS, resolution=10, x=750, ymin=-200, ymax=200, zmin=0, zmax=250, verbosity=0
)
for fig in g:
    plt.show()
DefaultEngine: Selecting engine 'single'
SingleChunkEngine: Calculating data at 1066 points for 3 states
SingleChunkEngine: Running single chunk calculation for 3 states
../_images/notebooks_yawed_wake_18_1.png
../_images/notebooks_yawed_wake_18_2.png
../_images/notebooks_yawed_wake_18_3.png

Clearly, the model bends the wake and a frontal hit of the downstream rotor is avoided under yawed conditions. This also reflects in the rotor equivalent wind speed (REWS) values:

In [11]:
farm_df = farm_results.to_dataframe()
print("\nFarm results:\n")
print(farm_df[[FV.X, FV.WD, FV.YAW, FV.YAWM, FV.REWS]])

Farm results:

                    X     WD    YAW  YAWM      REWS
state turbine
0     0           0.0  270.0  300.0  30.0  8.000000
      1        1000.0  270.0  270.0   0.0  7.419935
1     0           0.0  270.0  270.0   0.0  8.000000
      1        1000.0  270.0  270.0   0.0  5.173606
2     0           0.0  270.0  240.0 -30.0  8.000000
      1        1000.0  270.0  270.0   0.0  7.419935

By default, the turbine type models in foxes apply \(\cos(\gamma)^p\) type yaw corection factors to the variables FV.P and FV.CT, where \gamma is the yaw misalignment angle (i.e., FV.YAWM translated to radiants), and p is a constant exponent (in fact there is one for power and another for thrust). These exponents can be setup as desired, here the default values \(p_P = 1.88\) and \(p_{ct}=1.0\) were invoked.

Note that the yawed wake frame can bend any kind of wake model. For example, we invoked the CrespoHernandez model for turbulence intensity wake effects above. This wake model follows the same bending rules as the wind deficit model, even though it is a simple top-hat type model:

In [12]:
o = foxes.output.FlowPlots2D(algo, farm_results)
g = o.gen_states_fig_xy(FV.TI, resolution=10, xmin=-100, xmax=3000, verbosity=0)
for fig in g:
    plt.show()
DefaultEngine: Selecting engine 'process'
ProcessEngine: Calculating data at 31411 points for 3 states
ProcessEngine: Computing 48 chunks using 16 processes
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 48/48 [00:00<00:00, 472.08it/s]
../_images/notebooks_yawed_wake_22_2.png
../_images/notebooks_yawed_wake_22_3.png
../_images/notebooks_yawed_wake_22_4.png

Similarly any other choice of wake models can be combined with the yawed wake frame. Note, however, that the intensity of most wake models only depends on the yaw angle implicitely via ct in such setups.