Skip to content

Commit

Permalink
Merge pull request #994 from NREL/develop
Browse files Browse the repository at this point in the history
FLORIS v4.2
  • Loading branch information
misi9170 authored Oct 26, 2024
2 parents 2c3be8f + 8a2bdd0 commit 440549c
Show file tree
Hide file tree
Showing 59 changed files with 6,670 additions and 514 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ PACKAGE CONTENTS
wind_data

VERSION
4.1.1
4.2

FILE
~/floris/floris/__init__.py
Expand Down
2 changes: 1 addition & 1 deletion docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ only_build_toc_files: false
# See https://jupyterbook.org/content/execute.html
execute:
execute_notebooks: auto
timeout: 360 # Give each notebook cell 6 minutes to execute
timeout: 420 # Give each notebook cell 7 minutes to execute

# Define the name of the latex output file for PDF builds
latex:
Expand Down
3 changes: 2 additions & 1 deletion docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ parts:
- caption: User Reference
chapters:
- file: intro_concepts
- file: advanced_concepts
- file: wind_data_user
- file: floris_models
- file: advanced_concepts
- file: heterogeneous_map
- file: floating_wind_turbine
- file: turbine_interaction
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced_concepts.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"# Advanced Concepts\n",
"\n",
"More information regarding the numerical and computational formulation in FLORIS\n",
"are detailed here. See [](concepts_intro) for a guide on the basics."
"are detailed here. See [Introductory Concepts](intro_concepts) for a guide on the basics."
]
},
{
Expand Down
4 changes: 2 additions & 2 deletions docs/dev_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ developer's guide, so please read on to learn more about each of these steps.

## Git and GitHub Workflows

The majority of the collaboration and development for FLORIS takes place
in the [GitHub repository](http://github.com/nrel/floris). There,
The majority of the collaboration and development for FLORIS takes place in
the [GitHub repository](http://github.com/nrel/floris). There,
[issues](http://github.com/nrel/floris/issues) and
[pull requests](http://github.com/nrel/floris/pulls) are managed,
questions and ideas are [discussed](https://github.com/NREL/floris/discussions),
Expand Down
390 changes: 390 additions & 0 deletions docs/floris_models.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/intro_concepts.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"id": "86e53920",
"metadata": {},
"source": [
"(concepts_intro)=\n",
"(intro_concepts)=\n",
"# Introductory Concepts\n",
"\n",
"FLORIS is a Python-based software library for calculating wind farm performance considering\n",
Expand Down
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.
13 changes: 13 additions & 0 deletions docs/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,19 @@ @Article{bay_2022
DOI = {10.5194/wes-2022-17}
}

@article{Pedersen_2022_turbopark2,
url = {https://dx.doi.org/10.1088/1742-6596/2265/2/022063},
year = {2022},
month = {may},
publisher = {IOP Publishing},
volume = {2265},
number = {2},
pages = {022063},
author = {J G Pedersen and E Svensson and L Poulsen and N G Nygaard},
title = {Turbulence Optimized Park model with Gaussian wake profile},
journal = {Journal of Physics: Conference Series},
}

@article{SinnerFleming2024grs,
doi = {10.1088/1742-6596/2767/3/032036},
url = {https://dx.doi.org/10.1088/1742-6596/2767/3/032036},
Expand Down
167 changes: 37 additions & 130 deletions docs/wake_models.ipynb

Large diffs are not rendered by default.

357 changes: 287 additions & 70 deletions docs/wind_data_user.ipynb

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions examples/009_parallel_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Example 9: Parallel Models
This example demonstrates how to use the ParFlorisModel class to parallelize the
calculation of the FLORIS model. ParFlorisModel inherits from the FlorisModel
and so can be used in the same way with a consistent interface. ParFlorisModel
replaces the ParallelFlorisModel, which will be deprecated in a future release.
"""

import numpy as np

from floris import (
FlorisModel,
ParFlorisModel,
TimeSeries,
UncertainFlorisModel,
)


# When using parallel optimization it is important the "root" script include this
# if __name__ == "__main__": block to avoid problems
if __name__ == "__main__":
# Instantiate the FlorisModel
fmodel = FlorisModel("inputs/gch.yaml")

# The ParFlorisModel can be instantiated either from a FlorisModel or from
# the input file.
pfmodel_1 = ParFlorisModel("inputs/gch.yaml") # Via input file
pfmodel_2 = ParFlorisModel(fmodel) # Via FlorisModel

# The ParFlorisModel has additional inputs which define the parallelization
# but don't affect the output.
pfmodel_3 = ParFlorisModel(
fmodel,
interface="multiprocessing", # Default
max_workers=2, # Defaults to num_cpu
n_wind_condition_splits=2, # Defaults to max_workers
)

# Define a simple inflow
time_series = TimeSeries(
wind_speeds=np.arange(1, 25, 0.5), wind_directions=270.0, turbulence_intensities=0.06
)

# Demonstrate that interface and results are the same
fmodel.set(wind_data=time_series)
pfmodel_1.set(wind_data=time_series)
pfmodel_2.set(wind_data=time_series)
pfmodel_3.set(wind_data=time_series)

fmodel.run()
pfmodel_1.run()
pfmodel_2.run()
pfmodel_3.run()

# Compare the results
powers_fmodel = fmodel.get_turbine_powers()
powers_pfmodel_1 = pfmodel_1.get_turbine_powers()
powers_pfmodel_2 = pfmodel_2.get_turbine_powers()
powers_pfmodel_3 = pfmodel_3.get_turbine_powers()

print(
f"Testing if outputs of fmodel and pfmodel_1 are "
f"close: {np.allclose(powers_fmodel, powers_pfmodel_1)}"
)
print(
f"Testing if outputs of fmodel and pfmodel_2 are "
f"close: {np.allclose(powers_fmodel, powers_pfmodel_2)}"
)
print(
f"Testing if outputs of fmodel and pfmodel_3 are "
f"close: {np.allclose(powers_fmodel, powers_pfmodel_3)}"
)

# Because ParFlorisModel is a subclass of FlorisModel, it can also be used as
# an input to the UncertainFlorisModel class. This allows for parallelization of
# the uncertainty calculations.
ufmodel = UncertainFlorisModel(fmodel)
pufmodel = UncertainFlorisModel(pfmodel_1)

# Demonstrate matched results
ufmodel.set(wind_data=time_series)
pufmodel.set(wind_data=time_series)

ufmodel.run()
pufmodel.run()

powers_ufmodel = ufmodel.get_turbine_powers()
powers_pufmodel = pufmodel.get_turbine_powers()

print("--------------------")
print(
f"Testing if outputs of ufmodel and pufmodel are "
f"close: {np.allclose(powers_ufmodel, powers_pufmodel)}"
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Example 9: Compare farm power with neighboring farm
"""Example 10: Compare farm power with neighboring farm
This example demonstrates how to use turbine_weights to define a set of turbines belonging
to a neighboring farm which impacts the power production of the farm under consideration
Expand Down
25 changes: 25 additions & 0 deletions examples/examples_control_optimization/001_opt_yaw_single_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import matplotlib.pyplot as plt
import numpy as np

import floris.flow_visualization as flowviz
import floris.layout_visualization as layoutviz
from floris import FlorisModel, TimeSeries
from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR

Expand Down Expand Up @@ -63,4 +65,27 @@
ax.legend()
ax.grid(True)

# Visualize results for a single wind direction (270 deg) and wind speed (8 m/s)
fig, axarr = plt.subplots(2, 1, figsize=(10, 5), sharex=False)
ax = axarr[0] # Baseline aligned operation
fmodel.reset_operation()
fmodel.set(wind_directions=[270.0], wind_speeds=[8.0], turbulence_intensities=[0.06])
fmodel.run()
horizontal_plane = fmodel.calculate_horizontal_plane(height=90.0)
flowviz.visualize_cut_plane(horizontal_plane, ax=ax)
layoutviz.plot_turbine_rotors(fmodel, ax=ax)
ax.set_title("Turbines aligned")

ax = axarr[1] # Optimized yaw angles
optimal_yaw_angles = (
df_opt[(df_opt["wind_direction"] == 270.0) & (df_opt["wind_speed"] == 8.0)]
.yaw_angles_opt.values[0]
).reshape(1,-1)
fmodel.set(yaw_angles=optimal_yaw_angles)
fmodel.run()
horizontal_plane = fmodel.calculate_horizontal_plane(height=90.0)
flowviz.visualize_cut_plane(horizontal_plane, ax=ax)
layoutviz.plot_turbine_rotors(fmodel, ax=ax, yaw_angles=optimal_yaw_angles)
ax.set_title("Optimized yaw angles")

plt.show()
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
import numpy as np

from floris import (
FlorisModel,
ParallelFlorisModel,
ParFlorisModel,
TimeSeries,
WindRose,
)
from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR


# When using parallel optimization it is importat the "root" script include this
# When using parallel optimization it is important the "root" script include this
# if __name__ == "__main__": block to avoid problems
if __name__ == "__main__":

Expand All @@ -33,66 +33,52 @@
ti_col_or_value=0.06
)

# Load FLORIS
fmodel = FlorisModel("../inputs/gch.yaml")
# Load FLORIS as a parallel model
max_workers = 16
pfmodel = ParFlorisModel(
"../inputs/gch.yaml",
max_workers=max_workers,
n_wind_condition_splits=max_workers,
interface="pathos",
print_timings=True,
)

# Specify wind farm layout and update in the floris object
N = 2 # number of turbines per row and per column
X, Y = np.meshgrid(
5.0 * fmodel.core.farm.rotor_diameters_sorted[0][0] * np.arange(0, N, 1),
5.0 * fmodel.core.farm.rotor_diameters_sorted[0][0] * np.arange(0, N, 1),
5.0 * pfmodel.core.farm.rotor_diameters_sorted[0][0] * np.arange(0, N, 1),
5.0 * pfmodel.core.farm.rotor_diameters_sorted[0][0] * np.arange(0, N, 1),
)
fmodel.set(layout_x=X.flatten(), layout_y=Y.flatten())
pfmodel.set(layout_x=X.flatten(), layout_y=Y.flatten())

# Get the number of turbines
n_turbines = len(fmodel.layout_x)
n_turbines = len(pfmodel.layout_x)

# Optimize the yaw angles. This could be done for every wind direction and wind speed
# but in practice it is much faster to optimize only for one speed and infer the rest
# using a rule of thumb
time_series = TimeSeries(
wind_directions=wind_rose.wind_directions, wind_speeds=8.0, turbulence_intensities=0.06
)
fmodel.set(wind_data=time_series)

# Set up the parallel model
parallel_interface = "concurrent"
max_workers = 16
pfmodel = ParallelFlorisModel(
fmodel=fmodel,
max_workers=max_workers,
n_wind_condition_splits=max_workers,
interface=parallel_interface,
print_timings=True,
)
pfmodel.set(wind_data=time_series)

# Get the optimal angles using the parallel interface
start_time = timerpc()
# Now optimize the yaw angles using the Serial Refine method
df_opt = pfmodel.optimize_yaw_angles(
minimum_yaw_angle=0.0,
maximum_yaw_angle=20.0,
yaw_opt = YawOptimizationSR(
fmodel=pfmodel,
minimum_yaw_angle=0.0, # Allowable yaw angles lower bound
maximum_yaw_angle=20.0, # Allowable yaw angles upper bound
Ny_passes=[5, 4],
exclude_downstream_turbines=False,
exclude_downstream_turbines=True,
)
df_opt = yaw_opt.optimize()
end_time = timerpc()
t_tot = end_time - start_time
print("Optimization finished in {:.2f} seconds.".format(t_tot))


# Calculate the AEP in the baseline case, using the parallel interface
fmodel.set(wind_data=wind_rose)
pfmodel = ParallelFlorisModel(
fmodel=fmodel,
max_workers=max_workers,
n_wind_condition_splits=max_workers,
interface=parallel_interface,
print_timings=True,
)

# Note the pfmodel does not use run() but instead uses the get_farm_power() and get_farm_AEP()
# directly, this is necessary for the parallel interface
aep_baseline = pfmodel.get_farm_AEP(freq=wind_rose.unpack_freq())
pfmodel.set(wind_data=wind_rose)
pfmodel.run()
aep_baseline = pfmodel.get_farm_AEP()

# Now need to apply the optimal yaw angles to the wind rose to get the optimized AEP
# do this by applying a rule of thumb where the optimal yaw is applied between 6 and 12 m/s
Expand All @@ -102,9 +88,10 @@
# yaw angles will need to be n_findex long, and accounting for the fact that some wind
# directions and wind speeds may not be present in the wind rose (0 frequency) and aren't
# included in the fmodel
wind_directions = fmodel.wind_directions
wind_speeds = fmodel.wind_speeds
n_findex = fmodel.n_findex
# TODO: add operation wind rose to example, once built
wind_directions = pfmodel.wind_directions
wind_speeds = pfmodel.wind_speeds
n_findex = pfmodel.n_findex


# Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds
Expand Down Expand Up @@ -133,15 +120,9 @@


# Now apply the optimal yaw angles and get the AEP
fmodel.set(yaw_angles=yaw_angles_wind_rose)
pfmodel = ParallelFlorisModel(
fmodel=fmodel,
max_workers=max_workers,
n_wind_condition_splits=max_workers,
interface=parallel_interface,
print_timings=True,
)
aep_opt = pfmodel.get_farm_AEP(freq=wind_rose.unpack_freq(), yaw_angles=yaw_angles_wind_rose)
pfmodel.set(yaw_angles=yaw_angles_wind_rose)
pfmodel.run()
aep_opt = pfmodel.get_farm_AEP()
aep_uplift = 100.0 * (aep_opt / aep_baseline - 1)

print("Baseline AEP: {:.2f} GWh.".format(aep_baseline/1E9))
Expand Down
Loading

0 comments on commit 440549c

Please sign in to comment.