Dynamic Wakes 2¶
Horizontally homogeneous wake propagation¶
For horizontally homogeneous timeseries input data (i.e., dependency on time and optionally also on z
, but not on x, y
coordinates), foxes
offers a simplified way to compute dynamic wake propagation. This in principle works by following a flow trace backwards in time from each point of interest, and identifying it with a wake trajectory if it approaches the vicinity of a rotor. For horizontally homogeneous inflow the steps of these traces are independent of the evaluation point.
Similarly to the prevously discussed DynamicWakes
approach, this concept only works if
either all states fall into a single chunk,
or the
Iterative
algorithm is used for 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
This is the engine that we are going to use throughout all calculations in this notebook:
In [2]:
engine = foxes.Engine.new("process", chunk_size_states=20, chunk_size_points=3000)
engine.initialize()
We create a case with a regular 3 x 3 wind farm layout:
In [3]:
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()
foxes.input.farm_layout.add_grid(
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_lim_k004"],
wake_frame="timelines",
partial_wakes="rotor_points",
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 [4]:
farm_results = algo.calc_farm()
Algorithm Iterative: Iteration 0
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 5 chunks using 16 processes
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 17.25it/s]
Algorithm Iterative: Iteration 1
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 5 chunks using 16 processes
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 12.79it/s]
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.099e-03, lim = 1.000e-07 -- FAILED
Algorithm Iterative: Iteration 2
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 5 chunks using 16 processes
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 15.29it/s]
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.
Starting final run
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 5 chunks using 16 processes
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 481.29it/s]
Notice the iterations and the convergence behaviour, resulting in less iterations than the previous DynamicWakes
example. Now the farm results are ready:
In [5]:
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 6.000000 1532.700000
4 6.0 6.000000 1532.700000
... ... ... ...
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.551830
7 6.0 4.458187 521.551817
8 6.0 4.458187 521.551830
[900 rows x 3 columns]
This timeseries has a time step of 1 minute. Let’s visualize the wake dynamics in an animation:
In [6]:
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)
anim.add_generator(
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)
anim.add_generator(
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
States 'Timeseries': Reading file /home/jonas/gits/wakes/foxes/foxes/data/states/timeseries_100.csv.gz
ProcessEngine: Calculating data at 33856 points for 100 states
ProcessEngine: Computing 60 chunks using 16 processes
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 60/60 [00:27<00:00, 2.22it/s]
done.
Creating animation
Out[6]:
For the fun of it, let’s re-run this case assuming the time step was 10 s instead of 1 min. We can do so by using the wake frame Timelines(dt_min=1/6)
, which is called timelines_10s
in the model book:
In [7]:
algo = foxes.algorithms.Iterative(
farm,
states,
rotor_model="grid25",
wake_models=["Bastankhah2014_linear_lim_k004"],
wake_frame="timelines_10s",
partial_wakes="rotor_points",
verbosity=1,
)
farm_results = algo.calc_farm()
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)
anim.add_generator(
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,
title=lambda si, s: f"t = {si/6:3.2f} min",
ret_im=True,
animated=True,
)
)
# This adds the REWS signal animation to the lower panel:
o = foxes.output.FarmResultsEval(farm_results)
anim.add_generator(
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
Algorithm Iterative: Iteration 0
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 5 chunks using 16 processes
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 14.89it/s]
Algorithm Iterative: Iteration 1
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 5 chunks using 16 processes
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 14.77it/s]
DefaultConv: Convergence check
REWS: delta = 1.996e-01, lim = 1.000e-06 -- FAILED
TI : delta = 0.000e+00, lim = 1.000e-07 -- OK
CT : delta = 2.568e-03, lim = 1.000e-07 -- FAILED
Algorithm Iterative: Iteration 2
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 5 chunks using 16 processes
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 15.24it/s]
DefaultConv: Convergence check
REWS: delta = 6.094e-08, lim = 1.000e-06 -- OK
TI : delta = 0.000e+00, lim = 1.000e-07 -- OK
CT : delta = 9.119e-10, lim = 1.000e-07 -- OK
Algorithm Iterative: Convergence reached.
Starting final run
ProcessEngine: Calculating 100 states for 9 turbines
ProcessEngine: Computing 5 chunks using 16 processes
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 401.38it/s]
Creating animation data
States 'Timeseries': Reading file /home/jonas/gits/wakes/foxes/foxes/data/states/timeseries_100.csv.gz
ProcessEngine: Calculating data at 33856 points for 100 states
ProcessEngine: Computing 60 chunks using 16 processes
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 60/60 [00:35<00:00, 1.67it/s]
done.
Creating animation
Out[7]:
Finally, we can shutdown the engine:
In [8]:
engine.finalize()