Skip to content

Commit

Permalink
Add gridded layout optimizer (#976)
Browse files Browse the repository at this point in the history
  • Loading branch information
misi9170 committed Sep 11, 2024
1 parent b7032c4 commit 56921d8
Show file tree
Hide file tree
Showing 6 changed files with 601 additions and 21 deletions.
24 changes: 24 additions & 0 deletions docs/layout_optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,27 @@ shading indicating wind speed heterogeneity (lighter shade is lower wind speed,
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)

## Gridded layout optimization
The `LayoutOptimizationGridded` class allows users to quickly find a layout that fits the most
turbines possible into the specified boundary area, given that the turbines are arranged in a
gridded layout.
To do so, a range of different rotations and translations of a generic gridded arrangement are
tried, and the one that fits the most turbines into the boundary area is selected. No AEP
evaluations are performed; rather, the cost function $f$ to be maximized is simply $N$, the number
of turbines, and there is an additional constraint that the turbines are arranged in a gridded
fashion. Note that in other layout optimizers, $N$ is fixed.

We envisage that this will be useful for users that want to quickly generate a layout to
"fill" a boundary region in a gridded manner. By default, the gridded arrangement is a square grid
with spacing of `min_dist` (or `min_dist_D`); however, instantiating with the `hexagonal_packing`
keyword argument set to `True` will provide a grid that offsets the rows to enable tighter packing
of turbines while still satisfying the `min_dist`.

As with the `LayoutOptimizationRandomSearch` class, the boundaries specified can be complex (and
may contain separate areas).
User settings include `rotation_step`, which specifies the step size for rotating the grid
(in degrees); `rotation_range`, which specifies the range of rotation angles; `translation_step` or
`translation_step_D`, which specifies the step size for translating the grid in meters or rotor
diameters, respectively; and `translation_range`, which specifies the range of possible
translations. All come with default values, which we expect to be suitable for many or most users.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Example: Gridded layout design
This example shows a layout optimization that places as many turbines as
possible into a given boundary using a gridded layout pattern.
"""

import matplotlib.pyplot as plt
import numpy as np

from floris import FlorisModel, WindRose
from floris.optimization.layout_optimization.layout_optimization_gridded import (
LayoutOptimizationGridded,
)


if __name__ == '__main__':
# Load the Floris model
fmodel = FlorisModel('../inputs/gch.yaml')

# Set the boundaries
# The boundaries for the turbines, specified as vertices
boundaries = [(0.0, 0.0), (0.0, 1000.0), (1000.0, 1000.0), (1000.0, 0.0), (0.0, 0.0)]

# Set up the optimization object with 5D spacing
layout_opt = LayoutOptimizationGridded(
fmodel,
boundaries,
min_dist_D=5., # results in spacing of 5*125.88 = 629.4 m
min_dist=None, # Alternatively, can specify spacing directly in meters
)

layout_opt.optimize()

# Note that the "initial" layout that is provided with the fmodel is
# not used by the layout optimization.
layout_opt.plot_layout_opt_results()

plt.show()
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Example: Separated boundaries layout optimization
Demonstrates the capabilities of LayoutOptimizationGridded and
LayoutOptimizationRandomSearch to optimize turbine layouts with complex
boundaries.
"""

import matplotlib.pyplot as plt
import numpy as np

from floris import FlorisModel, WindRose
from floris.optimization.layout_optimization.layout_optimization_gridded import (
LayoutOptimizationGridded,
)
from floris.optimization.layout_optimization.layout_optimization_random_search import (
LayoutOptimizationRandomSearch,
)


if __name__ == '__main__':
# Load the Floris model
fmodel = FlorisModel('../inputs/gch.yaml')

# Set the boundaries
# The boundaries for the turbines, specified as vertices
boundaries = [
[(0.0, 0.0), (0.0, 1000.0), (1000.0, 1000.0), (1000.0, 0.0), (0.0, 0.0)],
[(1500.0, 0.0), (1500.0, 1000.0), (2500.0, 0.0), (1500.0, 0.0)],
]

# Set up the wind data information
wind_directions = np.arange(0, 360.0, 5.0)
np.random.seed(1)
wind_speeds = 8.0 + np.random.randn(1) * 0.0
# Shape frequency distribution to match number of wind directions and wind speeds
freq = (
np.abs(
np.sort(
np.random.randn(len(wind_directions))
)
)
.reshape( ( len(wind_directions), len(wind_speeds) ) )
)
freq = freq / freq.sum()
# Set wind data in the FlorisModel
fmodel.set(
wind_data=WindRose(
wind_directions=wind_directions,
wind_speeds=wind_speeds,
freq_table=freq,
ti_table=0.06
)
)

# Begin by placing as many turbines as possible using a gridded layout at 6D spacing
layout_opt_gridded = LayoutOptimizationGridded(
fmodel,
boundaries,
min_dist_D=6.,
min_dist=None,
)
layout_opt_gridded.optimize()
print("Gridded layout complete.")

# Set the layout on the fmodel
fmodel.set(layout_x=layout_opt_gridded.x_opt, layout_y=layout_opt_gridded.y_opt)

# Update the layout using a random search optimization with 5D minimum spacing
layout_opt_rs = LayoutOptimizationRandomSearch(
fmodel,
boundaries,
min_dist_D=5.,
seconds_per_iteration=10,
total_optimization_seconds=60.,
use_dist_based_init=False,
)
layout_opt_rs.optimize()

layout_opt_rs.plot_layout_opt_results(
initial_locs_plotting_dict={"label": "Gridded initial layout"},
final_locs_plotting_dict={"label": "Random search optimized layout"},
)

plt.show()
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def __init__(
b_depth = list_depth(boundaries)

boundary_specification_error_msg = (
"boundaries should be a list of coordinates (specifed as (x,y) "+\
"tuples) or as a list of list of tuples (for seperable regions)."
"boundaries should be a list of coordinates (specified as (x,y) "+\
"tuples) or as a list of list of tuples (for separable regions)."
)

if b_depth == 1:
Expand Down Expand Up @@ -130,7 +130,14 @@ def optimize(self):
sol = self._optimize()
return sol

def plot_layout_opt_results(self, plot_boundary_dict={}, ax=None, fontsize=16):
def plot_layout_opt_results(
self,
plot_boundary_dict={},
initial_locs_plotting_dict={},
final_locs_plotting_dict={},
ax=None,
fontsize=16
):

x_initial, y_initial, x_opt, y_opt = self._get_initial_and_final_locs()

Expand All @@ -140,6 +147,7 @@ def plot_layout_opt_results(self, plot_boundary_dict={}, ax=None, fontsize=16):
ax = fig.add_subplot(111)
ax.set_aspect("equal")

# Handle default boundary plotting
default_plot_boundary_dict = {
"color":"None",
"alpha":1,
Expand All @@ -148,9 +156,30 @@ def plot_layout_opt_results(self, plot_boundary_dict={}, ax=None, fontsize=16):
}
plot_boundary_dict = {**default_plot_boundary_dict, **plot_boundary_dict}

# Handle default initial location plotting
default_initial_locs_plotting_dict = {
"marker":"o",
"color":"b",
"linestyle":"None",
"label":"Initial locations",
}
initial_locs_plotting_dict = {
**default_initial_locs_plotting_dict,
**initial_locs_plotting_dict
}

# Handle default final location plotting
default_final_locs_plotting_dict = {
"marker":"o",
"color":"r",
"linestyle":"None",
"label":"New locations",
}
final_locs_plotting_dict = {**default_final_locs_plotting_dict, **final_locs_plotting_dict}

self.plot_layout_opt_boundary(plot_boundary_dict, ax=ax)
ax.plot(x_initial, y_initial, "ob", label="Initial locations")
ax.plot(x_opt, y_opt, "or", label="New locations")
ax.plot(x_initial, y_initial, **initial_locs_plotting_dict)
ax.plot(x_opt, y_opt, **final_locs_plotting_dict)
ax.set_xlabel("x (m)", fontsize=fontsize)
ax.set_ylabel("y (m)", fontsize=fontsize)
ax.grid(True)
Expand Down
Loading

0 comments on commit 56921d8

Please sign in to comment.