-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #921 from NREL/develop
FLORIS v4.1
- Loading branch information
Showing
53 changed files
with
4,354 additions
and
364 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
|
||
(layout_optimization)= | ||
# Layout optimization | ||
|
||
The FLORIS package provides layout optimization tools to place turbines within a specified | ||
boundary area to optimize annual energy production (AEP) or wind plant value. Layout | ||
optimizers accept an instantiated `FlorisModel` and alter the turbine layouts in order to | ||
improve the objective function value (AEP or value). | ||
|
||
## Background | ||
|
||
Layout optimization entails placing turbines in a wind farm in a configuration that maximizes | ||
an objective function, usually the AEP. Turbines are moved to minimize their wake interactions | ||
in the most dominant wind directions, while respecting the boundaries of the area for turbine | ||
placement as well as minimum distance requirements between neighboring turbines. | ||
|
||
Mathematically, we represent this as a (nonconvex) optimization problem. | ||
Let $x = \{x_i\}_{i=1,\dots,N}$, $x_i \in \mathbb{R}^2$ represent the set of | ||
coordinates of turbines within a farm (that is, $x_i$ represents the $(X, Y)$ | ||
location of turbine $i$). Further, let $R \subset \mathbb{R}^2$ be a closed | ||
region in which to place turbines. Finally, let $d$ represent the minimum | ||
allowable distance between two turbines. Then, the layout optimization problem | ||
is expressed as | ||
|
||
$$ | ||
\begin{aligned} | ||
\underset{x}{\text{maximize}} & \:\: f(x)\\ | ||
\text{subject to} & \:\: x \subset R \\ | ||
& \:\: ||x_i - x_j|| \geq d \:\: \forall j = 1,\dots,N, j\neq i | ||
\end{aligned} | ||
$$ | ||
|
||
Here, $||\cdot||$ denotes the Euclidean norm, and $f(x)$ is the cost function to be maximized. | ||
|
||
When maximizing the AEP, $f = \sum_w P(w, x)p_W(w)$, where $w$ is the wind condition bin | ||
(e.g., wind speed, wind direction pair); $P(w, x)$ is the power produced by the wind farm in | ||
condition $w$ with layout $x$; and $p_W(w)$ is the annual frequency of occurrence of | ||
condition $w$. | ||
|
||
Layout optimizers take iterative approaches to solving the layout optimization problem | ||
specified above. Optimization routines available in FLORIS are described below. | ||
|
||
## Scipy layout optimization | ||
The `LayoutOptimizationScipy` class is built around `scipy.optimize`s `minimize` | ||
routine, using the `SLSQP` solver by default. Options for adjusting | ||
`minimize`'s behavior are exposed to the user with the `optOptions` argument. | ||
Other options include enabling fast wake steering at each layout optimizer | ||
iteration with the `enable_geometric_yaw` argument, and switch from AEP | ||
optimization to value optimization with the `use_value` argument. | ||
|
||
## Genetic random search layout optimization | ||
The `LayoutOptimizationRandomSearch` class is a custom optimizer designed specifically for | ||
layout optimization via random perturbations of the turbine locations. It is designed to have | ||
the following features: | ||
- Robust to complex wind conditions and complex boundaries, including disjoint regions for | ||
turbine placement | ||
- Easy to parallelize and wrapped in a genetic algorithm for propagating candidate solutions | ||
- Simple to set up and tune for non-optimization experts | ||
- Set up to run cheap constraint checks prior to more expensive objective function evaluations | ||
to accelerate optimization | ||
|
||
The algorithm, described in full in an upcoming paper that will be linked here when it is | ||
publicly available, moves a random turbine and random distance in a random direction; checks | ||
that constraints are satisfied; evaluates the objective function (AEP or value); and then | ||
commits to the move if there is an objective function improvement. The main tuning parameter | ||
is the probability mass function for the random movement distance, which is a dictionary to be | ||
passed to the `distance_pmf` argument. | ||
|
||
The `distance_pmf` dictionary should contain two keys, each containing a 1D array of equal | ||
length: `"d"`, which specifies the perturbation distance _in units of the rotor diameter_, | ||
and `"p"`, which specifies the probability that the corresponding perturbation distance is | ||
chosen at any iteration of the random search algorithm. The `distance_pmf` can therefore be | ||
used to encourage or discourage more aggressive or more conservative movements, and to enable | ||
or disable jumps between disjoint regions for turbine placement. | ||
|
||
The figure below shows an example of the optimized layout of a farm using the GRS algorithm, with | ||
the black dots indicating the initial layout; red dots indicating the final layout; and blue | ||
shading indicating wind speed heterogeneity (lighter shade is lower wind speed, darker shade is | ||
higher wind speed). The progress of each of the genetic individuals in the optimization process is | ||
shown in the right-hand plot. | ||
![](plot_complex_docs.png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
"""Example of using the peak-shaving turbine operation model. | ||
This example demonstrates how to use the peak-shaving operation model in FLORIS. | ||
The peak-shaving operation model allows the user to a thrust reduction near rated wind speed to | ||
reduce loads on the turbine. The power is reduced accordingly, and wind turbine wakes | ||
are shallower due to the reduced thrust. | ||
""" | ||
|
||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
|
||
from floris import FlorisModel, TimeSeries | ||
|
||
|
||
fmodel = FlorisModel("../inputs/gch.yaml") | ||
fmodel.set(layout_x=[0, 1000.0], layout_y=[0.0, 0.0]) | ||
wind_speeds = np.linspace(0, 30, 100) | ||
fmodel.set( | ||
wind_data=TimeSeries( | ||
wind_directions=270 * np.ones_like(wind_speeds), | ||
wind_speeds=wind_speeds, | ||
turbulence_intensities=0.10, # High enough to engage peak shaving | ||
) | ||
) | ||
|
||
# Start with "normal" operation under the simple turbine operation model | ||
fmodel.set_operation_model("simple") | ||
fmodel.run() | ||
powers_base = fmodel.get_turbine_powers()/1000 | ||
thrust_coefficients_base = fmodel.get_turbine_thrust_coefficients() | ||
|
||
# Switch to the peak-shaving operation model | ||
fmodel.set_operation_model("peak-shaving") | ||
fmodel.run() | ||
powers_peak_shaving = fmodel.get_turbine_powers()/1000 | ||
thrust_coefficients_peak_shaving = fmodel.get_turbine_thrust_coefficients() | ||
|
||
# Compare the power and thrust coefficients of the upstream turbine | ||
fig, ax = plt.subplots(2,1,sharex=True) | ||
ax[0].plot( | ||
wind_speeds, | ||
thrust_coefficients_base[:,0], | ||
label="Without peak shaving", | ||
color="black" | ||
) | ||
ax[0].plot( | ||
wind_speeds, | ||
thrust_coefficients_peak_shaving[:,0], | ||
label="With peak shaving", | ||
color="C0" | ||
) | ||
ax[1].plot( | ||
wind_speeds, | ||
powers_base[:,0], | ||
label="Without peak shaving", | ||
color="black" | ||
) | ||
ax[1].plot( | ||
wind_speeds, | ||
powers_peak_shaving[:,0], | ||
label="With peak shaving", | ||
color="C0" | ||
) | ||
ax[1].grid() | ||
ax[0].grid() | ||
ax[0].legend() | ||
ax[0].set_ylabel("Thrust coefficient [-]") | ||
ax[1].set_xlabel("Wind speed [m/s]") | ||
ax[1].set_ylabel("Power [kW]") | ||
|
||
# Look at the total power across the two turbines for each case | ||
fig, ax = plt.subplots(2,1,sharex=True,sharey=True) | ||
ax[0].fill_between( | ||
wind_speeds, | ||
0, | ||
powers_base[:, 0]/1e6, | ||
color='C0', | ||
label='Turbine 1' | ||
) | ||
ax[0].fill_between( | ||
wind_speeds, | ||
powers_base[:, 0]/1e6, | ||
powers_base[:, :2].sum(axis=1)/1e6, | ||
color='C1', | ||
label='Turbine 2' | ||
) | ||
ax[0].plot( | ||
wind_speeds, | ||
powers_base[:,:2].sum(axis=1)/1e6, | ||
color='k', | ||
label='Farm' | ||
) | ||
ax[1].fill_between( | ||
wind_speeds, | ||
0, | ||
powers_peak_shaving[:, 0]/1e6, | ||
color='C0', | ||
label='Turbine 1' | ||
) | ||
ax[1].fill_between( | ||
wind_speeds, | ||
powers_peak_shaving[:, 0]/1e6, | ||
powers_peak_shaving[:, :2].sum(axis=1)/1e6, | ||
color='C1', | ||
label='Turbine 2' | ||
) | ||
ax[1].plot( | ||
wind_speeds, | ||
powers_peak_shaving[:,:2].sum(axis=1)/1e6, | ||
color='k', | ||
label='Farm' | ||
) | ||
ax[0].legend() | ||
ax[0].set_title("Without peak shaving") | ||
ax[1].set_title("With peak shaving") | ||
ax[0].set_ylabel("Power [MW]") | ||
ax[1].set_ylabel("Power [MW]") | ||
ax[0].grid() | ||
ax[1].grid() | ||
|
||
ax[1].set_xlabel("Free stream wind speed [m/s]") | ||
|
||
plt.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.