# Dynamic wakes 1¶

## Spatially homogeneous wake propagation¶

For spatially homogeneous timeseries input data, `foxes` can compute dynamic wake propagation. This in principle works by following a flow trace backwards in time for each point of interest, and identifying it with a wake trajectory if it hits a rotor.

Since all `foxes` computations are based on chunks of input states, this concept only works if

• either all states fall into a single chunk,

• or the `Iterative` algorithm is used for the calculation.

The later is necessary in case the wake originates from a state previous to the chunk of evaluation, since the default `Downwind` algorithm does not allow cross-chunk communication during the calculation.

These are the inlcudes for this example:

``````In [1]:
``````
``````%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["animation.html"] = "jshtml"

import foxes
import foxes.variables as FV
import foxes.constants as FC
``````

We create a case with a regular 3 x 3 wind farm layout:

``````In [2]:
``````
``````states = foxes.input.states.Timeseries(
data_source="timeseries_100.csv.gz",
output_vars=[FV.WS, FV.WD, FV.TI, FV.RHO],
var2col={FV.WS: "ws", FV.WD: "wd", FV.TI: "ti"},
fixed_vars={FV.RHO: 1.225, FV.TI: 0.07},
)

farm = foxes.WindFarm()
farm,
xy_base=np.array([0.0, 0.0]),
step_vectors=np.array([[1000.0, 0], [0, 800.0]]),
steps=(3, 3),
turbine_models=["DTU10MW"],
verbosity=0
)

algo = foxes.algorithms.Iterative(
farm,
states,
rotor_model="grid25",
wake_models=["Bastankhah2014_linear_k004"],
wake_frame="timelines",
partial_wakes="rotor_points",
chunks={FC.STATE: 500, FC.POINT: 5000},
verbosity=1
)
``````

Notice the wake frame choice `timelines`, which is a pre-defined instance of the class `Timelines` from the model book.

Let’s run the wind farm calculation:

``````In [3]:
``````
``````with foxes.utils.runners.DaskRunner() as runner:
farm_results = runner.run(algo.calc_farm)
``````
```
Algorithm Iterative: Iteration 0

Initializing algorithm 'Iterative'
Initializing model 'Iterative'
Initializing model 'Timeseries'
States 'Timeseries': Reading static data 'timeseries_100.csv.gz' from context 'states'
Path: /home/jonas/gits/wakes/foxes/foxes/data/states/timeseries_100.csv.gz
Initializing model 'grid25'
Initializing model 'basic_ctrl_prer'
Initializing model 'DTU10MW'
Turbine type 'DTU10MW': Reading static data from context 'power_ct_curve'
Path: /home/jonas/gits/wakes/foxes/foxes/data/power_ct_curves/DTU-10MW-D178d3-H119.csv
Initializing model 'basic_ctrl_postr'
Initializing model 'basic_ctrl'
Initializing model 'timelines'
timelines: Pre-calculating ambient wind vectors
Initializing model 'WakeK'
Initializing model 'Bastankhah2014_linear_k004'
Initializing model 'rotor_points'

--------------------------------------------------
Running Iterative: calc_farm
--------------------------------------------------
n_states : 100
n_turbines: 9
--------------------------------------------------
states   : Timeseries()
rotor    : GridRotor(n=5)
controller: BasicFarmController()
wake frame: Timelines(dt_min=None)
--------------------------------------------------
wakes:
--------------------------------------------------
partial wakes:
0) Bastankhah2014_linear_k004: rotor_points, RotorPoints()
--------------------------------------------------
turbine models:
0) DTU10MW: PCtFile(D=178.3, H=119.0, P_nominal=10000.0, P_unit=kW, rho=1.225, var_ws_ct=REWS2, var_ws_P=REWS3)
--------------------------------------------------

Initializing model 'InitFarmData'
Initializing model 'SetAmbFarmResults'
Initializing model 'FarmWakesCalculation'
Initializing model 'Iterative_calc'

--------------------------------------------------
Model oder
--------------------------------------------------
00) basic_ctrl
01) InitFarmData
02) grid25
03) basic_ctrl
03.0) Post-rotor: DTU10MW
04) SetAmbFarmResults
05) FarmWakesCalculation
--------------------------------------------------

[########################################] | 100% Completed | 101.37 ms

Input data:

<xarray.Dataset> Size: 11kB
Dimensions:          (state: 100, turbine: 9, Timeseries_vars: 2, tmodels: 1)
Coordinates:
* state            (state) datetime64[ns] 800B 2023-07-07T12:00:00 ... 2023...
* Timeseries_vars  (Timeseries_vars) <U2 16B 'WD' 'WS'
* tmodels          (tmodels) <U7 28B 'DTU10MW'
Dimensions without coordinates: turbine
Data variables:
weight           (state, turbine) float64 7kB dask.array<chunksize=(100, 9), meta=np.ndarray>
Timeseries_data  (state, Timeseries_vars) float64 2kB dask.array<chunksize=(100, 2), meta=np.ndarray>
tmodel_sels      (state, turbine, tmodels) bool 900B dask.array<chunksize=(100, 9, 1), meta=np.ndarray>

Farm variables: AMB_CT, AMB_P, AMB_REWS, AMB_REWS2, AMB_REWS3, AMB_RHO, AMB_TI, AMB_WD, AMB_YAW, CT, D, H, P, REWS, REWS2, REWS3, RHO, TI, WD, X, Y, YAW, order, order_inv, order_ssel, weight

Output variables: AMB_CT, AMB_P, AMB_REWS, AMB_REWS2, AMB_REWS3, AMB_RHO, AMB_TI, AMB_WD, AMB_YAW, CT, D, H, P, REWS, REWS2, REWS3, RHO, TI, WD, X, Y, YAW, order, order_inv, order_ssel, weight

Chunks: {'state': 500, 'point': 5000, 'target': 5000}

Calculating 100 states for 9 turbines
[########################################] | 100% Completed | 606.33 ms

Algorithm Iterative: Iteration 1

[########################################] | 100% Completed | 101.56 ms
[########################################] | 100% Completed | 608.54 ms

DefaultConv: Convergence check
REWS: delta = 2.404e-01, lim = 1.000e-06  --  FAILED
TI  : delta = 0.000e+00, lim = 1.000e-07  --  OK
CT  : delta = 3.205e-03, lim = 1.000e-07  --  FAILED

Algorithm Iterative: Iteration 2

[########################################] | 100% Completed | 101.39 ms
[########################################] | 100% Completed | 607.59 ms

DefaultConv: Convergence check
REWS: delta = 0.000e+00, lim = 1.000e-06  --  OK
TI  : delta = 0.000e+00, lim = 1.000e-07  --  OK
CT  : delta = 0.000e+00, lim = 1.000e-07  --  OK

Algorithm Iterative: Convergence reached.

[########################################] | 100% Completed | 101.24 ms
[########################################] | 100% Completed | 101.97 ms
```

Notice the iterations and the convergence behaviour. Now the farm results are ready:

``````In [4]:
``````
``````farm_df = farm_results.to_dataframe()
print("\nFarm results data:\n")
print(farm_df[[FV.AMB_REWS, FV.REWS, FV.P]])
``````
```
Farm results data:

AMB_REWS      REWS            P
state               turbine
2023-07-07 12:00:00 0             6.0  6.000000  1532.700000
1             6.0  6.000000  1532.700000
2             6.0  6.000000  1532.700000
3             6.0  4.806876   702.119290
4             6.0  4.806876   702.119290
...                               ...       ...          ...
2023-07-07 13:39:00 4             6.0  4.806876   702.119290
5             6.0  4.806876   702.119290
6             6.0  4.458187   521.551842
7             6.0  4.458187   521.551842
8             6.0  4.458187   521.551842

[900 rows x 3 columns]
```

This timeseries has a time step of 1 minute. Let’s visualize the wake dynamics in am animation:

``````In [5]:
``````
``````with foxes.utils.runners.DaskRunner() as runner:

fig, axs = plt.subplots(2, 1, figsize=(5.2,7),
gridspec_kw={'height_ratios': [3, 1]})

anim = foxes.output.Animator(fig)

# this adds the flow anomation to the upper panel:
of = foxes.output.FlowPlots2D(algo, farm_results, runner=runner)
of.gen_states_fig_xy(
FV.WS,
resolution=30,
quiver_pars=dict(scale=0.013),
quiver_n=35,
xmax=5000,
ymax=5000,
fig=fig,
ax=axs[0],
vmin=0,
vmax=6,
title=None,
ret_im=True,
animated=True,
)
)

# This adds the REWS signal animation to the lower panel:
o = foxes.output.FarmResultsEval(farm_results)
o.gen_stdata(
turbines=[4, 7],
variable=FV.REWS,
fig=fig,
ax=axs[1],
ret_im=True,
legloc="upper left",
animated=True,
)
)

# This adds turbine indices at turbine positions:
lo = foxes.output.FarmLayoutOutput(farm)
lo.get_figure(
fig=fig,
ax=axs[0],
title="",
annotate=1,
anno_delx=-120,
anno_dely=-60,
alpha=0,
)

ani = anim.animate()
plt.close()
print("done.")

print("Creating animation")
ani
``````
```Creating animation data
``````Out[5]: