Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fix: revise verify_convergence to support multiple wind speeds #385

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions examples/09_opt_yaw_multiple_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,17 @@
)

# Initialize optimizer object and run optimization using the Serial-Refine method
yaw_opt = YawOptimizationSR(fi)
# Now, we enable the verify_convergence option. This function is useful to prevent
# yaw misalignment that increases the wind farm power production by a negligible
# amount. For example, at high wind speeds (e.g., 16 m/s), a turbine might yaw
# by a substantial amount to increase the power production by less than 1 W. This
# is typically the result of numerical inprecision of the power coefficient curve,
# which slightly differs for different above-rated wind speeds. The option
# verify_convergence therefore refines and validates the yaw angle choices
# but has no effect on the predicted power uplift from wake steering.
# Hence, it should mostly be used when actually synthesizing a practicable
# wind farm controller.
yaw_opt = YawOptimizationSR(fi, verify_convergence=True)
df_opt = yaw_opt.optimize()

print("Optimization results:")
Expand All @@ -49,7 +59,13 @@
df_opt['t%d' % t] = df_opt.yaw_angles_opt.apply(lambda x: x[t])

# Show the results: optimal yaw angles
fig, axarr = plt.subplots(nrows=4, ncols=4, sharex=True, sharey=True, figsize=(10, 8))
fig, axarr = plt.subplots(
nrows=4,
ncols=4,
sharex=True,
sharey=True,
figsize=(10, 8)
)
jj = 0
for ii, ws in enumerate(fi.floris.flow_field.wind_speeds):
xi = np.remainder(ii, 4)
Expand All @@ -73,7 +89,13 @@
plt.tight_layout()

# Show the results: baseline and optimized farm power
fig, axarr = plt.subplots(nrows=4, ncols=4, sharex=True, figsize=(10, 8))
fig, axarr = plt.subplots(
nrows=4,
ncols=4,
sharex=True,
sharey=True,
figsize=(10, 8)
)
jj = 0
for ii, ws in enumerate(fi.floris.flow_field.wind_speeds):
xi = np.remainder(ii, 4)
Expand All @@ -87,6 +109,7 @@
ax.plot(wd, power_baseline / 1e6, color='k', label='Baseline')
ax.plot(wd, power_opt / 1e6, color='r', label='Optimized')
ax.set_title("Wind speed = {:.1f} m/s".format(ws), size=10)
ax.set_ylim([0.0, 16.0])
if ((ii == 0) & (jj == 0)):
ax.legend()
ax.grid(True)
Expand Down
116 changes: 76 additions & 40 deletions floris/tools/optimization/yaw_optimization/yaw_optimization_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@


import copy
from time import perf_counter as timerpc

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -522,7 +523,7 @@ def _verify_solutions_for_convergence(
self,
farm_power_opt_subset,
yaw_angles_opt_subset,
min_yaw_offset=0.10,
min_yaw_offset=0.01,
min_power_gain_for_yaw=0.02,
verbose=True,
):
Expand Down Expand Up @@ -560,12 +561,16 @@ def _verify_solutions_for_convergence(

print("Verifying convergence of the found optimal yaw angles.")

# Start timer
start_time = timerpc()

# Define variables locally
yaw_angles_opt_subset = np.array(yaw_angles_opt_subset, copy=True)
yaw_angles_baseline_subset = self._yaw_angles_baseline_subset
farm_power_baseline_subset = self._farm_power_baseline_subset
turbs_to_opt_subset = self._turbs_to_opt_subset

# Round small nonzero yaw angles
# Round small nonzero yaw angles to zero
ydiff = np.abs(yaw_angles_opt_subset - yaw_angles_baseline_subset)
ids = np.where((ydiff < min_yaw_offset) & (ydiff > 0.0))
if len(ids[0]) > 0:
Expand All @@ -576,79 +581,110 @@ def _verify_solutions_for_convergence(
ydiff[ids] = 0.0

# Turbines to test whether their angles sufficiently improve farm power
ids = np.where((self._turbs_to_opt_subset) & (ydiff > min_yaw_offset))
ids = np.where((turbs_to_opt_subset) & (ydiff > min_yaw_offset))

# Define situations that need to be calculated and find farm power.
# Each situation basically contains the exact same conditions as the
# baseline conditions and optimal yaw angles, besides for a single
# turbine for which its yaw angle was set to its baseline value (
# typically 0.0 deg). This way, we investigate whether the yaw offset
# of that turbine really adds significant uplift to the farm power
# production.
wd_array = self.fi_subset.floris.flow_field.wind_directions[ids[0]]
# ws_array = self.fi_subset.floris.flow_field.wind_speeds[ids[1]]
turbine_weights = self._turbine_weights_subset[ids[0]]
yaw_angles = yaw_angles_opt_subset[ids[0], :]
for wdii, ti in enumerate(ids[2]):
yaw_angles[wdii, 0, ti] = \
yaw_angles_baseline_subset[ids[0][wdii], 0, ti]

# For each turbine in the farm, reset its values to baseline. Thus,
# we copy the atmospheric conditions n_turbs times and for each
# copy of atmospheric conditions, we reset that turbine's yaw angle
# to its baseline value for all conditions.
n_turbs = len(self.fi.layout_x)
sp = (n_turbs, 1, 1) # Tile shape for matrix expansion
wd_array_nominal = self.fi_subset.floris.flow_field.wind_directions
n_wind_directions = len(wd_array_nominal)
yaw_angles_verify = np.tile(yaw_angles_opt_subset, sp)
yaw_angles_bl_verify = np.tile(yaw_angles_baseline_subset, sp)
turbine_id_array = np.zeros(np.shape(yaw_angles_verify)[0], dtype=int)
for ti in range(n_turbs):
ids = ti * n_wind_directions + np.arange(n_wind_directions)
yaw_angles_verify[ids, :, ti] = yaw_angles_bl_verify[ids, :, ti]
turbine_id_array[ids] = ti

# Now evaluate all situations
farm_power_baseline_verify = np.tile(farm_power_baseline_subset, (n_turbs, 1))
farm_power = self._calculate_farm_power(
yaw_angles=yaw_angles,
wd_array=wd_array,
turbine_weights=turbine_weights
yaw_angles=yaw_angles_verify,
wd_array=np.tile(wd_array_nominal, n_turbs),
turbine_weights=np.tile(self._turbs_to_opt_subset, sp)
)

# Calculate power uplift for optimal solutions
uplift_o = 100.0 * (
farm_power_opt_subset[ids[0]].flatten() /
farm_power_baseline_subset[ids[0]].flatten()
) - 100.0
uplift_o = 100 * (
np.tile(farm_power_opt_subset, (n_turbs, 1)) /
farm_power_baseline_verify - 1.0
)

# Calculate power uplift for all cases we evaluated
uplift_n = 100.0 * (
farm_power.flatten() /
farm_power_baseline_subset[ids[0]].flatten()
) - 100.0
uplift_n = 100.0 * (farm_power / farm_power_baseline_verify - 1.0)

# Check difference in uplift, where each row represents a different
# situation (i.e., where one turbine was set to its baseline yaw angle
# instead of its optimal yaw angle).
dp = uplift_o - uplift_n
ids_to_simplify = np.where(dp < min_power_gain_for_yaw)[0]
ids = (ids[0][ids_to_simplify], 0, ids[2][ids_to_simplify])
yaw_angles_opt_subset[ids] = yaw_angles_baseline_subset[ids]
n = len(ids_to_simplify)
ids_to_simplify = np.where(dp < min_power_gain_for_yaw)
ids_to_simplify = (
np.remainder(ids_to_simplify[0], n_wind_directions), # Wind direction identifier
ids_to_simplify[1], # Wind speed identifier
turbine_id_array[ids_to_simplify[0]], # Turbine identifier
)

# Overwrite yaw angles that insufficiently increased farm power with baseline values
yaw_angles_opt_subset[ids_to_simplify] = (
yaw_angles_baseline_subset[ids_to_simplify]
)

n = len(ids_to_simplify[0])
if n > 0:
# Yaw angles notably changed: recalculate farm powers
farm_power_opt_subset_new = self._calculate_farm_power(
yaw_angles_opt_subset)
farm_power_opt_subset_new = (
self._calculate_farm_power(yaw_angles_opt_subset)
)

if verbose:
# Calculate old uplift for all conditions
uplift_o = 100.0 * (
farm_power_opt_subset.flatten() /
farm_power_baseline_subset.flatten()
dP_old = 100.0 * (
farm_power_opt_subset /
farm_power_baseline_subset
) - 100.0

# Calculate new uplift for all conditions
uplift_n = 100.0 * (
farm_power_opt_subset_new.flatten() /
farm_power_baseline_subset.flatten()
dP_new = 100.0 * (
farm_power_opt_subset_new /
farm_power_baseline_subset
) - 100.0

# Calculate differences in power uplift
dp = uplift_o - uplift_n
id_max_loss = np.argmax(dp)
diff_uplift = dP_old - dP_new
ids_max_loss = np.where(np.nanmax(diff_uplift) == diff_uplift)
jj = (ids_max_loss[0][0], ids_max_loss[1][0])
ws_array_nominal = self.fi_subset.floris.flow_field.wind_speeds
print(
"Nullified the optimal yaw offset for {:d}".format(n) +
" turbines over all wind directions. The maximum change " +
"in power uplift: from {:.3f}% to {:.3f}%.".format(
uplift_o[id_max_loss], uplift_n[id_max_loss]
" conditions and turbines."
)
print(
"Simplifying the yaw angles for these conditions lead " +
"to a maximum change in wake-steering power uplift from "
+ "{:.5f}% to {:.5f}% at ".format(dP_old[jj], dP_new[jj])
+ " WD = {:.1f} deg and WS = {:.1f} m/s.".format(
wd_array_nominal[jj[0]], ws_array_nominal[jj[1]],
)
)

t = timerpc() - start_time
print(
"Time spent to verify the convergence of the optimal " +
"yaw angles: {:.3f} s.".format(t)
)

# Return optimal solutions to the user
farm_power_opt_subset = farm_power_opt_subset_new

return yaw_angles_opt_subset, farm_power_opt_subset
Expand Down