Source code for floris.optimization.yaw_optimization.yaw_optimization_tools
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
[docs]
def derive_downstream_turbines(fmodel, wind_direction, wake_slope=0.30, plot_lines=False):
"""Determine which turbines have no effect on other turbines in the
farm, i.e., which turbines have wakes that do not impact the other
turbines in the farm. This allows the user to exclude these turbines
from a control setpoint optimization, for example. This function
assumes a very simplified wake function where the wakes are assumed
to have a linearly diverging profile. In comparisons with the FLORIS
GCH model, the wake_slope matches well with the FLORIS' wake profiles
for a value of wake_slope = 0.5 * turbulence_intensity, where
turbulence_intensity is an input to the FLORIS model at the default
GCH parameterization. Note that does not include wind direction variability.
To be conservative, the user is recommended to use the rule of thumb:
`wake_slope = turbulence_intensity`. Hence, the default value for
`wake_slope=0.30` should be conservative for turbulence intensities up to
0.30 and is likely to provide valid estimates of which turbines are
downstream until a turbulence intensity of 0.50. This simple model saves
time compared to FLORIS.
Args:
fmodel (FlorisModel): A FlorisModel object.
wind_direction (float): The wind direction in the FLORIS frame
of reference for which the downstream turbines are to be determined.
wake_slope (float, optional): linear slope of the wake (dy/dx)
plot_lines (bool, optional): Enable plotting wakes/turbines.
Defaults to False.
Returns:
turbs_downstream (iterable): A list containing the turbine
numbers that have a wake that does not affect any other
turbine inside the farm.
"""
# Get farm layout
x = fmodel.layout_x
y = fmodel.layout_y
D = np.ones_like(x) * fmodel.core.farm.rotor_diameters_sorted[0][0]
n_turbs = len(x)
# Rotate farm and determine freestream/waked turbines
is_downstream = [False for _ in range(n_turbs)]
x_rot = (
np.cos((wind_direction - 270.0) * np.pi / 180.0) * x
- np.sin((wind_direction - 270.0) * np.pi / 180.0) * y
)
y_rot = (
np.sin((wind_direction - 270.0) * np.pi / 180.0) * x
+ np.cos((wind_direction - 270.0) * np.pi / 180.0) * y
)
if plot_lines:
fig, ax = plt.subplots()
for ii in range(n_turbs):
ax.plot(
x_rot[ii] * np.ones(2),
[y_rot[ii] - D[ii] / 2, y_rot[ii] + D[ii] / 2],
"k",
)
for ii in range(n_turbs):
ax.text(x_rot[ii], y_rot[ii], "T%03d" % ii)
ax.axis("equal")
srt = np.argsort(x_rot)
x_rot_srt = x_rot[srt]
y_rot_srt = y_rot[srt]
for ii in range(n_turbs):
x0 = x_rot_srt[ii]
y0 = y_rot_srt[ii]
def wake_profile_ub_turbii(x):
y = (y0 + D[ii]) + (x - x0) * wake_slope
if isinstance(y, (float, np.float64, np.float32)):
if x < (x0 + 0.01):
y = -np.Inf
else:
y[x < x0 + 0.01] = -np.Inf
return y
def wake_profile_lb_turbii(x):
y = (y0 - D[ii]) - (x - x0) * wake_slope
if isinstance(y, (float, np.float64, np.float32)):
if x < (x0 + 0.01):
y = -np.Inf
else:
y[x < x0 + 0.01] = -np.Inf
return y
def determine_if_in_wake(xt, yt):
return (yt < wake_profile_ub_turbii(xt)) & (yt > wake_profile_lb_turbii(xt))
is_downstream[ii] = not any(
determine_if_in_wake(x_rot_srt[iii], y_rot_srt[iii]) for iii in range(n_turbs)
)
if plot_lines:
x1 = np.max(x_rot_srt) + 500.0
ax.fill_between(
[x0, x1, x1, x0],
[
wake_profile_ub_turbii(x0 + 0.02),
wake_profile_ub_turbii(x1),
wake_profile_lb_turbii(x1),
wake_profile_lb_turbii(x0 + 0.02),
],
alpha=0.1,
color="k",
edgecolor=None,
)
usrt = np.argsort(srt)
is_downstream = [is_downstream[i] for i in usrt]
turbs_downstream = list(np.where(is_downstream)[0])
if plot_lines:
ax.set_title("wind_direction = %03d" % wind_direction)
ax.set_xlim([np.min(x_rot) - 500.0, x1])
ax.set_ylim([np.min(y_rot) - 500.0, np.max(y_rot) + 500.0])
ax.plot(
x_rot[turbs_downstream],
y_rot[turbs_downstream],
"o",
color="green",
)
return turbs_downstream