From cccc604782b67faf79944c76458b949c92aad08e Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 13 Mar 2024 10:15:58 -0600 Subject: [PATCH 001/120] Rename example 1 --- ...s_computing_power.py => 001_opening_floris_computing_power.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{01_opening_floris_computing_power.py => 001_opening_floris_computing_power.py} (100%) diff --git a/examples/01_opening_floris_computing_power.py b/examples/001_opening_floris_computing_power.py similarity index 100% rename from examples/01_opening_floris_computing_power.py rename to examples/001_opening_floris_computing_power.py From a86d7523ade103ad4dff5ada25648ccb99469733 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 13 Mar 2024 12:30:59 -0600 Subject: [PATCH 002/120] Start example 2 --- examples/002_wind_data_objects.py | 65 +++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 examples/002_wind_data_objects.py diff --git a/examples/002_wind_data_objects.py b/examples/002_wind_data_objects.py new file mode 100644 index 000000000..747274958 --- /dev/null +++ b/examples/002_wind_data_objects.py @@ -0,0 +1,65 @@ +"""Example 2: Wind Data Objects + +This second example demonstrates the use of wind data objects in FLORIS: + TimeSeries, + WindRose, and WindTIRose. + + +Main concept is introduce FLORIS and illustrate essential structure +of most-used FLORIS calls +""" + + +import numpy as np + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, + WindTIRose, +) + + +# Initialize FLORIS with the given input file. +# The Floris class is the entry point for most usage. +fmodel = FlorisModel("inputs/gch.yaml") + +# Changing the wind farm layout uses FLORIS' set method to a two-turbine layout +fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0]) + +# Changing wind speed, wind direction, and turbulence intensity using the set method +# as well. Note that the wind_speeds, wind_directions, and turbulence_intensities +# are all specified as arrays of the same length. +fmodel.set(wind_directions=np.array([270.0]), + wind_speeds=[8.0], + turbulence_intensities=np.array([0.06])) + +# Note that typically all 3, wind_directions, wind_speeds and turbulence_intensities +# must be supplied to set. However, the exception is if not changing the lenght +# of the arrays, then only one or two may be supplied. +fmodel.set(turbulence_intensities=np.array([0.07])) + +# The number of elements in the wind_speeds, wind_directions, and turbulence_intensities +# corresponds to the number of conditions to be simulated. In FLORIS, each of these are +# tracked by a simple index called a findex. There is no requirement that the values +# be unique. Internally in FLORIS, most data structures will have the findex as their +# 0th dimension. The value n_findex is the total number of conditions to be simulated. +# This command would simulate 4 conditions (n_findex = 4). +fmodel.set(wind_directions=np.array([270.0, 270.0, 270.0, 270.0]), + wind_speeds=[8.0, 8.0, 10.0, 10.0], + turbulence_intensities=np.array([0.06, 0.06, 0.06, 0.06])) + +# After the set method, the run method is called to perform the simulation +fmodel.run() + +# There are functions to get either the power of each turbine, or the farm power +turbine_powers = fmodel.get_turbine_powers() / 1000.0 +farm_power = fmodel.get_farm_power() / 1000.0 + +print("The turbine power matrix should be of dimensions 4 (n_findex) X 2 (n_turbines)") +print(turbine_powers) +print("Shape: ", turbine_powers.shape) + +print("The farm power should be a 1D array of length 4 (n_findex)") +print(farm_power) +print("Shape: ", farm_power.shape) From c0ea9fdc1a028ce43f7cd469ba135ddb4c63c7b9 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 13 Mar 2024 16:01:02 -0600 Subject: [PATCH 003/120] Update visualization examples --- examples/002_visualizations.py | 95 +++++++++++ examples/02_visualizations.py | 149 ------------------ .../layout_visualizations.py} | 43 +++-- .../visualize_cross_plane.py | 37 +++++ .../visualize_flow_by_sweeping_turbines.py | 43 +++++ .../visualize_rotor_values.py | 33 ++++ .../visualize_y_cut_plane.py | 33 ++++ 7 files changed, 261 insertions(+), 172 deletions(-) create mode 100644 examples/002_visualizations.py delete mode 100644 examples/02_visualizations.py rename examples/{23_layout_visualizations.py => examples_visualizations/layout_visualizations.py} (69%) create mode 100644 examples/examples_visualizations/visualize_cross_plane.py create mode 100644 examples/examples_visualizations/visualize_flow_by_sweeping_turbines.py create mode 100644 examples/examples_visualizations/visualize_rotor_values.py create mode 100644 examples/examples_visualizations/visualize_y_cut_plane.py diff --git a/examples/002_visualizations.py b/examples/002_visualizations.py new file mode 100644 index 000000000..affec77d8 --- /dev/null +++ b/examples/002_visualizations.py @@ -0,0 +1,95 @@ +"""Example 2: Visualizations + +This example demonstrates the use of the flow and layout visualizations in FLORIS. + +""" + + +import matplotlib.pyplot as plt +import numpy as np + +import floris.layout_visualization as layoutviz +from floris import FlorisModel +from floris.flow_visualization import visualize_cut_plane + + +# FLORIS includes two modules for visualization: +# 1) flow_visualization: for visualizing the flow field +# 2) layout_visualization: for visualizing the layout of the wind farm +# The two modules can be used together to visualize the flow field and the layout +# of the wind farm. + +# Initialize FLORIS with the given input file via FlorisModel. +# For basic usage, FlorisModel provides a simplified and expressive +# entry point to the simulation routines. +fmodel = FlorisModel("inputs/gch.yaml") + +# Set the farm layout to have 8 turbines irregularly placed +layout_x = [0, 500, 0, 128, 1000, 900, 1500, 1250] +layout_y = [0, 300, 750, 1400, 0, 567, 888, 1450] +fmodel.set(layout_x=layout_x, layout_y=layout_y) + + +# Layout visualization contains the functions for visualizing the layout: +# plot_turbine_points +# plot_turbine_labels +# plot_turbine_rotors +# plot_waking_directions +# Each of which can be overlaid to provide further information about the layout +# This series of 4 subplots shows the different ways to visualize the layout + +# Create the plotting objects using matplotlib +fig, axarr = plt.subplots(2, 2, figsize=(15, 10), sharex=False) +axarr = axarr.flatten() + +ax = axarr[0] +layoutviz.plot_turbine_points(fmodel, ax=ax) +ax.set_title("Turbine Points") + +ax = axarr[1] +layoutviz.plot_turbine_points(fmodel, ax=ax) +layoutviz.plot_turbine_labels(fmodel, ax=ax) +ax.set_title("Turbine Points and Labels") + +ax = axarr[2] +layoutviz.plot_turbine_points(fmodel, ax=ax) +layoutviz.plot_turbine_labels(fmodel, ax=ax) +layoutviz.plot_waking_directions(fmodel, ax=ax, limit_num=2) +ax.set_title("Turbine Points, Labels, and Waking Directions") + +# In the final subplot, use provided turbine names in place of the t_index +ax = axarr[3] +turbine_names = ["T1", "T2", "T3", "T4", "T9", "T10", "T75", "T78"] +layoutviz.plot_turbine_points(fmodel, ax=ax) +layoutviz.plot_turbine_labels(fmodel, ax=ax, turbine_names=turbine_names) +layoutviz.plot_waking_directions(fmodel, ax=ax, limit_num=2) +ax.set_title("Use Provided Turbine Names") + + +# Visualizations of the flow field are made by using calculate plane methods. In this example +# we show the horizontal plane at hub height, further examples are provided within +# the examples_visualizations folder + +# For flow visualizations, the FlorisModel must be set to run a single condition +# (n_findex = 1) +fmodel.set(wind_speeds=[8.0], wind_directions=[290.0], turbulence_intensities=[0.06]) +horizontal_plane = fmodel.calculate_horizontal_plane( + x_resolution=200, + y_resolution=100, + height=90.0, +) + +# Plot the flow field with rotors +fig, ax = plt.subplots() +visualize_cut_plane( + horizontal_plane, + ax=ax, + label_contours=False, + title="Horizontal Flow with Turbine Rotors and labels", +) + +# Plot the turbine rotors +layoutviz.plot_turbine_rotors(fmodel, ax=ax) +layoutviz.plot_turbine_labels(fmodel, ax=ax, turbine_names=turbine_names) + +plt.show() diff --git a/examples/02_visualizations.py b/examples/02_visualizations.py deleted file mode 100644 index de526328f..000000000 --- a/examples/02_visualizations.py +++ /dev/null @@ -1,149 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -import floris.flow_visualization as flowviz -from floris import FlorisModel - - -""" -This example initializes the FLORIS software, and then uses internal -functions to run a simulation and plot the results. In this case, -we are plotting three slices of the resulting flow field: -1. Horizontal slice parallel to the ground and located at the hub height -2. Vertical slice of parallel with the direction of the wind -3. Vertical slice parallel to to the turbine disc plane - -Additionally, an alternative method of plotting a horizontal slice -is shown. Rather than calculating points in the domain behind a turbine, -this method adds an additional turbine to the farm and moves it to -locations throughout the farm while calculating the velocity at it's -rotor. -""" - -# Initialize FLORIS with the given input file via FlorisModel. -# For basic usage, FlorisModel provides a simplified and expressive -# entry point to the simulation routines. -fmodel = FlorisModel("inputs/gch.yaml") - -# The rotor plots show what is happening at each turbine, but we do not -# see what is happening between each turbine. For this, we use a -# grid that has points regularly distributed throughout the fluid domain. -# The FlorisModel contains functions for configuring the new grid, -# running the simulation, and generating plots of 2D slices of the -# flow field. - -# Note this visualization grid created within the calculate_horizontal_plane function will be reset -# to what existed previously at the end of the function - -# Using the FlorisModel functions, get 2D slices. -horizontal_plane = fmodel.calculate_horizontal_plane( - x_resolution=200, - y_resolution=100, - height=90.0, - yaw_angles=np.array([[25.,0.,0.]]), -) - -y_plane = fmodel.calculate_y_plane( - x_resolution=200, - z_resolution=100, - crossstream_dist=0.0, - yaw_angles=np.array([[25.,0.,0.]]), -) -cross_plane = fmodel.calculate_cross_plane( - y_resolution=100, - z_resolution=100, - downstream_dist=630.0, - yaw_angles=np.array([[25.,0.,0.]]), -) - -# Create the plots -fig, ax_list = plt.subplots(3, 1, figsize=(10, 8)) -ax_list = ax_list.flatten() -flowviz.visualize_cut_plane( - horizontal_plane, - ax=ax_list[0], - label_contours=True, - title="Horizontal" -) -flowviz.visualize_cut_plane( - y_plane, - ax=ax_list[1], - label_contours=True, - title="Streamwise profile" -) -flowviz.visualize_cut_plane( - cross_plane, - ax=ax_list[2], - label_contours=True, - title="Spanwise profile" -) - -# Some wake models may not yet have a visualization method included, for these cases can use -# a slower version which scans a turbine model to produce the horizontal flow -horizontal_plane_scan_turbine = flowviz.calculate_horizontal_plane_with_turbines( - fmodel, - x_resolution=20, - y_resolution=10, - yaw_angles=np.array([[25.,0.,0.]]), -) - -fig, ax = plt.subplots() -flowviz.visualize_cut_plane( - horizontal_plane_scan_turbine, - ax=ax, - label_contours=True, - title="Horizontal (coarse turbine scan method)", -) - -# FLORIS further includes visualization methods for visualing the rotor plane of each -# Turbine in the simulation - -# Run the wake calculation to get the turbine-turbine interfactions -# on the turbine grids -fmodel.run() - -# Plot the values at each rotor -fig, axes, _ , _ = flowviz.plot_rotor_values( - fmodel.core.flow_field.u, - findex=0, - n_rows=1, - n_cols=3, - return_fig_objects=True -) -fig.suptitle("Rotor Plane Visualization, Original Resolution") - -# FLORIS supports multiple types of grids for capturing wind speed -# information. The current input file is configured with a square grid -# placed on each rotor plane with 9 points in a 3x3 layout. For visualization, -# this resolution can be increased. Note this operation, unlike the -# calc_x_plane above operations does not automatically reset the grid to -# the initial status as definied by the input file - -# Increase the resolution of points on each turbien plane -solver_settings = { - "type": "turbine_grid", - "turbine_grid_points": 10 -} -fmodel.set(solver_settings=solver_settings) - -# Run the wake calculation to get the turbine-turbine interfactions -# on the turbine grids -fmodel.run() - -# Plot the values at each rotor -fig, axes, _ , _ = flowviz.plot_rotor_values( - fmodel.core.flow_field.u, - findex=0, - n_rows=1, - n_cols=3, - return_fig_objects=True -) -fig.suptitle("Rotor Plane Visualization, 10x10 Resolution") - -# Show plots -plt.show() - -# Note if the user doesn't import matplotlib.pyplot as plt, the user can -# use the following to show the plots: -# flowviz.show() diff --git a/examples/23_layout_visualizations.py b/examples/examples_visualizations/layout_visualizations.py similarity index 69% rename from examples/23_layout_visualizations.py rename to examples/examples_visualizations/layout_visualizations.py index 465490e6e..cbf46a52a 100644 --- a/examples/23_layout_visualizations.py +++ b/examples/examples_visualizations/layout_visualizations.py @@ -1,3 +1,8 @@ +"""Example: Layout Visualizations + +Demonstrate the use of all the functions within the layout_visualization module + +""" import matplotlib.pyplot as plt import numpy as np @@ -7,10 +12,6 @@ from floris.flow_visualization import visualize_cut_plane -""" -This example shows a number of different ways to visualize a farm layout using FLORIS -""" - # Create the plotting objects using matplotlib fig, axarr = plt.subplots(3, 3, figsize=(16, 10), sharex=False) axarr = axarr.flatten() @@ -19,7 +20,7 @@ MAX_WS = 8.0 # Initialize FLORIS with the given input file. -fmodel = FlorisModel("inputs/gch.yaml") +fmodel = FlorisModel("../inputs/gch.yaml") # Change to 5-turbine layout with a wind direction from northwest fmodel.set( @@ -38,31 +39,25 @@ ) # Plot the turbine points, setting the color to white layoutviz.plot_turbine_points(fmodel, ax=ax, plotting_dict={"color": "w"}) -ax.set_title('Flow visualization and turbine points') +ax.set_title("Flow visualization and turbine points") # Plot 2: Show a particular flow case ax = axarr[1] turbine_names = [f"T{i}" for i in [10, 11, 12, 13, 22]] layoutviz.plot_turbine_points(fmodel, ax=ax) -layoutviz.plot_turbine_labels(fmodel, - ax=ax, - turbine_names=turbine_names, - show_bbox=True, - bbox_dict={'facecolor':'r'}) +layoutviz.plot_turbine_labels( + fmodel, ax=ax, turbine_names=turbine_names, show_bbox=True, bbox_dict={"facecolor": "r"} +) ax.set_title("Show turbine names with a red bounding box") # Plot 2: Show turbine rotors on flow ax = axarr[2] -horizontal_plane = fmodel.calculate_horizontal_plane(height=90.0, - yaw_angles=np.array([[0., 30., 0., 0., 0.]])) -visualize_cut_plane( - horizontal_plane, - ax=ax, - min_speed=MIN_WS, - max_speed=MAX_WS +horizontal_plane = fmodel.calculate_horizontal_plane( + height=90.0, yaw_angles=np.array([[0.0, 30.0, 0.0, 0.0, 0.0]]) ) -layoutviz.plot_turbine_rotors(fmodel,ax=ax,yaw_angles=np.array([[0., 30., 0., 0., 0.]])) +visualize_cut_plane(horizontal_plane, ax=ax, min_speed=MIN_WS, max_speed=MAX_WS) +layoutviz.plot_turbine_rotors(fmodel, ax=ax, yaw_angles=np.array([[0.0, 30.0, 0.0, 0.0, 0.0]])) ax.set_title("Flow visualization with yawed turbine") # Plot 3: Show the layout, including wake directions @@ -74,15 +69,17 @@ # Plot 4: Plot a subset of the layout, and limit directions less than 7D ax = axarr[4] -layoutviz.plot_turbine_points(fmodel, ax=ax, turbine_indices=[0,1,2,3]) -layoutviz.plot_turbine_labels(fmodel, ax=ax, turbine_names=turbine_names, turbine_indices=[0,1,2,3]) -layoutviz.plot_waking_directions(fmodel, ax=ax, turbine_indices=[0,1,2,3], limit_dist_D=7) +layoutviz.plot_turbine_points(fmodel, ax=ax, turbine_indices=[0, 1, 2, 3]) +layoutviz.plot_turbine_labels( + fmodel, ax=ax, turbine_names=turbine_names, turbine_indices=[0, 1, 2, 3] +) +layoutviz.plot_waking_directions(fmodel, ax=ax, turbine_indices=[0, 1, 2, 3], limit_dist_D=7) ax.set_title("Plot a subset and limit wake line distance") # Plot with a shaded region ax = axarr[5] layoutviz.plot_turbine_points(fmodel, ax=ax) -layoutviz.shade_region(np.array([[0,0],[300,0],[300,1000],[0,700]]),ax=ax) +layoutviz.shade_region(np.array([[0, 0], [300, 0], [300, 1000], [0, 700]]), ax=ax) ax.set_title("Plot with a shaded region") # Change hub heights and plot as a proxy for terrain diff --git a/examples/examples_visualizations/visualize_cross_plane.py b/examples/examples_visualizations/visualize_cross_plane.py new file mode 100644 index 000000000..1aa00006e --- /dev/null +++ b/examples/examples_visualizations/visualize_cross_plane.py @@ -0,0 +1,37 @@ +"""Example: Visualize cross plane + +Demonstrate visualizing a plane cut vertically through the flow field across the wind direction. + +""" + +import matplotlib.pyplot as plt + +from floris import FlorisModel +from floris.flow_visualization import visualize_cut_plane + + +fmodel = FlorisModel("../inputs/gch.yaml") + +# Set a 1 turbine layout +fmodel.set( + layout_x=[0], + layout_y=[0], + wind_directions=[270], + wind_speeds=[8], + turbulence_intensities=[0.06], +) + +# Collect the cross plane downstream of the turbine +cross_plane = fmodel.calculate_cross_plane( + y_resolution=100, + z_resolution=100, + downstream_dist=500.0, +) + +# Plot the flow field +fig, ax = plt.subplots(figsize=(4, 6)) +visualize_cut_plane( + cross_plane, ax=ax, min_speed=3, max_speed=9, label_contours=True, title="Cross Plane" +) + +plt.show() diff --git a/examples/examples_visualizations/visualize_flow_by_sweeping_turbines.py b/examples/examples_visualizations/visualize_flow_by_sweeping_turbines.py new file mode 100644 index 000000000..3614e74bc --- /dev/null +++ b/examples/examples_visualizations/visualize_flow_by_sweeping_turbines.py @@ -0,0 +1,43 @@ +"""Example: Visualize flow by sweeping turbines + +Demonstrate the use calculate_horizontal_plane_with_turbines + +""" + +import matplotlib.pyplot as plt + +import floris.flow_visualization as flowviz +from floris import FlorisModel + + +fmodel = FlorisModel("../inputs/gch.yaml") + +# # Some wake models may not yet have a visualization method included, for these cases can use +# # a slower version which scans a turbine model to produce the horizontal flow + + +# Set a 2 turbine layout +fmodel.set( + layout_x=[0, 500], + layout_y=[0, 0], + wind_directions=[270], + wind_speeds=[8], + turbulence_intensities=[0.06], +) + +horizontal_plane_scan_turbine = flowviz.calculate_horizontal_plane_with_turbines( + fmodel, + x_resolution=20, + y_resolution=10, +) + +fig, ax = plt.subplots(figsize=(10, 4)) +flowviz.visualize_cut_plane( + horizontal_plane_scan_turbine, + ax=ax, + label_contours=True, + title="Horizontal (coarse turbine scan method)", +) + + +plt.show() diff --git a/examples/examples_visualizations/visualize_rotor_values.py b/examples/examples_visualizations/visualize_rotor_values.py new file mode 100644 index 000000000..e1d40c14b --- /dev/null +++ b/examples/examples_visualizations/visualize_rotor_values.py @@ -0,0 +1,33 @@ +"""Example: Visualize rotor velocities + +Demonstrate visualizing the flow velocities at the rotor using plot_rotor_values + +""" + +import matplotlib.pyplot as plt + +import floris.flow_visualization as flowviz +from floris import FlorisModel + + +fmodel = FlorisModel("../inputs/gch.yaml") + +# Set a 2 turbine layout +fmodel.set( + layout_x=[0, 500], + layout_y=[0, 0], + wind_directions=[270], + wind_speeds=[8], + turbulence_intensities=[0.06], +) + +# Run the model +fmodel.run() + +# Plot the values at each rotor +fig, axes, _, _ = flowviz.plot_rotor_values( + fmodel.core.flow_field.u, findex=0, n_rows=1, n_cols=2, return_fig_objects=True +) +fig.suptitle("Rotor Plane Visualization, Original Resolution") + +plt.show() diff --git a/examples/examples_visualizations/visualize_y_cut_plane.py b/examples/examples_visualizations/visualize_y_cut_plane.py new file mode 100644 index 000000000..7e9ef8cd4 --- /dev/null +++ b/examples/examples_visualizations/visualize_y_cut_plane.py @@ -0,0 +1,33 @@ +"""Example: Visualize y cut plane + +Demonstrate visualizing a plane cut vertically through the flow field along the wind direction. + +""" + +import matplotlib.pyplot as plt + +from floris import FlorisModel +from floris.flow_visualization import visualize_cut_plane + + +fmodel = FlorisModel("../inputs/gch.yaml") + +# Set a 3 turbine layout with wind direction along the row +fmodel.set( + layout_x=[0, 500, 1000], + layout_y=[0, 0, 0], + wind_directions=[270], + wind_speeds=[8], + turbulence_intensities=[0.06], +) + +# Collect the yplane +y_plane = fmodel.calculate_y_plane(x_resolution=200, z_resolution=100, crossstream_dist=0.0) + +# Plot the flow field +fig, ax = plt.subplots(figsize=(10, 4)) +visualize_cut_plane( + y_plane, ax=ax, min_speed=3, max_speed=9, label_contours=True, title="Y Cut Plane" +) + +plt.show() From dc87b6622a1470d915c13e903d8414a7eaca8eb0 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 13 Mar 2024 16:01:18 -0600 Subject: [PATCH 004/120] Rename wind data example --- examples/002_wind_data_objects.py | 65 ---------- examples/003_wind_data_objects.py | 205 ++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 65 deletions(-) delete mode 100644 examples/002_wind_data_objects.py create mode 100644 examples/003_wind_data_objects.py diff --git a/examples/002_wind_data_objects.py b/examples/002_wind_data_objects.py deleted file mode 100644 index 747274958..000000000 --- a/examples/002_wind_data_objects.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Example 2: Wind Data Objects - -This second example demonstrates the use of wind data objects in FLORIS: - TimeSeries, - WindRose, and WindTIRose. - - -Main concept is introduce FLORIS and illustrate essential structure -of most-used FLORIS calls -""" - - -import numpy as np - -from floris import ( - FlorisModel, - TimeSeries, - WindRose, - WindTIRose, -) - - -# Initialize FLORIS with the given input file. -# The Floris class is the entry point for most usage. -fmodel = FlorisModel("inputs/gch.yaml") - -# Changing the wind farm layout uses FLORIS' set method to a two-turbine layout -fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0]) - -# Changing wind speed, wind direction, and turbulence intensity using the set method -# as well. Note that the wind_speeds, wind_directions, and turbulence_intensities -# are all specified as arrays of the same length. -fmodel.set(wind_directions=np.array([270.0]), - wind_speeds=[8.0], - turbulence_intensities=np.array([0.06])) - -# Note that typically all 3, wind_directions, wind_speeds and turbulence_intensities -# must be supplied to set. However, the exception is if not changing the lenght -# of the arrays, then only one or two may be supplied. -fmodel.set(turbulence_intensities=np.array([0.07])) - -# The number of elements in the wind_speeds, wind_directions, and turbulence_intensities -# corresponds to the number of conditions to be simulated. In FLORIS, each of these are -# tracked by a simple index called a findex. There is no requirement that the values -# be unique. Internally in FLORIS, most data structures will have the findex as their -# 0th dimension. The value n_findex is the total number of conditions to be simulated. -# This command would simulate 4 conditions (n_findex = 4). -fmodel.set(wind_directions=np.array([270.0, 270.0, 270.0, 270.0]), - wind_speeds=[8.0, 8.0, 10.0, 10.0], - turbulence_intensities=np.array([0.06, 0.06, 0.06, 0.06])) - -# After the set method, the run method is called to perform the simulation -fmodel.run() - -# There are functions to get either the power of each turbine, or the farm power -turbine_powers = fmodel.get_turbine_powers() / 1000.0 -farm_power = fmodel.get_farm_power() / 1000.0 - -print("The turbine power matrix should be of dimensions 4 (n_findex) X 2 (n_turbines)") -print(turbine_powers) -print("Shape: ", turbine_powers.shape) - -print("The farm power should be a 1D array of length 4 (n_findex)") -print(farm_power) -print("Shape: ", farm_power.shape) diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py new file mode 100644 index 000000000..ae6247eec --- /dev/null +++ b/examples/003_wind_data_objects.py @@ -0,0 +1,205 @@ +"""Example 2: Wind Data Objects + +This second example demonstrates the use of wind data objects in FLORIS: + TimeSeries, + WindRose, and WindTIRose. + + +Main concept is introduce FLORIS and illustrate essential structure +of most-used FLORIS calls +""" + + +import matplotlib.pyplot as plt +import numpy as np + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, + WindTIRose, +) + + +################################################## +# Initializing +################################################## + +# FLORIS provides a set of wind data objects to hold the ambient wind conditions in a +# convenient classes that include capabilities and methods to manipulate and visualize +# the data. + +# The TimeSeries class is used to hold time series data, such as wind speed, wind direction, +# and turbulence intensity. + +# Generate wind speeds, directions, and turbulence intensities via random signals +N = 100 +wind_speeds = 8 + 2 * np.random.randn(N) +wind_directions = 270 + 30 * np.random.randn(N) +turbulence_intensities = 0.06 + 0.02 * np.random.randn(N) + +time_series = TimeSeries( + wind_directions=wind_directions, + wind_speeds=wind_speeds, + turbulence_intensities=turbulence_intensities, +) + +# The WindRose class is used to hold wind rose data, such as wind speed, wind direction, +# and frequency. TI is represented as a bin average per wind direction and speed bin. +wind_directions = np.arange(0, 360, 3.0) +wind_speeds = np.arange(4, 20, 2.0) + +# Make TI table 6% TI for all wind directions and speeds +ti_table = 0.06 * np.ones((len(wind_directions), len(wind_speeds))) + +# Uniform frequency +freq_table = np.ones((len(wind_directions), len(wind_speeds))) +freq_table = freq_table / np.sum(freq_table) + +wind_rose = WindRose( + wind_directions=wind_directions, + wind_speeds=wind_speeds, + ti_table=ti_table, + freq_table=freq_table, +) + +# The WindTIRose class is similar to the WindRose table except that TI is also binned +# making the frequency table a 3D array. +turbulence_intensities = np.arange(0.05, 0.15, 0.01) + +# Uniform frequency +freq_table = np.ones((len(wind_directions), len(wind_speeds), len(turbulence_intensities))) + +wind_ti_rose = WindTIRose( + wind_directions=wind_directions, + wind_speeds=wind_speeds, + turbulence_intensities=turbulence_intensities, + freq_table=freq_table, +) + +################################################## +# Broadcasting +################################################## + +# A convenience method of the wind data objects is that, unlike the lower-level +# FlorisModel.set() method, the wind data objects can broadcast upward data provided +# as a scalar to the full array. This is useful for setting the same wind conditions +# for all turbines in a wind farm. + +# For TimeSeries, as long as one condition is given as an array, the other 2 +# conditions can be given as scalars. The TimeSeries object will broadcast the +# scalars to the full array (uniform) +wind_directions = 270 + 30 * np.random.randn(N) +time_series = TimeSeries( + wind_directions=wind_directions, wind_speeds=8.0, turbulence_intensities=0.06 +) + + +# For WindRose, wind directions and wind speeds must be given as arrays, but the +# ti_table can be supplied as a scalar which will apply uniformly to all wind +# directions and speeds. Not supplying a freq table will similarly generate +# a uniform frequency table. +wind_directions = np.arange(0, 360, 3.0) +wind_speeds = np.arange(4, 20, 2.0) +wind_rose = WindRose(wind_directions=wind_directions, wind_speeds=wind_speeds, ti_table=0.06) + + +################################################## +# Wind Rose from Time Series +################################################## + +# The TimeSeries class has a method to generate a wind rose from a time series based on binning +wind_rose = time_series.to_wind_rose( + wd_edges=np.arange(0, 360, 3.0), ws_edges=np.arange(4, 20, 2.0) +) + + +################################################## +# Setting turbulence intensity +################################################## + +# Each of the wind data objects also has the ability to set the turbulence intensity +# according to a function of wind speed and direction. This can be done using +# a custom function by using the function assign_ti_using_IEC_method which assigns +# TI based on the IEC 61400-1 standard +wind_rose.assign_ti_using_IEC_method() # Assign using default settings for Iref and offset + + +################################################## +# Plotting Wind Data Objects +################################################## + +# Certain plotting methods are included to enable visualization of the wind data objects +# Plotting a wind rose +wind_rose.plot_wind_rose() + +# Showing TI over wind speed for a WindRose +wind_rose.plot_ti_over_ws() + +################################################## +# Assigning value to wind data objects +################################################## + +# Wind data objects can also hold value information, such as the price of electricity for different +# time periods or wind conditions. These can then be used in later optimization methods to optimize +# for quantities besides AEP. + +N = 100 +wind_speeds = 8 + 2 * np.random.randn(N) +values = 1 / wind_speeds # Assume Value is inversely proportional to wind speed + +time_series = TimeSeries( + wind_directions=270.0, wind_speeds=wind_speeds, turbulence_intensities=0.06, values=values +) + +################################################## +# Setting the FLORIS model via wind data +################################################## + +# Each of the wind data objects can be used to set the FLORIS model by passing +# them in as is to the set method. The FLORIS model will then use the member functions +# of the wind data to extract the wind conditions for the simulation. Frequency tables +# are also extracted for AEP calculations. + +fmodel = FlorisModel("inputs/gch.yaml") + +# Set the wind conditions using the TimeSeries object +fmodel.set(wind_data=time_series) + +# Set the wind conditions using the WindRose object +fmodel.set(wind_data=wind_rose) + +# Note that in the case of the wind_rose, under the default settings, wind direction and wind speed +# bins for which frequency is zero are not simulated. This can be changed by setting the +# compute_zero_freq_occurrence parameter to True. +wind_directions = np.array([200.0, 300.0]) +wind_speeds = np.array([5.0, 1.00]) +freq_table = np.array([[0.5, 0], [0.5, 0]]) +wind_rose = WindRose( + wind_directions=wind_directions, wind_speeds=wind_speeds, ti_table=0.06, freq_table=freq_table +) +fmodel.set(wind_data=wind_rose) + +print( + f"Number of conditions to simulate with compute_zero_freq_occurrence" + f"False: {fmodel.core.flow_field.n_findex}" +) + +wind_rose = WindRose( + wind_directions=wind_directions, + wind_speeds=wind_speeds, + ti_table=0.06, + freq_table=freq_table, + compute_zero_freq_occurrence=True, +) +fmodel.set(wind_data=wind_rose) + +print( + f"Number of conditions to simulate with compute_zero_freq_occurrence" + f"True: {fmodel.core.flow_field.n_findex}" +) + +# Set the wind conditions using the WindTIRose object +fmodel.set(wind_data=wind_ti_rose) + +plt.show() From e6353e29c3b9b8d4d498a7fb8ee6f9ec867db77f Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 13 Mar 2024 16:01:28 -0600 Subject: [PATCH 005/120] Use tindex in place of T --- floris/flow_visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/floris/flow_visualization.py b/floris/flow_visualization.py index 3afaf1a38..c8d30d141 100644 --- a/floris/flow_visualization.py +++ b/floris/flow_visualization.py @@ -442,7 +442,7 @@ def plot_rotor_values( if n_rows == 1 and n_cols == 1: axes = np.array([axes]) - titles = np.array([f"T{i}" for i in t_range]) + titles = np.array([f"tindex: {i}" for i in t_range]) for ax, t, i in zip(axes.flatten(), titles, t_range): From 31e3d68c577edccef0a98a1a09f2826f5eb6897c Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 09:48:32 -0600 Subject: [PATCH 006/120] Update making adjustments to set example --- examples/004_set.py | 111 +++++++++++++++++++++++++++++ examples/03_making_adjustments.py | 114 ------------------------------ 2 files changed, 111 insertions(+), 114 deletions(-) create mode 100644 examples/004_set.py delete mode 100644 examples/03_making_adjustments.py diff --git a/examples/004_set.py b/examples/004_set.py new file mode 100644 index 000000000..a7cb1eed9 --- /dev/null +++ b/examples/004_set.py @@ -0,0 +1,111 @@ +"""Example 4: Set + +This example illustrates the use of the set method. + +""" + + +import matplotlib.pyplot as plt +import numpy as np +import yaml + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, + WindTIRose, +) + + +# Initialize FLORIS with the given input file via FlorisModel +fmodel = FlorisModel("inputs/gch.yaml") + +###################################################### +# Atmospheric Conditions +###################################################### + + +# Change the wind directions, wind speeds, and turbulence intensities using numpy arrays +fmodel.set( + wind_directions=np.array([270.0, 270.0, 270.0]), + wind_speeds=[8.0, 9.0, 10.0], + turbulence_intensities=np.array([0.06, 0.06, 0.06]), +) + +# Set the wind conditions as above using the TimeSeries object +fmodel.set( + wind_data=TimeSeries( + wind_directions=270.0, wind_speeds=np.array([8.0, 9.0, 10.0]), turbulence_intensities=0.06 + ) +) + +# Set the wind conditions as above using the WindRose object +fmodel.set( + wind_data=WindRose( + wind_directions=np.array([270.0]), + wind_speeds=np.array([8.0, 9.0, 10.0]), + ti_table=0.06, + ) +) + +# Set the wind shear +fmodel.set(wind_shear=0.2) + + +# Set the air density +fmodel.set(air_density=1.1) + +# Set the reference wind height (which is the height at which the wind speed is given) +fmodel.set(reference_wind_height=92.0) + + +###################################################### +# Array Settings +###################################################### + +# Changing the wind farm layout uses FLORIS' set method to a two-turbine layout +fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0]) + +# Change the turbine type for the 0th turbine +# TODO: Going to write this one when the param functions are available + +###################################################### +# Controls Settings +###################################################### + +# Changes to controls settings can be made using the set method +# Note the dimension must match (n_findex, n_tindex) or (number of conditions, number of turbines) +# Above we n_findex = 3 and n_tindex = 2 so the matrix of yaw angles must be 3x2 +yaw_angles = np.array([[0.0, 0.0], [25.0, 0.0], [0.0, 0.0]]) +fmodel.set(yaw_angles=yaw_angles) + +# By default for the turbines in the turbine_libary, the power +# thrust model is set to "cosine-loss" which adjusts +# power and thrust according to cos^cosine_loss_exponent(yaw | tilt) +# where the default exponent is 1.88. For other +# control capabilities, the power thrust model can be set to "mixed" +# which provides the same cosine loss model, and +# additionally methods for specifying derating levels for power and disabling turbines. + +# Change to the mixed model turbine +# TODO: Could this process be added to the fmodel_utils? +with open( + str( + fmodel.core.as_dict()["farm"]["turbine_library_path"] + / (fmodel.core.as_dict()["farm"]["turbine_type"][0] + ".yaml") + ) +) as t: + turbine_type = yaml.safe_load(t) +turbine_type["power_thrust_model"] = "mixed" +fmodel.set(turbine_type=[turbine_type]) + +# Shut down the front turbine for the first two findex +disable_turbines = np.array([[True, False], [True, False], [False, False]]) +fmodel.set(disable_turbines=disable_turbines) + +# Derate the front turbine for the first two findex +RATED_POWER = 5e6 # 5MW +power_setpoints = np.array( + [[RATED_POWER * 0.3, RATED_POWER], [RATED_POWER * 0.3, RATED_POWER], [RATED_POWER, RATED_POWER]] +) +fmodel.set(power_setpoints=power_setpoints) diff --git a/examples/03_making_adjustments.py b/examples/03_making_adjustments.py deleted file mode 100644 index 0bac6e98b..000000000 --- a/examples/03_making_adjustments.py +++ /dev/null @@ -1,114 +0,0 @@ - -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 - - -""" -This example makes changes to the given input file through the script. -First, we plot simulation from the input file as given. Then, we make a series -of changes and generate plots from those simulations. -""" - -# Create the plotting objects using matplotlib -fig, axarr = plt.subplots(2, 3, figsize=(12, 5)) -axarr = axarr.flatten() - -MIN_WS = 1.0 -MAX_WS = 8.0 - -# Initialize FLORIS with the given input file via FlorisModel -fmodel = FlorisModel("inputs/gch.yaml") - - -# Plot a horizatonal slice of the initial configuration -horizontal_plane = fmodel.calculate_horizontal_plane(height=90.0) -flowviz.visualize_cut_plane( - horizontal_plane, - ax=axarr[0], - title="Initial setup", - min_speed=MIN_WS, - max_speed=MAX_WS -) - -# Change the wind speed -horizontal_plane = fmodel.calculate_horizontal_plane(ws=[7.0], height=90.0) -flowviz.visualize_cut_plane( - horizontal_plane, - ax=axarr[1], - title="Wind speed at 7 m/s", - min_speed=MIN_WS, - max_speed=MAX_WS -) - - -# Change the wind shear, reset the wind speed, and plot a vertical slice -fmodel.set(wind_shear=0.2, wind_speeds=[8.0]) -y_plane = fmodel.calculate_y_plane(crossstream_dist=0.0) -flowviz.visualize_cut_plane( - y_plane, - ax=axarr[2], - title="Wind shear at 0.2", - min_speed=MIN_WS, - max_speed=MAX_WS -) - -# # Change the farm layout -N = 3 # Number of turbines per row and per column -X, Y = np.meshgrid( - 5.0 * fmodel.core.farm.rotor_diameters[0,0] * np.arange(0, N, 1), - 5.0 * fmodel.core.farm.rotor_diameters[0,0] * np.arange(0, N, 1), -) -fmodel.set(layout_x=X.flatten(), layout_y=Y.flatten(), wind_directions=[270.0]) -horizontal_plane = fmodel.calculate_horizontal_plane(height=90.0) -flowviz.visualize_cut_plane( - horizontal_plane, - ax=axarr[3], - title="3x3 Farm", - min_speed=MIN_WS, - max_speed=MAX_WS -) -layoutviz.plot_turbine_labels(fmodel, axarr[3], plotting_dict={'color':"w"}) #, backgroundcolor="k") -layoutviz.plot_turbine_rotors(fmodel, axarr[3]) - -# Change the yaw angles and configure the plot differently -yaw_angles = np.zeros((1, N * N)) - -## First row -yaw_angles[:,0] = 30.0 -yaw_angles[:,3] = -30.0 -yaw_angles[:,6] = 30.0 - -## Second row -yaw_angles[:,1] = -30.0 -yaw_angles[:,4] = 30.0 -yaw_angles[:,7] = -30.0 - -horizontal_plane = fmodel.calculate_horizontal_plane(yaw_angles=yaw_angles, height=90.0) -flowviz.visualize_cut_plane( - horizontal_plane, - ax=axarr[4], - title="Yawesome art", - cmap="PuOr", - min_speed=MIN_WS, - max_speed=MAX_WS -) - -layoutviz.plot_turbine_rotors(fmodel, axarr[4], yaw_angles=yaw_angles, color="c") - -# Plot the cross-plane of the 3x3 configuration -cross_plane = fmodel.calculate_cross_plane(yaw_angles=yaw_angles, downstream_dist=610.0) -flowviz.visualize_cut_plane( - cross_plane, - ax=axarr[5], - title="Cross section at 610 m", - min_speed=MIN_WS, - max_speed=MAX_WS -) -axarr[5].invert_xaxis() - - -plt.show() From 9916024af0fc2fb5be1a2a7cf179b060d23e1337 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 09:48:49 -0600 Subject: [PATCH 007/120] Touching up --- examples/001_opening_floris_computing_power.py | 2 +- examples/003_wind_data_objects.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/001_opening_floris_computing_power.py b/examples/001_opening_floris_computing_power.py index dcb1987c1..6504ea9bb 100644 --- a/examples/001_opening_floris_computing_power.py +++ b/examples/001_opening_floris_computing_power.py @@ -1,6 +1,6 @@ """Example 1: Opening FLORIS and Computing Power -This first example illustrates several of the key concepts in FLORIS. It: +This example illustrates several of the key concepts in FLORIS. It: 1) Initializing FLORIS 2) Changing the wind farm layout diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index ae6247eec..0c6f48d71 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -1,6 +1,6 @@ -"""Example 2: Wind Data Objects +"""Example 3: Wind Data Objects -This second example demonstrates the use of wind data objects in FLORIS: +This example demonstrates the use of wind data objects in FLORIS: TimeSeries, WindRose, and WindTIRose. From 2c0be8d3471bb2e24277afba3d1d67df83e8b601 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 09:48:55 -0600 Subject: [PATCH 008/120] Start controls folder --- examples/{ => examples_control_settings}/40_test_derating.py | 0 .../{ => examples_control_settings}/41_test_disable_turbines.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ => examples_control_settings}/40_test_derating.py (100%) rename examples/{ => examples_control_settings}/41_test_disable_turbines.py (100%) diff --git a/examples/40_test_derating.py b/examples/examples_control_settings/40_test_derating.py similarity index 100% rename from examples/40_test_derating.py rename to examples/examples_control_settings/40_test_derating.py diff --git a/examples/41_test_disable_turbines.py b/examples/examples_control_settings/41_test_disable_turbines.py similarity index 100% rename from examples/41_test_disable_turbines.py rename to examples/examples_control_settings/41_test_disable_turbines.py From 09d0fb55bd60442cf302bc1f847c6a6fc1ef3ecb Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 11:09:38 -0600 Subject: [PATCH 009/120] Finish 004 example --- examples/004_set.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/004_set.py b/examples/004_set.py index a7cb1eed9..1e7d6daab 100644 --- a/examples/004_set.py +++ b/examples/004_set.py @@ -1,6 +1,8 @@ """Example 4: Set -This example illustrates the use of the set method. +This example illustrates the use of the set method. The set method is used to +change the wind conditions, the wind farm layout, the turbine type, +and the controls settings. """ @@ -13,7 +15,6 @@ FlorisModel, TimeSeries, WindRose, - WindTIRose, ) From e42b50824a0e86530e387953153c6d4d08697f62 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 11:10:16 -0600 Subject: [PATCH 010/120] start 005 example --- examples/005_getting_power.py | 107 ++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 examples/005_getting_power.py diff --git a/examples/005_getting_power.py b/examples/005_getting_power.py new file mode 100644 index 000000000..da8c8d9e9 --- /dev/null +++ b/examples/005_getting_power.py @@ -0,0 +1,107 @@ +"""Example 4: Getting Power + +After setting the FlorisModel and running, the next step is typically to get the power output +of the turbines. + +""" + +import matplotlib.pyplot as plt +import numpy as np +import yaml + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, + WindTIRose, +) + + +# Initialize FLORIS with the given input file via FlorisModel +fmodel = FlorisModel("inputs/gch.yaml") + +# Set to a 3-turbine layout +fmodel.set(layout_x=[0, 126*5, 126*10], layout_y=[0, 0, 0]) + +###################################################### +# Using TimeSeries +###################################################### + +# Set up a time series in which the wind speed and TI are constant but the wind direction +# sweeps the range from 250 to 290 degrees +wind_directions = np.arange(250, 290, 1.0) +time_series = TimeSeries( + wind_directions=wind_directions, wind_speeds=9.9, turbulence_intensities=0.06 +) +fmodel.set(wind_data=time_series) + +# Run the model +fmodel.run() + +# Get the turbine powers +turbine_powers = fmodel.get_turbine_powers() + +# Turbines powers will have shape (n_findex, n_tindex) where n_findex is the number of unique +# wind conditions and n_tindex is the number of turbines in the farm +print(f"Turbine power has shape {turbine_powers.shape}") + +# It is also possible to get the farm power directly +farm_power = fmodel.get_farm_power() + +# Farm power has length n_findex, and is the sum of the turbine powers +print(f"Farm power has shape {farm_power.shape}") + +# It's possible to get these powers with wake losses disabled, this can be useful +# for computing total wake losses +fmodel.run_no_wake() +farm_power_no_wake = fmodel.get_farm_power() + +# Plot the results +fig, axarr = plt.subplots(1, 3, figsize=(15, 5)) + +# Plot the turbine powers +ax = axarr[0] +for i in range(turbine_powers.shape[1]): + ax.plot(wind_directions, turbine_powers[:, i] / 1e3, label=f"Turbine {i+1} ") +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") +ax.grid(True) +ax.legend() +ax.set_title("Turbine Powers") + +# Plot the farm power +ax = axarr[1] +ax.plot(wind_directions, farm_power / 1e3, label='Farm Power With Wakes', color='k') +ax.plot(wind_directions, farm_power_no_wake / 1e3, label='Farm Power No Wakes', color='r') +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") +ax.grid(True) +ax.legend() +ax.set_title("Farm Power") + +# Plot the percent wake losses +ax = axarr[2] +percent_wake_losses = 100 * (farm_power_no_wake - farm_power) / farm_power_no_wake +ax.plot(wind_directions, percent_wake_losses, label='Percent Wake Losses', color='k') +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Percent Wake Losses") +ax.grid(True) +ax.legend() +ax.set_title("Percent Wake Losses") + + +###################################################### +# Using WindRose +###################################################### + +# When running FLORIS using a wind rose, the wind data is held in a +# wind_directions x wind_speeds table +# form, which is unpacked into a 1D array within the FlorisModel. +# Additionally wind direction and +# wind speed combinations which have 0 frequency are not computed, unless the user specifies +# the `compute_zero_freq_occurrence=True` option in the WindRose constructor. + +# When calculating AEP, the bins can be combined automatically + + +plt.show() From e5d2b6d46a8e425fba171346be8b8eda3b3a83f7 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 11:11:11 -0600 Subject: [PATCH 011/120] Add place holder --- .../examples_control_settings/setting_yaw_and_dertating.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/examples_control_settings/setting_yaw_and_dertating.py diff --git a/examples/examples_control_settings/setting_yaw_and_dertating.py b/examples/examples_control_settings/setting_yaw_and_dertating.py new file mode 100644 index 000000000..58d878877 --- /dev/null +++ b/examples/examples_control_settings/setting_yaw_and_dertating.py @@ -0,0 +1,3 @@ +# TO BE WRITTEN + +# Show that can use both but not at same turbine at same findex From c665bf47082d5bcea3c4762ccad1034f48e9bc14 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 12:31:28 -0600 Subject: [PATCH 012/120] Add todo --- examples/005_getting_power.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/005_getting_power.py b/examples/005_getting_power.py index da8c8d9e9..0ba372094 100644 --- a/examples/005_getting_power.py +++ b/examples/005_getting_power.py @@ -103,5 +103,6 @@ # When calculating AEP, the bins can be combined automatically +#TODO: Revist this section after https://github.com/NREL/floris/pull/844 is merged plt.show() From 1e777b210357118d046eea0310714a9429a2c577 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 13:19:41 -0600 Subject: [PATCH 013/120] minor fix --- examples/005_getting_power.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/005_getting_power.py b/examples/005_getting_power.py index 0ba372094..8593d6f7e 100644 --- a/examples/005_getting_power.py +++ b/examples/005_getting_power.py @@ -1,4 +1,4 @@ -"""Example 4: Getting Power +"""Example 5: Getting Turbine and Farm Power After setting the FlorisModel and running, the next step is typically to get the power output of the turbines. From 02d6f7c7775e93e02faa7bf47c58cedd480fb18f Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 13:20:45 -0600 Subject: [PATCH 014/120] Add AEP example --- examples/006_get_farm_aep.py | 143 +++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 examples/006_get_farm_aep.py diff --git a/examples/006_get_farm_aep.py b/examples/006_get_farm_aep.py new file mode 100644 index 000000000..c944dbcc3 --- /dev/null +++ b/examples/006_get_farm_aep.py @@ -0,0 +1,143 @@ +"""Example 6: Getting AEP + +AEP is annual energy production and can is typically a weighted sum over farm power. This +example demonstrates how to calculate the AEP + +""" + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from scipy.interpolate import NearestNDInterpolator + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, +) + + +# Initialize FLORIS with the given input file via FlorisModel +fmodel = FlorisModel("inputs/gch.yaml") + + +# Set to a 5-turbine layout +fmodel.set(layout_x=[0, 126 * 5, 126 * 10, 126 * 15, 126 * 20], layout_y=[0, 0, 0, 0, 0]) + +# Using TimeSeries + +# In the case of time series data, although not required, the typical assumption is +# that each time step is equally likely. + +# Randomly generated a time series with time steps = 365 * 24 +N = 365 * 24 +wind_directions = np.random.uniform(0, 360, N) +wind_speeds = np.random.uniform(5, 25, N) + +# Set up a time series +time_series = TimeSeries( + wind_directions=wind_directions, wind_speeds=wind_speeds, turbulence_intensities=0.06 +) + +# Note that the AEP functions run the model +# So it is not necessary to call run() +fmodel.set(wind_data=time_series) + +aep = fmodel.get_farm_AEP_with_wind_data(time_series) + +# Note this is equivalent to the following +aep_b = fmodel.get_farm_AEP(time_series.unpack_freq()) + +print(f"AEP from time series: {aep}, and re-computed AEP: {aep_b}") + +# Using WindRose + +# Assume a provided wind rose of frequency by wind direction and wind speed +df_wr = pd.read_csv("inputs/wind_rose.csv") + +# Get the wind directions, wind speeds, and frequency table +wind_direction_values = df_wr["wd"].values +wind_speed_values = df_wr["ws"].values +wind_directions = df_wr["wd"].unique() +wind_speeds = df_wr["ws"].unique() +freq_vals = df_wr["freq_val"].values / df_wr["freq_val"].sum() + +n_row = df_wr.shape[0] +n_wd = len(wind_directions) +n_ws = len(wind_speeds) + +wd_step = wind_directions[1] - wind_directions[0] +ws_step = wind_speeds[1] - wind_speeds[0] + +print("The wind rose dataframe looks as follows:") +print(df_wr.head()) +print(f"There are {n_row} rows, {n_wd} unique wind directions, and {n_ws} unique wind speeds") +print(f"The wind direction has a step of {wd_step} and the wind speed has a step of {ws_step}") + +# Declare a frequency table of size (n_wd, n_ws) +freq_table = np.zeros((n_wd, n_ws)) + +# Populate the frequency table using the values of wind_direction_values, +# wind_speed_values, and freq_vals +for i in range(n_row): + wd = wind_direction_values[i] + ws = wind_speed_values[i] + freq = freq_vals[i] + + # Find the index of the wind direction and wind speed + wd_idx = np.where(wind_directions == wd)[0][0] + ws_idx = np.where(wind_speeds == ws)[0][0] + + # Populate the frequency table + freq_table[wd_idx, ws_idx] = freq + +# Normalize the frequency table +freq_table = freq_table / freq_table.sum() + +print(f"The frequency table has shape {freq_table.shape}") + +# Set up a wind rose +wind_rose = WindRose( + wind_directions=wind_directions, + wind_speeds=wind_speeds, + freq_table=freq_table, + ti_table=0.06, # Assume contant TI +) + +# Note that the wind rose could have been computed directly +# by first building a TimeSeries and applying +# the provided frequencies as bin weights in resampling +time_series = TimeSeries( + wind_directions=wind_direction_values, + wind_speeds=wind_speed_values, + turbulence_intensities=0.06, +) + +# Convert time series to wind rose using the frequencies as bin weights +wind_rose_from_time_series = time_series.to_wind_rose( + wd_step=wd_step, ws_step=ws_step, bin_weights=freq_vals +) + + +print("Wind rose from wind_rose and wind_rose_from_time_series are equivalent:") +print( + " -- Directions: " + f"{np.allclose(wind_rose.wind_directions, wind_rose_from_time_series.wind_directions)}" +) +print(f" -- Speeds: {np.allclose(wind_rose.wind_speeds, wind_rose_from_time_series.wind_speeds)}") +print(f" -- Freq: {np.allclose(wind_rose.freq_table, wind_rose_from_time_series.freq_table)}") + +# Set the wind rose +fmodel.set(wind_data=wind_rose) + +# Note that the frequency table contains 0 frequency for some wind directions and wind speeds +# and we've not selected to compute 0 frequency bins, therefore the n_findex will be less than +# the total number of wind directions and wind speed combinations +print(f"Total number of rows in input wind rose: {n_row}") +print(f"n_findex: {fmodel.core.flow_field.n_findex}") + +# Get the AEP +aep = fmodel.get_farm_AEP_with_wind_data(wind_rose) + +# Print the AEP +print(f"AEP from wind rose: {aep/1E9:.1f} (GW-h)") From 3b2e3b2a3ace92b0eb616090051fb172f6a83c00 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 13:21:06 -0600 Subject: [PATCH 015/120] Delete old AEP example --- examples/07_calc_aep_from_rose.py | 80 ------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 examples/07_calc_aep_from_rose.py diff --git a/examples/07_calc_aep_from_rose.py b/examples/07_calc_aep_from_rose.py deleted file mode 100644 index cc2de88d4..000000000 --- a/examples/07_calc_aep_from_rose.py +++ /dev/null @@ -1,80 +0,0 @@ - -import numpy as np -import pandas as pd -from scipy.interpolate import NearestNDInterpolator - -from floris import FlorisModel - - -""" -This example demonstrates how to calculate the Annual Energy Production (AEP) -of a wind farm using wind rose information stored in a .csv file. - -The wind rose information is first loaded, after which we initialize our FlorisModel. -A 3 turbine farm is generated, and then the turbine wakes and powers -are calculated across all the wind directions. Finally, the farm power is -converted to AEP and reported out. -""" - -# Read the windrose information file and display -df_wr = pd.read_csv("inputs/wind_rose.csv") -print("The wind rose dataframe looks as follows: \n\n {} \n".format(df_wr)) - -# Derive the wind directions and speeds we need to evaluate in FLORIS -wd_grid, ws_grid = np.meshgrid( - np.array(df_wr["wd"].unique(), dtype=float), # wind directions - np.array(df_wr["ws"].unique(), dtype=float), # wind speeds - indexing="ij" -) -wind_directions = wd_grid.flatten() -wind_speeds = ws_grid.flatten() -turbulence_intensities = np.ones_like(wind_directions) * 0.06 - -# Format the frequency array into the conventional FLORIS v3 format, which is -# an np.array with shape (n_wind_directions, n_wind_speeds). To avoid having -# to manually derive how the variables are sorted and how to reshape the -# one-dimensional frequency array, we use a nearest neighbor interpolant. This -# ensures the frequency values are mapped appropriately to the new 2D array. -freq_interp = NearestNDInterpolator(df_wr[["wd", "ws"]], df_wr["freq_val"]) -freq = freq_interp(wd_grid, ws_grid).flatten() - -# Normalize the frequency array to sum to exactly 1.0 -freq = freq / np.sum(freq) - -# Load the FLORIS object -fmodel = FlorisModel("inputs/gch.yaml") # GCH model -# fmodel = FlorisModel("inputs/cc.yaml") # CumulativeCurl model - -# Assume a three-turbine wind farm with 5D spacing. We reinitialize the -# floris object and assign the layout, wind speed and wind direction arrays. -D = fmodel.core.farm.rotor_diameters[0] # Rotor diameter for the NREL 5 MW -fmodel.set( - layout_x=[0.0, 5 * D, 10 * D], - layout_y=[0.0, 0.0, 0.0], - wind_directions=wind_directions, - wind_speeds=wind_speeds, - turbulence_intensities=turbulence_intensities, -) - -# Compute the AEP using the default settings -aep = fmodel.get_farm_AEP(freq=freq) -print("Farm AEP (default options): {:.3f} GWh".format(aep / 1.0e9)) - -# Compute the AEP again while specifying a cut-in and cut-out wind speed. -# The wake calculations are skipped for any wind speed below respectively -# above the cut-in and cut-out wind speed. This can speed up computation and -# prevent unexpected behavior for zero/negative and very high wind speeds. -# In this example, the results should not change between this and the default -# call to 'get_farm_AEP()'. -aep = fmodel.get_farm_AEP( - freq=freq, - cut_in_wind_speed=3.0, # Wakes are not evaluated below this wind speed - cut_out_wind_speed=25.0, # Wakes are not evaluated above this wind speed -) -print("Farm AEP (with cut_in/out specified): {:.3f} GWh".format(aep / 1.0e9)) - -# Finally, we can also compute the AEP while ignoring all wake calculations. -# This can be useful to quantity the annual wake losses in the farm. Such -# calculations can be facilitated by enabling the 'no_wake' handle. -aep_no_wake = fmodel.get_farm_AEP(freq, no_wake=True) -print("Farm AEP (no_wake=True): {:.3f} GWh".format(aep_no_wake / 1.0e9)) From 707ca5adb1c153dc327a0aaaa6dedb1ba7c8528f Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 13:42:37 -0600 Subject: [PATCH 016/120] Ensure consistency with previous result --- examples/006_get_farm_aep.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/006_get_farm_aep.py b/examples/006_get_farm_aep.py index c944dbcc3..b07cfccb4 100644 --- a/examples/006_get_farm_aep.py +++ b/examples/006_get_farm_aep.py @@ -21,8 +21,10 @@ fmodel = FlorisModel("inputs/gch.yaml") -# Set to a 5-turbine layout -fmodel.set(layout_x=[0, 126 * 5, 126 * 10, 126 * 15, 126 * 20], layout_y=[0, 0, 0, 0, 0]) +# Set to a 3-turbine layout +D = 126. +fmodel.set(layout_x=[0.0, 5 * D, 10 * D], + layout_y=[0.0, 0.0, 0.0]) # Using TimeSeries @@ -140,4 +142,4 @@ aep = fmodel.get_farm_AEP_with_wind_data(wind_rose) # Print the AEP -print(f"AEP from wind rose: {aep/1E9:.1f} (GW-h)") +print(f"AEP from wind rose: {aep/1E9:.3f} (GW-h)") From b28d925b605a1521ce58556a39a834d7fdd7cebd Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 16:09:45 -0600 Subject: [PATCH 017/120] Add sweeping example --- examples/007_sweeping_variables.py | 219 +++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 examples/007_sweeping_variables.py diff --git a/examples/007_sweeping_variables.py b/examples/007_sweeping_variables.py new file mode 100644 index 000000000..5abe969a1 --- /dev/null +++ b/examples/007_sweeping_variables.py @@ -0,0 +1,219 @@ +"""Example 7: Sweeping Variables + +Demonstrate methods for sweeping across variables. Wind directions, wind speeds, +turbulence intensities, +as well as control inputs are passed to set() as arrays and so can be swept +and run in one call to run(). + +""" + +import matplotlib.pyplot as plt +import numpy as np +import yaml + +from floris import ( + FlorisModel, + TimeSeries, +) + + +# Initialize FLORIS with the given input file via FlorisModel +fmodel = FlorisModel("inputs/gch.yaml") + +# Set to a 2 turbine layout +fmodel.set(layout_x=[0.0, 126 * 5], layout_y=[0.0, 0.0]) + +# Start a figure for the results +fig, axarr = plt.subplots(2, 3, figsize=(15, 10), sharey=True) +axarr = axarr.flatten() + +###################################################### +# Sweep wind speeds +###################################################### + + +# The TimeSeries object is the most convenient for sweeping +# wind speeds while keeping the wind direction and turbulence +# intensity constant +wind_speeds = np.arange(5, 10, 0.1) +fmodel.set( + wind_data=TimeSeries( + wind_speeds=wind_speeds, wind_directions=270.0, turbulence_intensities=0.06 + ) +) +fmodel.run() +turbine_powers = fmodel.get_turbine_powers() / 1e3 + +# Plot the results +ax = axarr[0] +ax.plot(wind_speeds, turbine_powers[:, 0], label="Upstream Turbine", color="k") +ax.plot(wind_speeds, turbine_powers[:, 1], label="Downstream Turbine", color="r") +ax.set_ylabel("Power (kW)") +ax.set_xlabel("Wind Speed (m/s)") +ax.legend() + +###################################################### +# Sweep wind directions +###################################################### + + +wind_directions = np.arange(250, 290, 1.0) +fmodel.set( + wind_data=TimeSeries( + wind_speeds=8.0, wind_directions=wind_directions, turbulence_intensities=0.06 + ) +) +fmodel.run() +turbine_powers = fmodel.get_turbine_powers() / 1e3 + +# Plot the results +ax = axarr[1] +ax.plot(wind_directions, turbine_powers[:, 0], label="Upstream Turbine", color="k") +ax.plot(wind_directions, turbine_powers[:, 1], label="Downstream Turbine", color="r") +ax.set_xlabel("Wind Direction (deg)") + +###################################################### +# Sweep turbulence intensities +###################################################### + +turbulence_intensities = np.arange(0.03, 0.2, 0.01) +fmodel.set( + wind_data=TimeSeries( + wind_speeds=8.0, wind_directions=270.0, turbulence_intensities=turbulence_intensities + ) +) +fmodel.run() + +turbine_powers = fmodel.get_turbine_powers() / 1e3 + +# Plot the results +ax = axarr[2] +ax.plot(turbulence_intensities, turbine_powers[:, 0], label="Upstream Turbine", color="k") +ax.plot(turbulence_intensities, turbine_powers[:, 1], label="Downstream Turbine", color="r") +ax.set_xlabel("Turbulence Intensity") + +###################################################### +# Sweep the upstream yaw angle +###################################################### + +# First set the conditions to uniform for N yaw_angles +n_yaw = 100 +wind_directions = np.ones(n_yaw) * 270.0 +fmodel.set( + wind_data=TimeSeries( + wind_speeds=8.0, wind_directions=wind_directions, turbulence_intensities=0.06 + ) +) + +yaw_angles_upstream = np.linspace(-30, 30, n_yaw) +yaw_angles = np.zeros((n_yaw, 2)) +yaw_angles[:, 0] = yaw_angles_upstream + +fmodel.set(yaw_angles=yaw_angles) +fmodel.run() +turbine_powers = fmodel.get_turbine_powers() / 1e3 + +# Plot the results +ax = axarr[3] +ax.plot(yaw_angles_upstream, turbine_powers[:, 0], label="Upstream Turbine", color="k") +ax.plot(yaw_angles_upstream, turbine_powers[:, 1], label="Downstream Turbine", color="r") +ax.set_xlabel("Upstream Yaw Angle (deg)") +ax.set_ylabel("Power (kW)") + +###################################################### +# Sweep the upstream power rating +###################################################### + +# Since we're changing control modes, need to reset the operation +fmodel.reset_operation() + +# To the de-rating need to change the power_thrust_mode to mixed or simple de-rating +with open( + str( + fmodel.core.as_dict()["farm"]["turbine_library_path"] + / (fmodel.core.as_dict()["farm"]["turbine_type"][0] + ".yaml") + ) +) as t: + turbine_type = yaml.safe_load(t) +turbine_type["power_thrust_model"] = "mixed" +fmodel.set(turbine_type=[turbine_type]) + +# Sweep the de-rating levels +RATED_POWER = 5e6 # For NREL 5MW +n_derating_levels = 150 +upstream_power_setpoint = np.linspace(0.0, RATED_POWER * 0.5, n_derating_levels) +power_setpoints = np.ones((n_derating_levels, 2)) * RATED_POWER +power_setpoints[:, 0] = upstream_power_setpoint + +# Set the wind conditions to fixed +wind_directions = np.ones(n_derating_levels) * 270.0 +fmodel.set( + wind_data=TimeSeries( + wind_speeds=8.0, wind_directions=wind_directions, turbulence_intensities=0.06 + ) +) + +# Set the de-rating levels +fmodel.set(power_setpoints=power_setpoints) +fmodel.run() + +# Get the turbine powers +turbine_powers = fmodel.get_turbine_powers() / 1e3 + +# Plot the results +ax = axarr[4] +ax.plot(upstream_power_setpoint / 1e3, turbine_powers[:, 0], label="Upstream Turbine", color="k") +ax.plot(upstream_power_setpoint / 1e3, turbine_powers[:, 1], label="Downstream Turbine", color="r") +ax.plot( + upstream_power_setpoint / 1e3, + upstream_power_setpoint / 1e3, + label="De-Rating Level", + color="b", + linestyle="--", +) +ax.set_xlabel("Upstream Power Setpoint (kW)") +ax.legend() + +###################################################### +# Sweep through disabling turbine combinations +###################################################### + +# Reset the control settings +fmodel.reset_operation() + +# Make a list of possible turbine disable combinations +disable_combinations = np.array([[False, False], [True, False], [False, True], [True, True]]) +n_combinations = disable_combinations.shape[0] + +# Make a list of strings representing the combinations +disable_combination_strings = ["None", "T0", "T1", "T0 & T1"] + +# Set the wind conditions to fixed +wind_directions = np.ones(n_combinations) * 270.0 +fmodel.set( + wind_data=TimeSeries( + wind_speeds=8.0, wind_directions=wind_directions, turbulence_intensities=0.06 + ) +) + +# Assign the disable settings +fmodel.set(disable_turbines=disable_combinations) + +# Run the model +fmodel.run() + +# Get the turbine powers +turbine_powers = fmodel.get_turbine_powers() / 1e3 + +# Plot the results +ax = axarr[5] +ax.plot(disable_combination_strings, turbine_powers[:, 0], "ks-", label="Upstream Turbine") +ax.plot(disable_combination_strings, turbine_powers[:, 1], "ro-", label="Downstream Turbine") +ax.set_xlabel("Turbine Disable Combination") + + +for ax in axarr: + ax.grid(True) + + +plt.show() From fc807eac6fa33f47058b94bed247f8a278e3c079 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 16:10:23 -0600 Subject: [PATCH 018/120] Remove old sweeping examples --- examples/04_sweep_wind_directions.py | 62 ------------------- examples/05_sweep_wind_speeds.py | 61 ------------------ examples/06_sweep_wind_conditions.py | 92 ---------------------------- 3 files changed, 215 deletions(-) delete mode 100644 examples/04_sweep_wind_directions.py delete mode 100644 examples/05_sweep_wind_speeds.py delete mode 100644 examples/06_sweep_wind_conditions.py diff --git a/examples/04_sweep_wind_directions.py b/examples/04_sweep_wind_directions.py deleted file mode 100644 index d049a0772..000000000 --- a/examples/04_sweep_wind_directions.py +++ /dev/null @@ -1,62 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel - - -""" -04_sweep_wind_directions - -This example sweeps across wind directions while holding wind speed -constant via an array of constant wind speed - -The power of both turbines for each wind direction is then plotted - -""" - -# Instantiate FLORIS using either the GCH or CC model -fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 - -# Define a two turbine farm -D = 126. -layout_x = np.array([0, D*6]) -layout_y = [0, 0] -fmodel.set(layout_x=layout_x, layout_y=layout_y) - -# Sweep wind speeds but keep wind direction fixed -wd_array = np.arange(250,291,1.) -ws_array = 8.0 * np.ones_like(wd_array) -ti_array = 0.06 * np.ones_like(wd_array) -fmodel.set(wind_directions=wd_array, wind_speeds=ws_array, turbulence_intensities=ti_array) - -# Define a matrix of yaw angles to be all 0 -# Note that yaw angles is now specified as a matrix whose dimensions are -# wd/ws/turbine -num_wd = len(wd_array) # Number of wind directions -num_ws = len(ws_array) # Number of wind speeds -n_findex = num_wd # Could be either num_wd or num_ws -num_turbine = len(layout_x) # Number of turbines -yaw_angles = np.zeros((n_findex, num_turbine)) -fmodel.set(yaw_angles=yaw_angles) - -# Calculate -fmodel.run() - -# Collect the turbine powers -turbine_powers = fmodel.get_turbine_powers() / 1E3 # In kW - -# Pull out the power values per turbine -pow_t0 = turbine_powers[:,0].flatten() -pow_t1 = turbine_powers[:,1].flatten() - -# Plot -fig, ax = plt.subplots() -ax.plot(wd_array,pow_t0,color='k',label='Upstream Turbine') -ax.plot(wd_array,pow_t1,color='r',label='Downstream Turbine') -ax.grid(True) -ax.legend() -ax.set_xlabel('Wind Direction (deg)') -ax.set_ylabel('Power (kW)') - -plt.show() diff --git a/examples/05_sweep_wind_speeds.py b/examples/05_sweep_wind_speeds.py deleted file mode 100644 index e5cd07c3a..000000000 --- a/examples/05_sweep_wind_speeds.py +++ /dev/null @@ -1,61 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel - - -""" -05_sweep_wind_speeds - -This example sweeps wind speeds while holding wind direction constant - -The power of both turbines for each wind speed is then plotted - -""" - - -# Instantiate FLORIS using either the GCH or CC model -fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 - -# Define a two turbine farm -D = 126. -layout_x = np.array([0, D*6]) -layout_y = [0, 0] -fmodel.set(layout_x=layout_x, layout_y=layout_y) - -# Sweep wind speeds but keep wind direction fixed -ws_array = np.arange(5,25,0.5) -wd_array = 270.0 * np.ones_like(ws_array) -ti_array = 0.06 * np.ones_like(ws_array) -fmodel.set(wind_directions=wd_array,wind_speeds=ws_array, turbulence_intensities=ti_array) - -# Define a matrix of yaw angles to be all 0 -# Note that yaw angles is now specified as a matrix whose dimensions are -# wd/ws/turbine -num_wd = len(wd_array) -num_ws = len(ws_array) -n_findex = num_wd # Could be either num_wd or num_ws -num_turbine = len(layout_x) -yaw_angles = np.zeros((n_findex, num_turbine)) -fmodel.set(yaw_angles=yaw_angles) - -# Calculate -fmodel.run() - -# Collect the turbine powers -turbine_powers = fmodel.get_turbine_powers() / 1E3 # In kW - -# Pull out the power values per turbine -pow_t0 = turbine_powers[:,0].flatten() -pow_t1 = turbine_powers[:,1].flatten() - -# Plot -fig, ax = plt.subplots() -ax.plot(ws_array,pow_t0,color='k',label='Upstream Turbine') -ax.plot(ws_array,pow_t1,color='r',label='Downstream Turbine') -ax.grid(True) -ax.legend() -ax.set_xlabel('Wind Speed (m/s)') -ax.set_ylabel('Power (kW)') -plt.show() diff --git a/examples/06_sweep_wind_conditions.py b/examples/06_sweep_wind_conditions.py deleted file mode 100644 index e9f42487b..000000000 --- a/examples/06_sweep_wind_conditions.py +++ /dev/null @@ -1,92 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel - - -""" -This example demonstrates the vectorized wake calculation for -a set of wind speeds and directions combinations. When given -a list of conditions, FLORIS leverages features of the CPU -to perform chunks of the computations at once rather than -looping over each condition. - -This calculation is performed for a single-row 5 turbine farm. In addition -to plotting the powers of the individual turbines, an energy by turbine -calculation is made and plotted by summing over the wind speed and wind direction -axes of the power matrix returned by get_turbine_powers() - -""" - -# Instantiate FLORIS using either the GCH or CC model -fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 -# fmodel = FlorisModel("inputs/cc.yaml") # New CumulativeCurl model - -# Define a 5 turbine farm -D = 126.0 -layout_x = np.array([0, D*6, D*12, D*18, D*24]) -layout_y = [0, 0, 0, 0, 0] -fmodel.set(layout_x=layout_x, layout_y=layout_y) - -# In this case we want to check a grid of wind speed and direction combinations -wind_speeds_to_expand = np.arange(6, 9, 1.0) -wind_directions_to_expand = np.arange(250, 295, 1.0) -num_unique_ws = len(wind_speeds_to_expand) -num_unique_wd = len(wind_directions_to_expand) - -# Create grids to make combinations of ws/wd -wind_speeds_grid, wind_directions_grid = np.meshgrid( - wind_speeds_to_expand, - wind_directions_to_expand -) - -# Flatten the grids back to 1D arrays -ws_array = wind_speeds_grid.flatten() -wd_array = wind_directions_grid.flatten() -turbulence_intensities = 0.06 * np.ones_like(wd_array) - -# Now reinitialize FLORIS -fmodel.set( - wind_speeds=ws_array, - wind_directions=wd_array, - turbulence_intensities=turbulence_intensities -) - -# Define a matrix of yaw angles to be all 0 -# Note that yaw angles is now specified as a matrix whose dimensions are -# (findex, turbine) -num_wd = len(wd_array) -num_ws = len(ws_array) -n_findex = num_wd # Could be either num_wd or num_ws -num_turbine = len(layout_x) -yaw_angles = np.zeros((n_findex, num_turbine)) -fmodel.set(yaw_angles=yaw_angles) - -# Calculate -fmodel.run() - -# Collect the turbine powers -turbine_powers = fmodel.get_turbine_powers() / 1e3 # In kW - -# Show results by ws and wd -fig, axarr = plt.subplots(num_unique_ws, 1, sharex=True, sharey=True, figsize=(6, 10)) -for ws_idx, ws in enumerate(wind_speeds_to_expand): - indices = ws_array == ws - ax = axarr[ws_idx] - for t in range(num_turbine): - ax.plot(wd_array[indices], turbine_powers[indices, t].flatten(), label="T%d" % t) - ax.legend() - ax.grid(True) - ax.set_title("Wind Speed = %.1f" % ws) - ax.set_ylabel("Power (kW)") -ax.set_xlabel("Wind Direction (deg)") - -# Sum across wind speeds and directions to show energy produced by turbine as bar plot -# Sum over wind directions and speeds -energy_by_turbine = np.sum(turbine_powers, axis=0) -fig, ax = plt.subplots() -ax.bar(["T%d" % t for t in range(num_turbine)], energy_by_turbine) -ax.set_title("Energy Produced by Turbine") - -plt.show() From e4b10b331266e7e87e4b7fb5c6417281373f06a7 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 16:40:52 -0600 Subject: [PATCH 019/120] Clean out old examples --- ...0_calculate_farm_power_with_uncertainty.py | 135 ------------------ examples/21_demo_time_series.py | 66 --------- examples/34_wind_data.py | 89 ------------ examples/35_sweep_ti.py | 49 ------- examples/36_generate_ti.py | 75 ---------- 5 files changed, 414 deletions(-) delete mode 100644 examples/20_calculate_farm_power_with_uncertainty.py delete mode 100644 examples/21_demo_time_series.py delete mode 100644 examples/34_wind_data.py delete mode 100644 examples/35_sweep_ti.py delete mode 100644 examples/36_generate_ti.py diff --git a/examples/20_calculate_farm_power_with_uncertainty.py b/examples/20_calculate_farm_power_with_uncertainty.py deleted file mode 100644 index f15313c8f..000000000 --- a/examples/20_calculate_farm_power_with_uncertainty.py +++ /dev/null @@ -1,135 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel, UncertainFlorisModel - - -""" -This example demonstrates how one can create an "UncertainFlorisModel" object, -which adds uncertainty on the inflow wind direction on the FlorisModel -class. The UncertainFlorisModel class is interacted with in the exact same -manner as the FlorisModel class is. This example demonstrates how the -wind farm power production is calculated with and without uncertainty. -Other use cases of UncertainFlorisModel are, e.g., comparing FLORIS to -historical SCADA data and robust optimization. -""" - -# Instantiate FLORIS using either the GCH or CC model -fmodel = FlorisModel("inputs/gch.yaml") # GCH model -ufmodel_3 = UncertainFlorisModel( - "inputs/gch.yaml", verbose=True, wd_std=3 -) -ufmodel_5 = UncertainFlorisModel( - "inputs/gch.yaml", verbose=True, wd_std=5 -) - -# Define a two turbine farm -D = 126.0 -layout_x = np.array([0, D * 6]) -layout_y = [0, 0] -wd_array = np.arange(240.0, 300.0, 1.0) -wind_speeds = 8.0 * np.ones_like(wd_array) -ti_array = 0.06 * np.ones_like(wd_array) -fmodel.set( - layout_x=layout_x, - layout_y=layout_y, - wind_directions=wd_array, - wind_speeds=wind_speeds, - turbulence_intensities=ti_array, -) -ufmodel_3.set( - layout_x=layout_x, - layout_y=layout_y, - wind_directions=wd_array, - wind_speeds=wind_speeds, - turbulence_intensities=ti_array, -) -ufmodel_5.set( - layout_x=layout_x, - layout_y=layout_y, - wind_directions=wd_array, - wind_speeds=wind_speeds, - turbulence_intensities=ti_array, -) - - -# Run both models -fmodel.run() -ufmodel_3.run() -ufmodel_5.run() - -# Collect the nominal and uncertain farm power -turbine_powers_nom = fmodel.get_turbine_powers() / 1e3 -turbine_powers_unc_3 = ufmodel_3.get_turbine_powers() / 1e3 -turbine_powers_unc_5 = ufmodel_5.get_turbine_powers() / 1e3 -farm_powers_nom = fmodel.get_farm_power() / 1e3 -farm_powers_unc_3 = ufmodel_3.get_farm_power() / 1e3 -farm_powers_unc_5 = ufmodel_5.get_farm_power() / 1e3 - -# Plot results -fig, axarr = plt.subplots(1, 3, figsize=(15, 5)) -ax = axarr[0] -ax.plot(wd_array, turbine_powers_nom[:, 0].flatten(), color="k", label="Nominal power") -ax.plot( - wd_array, - turbine_powers_unc_3[:, 0].flatten(), - color="r", - label="Power with uncertainty = 3 deg", -) -ax.plot( - wd_array, turbine_powers_unc_5[:, 0].flatten(), color="m", label="Power with uncertainty = 5deg" -) -ax.grid(True) -ax.legend() -ax.set_xlabel("Wind Direction (deg)") -ax.set_ylabel("Power (kW)") -ax.set_title("Upstream Turbine") - -ax = axarr[1] -ax.plot(wd_array, turbine_powers_nom[:, 1].flatten(), color="k", label="Nominal power") -ax.plot( - wd_array, - turbine_powers_unc_3[:, 1].flatten(), - color="r", - label="Power with uncertainty = 3 deg", -) -ax.plot( - wd_array, - turbine_powers_unc_5[:, 1].flatten(), - color="m", - label="Power with uncertainty = 5 deg", -) -ax.set_title("Downstream Turbine") -ax.grid(True) -ax.legend() -ax.set_xlabel("Wind Direction (deg)") -ax.set_ylabel("Power (kW)") - -ax = axarr[2] -ax.plot(wd_array, farm_powers_nom.flatten(), color="k", label="Nominal farm power") -ax.plot( - wd_array, farm_powers_unc_3.flatten(), color="r", label="Farm power with uncertainty = 3 deg" -) -ax.plot( - wd_array, farm_powers_unc_5.flatten(), color="m", label="Farm power with uncertainty = 5 deg" -) -ax.set_title("Farm Power") -ax.grid(True) -ax.legend() -ax.set_xlabel("Wind Direction (deg)") -ax.set_ylabel("Power (kW)") - -# Compare the AEP calculation -freq = np.ones_like(wd_array) -freq = freq / freq.sum() - -aep_nom = fmodel.get_farm_AEP(freq=freq) -aep_unc_3 = ufmodel_3.get_farm_AEP(freq=freq) -aep_unc_5 = ufmodel_5.get_farm_AEP(freq=freq) - -print(f"AEP without uncertainty {aep_nom}") -print(f"AEP without uncertainty (3 deg) {aep_unc_3} ({100*aep_unc_3/aep_nom:.2f}%)") -print(f"AEP without uncertainty (5 deg) {aep_unc_5} ({100*aep_unc_5/aep_nom:.2f}%)") - - -plt.show() diff --git a/examples/21_demo_time_series.py b/examples/21_demo_time_series.py deleted file mode 100644 index 8afa28f2f..000000000 --- a/examples/21_demo_time_series.py +++ /dev/null @@ -1,66 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel - - -""" -This example demonstrates running FLORIS given a time series -of wind direction and wind speed combinations. -""" - -# Initialize FLORIS to simple 4 turbine farm -fmodel = FlorisModel("inputs/gch.yaml") - -# Convert to a simple two turbine layout -fmodel.set(layout_x=[0, 500.], layout_y=[0., 0.]) - -# Create a fake time history where wind speed steps in the middle while wind direction -# Walks randomly -time = np.arange(0, 120, 10.) # Each time step represents a 10-minute average -ws = np.ones_like(time) * 8. -ws[int(len(ws) / 2):] = 9. -wd = np.ones_like(time) * 270. -turbulence_intensities = np.ones_like(time) * 0.06 - -for idx in range(1, len(time)): - wd[idx] = wd[idx - 1] + np.random.randn() * 2. - - -# Now intiialize FLORIS object to this history using time_series flag -fmodel.set(wind_directions=wd, wind_speeds=ws, turbulence_intensities=turbulence_intensities) - -# Collect the powers -fmodel.run() -turbine_powers = fmodel.get_turbine_powers() / 1000. - -# Show the dimensions -num_turbines = len(fmodel.layout_x) -print( - f'There are {len(time)} time samples, and {num_turbines} turbines and ' - f'so the resulting turbine power matrix has the shape {turbine_powers.shape}.' -) - - -fig, axarr = plt.subplots(3, 1, sharex=True, figsize=(7,8)) - -ax = axarr[0] -ax.plot(time, ws, 'o-') -ax.set_ylabel('Wind Speed (m/s)') -ax.grid(True) - -ax = axarr[1] -ax.plot(time, wd, 'o-') -ax.set_ylabel('Wind Direction (Deg)') -ax.grid(True) - -ax = axarr[2] -for t in range(num_turbines): - ax.plot(time,turbine_powers[:, t], 'o-', label='Turbine %d' % t) -ax.legend() -ax.set_ylabel('Turbine Power (kW)') -ax.set_xlabel('Time (minutes)') -ax.grid(True) - -plt.show() diff --git a/examples/34_wind_data.py b/examples/34_wind_data.py deleted file mode 100644 index 3a4d56fe5..000000000 --- a/examples/34_wind_data.py +++ /dev/null @@ -1,89 +0,0 @@ -import matplotlib.pyplot as plt -import numpy as np - -from floris import ( - FlorisModel, - TimeSeries, - WindRose, -) -from floris.utilities import wrap_360 - - -""" -This example is meant to be temporary and may be updated by a later pull request. Before we -release v4, we intend to propagate the TimeSeries and WindRose objects through the other relevant -examples, and change this example to demonstrate more advanced (as yet, not implemented) -functionality of the WindData objects (such as electricity pricing etc). -""" - - -# Generate a random time series of wind speeds, wind directions and turbulence intensities -N = 500 -wd_array = wrap_360(270 * np.ones(N) + np.random.randn(N) * 20) -ws_array = np.clip(8 * np.ones(N) + np.random.randn(N) * 8, 3, 50) -ti_array = np.clip(0.1 * np.ones(N) + np.random.randn(N) * 0.05, 0, 0.25) - -fig, axarr = plt.subplots(3, 1, sharex=True, figsize=(7, 4)) -ax = axarr[0] -ax.plot(wd_array, marker=".", ls="None") -ax.set_ylabel("Wind Direction") -ax = axarr[1] -ax.plot(ws_array, marker=".", ls="None") -ax.set_ylabel("Wind Speed") -ax = axarr[2] -ax.plot(ti_array, marker=".", ls="None") -ax.set_ylabel("Turbulence Intensity") - - -# Build the time series -time_series = TimeSeries(wd_array, ws_array, turbulence_intensities=ti_array) - -# Now build the wind rose -wind_rose = time_series.to_wind_rose() - -# Plot the wind rose -fig, ax = plt.subplots(subplot_kw={"polar": True}) -wind_rose.plot_wind_rose(ax=ax,legend_kwargs={"title": "WS"}) -fig.suptitle("WindRose Plot") - -# Now build a wind rose with turbulence intensity -wind_ti_rose = time_series.to_wind_ti_rose() - -# Plot the wind rose with TI -fig, axs = plt.subplots(2, 1, figsize=(6,8), subplot_kw={"polar": True}) -wind_ti_rose.plot_wind_rose(ax=axs[0], wind_rose_var="ws",legend_kwargs={"title": "WS"}) -axs[0].set_title("Wind Direction and Wind Speed Frequencies") -wind_ti_rose.plot_wind_rose(ax=axs[1], wind_rose_var="ti",legend_kwargs={"title": "TI"}) -axs[1].set_title("Wind Direction and Turbulence Intensity Frequencies") -fig.suptitle("WindTIRose Plots") -plt.tight_layout() - -# Now set up a FLORIS model and initialize it using the time series and wind rose -fmodel = FlorisModel("inputs/gch.yaml") -fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0]) - -fmodel_time_series = fmodel.copy() -fmodel_wind_rose = fmodel.copy() -fmodel_wind_ti_rose = fmodel.copy() - -fmodel_time_series.set(wind_data=time_series) -fmodel_wind_rose.set(wind_data=wind_rose) -fmodel_wind_ti_rose.set(wind_data=wind_ti_rose) - -fmodel_time_series.run() -fmodel_wind_rose.run() -fmodel_wind_ti_rose.run() - -time_series_power = fmodel_time_series.get_farm_power() -wind_rose_power = fmodel_wind_rose.get_farm_power() -wind_ti_rose_power = fmodel_wind_ti_rose.get_farm_power() - -time_series_aep = fmodel_time_series.get_farm_AEP_with_wind_data(time_series) -wind_rose_aep = fmodel_wind_rose.get_farm_AEP_with_wind_data(wind_rose) -wind_ti_rose_aep = fmodel_wind_ti_rose.get_farm_AEP_with_wind_data(wind_ti_rose) - -print(f"AEP from TimeSeries {time_series_aep / 1e9:.2f} GWh") -print(f"AEP from WindRose {wind_rose_aep / 1e9:.2f} GWh") -print(f"AEP from WindTIRose {wind_ti_rose_aep / 1e9:.2f} GWh") - -plt.show() diff --git a/examples/35_sweep_ti.py b/examples/35_sweep_ti.py deleted file mode 100644 index 5bf2ffa34..000000000 --- a/examples/35_sweep_ti.py +++ /dev/null @@ -1,49 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import ( - FlorisModel, - TimeSeries, - WindRose, -) -from floris.utilities import wrap_360 - - -""" -Demonstrate the new behavior in V4 where TI is an array rather than a float. -Set up an array of two turbines and sweep TI while holding wd/ws constant. -Use the TimeSeries object to drive the FLORIS calculations. -""" - - -# Generate a random time series of wind speeds, wind directions and turbulence intensities -N = 50 -wd_array = 270.0 * np.ones(N) -ws_array = 8.0 * np.ones(N) -ti_array = np.linspace(0.03, 0.2, N) - - -# Build the time series -time_series = TimeSeries(wd_array, ws_array, turbulence_intensities=ti_array) - - -# Now set up a FLORIS model and initialize it using the time -fmodel = FlorisModel("inputs/gch.yaml") -fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0], wind_data=time_series) -fmodel.run() -turbine_power = fmodel.get_turbine_powers() - -fig, axarr = plt.subplots(2, 1, sharex=True, figsize=(6, 6)) -ax = axarr[0] -ax.plot(ti_array*100, turbine_power[:, 0]/1000, color="k") -ax.set_ylabel("Front turbine power [kW]") -ax = axarr[1] -ax.plot(ti_array*100, turbine_power[:, 1]/1000, color="k") -ax.set_ylabel("Rear turbine power [kW]") -ax.set_xlabel("Turbulence intensity [%]") - -for ax in axarr: - ax.grid(True) - -plt.show() diff --git a/examples/36_generate_ti.py b/examples/36_generate_ti.py deleted file mode 100644 index 317bc8dbe..000000000 --- a/examples/36_generate_ti.py +++ /dev/null @@ -1,75 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import ( - FlorisModel, - TimeSeries, - WindRose, -) -from floris.utilities import wrap_360 - - -""" -Demonstrate usage of TI generating and plotting functionality in the WindRose -and TimeSeries classes -""" - - -# Generate a random time series of wind speeds, wind directions and turbulence intensities -wind_directions = np.array([250, 260, 270]) -wind_speeds = np.array([5, 6, 7, 8, 9, 10]) -ti_table = 0.06 - -# Declare a WindRose object -wind_rose = WindRose(wind_directions=wind_directions, wind_speeds=wind_speeds, ti_table=ti_table) - - -# Define a custom function where TI = 1 / wind_speed -def custom_ti_func(wind_directions, wind_speeds): - return 1 / wind_speeds - - -wind_rose.assign_ti_using_wd_ws_function(custom_ti_func) - -fig, ax = plt.subplots() -wind_rose.plot_ti_over_ws(ax) -ax.set_title("Turbulence Intensity defined by custom function") - -# Now use the normal turbulence model approach from the IEC 61400-1 standard, -# wherein TI is defined as a function of wind speed: -# Iref is defined as the TI value at 15 m/s. Note that Iref = 0.07 is lower -# than the values of Iref used in the IEC standard, but produces TI values more -# in line with those typically used in FLORIS (TI=8.6% at 8 m/s). -Iref = 0.07 -wind_rose.assign_ti_using_IEC_method(Iref) -fig, ax = plt.subplots() -wind_rose.plot_ti_over_ws(ax) -ax.set_title(f"Turbulence Intensity defined by Iref = {Iref:0.2}") - - -# Demonstrate equivalent usage in time series -N = 100 -wind_directions = 270 * np.ones(N) -wind_speeds = np.linspace(5, 15, N) -turbulence_intensities = 0.06 * np.ones(N) -time_series = TimeSeries( - wind_directions=wind_directions, - wind_speeds=wind_speeds, - turbulence_intensities=turbulence_intensities -) -time_series.assign_ti_using_IEC_method(Iref=Iref) - -fig, axarr = plt.subplots(2, 1, sharex=True, figsize=(7, 8)) -ax = axarr[0] -ax.plot(wind_speeds) -ax.set_ylabel("Wind Speeds (m/s)") -ax.grid(True) -ax = axarr[1] -ax.plot(time_series.turbulence_intensities) -ax.set_ylabel("Turbulence Intensity (-)") -ax.grid(True) -fig.suptitle("Generating TI in TimeSeries") - - -plt.show() From 143d10d6879c1b1f711d9d4d8574abfa2137d495 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 16:41:42 -0600 Subject: [PATCH 020/120] Add uncertain model example --- examples/008_uncertain_models.py | 157 +++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 examples/008_uncertain_models.py diff --git a/examples/008_uncertain_models.py b/examples/008_uncertain_models.py new file mode 100644 index 000000000..f2ba3ed58 --- /dev/null +++ b/examples/008_uncertain_models.py @@ -0,0 +1,157 @@ +"""Example 8: Uncertain Models + +UncertainFlorisModel is a class that adds uncertainty to the inflow wind direction +on the FlorisModel class. The UncertainFlorisModel class is interacted with in the +same manner as the FlorisModel class is. This example demonstrates how the +wind farm power production is calculated with and without uncertainty. +Other use cases of UncertainFlorisModel are, e.g., comparing FLORIS to +historical SCADA data and robust optimization. + +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import ( + FlorisModel, + TimeSeries, + UncertainFlorisModel, +) + + +# Instantiate FLORIS FLORIS and UncertainFLORIS models +fmodel = FlorisModel("inputs/gch.yaml") # GCH model + +# The instantiation of the UncertainFlorisModel class is similar to the FlorisModel class +# with the addition of the wind direction standard deviation (wd_std) parameter +# and certain resolution parameters. Internally, the UncertainFlorisModel class +# expands the wind direction time series to include the uncertainty but then +# only runs the unique cases. The final result is computed via a gaussian weighting +# of the cases according to wd_std. Here we use the default resolution parameters. +# wd_resolution=1.0, # Degree +# ws_resolution=1.0, # m/s +# ti_resolution=0.01, + +ufmodel_3 = UncertainFlorisModel("inputs/gch.yaml", wd_std=3) +ufmodel_5 = UncertainFlorisModel("inputs/gch.yaml", wd_std=5) + +# Define an inflow where wind direction is swept while +# wind speed and turbulence intensity are held constant +wind_directions = np.arange(240.0, 300.0, 1.0) +time_series = TimeSeries( + wind_directions=wind_directions, + wind_speeds=8.0, + turbulence_intensities=0.06, +) + +# Define a two turbine farm and apply the inflow +D = 126.0 +layout_x = np.array([0, D * 6]) +layout_y = [0, 0] + +fmodel.set( + layout_x=layout_x, + layout_y=layout_y, + wind_data=time_series, +) +ufmodel_3.set( + layout_x=layout_x, + layout_y=layout_y, + wind_data=time_series, +) +ufmodel_5.set( + layout_x=layout_x, + layout_y=layout_y, + wind_data=time_series, +) + + +# Run both models +fmodel.run() +ufmodel_3.run() +ufmodel_5.run() + +# Collect the nominal and uncertain farm power +turbine_powers_nom = fmodel.get_turbine_powers() / 1e3 +turbine_powers_unc_3 = ufmodel_3.get_turbine_powers() / 1e3 +turbine_powers_unc_5 = ufmodel_5.get_turbine_powers() / 1e3 +farm_powers_nom = fmodel.get_farm_power() / 1e3 +farm_powers_unc_3 = ufmodel_3.get_farm_power() / 1e3 +farm_powers_unc_5 = ufmodel_5.get_farm_power() / 1e3 + +# Plot results +fig, axarr = plt.subplots(1, 3, figsize=(15, 5)) +ax = axarr[0] +ax.plot(wind_directions, turbine_powers_nom[:, 0].flatten(), color="k", label="Nominal power") +ax.plot( + wind_directions, + turbine_powers_unc_3[:, 0].flatten(), + color="r", + label="Power with uncertainty = 3 deg", +) +ax.plot( + wind_directions, + turbine_powers_unc_5[:, 0].flatten(), + color="m", + label="Power with uncertainty = 5deg", +) +ax.grid(True) +ax.legend() +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") +ax.set_title("Upstream Turbine") + +ax = axarr[1] +ax.plot(wind_directions, turbine_powers_nom[:, 1].flatten(), color="k", label="Nominal power") +ax.plot( + wind_directions, + turbine_powers_unc_3[:, 1].flatten(), + color="r", + label="Power with uncertainty = 3 deg", +) +ax.plot( + wind_directions, + turbine_powers_unc_5[:, 1].flatten(), + color="m", + label="Power with uncertainty = 5 deg", +) +ax.set_title("Downstream Turbine") +ax.grid(True) +ax.legend() +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") + +ax = axarr[2] +ax.plot(wind_directions, farm_powers_nom.flatten(), color="k", label="Nominal farm power") +ax.plot( + wind_directions, + farm_powers_unc_3.flatten(), + color="r", + label="Farm power with uncertainty = 3 deg", +) +ax.plot( + wind_directions, + farm_powers_unc_5.flatten(), + color="m", + label="Farm power with uncertainty = 5 deg", +) +ax.set_title("Farm Power") +ax.grid(True) +ax.legend() +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") + +# Compare the AEP calculation +freq = np.ones_like(wind_directions) +freq = freq / freq.sum() + +aep_nom = fmodel.get_farm_AEP(freq=freq) +aep_unc_3 = ufmodel_3.get_farm_AEP(freq=freq) +aep_unc_5 = ufmodel_5.get_farm_AEP(freq=freq) + +print(f"AEP without uncertainty {aep_nom}") +print(f"AEP without uncertainty (3 deg) {aep_unc_3} ({100*aep_unc_3/aep_nom:.2f}%)") +print(f"AEP without uncertainty (5 deg) {aep_unc_5} ({100*aep_unc_5/aep_nom:.2f}%)") + + +plt.show() From 0b5764100d43fdd887081e9e435d6a9ee91d1dcd Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 14 Mar 2024 16:41:52 -0600 Subject: [PATCH 021/120] Start wind data sub folder --- examples/examples_wind_data/34_wind_data.py | 89 +++++++++++++++++++ examples/examples_wind_data/36_generate_ti.py | 75 ++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 examples/examples_wind_data/34_wind_data.py create mode 100644 examples/examples_wind_data/36_generate_ti.py diff --git a/examples/examples_wind_data/34_wind_data.py b/examples/examples_wind_data/34_wind_data.py new file mode 100644 index 000000000..3a4d56fe5 --- /dev/null +++ b/examples/examples_wind_data/34_wind_data.py @@ -0,0 +1,89 @@ +import matplotlib.pyplot as plt +import numpy as np + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, +) +from floris.utilities import wrap_360 + + +""" +This example is meant to be temporary and may be updated by a later pull request. Before we +release v4, we intend to propagate the TimeSeries and WindRose objects through the other relevant +examples, and change this example to demonstrate more advanced (as yet, not implemented) +functionality of the WindData objects (such as electricity pricing etc). +""" + + +# Generate a random time series of wind speeds, wind directions and turbulence intensities +N = 500 +wd_array = wrap_360(270 * np.ones(N) + np.random.randn(N) * 20) +ws_array = np.clip(8 * np.ones(N) + np.random.randn(N) * 8, 3, 50) +ti_array = np.clip(0.1 * np.ones(N) + np.random.randn(N) * 0.05, 0, 0.25) + +fig, axarr = plt.subplots(3, 1, sharex=True, figsize=(7, 4)) +ax = axarr[0] +ax.plot(wd_array, marker=".", ls="None") +ax.set_ylabel("Wind Direction") +ax = axarr[1] +ax.plot(ws_array, marker=".", ls="None") +ax.set_ylabel("Wind Speed") +ax = axarr[2] +ax.plot(ti_array, marker=".", ls="None") +ax.set_ylabel("Turbulence Intensity") + + +# Build the time series +time_series = TimeSeries(wd_array, ws_array, turbulence_intensities=ti_array) + +# Now build the wind rose +wind_rose = time_series.to_wind_rose() + +# Plot the wind rose +fig, ax = plt.subplots(subplot_kw={"polar": True}) +wind_rose.plot_wind_rose(ax=ax,legend_kwargs={"title": "WS"}) +fig.suptitle("WindRose Plot") + +# Now build a wind rose with turbulence intensity +wind_ti_rose = time_series.to_wind_ti_rose() + +# Plot the wind rose with TI +fig, axs = plt.subplots(2, 1, figsize=(6,8), subplot_kw={"polar": True}) +wind_ti_rose.plot_wind_rose(ax=axs[0], wind_rose_var="ws",legend_kwargs={"title": "WS"}) +axs[0].set_title("Wind Direction and Wind Speed Frequencies") +wind_ti_rose.plot_wind_rose(ax=axs[1], wind_rose_var="ti",legend_kwargs={"title": "TI"}) +axs[1].set_title("Wind Direction and Turbulence Intensity Frequencies") +fig.suptitle("WindTIRose Plots") +plt.tight_layout() + +# Now set up a FLORIS model and initialize it using the time series and wind rose +fmodel = FlorisModel("inputs/gch.yaml") +fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0]) + +fmodel_time_series = fmodel.copy() +fmodel_wind_rose = fmodel.copy() +fmodel_wind_ti_rose = fmodel.copy() + +fmodel_time_series.set(wind_data=time_series) +fmodel_wind_rose.set(wind_data=wind_rose) +fmodel_wind_ti_rose.set(wind_data=wind_ti_rose) + +fmodel_time_series.run() +fmodel_wind_rose.run() +fmodel_wind_ti_rose.run() + +time_series_power = fmodel_time_series.get_farm_power() +wind_rose_power = fmodel_wind_rose.get_farm_power() +wind_ti_rose_power = fmodel_wind_ti_rose.get_farm_power() + +time_series_aep = fmodel_time_series.get_farm_AEP_with_wind_data(time_series) +wind_rose_aep = fmodel_wind_rose.get_farm_AEP_with_wind_data(wind_rose) +wind_ti_rose_aep = fmodel_wind_ti_rose.get_farm_AEP_with_wind_data(wind_ti_rose) + +print(f"AEP from TimeSeries {time_series_aep / 1e9:.2f} GWh") +print(f"AEP from WindRose {wind_rose_aep / 1e9:.2f} GWh") +print(f"AEP from WindTIRose {wind_ti_rose_aep / 1e9:.2f} GWh") + +plt.show() diff --git a/examples/examples_wind_data/36_generate_ti.py b/examples/examples_wind_data/36_generate_ti.py new file mode 100644 index 000000000..317bc8dbe --- /dev/null +++ b/examples/examples_wind_data/36_generate_ti.py @@ -0,0 +1,75 @@ + +import matplotlib.pyplot as plt +import numpy as np + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, +) +from floris.utilities import wrap_360 + + +""" +Demonstrate usage of TI generating and plotting functionality in the WindRose +and TimeSeries classes +""" + + +# Generate a random time series of wind speeds, wind directions and turbulence intensities +wind_directions = np.array([250, 260, 270]) +wind_speeds = np.array([5, 6, 7, 8, 9, 10]) +ti_table = 0.06 + +# Declare a WindRose object +wind_rose = WindRose(wind_directions=wind_directions, wind_speeds=wind_speeds, ti_table=ti_table) + + +# Define a custom function where TI = 1 / wind_speed +def custom_ti_func(wind_directions, wind_speeds): + return 1 / wind_speeds + + +wind_rose.assign_ti_using_wd_ws_function(custom_ti_func) + +fig, ax = plt.subplots() +wind_rose.plot_ti_over_ws(ax) +ax.set_title("Turbulence Intensity defined by custom function") + +# Now use the normal turbulence model approach from the IEC 61400-1 standard, +# wherein TI is defined as a function of wind speed: +# Iref is defined as the TI value at 15 m/s. Note that Iref = 0.07 is lower +# than the values of Iref used in the IEC standard, but produces TI values more +# in line with those typically used in FLORIS (TI=8.6% at 8 m/s). +Iref = 0.07 +wind_rose.assign_ti_using_IEC_method(Iref) +fig, ax = plt.subplots() +wind_rose.plot_ti_over_ws(ax) +ax.set_title(f"Turbulence Intensity defined by Iref = {Iref:0.2}") + + +# Demonstrate equivalent usage in time series +N = 100 +wind_directions = 270 * np.ones(N) +wind_speeds = np.linspace(5, 15, N) +turbulence_intensities = 0.06 * np.ones(N) +time_series = TimeSeries( + wind_directions=wind_directions, + wind_speeds=wind_speeds, + turbulence_intensities=turbulence_intensities +) +time_series.assign_ti_using_IEC_method(Iref=Iref) + +fig, axarr = plt.subplots(2, 1, sharex=True, figsize=(7, 8)) +ax = axarr[0] +ax.plot(wind_speeds) +ax.set_ylabel("Wind Speeds (m/s)") +ax.grid(True) +ax = axarr[1] +ax.plot(time_series.turbulence_intensities) +ax.set_ylabel("Turbulence Intensity (-)") +ax.grid(True) +fig.suptitle("Generating TI in TimeSeries") + + +plt.show() From 4b2b3f5374a3d6d90865240bfaf57dc373a78d4d Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 06:46:11 -0600 Subject: [PATCH 022/120] re-org yaw opt cases --- examples/10_opt_yaw_single_ws.py | 67 ------------------- .../11_opt_yaw_multiple_ws.py | 0 .../12_optimize_yaw.py | 0 .../12_optimize_yaw_in_parallel.py | 0 .../13_optimize_yaw_with_neighboring_farm.py | 0 .../14_compare_yaw_optimizers.py | 0 6 files changed, 67 deletions(-) delete mode 100644 examples/10_opt_yaw_single_ws.py rename examples/{ => examples_control_optimization}/11_opt_yaw_multiple_ws.py (100%) rename examples/{ => examples_control_optimization}/12_optimize_yaw.py (100%) rename examples/{ => examples_control_optimization}/12_optimize_yaw_in_parallel.py (100%) rename examples/{ => examples_control_optimization}/13_optimize_yaw_with_neighboring_farm.py (100%) rename examples/{ => examples_control_optimization}/14_compare_yaw_optimizers.py (100%) diff --git a/examples/10_opt_yaw_single_ws.py b/examples/10_opt_yaw_single_ws.py deleted file mode 100644 index f33878c9e..000000000 --- a/examples/10_opt_yaw_single_ws.py +++ /dev/null @@ -1,67 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel -from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR - - -""" -This example demonstrates how to perform a yaw optimization for multiple wind directions -and 1 wind speed. - -First, we initialize our Floris Interface, and then generate a 3 turbine wind farm. -Next, we create the yaw optimization object `yaw_opt` and perform the optimization using the -SerialRefine method. Finally, we plot the results. -""" - -# Load the default example floris object -fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 -# fmodel = FlorisModel("inputs/cc.yaml") # New CumulativeCurl model - -# Reinitialize as a 3-turbine farm with range of WDs and 1 WS -wd_array = np.arange(0.0, 360.0, 3.0) -ws_array = 8.0 * np.ones_like(wd_array) -turbulence_intensities = 0.06 * np.ones_like(wd_array) -D = 126.0 # Rotor diameter for the NREL 5 MW -fmodel.set( - layout_x=[0.0, 5 * D, 10 * D], - layout_y=[0.0, 0.0, 0.0], - wind_directions=wd_array, - wind_speeds=ws_array, - turbulence_intensities=turbulence_intensities, -) -print(fmodel.core.farm.rotor_diameters) - -# Initialize optimizer object and run optimization using the Serial-Refine method -yaw_opt = YawOptimizationSR(fmodel) -df_opt = yaw_opt.optimize() - -print("Optimization results:") -print(df_opt) - -# Split out the turbine results -for t in range(3): - df_opt['t%d' % t] = df_opt.yaw_angles_opt.apply(lambda x: x[t]) - -# Show the results -fig, axarr = plt.subplots(2,1,sharex=True,sharey=False,figsize=(8,8)) - -# Yaw results -ax = axarr[0] -for t in range(3): - ax.plot(df_opt.wind_direction,df_opt['t%d' % t],label='t%d' % t) -ax.set_ylabel('Yaw Offset (deg') -ax.legend() -ax.grid(True) - -# Power results -ax = axarr[1] -ax.plot(df_opt.wind_direction,df_opt.farm_power_baseline,color='k',label='Baseline Farm Power') -ax.plot(df_opt.wind_direction,df_opt.farm_power_opt,color='r',label='Optimized Farm Power') -ax.set_ylabel('Power (W)') -ax.set_xlabel('Wind Direction (deg)') -ax.legend() -ax.grid(True) - -plt.show() diff --git a/examples/11_opt_yaw_multiple_ws.py b/examples/examples_control_optimization/11_opt_yaw_multiple_ws.py similarity index 100% rename from examples/11_opt_yaw_multiple_ws.py rename to examples/examples_control_optimization/11_opt_yaw_multiple_ws.py diff --git a/examples/12_optimize_yaw.py b/examples/examples_control_optimization/12_optimize_yaw.py similarity index 100% rename from examples/12_optimize_yaw.py rename to examples/examples_control_optimization/12_optimize_yaw.py diff --git a/examples/12_optimize_yaw_in_parallel.py b/examples/examples_control_optimization/12_optimize_yaw_in_parallel.py similarity index 100% rename from examples/12_optimize_yaw_in_parallel.py rename to examples/examples_control_optimization/12_optimize_yaw_in_parallel.py diff --git a/examples/13_optimize_yaw_with_neighboring_farm.py b/examples/examples_control_optimization/13_optimize_yaw_with_neighboring_farm.py similarity index 100% rename from examples/13_optimize_yaw_with_neighboring_farm.py rename to examples/examples_control_optimization/13_optimize_yaw_with_neighboring_farm.py diff --git a/examples/14_compare_yaw_optimizers.py b/examples/examples_control_optimization/14_compare_yaw_optimizers.py similarity index 100% rename from examples/14_compare_yaw_optimizers.py rename to examples/examples_control_optimization/14_compare_yaw_optimizers.py From 6eec5a468d24d5043d2f22435971484bf6cd5058 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 06:46:31 -0600 Subject: [PATCH 023/120] first yaw opt example --- .../001_opt_yaw_single_ws.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 examples/examples_control_optimization/001_opt_yaw_single_ws.py diff --git a/examples/examples_control_optimization/001_opt_yaw_single_ws.py b/examples/examples_control_optimization/001_opt_yaw_single_ws.py new file mode 100644 index 000000000..533347a78 --- /dev/null +++ b/examples/examples_control_optimization/001_opt_yaw_single_ws.py @@ -0,0 +1,66 @@ + +"""Example: Optimize yaw for a single wind speed and multiple wind directions + +Use the serial-refine method to optimize the yaw angles for a 3-turbine wind farm + +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel, TimeSeries +from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR + + +# Load the default example floris object +fmodel = FlorisModel("../inputs/gch.yaml") + +# Define an inflow that +# keeps wind speed and TI constant while sweeping the wind directions +wind_directions = np.arange(0.0, 360.0, 3.0) +time_series = TimeSeries( + wind_directions=wind_directions, + wind_speeds=8.0, + turbulence_intensities=0.06, +) + +# Reinitialize as a 3-turbine using the above inflow +D = 126.0 # Rotor diameter for the NREL 5 MW +fmodel.set( + layout_x=[0.0, 5 * D, 10 * D], + layout_y=[0.0, 0.0, 0.0], + wind_data=time_series, +) + +# Initialize optimizer object and run optimization using the Serial-Refine method +yaw_opt = YawOptimizationSR(fmodel) +df_opt = yaw_opt.optimize() + +print("Optimization results:") +print(df_opt) + +# Split out the turbine results +for t in range(3): + df_opt['t%d' % t] = df_opt.yaw_angles_opt.apply(lambda x: x[t]) + +# Show the results +fig, axarr = plt.subplots(2,1,sharex=True,sharey=False,figsize=(8,8)) + +# Yaw results +ax = axarr[0] +for t in range(3): + ax.plot(df_opt.wind_direction,df_opt['t%d' % t],label='t%d' % t) +ax.set_ylabel('Yaw Offset (deg') +ax.legend() +ax.grid(True) + +# Power results +ax = axarr[1] +ax.plot(df_opt.wind_direction,df_opt.farm_power_baseline,color='k',label='Baseline Farm Power') +ax.plot(df_opt.wind_direction,df_opt.farm_power_opt,color='r',label='Optimized Farm Power') +ax.set_ylabel('Power (W)') +ax.set_xlabel('Wind Direction (deg)') +ax.legend() +ax.grid(True) + +plt.show() From ffe783a1c5493ad7356fa755d8a2778dff0ae703 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 08:04:06 -0600 Subject: [PATCH 024/120] Add core property and bugfix --- floris/uncertain_floris_model.py | 43 +++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/floris/uncertain_floris_model.py b/floris/uncertain_floris_model.py index b91b482a3..b234af7ea 100644 --- a/floris/uncertain_floris_model.py +++ b/floris/uncertain_floris_model.py @@ -108,6 +108,19 @@ def __init__( # Instantiate the expanded FlorisModel # self.core_interface = FlorisModel(configuration) + def copy(self): + """Create an independent copy of the current UncertainFlorisModel object""" + return UncertainFlorisModel( + self.fmodel_unexpanded.core.as_dict(), + wd_resolution=self.wd_resolution, + ws_resolution=self.ws_resolution, + ti_resolution=self.ti_resolution, + yaw_resolution=self.yaw_resolution, + power_setpoint_resolution=self.power_setpoint_resolution, + wd_std=self.wd_std, + wd_sample_points=self.wd_sample_points, + verbose=self.verbose, + ) def set( self, @@ -525,11 +538,18 @@ def _get_rounded_inputs( rounded_input_array[:, 2] = ( np.round(rounded_input_array[:, 2] / ti_resolution) * ti_resolution ) - rounded_input_array[:, 3] = ( - np.round(rounded_input_array[:, 3] / yaw_resolution) * yaw_resolution + rounded_input_array[:, 3 : 3 + self.fmodel_unexpanded.core.farm.n_turbines] = ( + np.round( + rounded_input_array[:, 3 : 3 + self.fmodel_unexpanded.core.farm.n_turbines] + / yaw_resolution + ) + * yaw_resolution ) - rounded_input_array[:, 4] = ( - np.round(rounded_input_array[:, 4] / power_setpoint_resolution) + rounded_input_array[:, 3 + self.fmodel_unexpanded.core.farm.n_turbines :] = ( + np.round( + rounded_input_array[:, 3 + self.fmodel_unexpanded.core.farm.n_turbines :] + / power_setpoint_resolution + ) * power_setpoint_resolution ) @@ -644,7 +664,7 @@ def layout_x(self): Returns: np.array: Wind turbine x-coordinate. """ - return self.core_interface.core.farm.layout_x + return self.fmodel_unexpanded.core.farm.layout_x @property def layout_y(self): @@ -654,7 +674,18 @@ def layout_y(self): Returns: np.array: Wind turbine y-coordinate. """ - return self.core_interface.core.farm.layout_y + return self.fmodel_unexpanded.core.farm.layout_y + + @property + def core(self): + """ + Returns the core of the unexpanded model. + + Returns: + Floris: The core of the unexpanded model. + """ + return self.fmodel_unexpanded.core + def map_turbine_powers_uncertain( unique_turbine_powers, From 9067920700d675eafe161a76e492c7640d01c161 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 08:04:47 -0600 Subject: [PATCH 025/120] Add 002 example optimization --- .../002_opt_yaw_single_ws_uncertain.py | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 examples/examples_control_optimization/002_opt_yaw_single_ws_uncertain.py diff --git a/examples/examples_control_optimization/002_opt_yaw_single_ws_uncertain.py b/examples/examples_control_optimization/002_opt_yaw_single_ws_uncertain.py new file mode 100644 index 000000000..4b9ceda1e --- /dev/null +++ b/examples/examples_control_optimization/002_opt_yaw_single_ws_uncertain.py @@ -0,0 +1,112 @@ +"""Example: Optimize yaw for a single wind speed and multiple wind directions. +Compare certain and uncertain results. + +Use the serial-refine method to optimize the yaw angles for a 3-turbine wind farm. In one +case use the FlorisModel without uncertainty and in the other use the UncertainFlorisModel +with a wind direction standard deviation of 3 degrees. Compare the results. + +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import ( + FlorisModel, + TimeSeries, + UncertainFlorisModel, +) +from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR + + +# Load the floris model and uncertain floris model +fmodel = FlorisModel("../inputs/gch.yaml") +ufmodel = UncertainFlorisModel("../inputs/gch.yaml", wd_std=3) + + +# Define an inflow that +# keeps wind speed and TI constant while sweeping the wind directions +wind_directions = np.arange(250, 290.0, 1.0) +time_series = TimeSeries( + wind_directions=wind_directions, + wind_speeds=8.0, + turbulence_intensities=0.06, +) + +# Reinitialize as a 3-turbine using the above inflow +D = 126.0 # Rotor diameter for the NREL 5 MW +fmodel.set( + layout_x=[0.0, 5 * D, 10 * D], + layout_y=[0.0, 0.0, 0.0], + wind_data=time_series, +) +ufmodel.set( + layout_x=[0.0, 5 * D, 10 * D], + layout_y=[0.0, 0.0, 0.0], + wind_data=time_series, +) + +# Initialize optimizer object and run optimization using the Serial-Refine method +print("++++++++++CERTAIN++++++++++++") +yaw_opt = YawOptimizationSR(fmodel) +df_opt = yaw_opt.optimize() + +# Repeat with uncertain model +print("++++++++++UNCERTAIN++++++++++++") +yaw_opt_u = YawOptimizationSR(ufmodel) +df_opt_uncertain = yaw_opt_u.optimize() + +# Split out the turbine results +for t in range(3): + df_opt["t%d" % t] = df_opt.yaw_angles_opt.apply(lambda x: x[t]) + df_opt_uncertain["t%d" % t] = df_opt_uncertain.yaw_angles_opt.apply(lambda x: x[t]) + +# Show the yaw and turbine results +fig, axarr = plt.subplots(3, sharex=True, sharey=False, figsize=(15, 8)) + +# Yaw results +for tindex in range(3): + ax = axarr[tindex] + ax.plot( + df_opt.wind_direction, df_opt["t%d" % tindex], label="FlorisModel", color="k", marker="o" + ) + ax.plot( + df_opt_uncertain.wind_direction, + df_opt_uncertain["t%d" % tindex], + label="UncertainFlorisModel", + color="r", + marker="x", + ) + ax.set_ylabel("Yaw Offset (deg") + ax.legend() + ax.grid(True) + + +# Power results +fig, axarr = plt.subplots(1, 2, figsize=(15, 5), sharex=True, sharey=True) +ax = axarr[0] +ax.plot(df_opt.wind_direction, df_opt.farm_power_baseline, color="k", label="Baseline Farm Power") +ax.plot(df_opt.wind_direction, df_opt.farm_power_opt, color="r", label="Optimized Farm Power") +ax.set_ylabel("Power (W)") +ax.set_xlabel("Wind Direction (deg)") +ax.legend() +ax.grid(True) +ax.set_title("Certain") +ax = axarr[1] +ax.plot( + df_opt_uncertain.wind_direction, + df_opt_uncertain.farm_power_baseline, + color="k", + label="Baseline Farm Power", +) +ax.plot( + df_opt_uncertain.wind_direction, + df_opt_uncertain.farm_power_opt, + color="r", + label="Optimized Farm Power", +) +ax.set_xlabel("Wind Direction (deg)") +ax.grid(True) +ax.set_title("Uncertain") + + +plt.show() From e88b41bc0ea60f926177870eea32bc6dfa297456 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 12:13:00 -0600 Subject: [PATCH 026/120] formatting --- examples/001_opening_floris_computing_power.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/001_opening_floris_computing_power.py b/examples/001_opening_floris_computing_power.py index 6504ea9bb..41158dcc9 100644 --- a/examples/001_opening_floris_computing_power.py +++ b/examples/001_opening_floris_computing_power.py @@ -27,12 +27,12 @@ # Changing wind speed, wind direction, and turbulence intensity using the set method # as well. Note that the wind_speeds, wind_directions, and turbulence_intensities # are all specified as arrays of the same length. -fmodel.set(wind_directions=np.array([270.0]), - wind_speeds=[8.0], - turbulence_intensities=np.array([0.06])) +fmodel.set( + wind_directions=np.array([270.0]), wind_speeds=[8.0], turbulence_intensities=np.array([0.06]) +) # Note that typically all 3, wind_directions, wind_speeds and turbulence_intensities -# must be supplied to set. However, the exception is if not changing the lenght +# must be supplied to set. However, the exception is if not changing the length # of the arrays, then only one or two may be supplied. fmodel.set(turbulence_intensities=np.array([0.07])) @@ -42,9 +42,11 @@ # be unique. Internally in FLORIS, most data structures will have the findex as their # 0th dimension. The value n_findex is the total number of conditions to be simulated. # This command would simulate 4 conditions (n_findex = 4). -fmodel.set(wind_directions=np.array([270.0, 270.0, 270.0, 270.0]), - wind_speeds=[8.0, 8.0, 10.0, 10.0], - turbulence_intensities=np.array([0.06, 0.06, 0.06, 0.06])) +fmodel.set( + wind_directions=np.array([270.0, 270.0, 270.0, 270.0]), + wind_speeds=[8.0, 8.0, 10.0, 10.0], + turbulence_intensities=np.array([0.06, 0.06, 0.06, 0.06]), +) # After the set method, the run method is called to perform the simulation fmodel.run() From 7ded17b9d25b0f93741818c150eb4bace82937dd Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 12:13:10 -0600 Subject: [PATCH 027/120] formatting --- examples/004_set.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/004_set.py b/examples/004_set.py index 1e7d6daab..21598f455 100644 --- a/examples/004_set.py +++ b/examples/004_set.py @@ -75,12 +75,12 @@ ###################################################### # Changes to controls settings can be made using the set method -# Note the dimension must match (n_findex, n_tindex) or (number of conditions, number of turbines) -# Above we n_findex = 3 and n_tindex = 2 so the matrix of yaw angles must be 3x2 +# Note the dimension must match (n_findex, n_turbines) or (number of conditions, number of turbines) +# Above we n_findex = 3 and n_turbines = 2 so the matrix of yaw angles must be 3x2 yaw_angles = np.array([[0.0, 0.0], [25.0, 0.0], [0.0, 0.0]]) fmodel.set(yaw_angles=yaw_angles) -# By default for the turbines in the turbine_libary, the power +# By default for the turbines in the turbine_library, the power # thrust model is set to "cosine-loss" which adjusts # power and thrust according to cos^cosine_loss_exponent(yaw | tilt) # where the default exponent is 1.88. For other @@ -105,7 +105,7 @@ fmodel.set(disable_turbines=disable_turbines) # Derate the front turbine for the first two findex -RATED_POWER = 5e6 # 5MW +RATED_POWER = 5e6 # 5MW (Anything above true rated power will still result in rated power) power_setpoints = np.array( [[RATED_POWER * 0.3, RATED_POWER], [RATED_POWER * 0.3, RATED_POWER], [RATED_POWER, RATED_POWER]] ) From cc983796a8cea5f287f7e56b6d1a83aa67d9dad1 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 12:13:18 -0600 Subject: [PATCH 028/120] formatting --- examples/005_getting_power.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/005_getting_power.py b/examples/005_getting_power.py index 8593d6f7e..ad0683551 100644 --- a/examples/005_getting_power.py +++ b/examples/005_getting_power.py @@ -41,7 +41,7 @@ # Get the turbine powers turbine_powers = fmodel.get_turbine_powers() -# Turbines powers will have shape (n_findex, n_tindex) where n_findex is the number of unique +# Turbines powers will have shape (n_findex, n_turbines) where n_findex is the number of unique # wind conditions and n_tindex is the number of turbines in the farm print(f"Turbine power has shape {turbine_powers.shape}") From 0c555b5ac9bc42124966b414b3659c0e5ee7117d Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 12:13:29 -0600 Subject: [PATCH 029/120] Move to subfolder --- .../26_empirical_gauss_velocity_deficit_parameters.py | 0 .../27_empirical_gauss_deflection_parameters.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ => examples_emgauss}/26_empirical_gauss_velocity_deficit_parameters.py (100%) rename examples/{ => examples_emgauss}/27_empirical_gauss_deflection_parameters.py (100%) diff --git a/examples/26_empirical_gauss_velocity_deficit_parameters.py b/examples/examples_emgauss/26_empirical_gauss_velocity_deficit_parameters.py similarity index 100% rename from examples/26_empirical_gauss_velocity_deficit_parameters.py rename to examples/examples_emgauss/26_empirical_gauss_velocity_deficit_parameters.py diff --git a/examples/27_empirical_gauss_deflection_parameters.py b/examples/examples_emgauss/27_empirical_gauss_deflection_parameters.py similarity index 100% rename from examples/27_empirical_gauss_deflection_parameters.py rename to examples/examples_emgauss/27_empirical_gauss_deflection_parameters.py From 9f3189990f6cc28fe5ad17898b9ddb6eb41475e4 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 15 Mar 2024 12:52:21 -0600 Subject: [PATCH 030/120] move files --- examples/{ => examples_layout_optimization}/15_optimize_layout.py | 0 .../16c_optimize_layout_with_heterogeneity.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ => examples_layout_optimization}/15_optimize_layout.py (100%) rename examples/{ => examples_layout_optimization}/16c_optimize_layout_with_heterogeneity.py (100%) diff --git a/examples/15_optimize_layout.py b/examples/examples_layout_optimization/15_optimize_layout.py similarity index 100% rename from examples/15_optimize_layout.py rename to examples/examples_layout_optimization/15_optimize_layout.py diff --git a/examples/16c_optimize_layout_with_heterogeneity.py b/examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py similarity index 100% rename from examples/16c_optimize_layout_with_heterogeneity.py rename to examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py From ad58dcb8693d73db60a3c7facd09d9175f02deaf Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 16 Mar 2024 21:27:59 -0600 Subject: [PATCH 031/120] Rename folder --- .../40_test_derating.py | 0 .../41_test_disable_turbines.py | 0 .../setting_yaw_and_dertating.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename examples/{examples_control_settings => examples_control_types}/40_test_derating.py (100%) rename examples/{examples_control_settings => examples_control_types}/41_test_disable_turbines.py (100%) rename examples/{examples_control_settings => examples_control_types}/setting_yaw_and_dertating.py (100%) diff --git a/examples/examples_control_settings/40_test_derating.py b/examples/examples_control_types/40_test_derating.py similarity index 100% rename from examples/examples_control_settings/40_test_derating.py rename to examples/examples_control_types/40_test_derating.py diff --git a/examples/examples_control_settings/41_test_disable_turbines.py b/examples/examples_control_types/41_test_disable_turbines.py similarity index 100% rename from examples/examples_control_settings/41_test_disable_turbines.py rename to examples/examples_control_types/41_test_disable_turbines.py diff --git a/examples/examples_control_settings/setting_yaw_and_dertating.py b/examples/examples_control_types/setting_yaw_and_dertating.py similarity index 100% rename from examples/examples_control_settings/setting_yaw_and_dertating.py rename to examples/examples_control_types/setting_yaw_and_dertating.py From a3b9c2d0d634dda29ecfeb9b2eef955248a804cd Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 16 Mar 2024 21:36:12 -0600 Subject: [PATCH 032/120] Update wind data examples --- ...d_data.py => 001_wind_data_comparisons.py} | 25 ++++++++++++------- .../{36_generate_ti.py => 002_generate_ti.py} | 15 ++++++----- 2 files changed, 23 insertions(+), 17 deletions(-) rename examples/examples_wind_data/{34_wind_data.py => 001_wind_data_comparisons.py} (83%) rename examples/examples_wind_data/{36_generate_ti.py => 002_generate_ti.py} (97%) diff --git a/examples/examples_wind_data/34_wind_data.py b/examples/examples_wind_data/001_wind_data_comparisons.py similarity index 83% rename from examples/examples_wind_data/34_wind_data.py rename to examples/examples_wind_data/001_wind_data_comparisons.py index 3a4d56fe5..913265868 100644 --- a/examples/examples_wind_data/34_wind_data.py +++ b/examples/examples_wind_data/001_wind_data_comparisons.py @@ -1,3 +1,18 @@ +"""Example: Wind Data Comparisons + +In this example, a random time series of wind speeds, wind directions + and turbulence intensities is generated. +This time series is then used to instantiate a TimeSeries object. + The TimeSeries object is then used to +instantiate a WindRose object and WindTIRose object based on the same data. + The three objects are then each used +to drive a FLORIS model of a simple two-turbine wind farm. The AEP output is + then compared and printed to the console. + +""" + + + import matplotlib.pyplot as plt import numpy as np @@ -9,14 +24,6 @@ from floris.utilities import wrap_360 -""" -This example is meant to be temporary and may be updated by a later pull request. Before we -release v4, we intend to propagate the TimeSeries and WindRose objects through the other relevant -examples, and change this example to demonstrate more advanced (as yet, not implemented) -functionality of the WindData objects (such as electricity pricing etc). -""" - - # Generate a random time series of wind speeds, wind directions and turbulence intensities N = 500 wd_array = wrap_360(270 * np.ones(N) + np.random.randn(N) * 20) @@ -59,7 +66,7 @@ plt.tight_layout() # Now set up a FLORIS model and initialize it using the time series and wind rose -fmodel = FlorisModel("inputs/gch.yaml") +fmodel = FlorisModel("../inputs/gch.yaml") fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0]) fmodel_time_series = fmodel.copy() diff --git a/examples/examples_wind_data/36_generate_ti.py b/examples/examples_wind_data/002_generate_ti.py similarity index 97% rename from examples/examples_wind_data/36_generate_ti.py rename to examples/examples_wind_data/002_generate_ti.py index 317bc8dbe..55bf09e4d 100644 --- a/examples/examples_wind_data/36_generate_ti.py +++ b/examples/examples_wind_data/002_generate_ti.py @@ -1,19 +1,18 @@ +"""Example: Generate TI + +Demonstrate usage of TI generating and plotting functionality in the WindRose +and TimeSeries classes + +""" + import matplotlib.pyplot as plt import numpy as np from floris import ( - FlorisModel, TimeSeries, WindRose, ) -from floris.utilities import wrap_360 - - -""" -Demonstrate usage of TI generating and plotting functionality in the WindRose -and TimeSeries classes -""" # Generate a random time series of wind speeds, wind directions and turbulence intensities From 77ba563251e5ebc8a711cf0f46d1f0a4410aa2a4 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 16 Mar 2024 22:17:45 -0600 Subject: [PATCH 033/120] add het examples --- .../001_heterogeneous_inflow_single.py | 79 +++++++++++ .../002_heterogeneous_inflow_multi.py | 123 ++++++++++++++++++ .../16_heterogeneous_inflow.py | 0 .../16b_heterogeneity_multiple_ws_wd.py | 0 4 files changed, 202 insertions(+) create mode 100644 examples/examples_heterogeneous/001_heterogeneous_inflow_single.py create mode 100644 examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py rename examples/{ => examples_heterogeneous}/16_heterogeneous_inflow.py (100%) rename examples/{ => examples_heterogeneous}/16b_heterogeneity_multiple_ws_wd.py (100%) diff --git a/examples/examples_heterogeneous/001_heterogeneous_inflow_single.py b/examples/examples_heterogeneous/001_heterogeneous_inflow_single.py new file mode 100644 index 000000000..f53ae1ec7 --- /dev/null +++ b/examples/examples_heterogeneous/001_heterogeneous_inflow_single.py @@ -0,0 +1,79 @@ +"""Example: Hetereogeneous Inflow for single case + +This example illustrates how to set up a heterogeneous inflow condition in FLORIS. It: + + 1) Initializes FLORIS + 2) Changes the wind farm layout + 3) Changes the incoming wind speed, wind direction and turbulence intensity + to a single condition + 4) Sets up a heterogeneous inflow condition for that single condition + 5) Runs the FLORIS simulation + 6) Gets the power output of the turbines + 7) Visualizes the horizontal plane at hub height + +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel, TimeSeries +from floris.flow_visualization import visualize_cut_plane +from floris.layout_visualization import plot_turbine_labels + + +# Initialize FlorisModel +fmodel = FlorisModel("../inputs/gch.yaml") + +# Change the layout to a 4 turbine layout in a box +fmodel.set(layout_x=[0, 0, 500.0, 500.0], layout_y=[0, 500.0, 0, 500.0]) + +# Set FLORIS to run for a single condition +fmodel.set(wind_speeds=[8.0], wind_directions=[270.0], turbulence_intensities=[0.06]) + +# Define the speed-ups of the heterogeneous inflow, and their locations. +# Note that heterogeneity is only applied within the bounds of the points defined in the +# heterogenous_inflow_config dictionary. In this case, set the inflow to be 1.25x the ambient +# wind speed for the upper turbines at y = 500m. +speed_ups = [[1.0, 1.25, 1.0, 1.25]] # Note speed-ups has dimensions of n_findex X n_points +x_locs = [-500.0, -500.0, 1000.0, 1000.0] +y_locs = [-500.0, 1000.0, -500.0, 1000.0] + +# Create the configuration dictionary to be used for the heterogeneous inflow. +heterogenous_inflow_config = { + "speed_multipliers": speed_ups, + "x": x_locs, + "y": y_locs, +} + +# Set the heterogeneous inflow configuration +fmodel.set(heterogenous_inflow_config=heterogenous_inflow_config) + +# Run the FLORIS simulation +fmodel.run() + +# Get the power output of the turbines +turbine_powers = fmodel.get_turbine_powers() / 1000.0 + +# Print the turbine powers +print(f"Turbine 0 power = {turbine_powers[0, 0]:.1f} kW") +print(f"Turbine 1 power = {turbine_powers[0, 1]:.1f} kW") +print(f"Turbine 2 power = {turbine_powers[0, 2]:.1f} kW") +print(f"Turbine 3 power = {turbine_powers[0, 3]:.1f} kW") + +# Extract the horizontal plane at hub height +horizontal_plane = fmodel.calculate_horizontal_plane( + x_resolution=200, y_resolution=100, height=90.0 +) + +# Plot the horizontal plane +fig, ax = plt.subplots() +visualize_cut_plane( + horizontal_plane, + ax=ax, + title="Horizontal plane at hub height", + color_bar=True, + label_contours=True, +) +plot_turbine_labels(fmodel, ax) + +plt.show() diff --git a/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py b/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py new file mode 100644 index 000000000..c4d025c43 --- /dev/null +++ b/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py @@ -0,0 +1,123 @@ +"""Example: Heterogeneous Inflow for multiple conditions + +When multiple cases are considered, the heterogeneous inflow conditions can be defined in two ways: + + 1. Passing heterogenous_inflow_config to the set method, with P points, + and speedups of size n_findex X P + 2. Assigning heterogenous_inflow_config_by_wd to the wind_data object + used to drive FLORIS. This object includes + n_wd wind_directions, and speedups is of size n_wd X P. When applied + to set, the heterogenous_inflow_config + is automatically generated by using the nearest wind direction + defined in heterogenous_inflow_config_by_wd + for each findex. + +This example: + + 1) Implements heterogeneous inflow for a 4 turbine layout using both of the above methods + 2) Compares the results of the two methods and shows that they are equivalent + +""" + + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel, TimeSeries + + +# Initialize FlorisModel +fmodel = FlorisModel("../inputs/gch.yaml") + +# Change the layout to a 4 turbine layout in a box +fmodel.set(layout_x=[0, 0, 500.0, 500.0], layout_y=[0, 500.0, 0, 500.0]) + +# Define a TimeSeries object with 4 wind directions and constant wind speed +# and turbulence intensity + +time_series = TimeSeries( + wind_directions=np.array([269.0, 270.0, 271.0, 282.0]), + wind_speeds=8.0, + turbulence_intensities=0.06, +) + +# Apply the time series to the FlorisModel +fmodel.set(wind_data=time_series) + +# Define the x_locs to be used in the heterogeneous inflow configuration that form +# a box around the turbines +x_locs = [-500.0, -500.0, 1000.0, 1000.0] +y_locs = [-500.0, 1000.0, -500.0, 1000.0] + +# Assume the speed-ups are defined such that they are the same 265-275 degrees and 275-285 degrees + +# If defining heterogenous_inflow_config directly, then the speedups are of size n_findex X P +# where the first 3 rows are identical, and the last row is different +speed_ups = [ + [1.0, 1.25, 1.0, 1.25], + [1.0, 1.25, 1.0, 1.25], + [1.0, 1.25, 1.0, 1.25], + [1.0, 1.35, 1.0, 1.35], +] + +heterogenous_inflow_config = { + "speed_multipliers": speed_ups, + "x": x_locs, + "y": y_locs, +} + +# Set the heterogeneous inflow configuration +fmodel.set(heterogenous_inflow_config=heterogenous_inflow_config) + +# Run the FLORIS simulation +fmodel.run() + +# Get the power output of the turbines +turbine_powers = fmodel.get_turbine_powers() / 1000.0 + +# Now repeat using the wind_data object and heterogenous_inflow_config_by_wd +# First, create the speedups for the two wind directions +speed_ups = [[1.0, 1.25, 1.0, 1.25], [1.0, 1.35, 1.0, 1.35]] + +# Create the heterogenous_inflow_config_by_wd dictionary +heterogenous_inflow_config_by_wd = { + "speed_multipliers": speed_ups, + "x": x_locs, + "y": y_locs, + "wind_directions": [270.0, 280.0], +} + +# Now create a new TimeSeries object including the heterogenous_inflow_config_by_wd +time_series = TimeSeries( + wind_directions=np.array([269.0, 270.0, 271.0, 282.0]), + wind_speeds=8.0, + turbulence_intensities=0.06, + heterogenous_inflow_config_by_wd=heterogenous_inflow_config_by_wd, +) + +# Apply the time series to the FlorisModel +fmodel.set(wind_data=time_series) + +# Run the FLORIS simulation +fmodel.run() + +# Get the power output of the turbines +turbine_powers_by_wd = fmodel.get_turbine_powers() / 1000.0 + +# Plot the results +wind_directions = fmodel.core.flow_field.wind_directions +fig, axarr = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(10, 10)) +axarr = axarr.flatten() + +for tindex in range(4): + ax = axarr[tindex] + ax.plot(wind_directions, turbine_powers[:, tindex], "ks-", label="Heterogeneous Inflow") + ax.plot( + wind_directions, turbine_powers_by_wd[:, tindex], ".--", label="Heterogeneous Inflow by WD" + ) + ax.set_title(f"Turbine {tindex}") + ax.set_xlabel("Wind Direction (deg)") + ax.set_ylabel("Power (kW)") + ax.legend() + +plt.show() diff --git a/examples/16_heterogeneous_inflow.py b/examples/examples_heterogeneous/16_heterogeneous_inflow.py similarity index 100% rename from examples/16_heterogeneous_inflow.py rename to examples/examples_heterogeneous/16_heterogeneous_inflow.py diff --git a/examples/16b_heterogeneity_multiple_ws_wd.py b/examples/examples_heterogeneous/16b_heterogeneity_multiple_ws_wd.py similarity index 100% rename from examples/16b_heterogeneity_multiple_ws_wd.py rename to examples/examples_heterogeneous/16b_heterogeneity_multiple_ws_wd.py From 2f149f13046efdea071dd33eec8c9f7e5ff5d8ed Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 08:55:46 -0600 Subject: [PATCH 034/120] Add example 3 --- ...flow.py => 003_heterogeneous_2d_and_3d.py} | 110 +++++++----------- .../16b_heterogeneity_multiple_ws_wd.py | 76 ------------ 2 files changed, 44 insertions(+), 142 deletions(-) rename examples/examples_heterogeneous/{16_heterogeneous_inflow.py => 003_heterogeneous_2d_and_3d.py} (65%) delete mode 100644 examples/examples_heterogeneous/16b_heterogeneity_multiple_ws_wd.py diff --git a/examples/examples_heterogeneous/16_heterogeneous_inflow.py b/examples/examples_heterogeneous/003_heterogeneous_2d_and_3d.py similarity index 65% rename from examples/examples_heterogeneous/16_heterogeneous_inflow.py rename to examples/examples_heterogeneous/003_heterogeneous_2d_and_3d.py index 26451ffa5..be36a6e60 100644 --- a/examples/examples_heterogeneous/16_heterogeneous_inflow.py +++ b/examples/examples_heterogeneous/003_heterogeneous_2d_and_3d.py @@ -1,13 +1,8 @@ +"""Example: Heterogeneous Inflow in 2D and 3D -import matplotlib.pyplot as plt - -from floris import FlorisModel -from floris.flow_visualization import visualize_cut_plane - - -""" This example showcases the heterogeneous inflow capabilities of FLORIS. -Heterogeneous flow can be defined in either 2- or 3-dimensions. +Heterogeneous flow can be defined in either 2- or 3-dimensions for a single +condition. For the 2-dimensional case, it can be seen that the freestream velocity only varies in the x direction. For the 3-dimensional case, it can be @@ -18,10 +13,21 @@ For each case, we are plotting three slices of the resulting flow field: 1. Horizontal slice parallel to the ground and located at the hub height 2. Vertical slice parallel with the direction of the wind -3. Veritical slice parallel to to the turbine disc plane +3. Vertical slice parallel to to the turbine disc plane + +Since the intention is for plotting, only a single condition is run and in +this case the heterogenous_inflow_config is more convenient to use than +heterogenous_inflow_config_by_wd. However, the latter is more convenient +when running multiple conditions. """ +import matplotlib.pyplot as plt + +from floris import FlorisModel +from floris.flow_visualization import visualize_cut_plane + + # Initialize FLORIS with the given input file via FlorisModel. # Note that the heterogeneous flow is defined in the input file. The heterogenous_inflow_config # dictionary is defined as below. The speed ups are multipliers of the ambient wind speed, @@ -34,7 +40,7 @@ # } -fmodel_2d = FlorisModel("inputs/gch_heterogeneous_inflow.yaml") +fmodel_2d = FlorisModel("../inputs/gch_heterogeneous_inflow.yaml") # Set shear to 0.0 to highlight the heterogeneous inflow fmodel_2d.set(wind_shear=0.0) @@ -42,47 +48,35 @@ # Using the FlorisModel functions for generating plots, run FLORIS # and extract 2D planes of data. horizontal_plane_2d = fmodel_2d.calculate_horizontal_plane( - x_resolution=200, - y_resolution=100, - height=90.0 + x_resolution=200, y_resolution=100, height=90.0 ) y_plane_2d = fmodel_2d.calculate_y_plane(x_resolution=200, z_resolution=100, crossstream_dist=0.0) cross_plane_2d = fmodel_2d.calculate_cross_plane( - y_resolution=100, - z_resolution=100, - downstream_dist=500.0 + y_resolution=100, z_resolution=100, downstream_dist=500.0 ) # Create the plots fig, ax_list = plt.subplots(3, 1, figsize=(10, 8)) ax_list = ax_list.flatten() visualize_cut_plane( - horizontal_plane_2d, - ax=ax_list[0], - title="Horizontal", - color_bar=True, - label_contours=True + horizontal_plane_2d, ax=ax_list[0], title="Horizontal", color_bar=True, label_contours=True ) -ax_list[0].set_xlabel('x') -ax_list[0].set_ylabel('y') +ax_list[0].set_xlabel("x") +ax_list[0].set_ylabel("y") visualize_cut_plane( - y_plane_2d, - ax=ax_list[1], - title="Streamwise profile", - color_bar=True, - label_contours=True + y_plane_2d, ax=ax_list[1], title="Streamwise profile", color_bar=True, label_contours=True ) -ax_list[1].set_xlabel('x') -ax_list[1].set_ylabel('z') +ax_list[1].set_xlabel("x") +ax_list[1].set_ylabel("z") visualize_cut_plane( cross_plane_2d, ax=ax_list[2], title="Spanwise profile at 500m downstream", color_bar=True, - label_contours=True + label_contours=True, ) -ax_list[2].set_xlabel('y') -ax_list[2].set_ylabel('z') +ax_list[2].set_xlabel("y") +ax_list[2].set_ylabel("z") # Define the speed ups of the heterogeneous inflow, and their locations. @@ -95,16 +89,16 @@ # Create the configuration dictionary to be used for the heterogeneous inflow. heterogenous_inflow_config = { - 'speed_multipliers': speed_multipliers, - 'x': x_locs, - 'y': y_locs, - 'z': z_locs, + "speed_multipliers": speed_multipliers, + "x": x_locs, + "y": y_locs, + "z": z_locs, } # Initialize FLORIS with the given input file. # Note that we initialize FLORIS with a homogenous flow input file, but # then configure the heterogeneous inflow via the reinitialize method. -fmodel_3d = FlorisModel("inputs/gch.yaml") +fmodel_3d = FlorisModel("../inputs/gch.yaml") fmodel_3d.set(heterogenous_inflow_config=heterogenous_inflow_config) # Set shear to 0.0 to highlight the heterogeneous inflow @@ -113,50 +107,34 @@ # Using the FlorisModel functions for generating plots, run FLORIS # and extract 2D planes of data. horizontal_plane_3d = fmodel_3d.calculate_horizontal_plane( - x_resolution=200, - y_resolution=100, - height=90.0 -) -y_plane_3d = fmodel_3d.calculate_y_plane( - x_resolution=200, - z_resolution=100, - crossstream_dist=0.0 + x_resolution=200, y_resolution=100, height=90.0 ) +y_plane_3d = fmodel_3d.calculate_y_plane(x_resolution=200, z_resolution=100, crossstream_dist=0.0) cross_plane_3d = fmodel_3d.calculate_cross_plane( - y_resolution=100, - z_resolution=100, - downstream_dist=500.0 + y_resolution=100, z_resolution=100, downstream_dist=500.0 ) # Create the plots fig, ax_list = plt.subplots(3, 1, figsize=(10, 8)) ax_list = ax_list.flatten() visualize_cut_plane( - horizontal_plane_3d, - ax=ax_list[0], - title="Horizontal", - color_bar=True, - label_contours=True + horizontal_plane_3d, ax=ax_list[0], title="Horizontal", color_bar=True, label_contours=True ) -ax_list[0].set_xlabel('x') -ax_list[0].set_ylabel('y') +ax_list[0].set_xlabel("x") +ax_list[0].set_ylabel("y") visualize_cut_plane( - y_plane_3d, - ax=ax_list[1], - title="Streamwise profile", - color_bar=True, - label_contours=True + y_plane_3d, ax=ax_list[1], title="Streamwise profile", color_bar=True, label_contours=True ) -ax_list[1].set_xlabel('x') -ax_list[1].set_ylabel('z') +ax_list[1].set_xlabel("x") +ax_list[1].set_ylabel("z") visualize_cut_plane( cross_plane_3d, ax=ax_list[2], title="Spanwise profile at 500m downstream", color_bar=True, - label_contours=True + label_contours=True, ) -ax_list[2].set_xlabel('y') -ax_list[2].set_ylabel('z') +ax_list[2].set_xlabel("y") +ax_list[2].set_ylabel("z") plt.show() diff --git a/examples/examples_heterogeneous/16b_heterogeneity_multiple_ws_wd.py b/examples/examples_heterogeneous/16b_heterogeneity_multiple_ws_wd.py deleted file mode 100644 index c183c4a26..000000000 --- a/examples/examples_heterogeneous/16b_heterogeneity_multiple_ws_wd.py +++ /dev/null @@ -1,76 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel -from floris.flow_visualization import visualize_cut_plane - - -""" -This example showcases the heterogeneous inflow capabilities of FLORIS -when multiple wind speeds and direction are considered. -""" - - -# Define the speed ups of the heterogeneous inflow, and their locations. -# For the 2-dimensional case, this requires x and y locations. -# The speed ups are multipliers of the ambient wind speed. -speed_ups = [[2.0, 1.0, 2.0, 1.0]] -x_locs = [-300.0, -300.0, 2600.0, 2600.0] -y_locs = [ -300.0, 300.0, -300.0, 300.0] - -# Initialize FLORIS with the given input. -# Note the heterogeneous inflow is defined in the input file. -fmodel = FlorisModel("inputs/gch_heterogeneous_inflow.yaml") - -# Set shear to 0.0 to highlight the heterogeneous inflow -fmodel.set( - wind_shear=0.0, - wind_speeds=[8.0], - wind_directions=[270.], - turbulence_intensities=[0.06], - layout_x=[0, 0], - layout_y=[-299., 299.], -) -fmodel.run() -turbine_powers = fmodel.get_turbine_powers().flatten() / 1000. - -# Show the initial results -print('------------------------------------------') -print('Given the speedups and turbine locations, ') -print(' the first turbine has an inflow wind speed') -print(' twice that of the second') -print(' Wind Speed = 8., Wind Direction = 270.') -print(f'T0: {turbine_powers[0]:.1f} kW') -print(f'T1: {turbine_powers[1]:.1f} kW') -print() - -# If the number of conditions in the calculation changes, a new heterogeneous map -# must be provided. -speed_multipliers = [[2.0, 1.0, 2.0, 1.0], [2.0, 1.0, 2.0, 1.0]] # Expand to two wind conditions -heterogenous_inflow_config = { - 'speed_multipliers': speed_multipliers, - 'x': x_locs, - 'y': y_locs, -} -fmodel.set( - wind_directions=[270.0, 275.0], - wind_speeds=[8.0, 8.0], - turbulence_intensities=[0.06, 0.06], - heterogenous_inflow_config=heterogenous_inflow_config -) -fmodel.run() -turbine_powers = np.round(fmodel.get_turbine_powers() / 1000.) -print('With wind directions now set to 270 and 275 deg') -print(f'T0: {turbine_powers[:, 0].flatten()} kW') -print(f'T1: {turbine_powers[:, 1].flatten()} kW') - -# # Uncomment if want to see example of error output -# # Note if we change wind directions to 3 without a matching change to het map we get an error -# print() -# print() -# print('~~ Now forcing an error by not matching wd and het_map') - -# fmodel.set(wind_directions=[270, 275, 280], wind_speeds=3*[8.0]) -# fmodel.run() -# turbine_powers = np.round(fmodel.get_turbine_powers() / 1000.) From e443f30b1f94d34b52d22602dcb0d37560a8dea1 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 08:56:07 -0600 Subject: [PATCH 035/120] Add place holder --- examples/examples_heterogeneous/xx_using_het_to_approx_farm.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 examples/examples_heterogeneous/xx_using_het_to_approx_farm.py diff --git a/examples/examples_heterogeneous/xx_using_het_to_approx_farm.py b/examples/examples_heterogeneous/xx_using_het_to_approx_farm.py new file mode 100644 index 000000000..f9bf1abc1 --- /dev/null +++ b/examples/examples_heterogeneous/xx_using_het_to_approx_farm.py @@ -0,0 +1 @@ +#TODO From 51abc6d4b59bfae9953512209d45e2b3f0e5b1e0 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 08:59:30 -0600 Subject: [PATCH 036/120] sort into subfolders --- .../25_tilt_driven_vertical_wake_deflection.py | 0 examples/{ => examples_floating}/24_floating_turbine_models.py | 0 examples/{ => examples_get_flow}/22_get_wind_speed_at_turbines.py | 0 .../{ => examples_get_flow}/28_extract_wind_speed_at_points.py | 0 .../{ => examples_get_flow}/32_plot_velocity_deficit_profiles.py | 0 examples/{ => examples_multidim}/30_multi_dimensional_cp_ct.py | 0 .../{ => examples_multidim}/31_multi_dimensional_cp_ct_2Hs.py | 0 examples/{ => examples_turbine}/17_multiple_turbine_types.py | 0 examples/{ => examples_turbine}/18_check_turbine.py | 0 examples/{ => examples_turbine}/33_specify_turbine_power_curve.py | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ => examples_emgauss}/25_tilt_driven_vertical_wake_deflection.py (100%) rename examples/{ => examples_floating}/24_floating_turbine_models.py (100%) rename examples/{ => examples_get_flow}/22_get_wind_speed_at_turbines.py (100%) rename examples/{ => examples_get_flow}/28_extract_wind_speed_at_points.py (100%) rename examples/{ => examples_get_flow}/32_plot_velocity_deficit_profiles.py (100%) rename examples/{ => examples_multidim}/30_multi_dimensional_cp_ct.py (100%) rename examples/{ => examples_multidim}/31_multi_dimensional_cp_ct_2Hs.py (100%) rename examples/{ => examples_turbine}/17_multiple_turbine_types.py (100%) rename examples/{ => examples_turbine}/18_check_turbine.py (100%) rename examples/{ => examples_turbine}/33_specify_turbine_power_curve.py (100%) diff --git a/examples/25_tilt_driven_vertical_wake_deflection.py b/examples/examples_emgauss/25_tilt_driven_vertical_wake_deflection.py similarity index 100% rename from examples/25_tilt_driven_vertical_wake_deflection.py rename to examples/examples_emgauss/25_tilt_driven_vertical_wake_deflection.py diff --git a/examples/24_floating_turbine_models.py b/examples/examples_floating/24_floating_turbine_models.py similarity index 100% rename from examples/24_floating_turbine_models.py rename to examples/examples_floating/24_floating_turbine_models.py diff --git a/examples/22_get_wind_speed_at_turbines.py b/examples/examples_get_flow/22_get_wind_speed_at_turbines.py similarity index 100% rename from examples/22_get_wind_speed_at_turbines.py rename to examples/examples_get_flow/22_get_wind_speed_at_turbines.py diff --git a/examples/28_extract_wind_speed_at_points.py b/examples/examples_get_flow/28_extract_wind_speed_at_points.py similarity index 100% rename from examples/28_extract_wind_speed_at_points.py rename to examples/examples_get_flow/28_extract_wind_speed_at_points.py diff --git a/examples/32_plot_velocity_deficit_profiles.py b/examples/examples_get_flow/32_plot_velocity_deficit_profiles.py similarity index 100% rename from examples/32_plot_velocity_deficit_profiles.py rename to examples/examples_get_flow/32_plot_velocity_deficit_profiles.py diff --git a/examples/30_multi_dimensional_cp_ct.py b/examples/examples_multidim/30_multi_dimensional_cp_ct.py similarity index 100% rename from examples/30_multi_dimensional_cp_ct.py rename to examples/examples_multidim/30_multi_dimensional_cp_ct.py diff --git a/examples/31_multi_dimensional_cp_ct_2Hs.py b/examples/examples_multidim/31_multi_dimensional_cp_ct_2Hs.py similarity index 100% rename from examples/31_multi_dimensional_cp_ct_2Hs.py rename to examples/examples_multidim/31_multi_dimensional_cp_ct_2Hs.py diff --git a/examples/17_multiple_turbine_types.py b/examples/examples_turbine/17_multiple_turbine_types.py similarity index 100% rename from examples/17_multiple_turbine_types.py rename to examples/examples_turbine/17_multiple_turbine_types.py diff --git a/examples/18_check_turbine.py b/examples/examples_turbine/18_check_turbine.py similarity index 100% rename from examples/18_check_turbine.py rename to examples/examples_turbine/18_check_turbine.py diff --git a/examples/33_specify_turbine_power_curve.py b/examples/examples_turbine/33_specify_turbine_power_curve.py similarity index 100% rename from examples/33_specify_turbine_power_curve.py rename to examples/examples_turbine/33_specify_turbine_power_curve.py From 00b4a31a0cf221dd58b13bf3c9ebbb7bf363eabb Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 08:59:39 -0600 Subject: [PATCH 037/120] sort into subfolder --- .../{ => examples_floating}/29_floating_vs_fixedbottom_farm.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{ => examples_floating}/29_floating_vs_fixedbottom_farm.py (100%) diff --git a/examples/29_floating_vs_fixedbottom_farm.py b/examples/examples_floating/29_floating_vs_fixedbottom_farm.py similarity index 100% rename from examples/29_floating_vs_fixedbottom_farm.py rename to examples/examples_floating/29_floating_vs_fixedbottom_farm.py From aa3b4812b13a220861934be9749821f332481c69 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 09:19:25 -0600 Subject: [PATCH 038/120] Add details --- examples/008_uncertain_models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/008_uncertain_models.py b/examples/008_uncertain_models.py index f2ba3ed58..9d151d687 100644 --- a/examples/008_uncertain_models.py +++ b/examples/008_uncertain_models.py @@ -7,6 +7,9 @@ Other use cases of UncertainFlorisModel are, e.g., comparing FLORIS to historical SCADA data and robust optimization. +For more details on using uncertain models, see further examples within the +examples_uncertain directory. + """ import matplotlib.pyplot as plt From 5a0897778235cb2d6e068d28c2b1be2f6b75db22 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 09:19:36 -0600 Subject: [PATCH 039/120] Add example 001 --- .../001_uncertain_model_params.py | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 examples/examples_uncertain/001_uncertain_model_params.py diff --git a/examples/examples_uncertain/001_uncertain_model_params.py b/examples/examples_uncertain/001_uncertain_model_params.py new file mode 100644 index 000000000..b03d91500 --- /dev/null +++ b/examples/examples_uncertain/001_uncertain_model_params.py @@ -0,0 +1,170 @@ +"""Example 8: Uncertain Model Parameters + +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import ( + FlorisModel, + TimeSeries, + UncertainFlorisModel, +) + + +# Instantiate FlorisModel for comparison +fmodel = FlorisModel("../inputs/gch.yaml") # GCH model + +################################################ +# Resolution parameters +################################################ + +# The resolution parameters are used to define the precision of the wind direction, +# wind speed, and turbulence intensity and control parameters. All the inputs +# passed into the UncertainFlorisModel class are rounded to this resolution. Then +# following expansion, non-unique cases are removed. Here we apply the default +# resolution parameters. +wd_resolution = 1.0 # Degree +ws_resolution = 1.0 # m/s +ti_resolution = 0.01 # Decimal fraction +yaw_resolution = 1.0 # Degree +power_setpoint_resolution = 100.0 # kW + +################################################ +# wd_sample_points +################################################ + +# The wind direction sample points (wd_sample_points) parameter is used to define +# the number of points to sample the wind direction uncertainty. For example, +# if the the single condition to analyze is 270 degrees, and the wd_sample_points +# is [-2, -1, 0, 1 ,2], then the cases to be run and weighted +# will be 268, 269, 270, 271, 272. If not supplied default is +# [-2 * wd_std, -1 * wd_std, 0, wd_std, 2 * wd_std] +wd_sample_points = [-6, -3, 0, 3, 6] + + +################################################ +# WT_STD +################################################ + +# The wind direction standard deviation (wd_std) parameter is the primary input +# to the UncertainFlorisModel class. This parameter is used to weight the points +# following expansion by the wd_sample_points. The smaller the value, the closer +# the weighting will be to the nominal case. +wd_std = 3 # Default is 3 degrees + +################################################ +# Verbosity +################################################ + +# Setting verbose = True will print out the sizes of teh cases run +verbose = True + +################################################ +# Define the UncertainFlorisModel +################################################ +print('*** Instantiating UncertainFlorisModel ***') +ufmodel = UncertainFlorisModel("../inputs/gch.yaml", + wd_resolution=wd_resolution, + ws_resolution=ws_resolution, + ti_resolution=ti_resolution, + yaw_resolution=yaw_resolution, + power_setpoint_resolution=power_setpoint_resolution, + wd_std=wd_std, + wd_sample_points=wd_sample_points, + verbose=verbose) + + +################################################ +# Run the models +################################################ + +# Define an inflow where wind direction is swept while +# wind speed and turbulence intensity are held constant +wind_directions = np.arange(240.0, 300.0, 1.0) +time_series = TimeSeries( + wind_directions=wind_directions, + wind_speeds=8.0, + turbulence_intensities=0.06, +) + +# Define a two turbine farm and apply the inflow +D = 126.0 +layout_x = np.array([0, D * 6]) +layout_y = [0, 0] + +fmodel.set( + layout_x=layout_x, + layout_y=layout_y, + wind_data=time_series, +) +print('*** Setting UncertainFlorisModel to 60 Wind Direction Inflow ***') +ufmodel.set( + layout_x=layout_x, + layout_y=layout_y, + wind_data=time_series, +) + +# Run both models +fmodel.run() +ufmodel.run() + + +# Collect the nominal and uncertain farm power +turbine_powers_nom = fmodel.get_turbine_powers() / 1e3 +turbine_powers_unc = ufmodel.get_turbine_powers() / 1e3 + +farm_powers_nom = fmodel.get_farm_power() / 1e3 +farm_powers_unc_3 = ufmodel.get_farm_power() / 1e3 + + +# Plot results +fig, axarr = plt.subplots(1, 3, figsize=(15, 5)) +ax = axarr[0] +ax.plot(wind_directions, turbine_powers_nom[:, 0].flatten(), color="k", label="Nominal power") +ax.plot( + wind_directions, + turbine_powers_unc[:, 0].flatten(), + color="r", + label="Power with uncertainty", +) + +ax.grid(True) +ax.legend() +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") +ax.set_title("Upstream Turbine") + +ax = axarr[1] +ax.plot(wind_directions, turbine_powers_nom[:, 1].flatten(), color="k", label="Nominal power") +ax.plot( + wind_directions, + turbine_powers_unc[:, 1].flatten(), + color="r", + label="Power with uncertainty", +) + +ax.set_title("Downstream Turbine") +ax.grid(True) +ax.legend() +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") + +ax = axarr[2] +ax.plot(wind_directions, farm_powers_nom.flatten(), color="k", label="Nominal farm power") +ax.plot( + wind_directions, + farm_powers_unc_3.flatten(), + color="r", + label="Farm power with uncertainty", +) + + +ax.set_title("Farm Power") +ax.grid(True) +ax.legend() +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") + + +plt.show() From 1f01efa63f00013e9fe0739117a80778b9f489f7 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 09:19:43 -0600 Subject: [PATCH 040/120] typos --- floris/uncertain_floris_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/floris/uncertain_floris_model.py b/floris/uncertain_floris_model.py index b234af7ea..05e4e02d7 100644 --- a/floris/uncertain_floris_model.py +++ b/floris/uncertain_floris_model.py @@ -70,7 +70,7 @@ def __init__( gaussian blends, in degrees. Defaults to 1.0. ws_resolution (float, optional): The resolution of wind speed, in m/s. Defaults to 1.0. ti_resolution (float, optional): The resolution of turbulence intensity. - efaults to 0.01. + Defaults to 0.01. yaw_resolution (float, optional): The resolution of yaw angle, in degrees. Defaults to 1.0. power_setpoint_resolution (int, optional): The resolution of power setpoints, in kW. @@ -129,7 +129,7 @@ def set( """ Set the wind farm conditions in the UncertainFlorisModel. - See FlorisInterace.set() for details of the contents of kwargs. + See FlorisModel.set() for details of the contents of kwargs. Args: **kwargs: The wind farm conditions to set. From aa6fbf0aa430fc6dbd99a10f40b3db70d418d459 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 09:39:03 -0600 Subject: [PATCH 041/120] Remove reference to n_tindex --- examples/005_getting_power.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/005_getting_power.py b/examples/005_getting_power.py index ad0683551..5a109532b 100644 --- a/examples/005_getting_power.py +++ b/examples/005_getting_power.py @@ -42,7 +42,7 @@ turbine_powers = fmodel.get_turbine_powers() # Turbines powers will have shape (n_findex, n_turbines) where n_findex is the number of unique -# wind conditions and n_tindex is the number of turbines in the farm +# wind conditions and n_turbines is the number of turbines in the farm print(f"Turbine power has shape {turbine_powers.shape}") # It is also possible to get the farm power directly From b1165e775903a22b4528da756c386c56f4adcedf Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 09:47:37 -0600 Subject: [PATCH 042/120] Add example 3 --- ...tiple_ws.py => 003_opt_yaw_multiple_ws.py} | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) rename examples/examples_control_optimization/{11_opt_yaw_multiple_ws.py => 003_opt_yaw_multiple_ws.py} (82%) diff --git a/examples/examples_control_optimization/11_opt_yaw_multiple_ws.py b/examples/examples_control_optimization/003_opt_yaw_multiple_ws.py similarity index 82% rename from examples/examples_control_optimization/11_opt_yaw_multiple_ws.py rename to examples/examples_control_optimization/003_opt_yaw_multiple_ws.py index 0a7d9668a..d5d63c20e 100644 --- a/examples/examples_control_optimization/11_opt_yaw_multiple_ws.py +++ b/examples/examples_control_optimization/003_opt_yaw_multiple_ws.py @@ -1,47 +1,39 @@ -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel -from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR - - -""" +"""Example: Optimize yaw for multiple wind directions and multiple wind speeds. This example demonstrates how to perform a yaw optimization for multiple wind directions -and multiple wind speeds. +and multiple wind speeds using the WindRose object First, we initialize our Floris Interface, and then generate a 3 turbine wind farm. Next, we create the yaw optimization object `yaw_opt` and perform the optimization using the SerialRefine method. Finally, we plot the results. """ +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel, WindRose +from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR + + # Load the default example floris object -fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 +fmodel = FlorisModel("../inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 # fmodel = FlorisModel("inputs/cc.yaml") # New CumulativeCurl model -# Define arrays of ws/wd -wind_speeds_to_expand = np.arange(2.0, 18.0, 1.0) -wind_directions_to_expand = np.arange(0.0, 360.0, 3.0) - -# Create grids to make combinations of ws/wd -wind_speeds_grid, wind_directions_grid = np.meshgrid( - wind_speeds_to_expand, - wind_directions_to_expand +# Define a WindRose object with uniform TI and frequency table +wind_rose = WindRose( + wind_directions=np.arange(0.0, 360.0, 3.0), + wind_speeds=np.arange(2.0, 18.0, 1.0), + ti_table=0.06, ) -# Flatten the grids back to 1D arrays -wd_array = wind_directions_grid.flatten() -ws_array = wind_speeds_grid.flatten() -turbulence_intensities = 0.06 * np.ones_like(wd_array) + # Reinitialize as a 3-turbine farm with range of WDs and WSs D = 126.0 # Rotor diameter for the NREL 5 MW fmodel.set( layout_x=[0.0, 5 * D, 10 * D], layout_y=[0.0, 0.0, 0.0], - wind_directions=wd_array, - wind_speeds=ws_array, - turbulence_intensities=turbulence_intensities, + wind_data=wind_rose, ) # Initialize optimizer object and run optimization using the Serial-Refine method @@ -49,7 +41,7 @@ # 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, +# is typically the result of numerical imprecision 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. From 31a27c4c0fe70ece307181e66e9ef9e2a99739c0 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 13:55:29 -0600 Subject: [PATCH 043/120] Start redoing 004 --- ...ptimize_yaw.py => 004_optimize_yaw_aep.py} | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) rename examples/examples_control_optimization/{12_optimize_yaw.py => 004_optimize_yaw_aep.py} (98%) diff --git a/examples/examples_control_optimization/12_optimize_yaw.py b/examples/examples_control_optimization/004_optimize_yaw_aep.py similarity index 98% rename from examples/examples_control_optimization/12_optimize_yaw.py rename to examples/examples_control_optimization/004_optimize_yaw_aep.py index d631d5437..1360db458 100644 --- a/examples/examples_control_optimization/12_optimize_yaw.py +++ b/examples/examples_control_optimization/004_optimize_yaw_aep.py @@ -1,15 +1,6 @@ -from time import perf_counter as timerpc - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd +"""Example: Optimize yaw and compare AEP -from floris import FlorisModel -from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR - - -""" This example demonstrates how to perform a yaw optimization and evaluate the performance over a full wind rose. @@ -24,9 +15,19 @@ shown in several plots. """ +from time import perf_counter as timerpc + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +from floris import FlorisModel +from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR + + def load_floris(): # Load the default example floris object - fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 + fmodel = FlorisModel("../inputs/gch.yaml") # GCH model # fmodel = FlorisModel("inputs/cc.yaml") # New CumulativeCurl model # Specify wind farm layout and update in the floris object @@ -41,7 +42,7 @@ def load_floris(): def load_windrose(): - fn = "inputs/wind_rose.csv" + fn = "../inputs/wind_rose.csv" df = pd.read_csv(fn) df = df[(df["ws"] < 22)].reset_index(drop=True) # Reduce size df["freq_val"] = df["freq_val"] / df["freq_val"].sum() # Normalize wind rose frequencies From dcc2af1aa6b190a5a74155c235fb6890c07963e0 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 18 Mar 2024 13:55:45 -0600 Subject: [PATCH 044/120] 04 not done --- .../{004_optimize_yaw_aep.py => 04_optimize_yaw_aep.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/examples_control_optimization/{004_optimize_yaw_aep.py => 04_optimize_yaw_aep.py} (100%) diff --git a/examples/examples_control_optimization/004_optimize_yaw_aep.py b/examples/examples_control_optimization/04_optimize_yaw_aep.py similarity index 100% rename from examples/examples_control_optimization/004_optimize_yaw_aep.py rename to examples/examples_control_optimization/04_optimize_yaw_aep.py From 63b47c8db3bbd9a20f058a8278ad967acfeb5879 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 20 Mar 2024 09:52:02 -0600 Subject: [PATCH 045/120] Use set power thrust function in examples --- examples/003_wind_data_objects.py | 5 +++++ examples/004_set.py | 13 +++---------- examples/007_sweeping_variables.py | 11 ++--------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index 0c6f48d71..bc5f2a820 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -113,6 +113,11 @@ wd_edges=np.arange(0, 360, 3.0), ws_edges=np.arange(4, 20, 2.0) ) +################################################## +# Wind Rose from long CSV FILE +################################################## + +#TODO ################################################## # Setting turbulence intensity diff --git a/examples/004_set.py b/examples/004_set.py index 21598f455..f747e8734 100644 --- a/examples/004_set.py +++ b/examples/004_set.py @@ -88,17 +88,10 @@ # which provides the same cosine loss model, and # additionally methods for specifying derating levels for power and disabling turbines. +#TODO: RESET OPERATION HERE? + # Change to the mixed model turbine -# TODO: Could this process be added to the fmodel_utils? -with open( - str( - fmodel.core.as_dict()["farm"]["turbine_library_path"] - / (fmodel.core.as_dict()["farm"]["turbine_type"][0] + ".yaml") - ) -) as t: - turbine_type = yaml.safe_load(t) -turbine_type["power_thrust_model"] = "mixed" -fmodel.set(turbine_type=[turbine_type]) +fmodel.set_power_thrust_model("mixed") # Shut down the front turbine for the first two findex disable_turbines = np.array([[True, False], [True, False], [False, False]]) diff --git a/examples/007_sweeping_variables.py b/examples/007_sweeping_variables.py index 5abe969a1..f461b6359 100644 --- a/examples/007_sweeping_variables.py +++ b/examples/007_sweeping_variables.py @@ -125,18 +125,11 @@ ###################################################### # Since we're changing control modes, need to reset the operation +#TODO: Needed? fmodel.reset_operation() # To the de-rating need to change the power_thrust_mode to mixed or simple de-rating -with open( - str( - fmodel.core.as_dict()["farm"]["turbine_library_path"] - / (fmodel.core.as_dict()["farm"]["turbine_type"][0] + ".yaml") - ) -) as t: - turbine_type = yaml.safe_load(t) -turbine_type["power_thrust_model"] = "mixed" -fmodel.set(turbine_type=[turbine_type]) +fmodel.set_power_thrust_model("simple-derating") # Sweep the de-rating levels RATED_POWER = 5e6 # For NREL 5MW From 92eef3ed16b24f87d2b591bcda39ed948b5c60d0 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 20 Mar 2024 09:53:00 -0600 Subject: [PATCH 046/120] Remove unused imports --- examples/002_visualizations.py | 1 - examples/004_set.py | 2 -- examples/005_getting_power.py | 3 --- examples/006_get_farm_aep.py | 2 -- examples/007_sweeping_variables.py | 1 - 5 files changed, 9 deletions(-) diff --git a/examples/002_visualizations.py b/examples/002_visualizations.py index affec77d8..27603c0a6 100644 --- a/examples/002_visualizations.py +++ b/examples/002_visualizations.py @@ -6,7 +6,6 @@ import matplotlib.pyplot as plt -import numpy as np import floris.layout_visualization as layoutviz from floris import FlorisModel diff --git a/examples/004_set.py b/examples/004_set.py index f747e8734..e1ce31c1a 100644 --- a/examples/004_set.py +++ b/examples/004_set.py @@ -7,9 +7,7 @@ """ -import matplotlib.pyplot as plt import numpy as np -import yaml from floris import ( FlorisModel, diff --git a/examples/005_getting_power.py b/examples/005_getting_power.py index 5a109532b..6ea5c1fb4 100644 --- a/examples/005_getting_power.py +++ b/examples/005_getting_power.py @@ -7,13 +7,10 @@ import matplotlib.pyplot as plt import numpy as np -import yaml from floris import ( FlorisModel, TimeSeries, - WindRose, - WindTIRose, ) diff --git a/examples/006_get_farm_aep.py b/examples/006_get_farm_aep.py index b07cfccb4..a22626798 100644 --- a/examples/006_get_farm_aep.py +++ b/examples/006_get_farm_aep.py @@ -5,10 +5,8 @@ """ -import matplotlib.pyplot as plt import numpy as np import pandas as pd -from scipy.interpolate import NearestNDInterpolator from floris import ( FlorisModel, diff --git a/examples/007_sweeping_variables.py b/examples/007_sweeping_variables.py index f461b6359..f285c2d33 100644 --- a/examples/007_sweeping_variables.py +++ b/examples/007_sweeping_variables.py @@ -9,7 +9,6 @@ import matplotlib.pyplot as plt import numpy as np -import yaml from floris import ( FlorisModel, From 23c7ee01a9bfc80b2aa291c3e0e89de1b4872b34 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 20 Mar 2024 11:19:56 -0600 Subject: [PATCH 047/120] Spelling heterogeneous --- .../001_heterogeneous_inflow_single.py | 8 +- .../002_heterogeneous_inflow_multi.py | 24 +-- .../003_heterogeneous_2d_and_3d.py | 12 +- .../16c_optimize_layout_with_heterogeneity.py | 4 +- examples/inputs/gch_heterogeneous_inflow.yaml | 2 +- floris/core/farm.py | 2 +- floris/core/flow_field.py | 28 +-- floris/floris_model.py | 24 +-- floris/flow_visualization.py | 4 +- .../yaw_optimization/yaw_optimization_base.py | 4 +- .../yaw_optimization/yaw_optimizer_scipy.py | 6 +- .../yaw_optimization/yaw_optimizer_sr.py | 6 +- floris/wind_data.py | 189 +++++++++--------- tests/wind_data_integration_test.py | 32 +-- 14 files changed, 174 insertions(+), 171 deletions(-) diff --git a/examples/examples_heterogeneous/001_heterogeneous_inflow_single.py b/examples/examples_heterogeneous/001_heterogeneous_inflow_single.py index f53ae1ec7..28f92d238 100644 --- a/examples/examples_heterogeneous/001_heterogeneous_inflow_single.py +++ b/examples/examples_heterogeneous/001_heterogeneous_inflow_single.py @@ -1,4 +1,4 @@ -"""Example: Hetereogeneous Inflow for single case +"""Example: Heterogeneous Inflow for single case This example illustrates how to set up a heterogeneous inflow condition in FLORIS. It: @@ -32,21 +32,21 @@ # Define the speed-ups of the heterogeneous inflow, and their locations. # Note that heterogeneity is only applied within the bounds of the points defined in the -# heterogenous_inflow_config dictionary. In this case, set the inflow to be 1.25x the ambient +# heterogeneous_inflow_config dictionary. In this case, set the inflow to be 1.25x the ambient # wind speed for the upper turbines at y = 500m. speed_ups = [[1.0, 1.25, 1.0, 1.25]] # Note speed-ups has dimensions of n_findex X n_points x_locs = [-500.0, -500.0, 1000.0, 1000.0] y_locs = [-500.0, 1000.0, -500.0, 1000.0] # Create the configuration dictionary to be used for the heterogeneous inflow. -heterogenous_inflow_config = { +heterogeneous_inflow_config = { "speed_multipliers": speed_ups, "x": x_locs, "y": y_locs, } # Set the heterogeneous inflow configuration -fmodel.set(heterogenous_inflow_config=heterogenous_inflow_config) +fmodel.set(heterogeneous_inflow_config=heterogeneous_inflow_config) # Run the FLORIS simulation fmodel.run() diff --git a/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py b/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py index c4d025c43..fe95a8f8a 100644 --- a/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py +++ b/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py @@ -2,14 +2,14 @@ When multiple cases are considered, the heterogeneous inflow conditions can be defined in two ways: - 1. Passing heterogenous_inflow_config to the set method, with P points, + 1. Passing heterogeneous_inflow_config to the set method, with P points, and speedups of size n_findex X P - 2. Assigning heterogenous_inflow_config_by_wd to the wind_data object + 2. Assigning heterogeneous_inflow_config_by_wd to the wind_data object used to drive FLORIS. This object includes n_wd wind_directions, and speedups is of size n_wd X P. When applied - to set, the heterogenous_inflow_config + to set, the heterogeneous_inflow_config is automatically generated by using the nearest wind direction - defined in heterogenous_inflow_config_by_wd + defined in heterogeneous_inflow_config_by_wd for each findex. This example: @@ -51,7 +51,7 @@ # Assume the speed-ups are defined such that they are the same 265-275 degrees and 275-285 degrees -# If defining heterogenous_inflow_config directly, then the speedups are of size n_findex X P +# If defining heterogeneous_inflow_config directly, then the speedups are of size n_findex X P # where the first 3 rows are identical, and the last row is different speed_ups = [ [1.0, 1.25, 1.0, 1.25], @@ -60,14 +60,14 @@ [1.0, 1.35, 1.0, 1.35], ] -heterogenous_inflow_config = { +heterogeneous_inflow_config = { "speed_multipliers": speed_ups, "x": x_locs, "y": y_locs, } # Set the heterogeneous inflow configuration -fmodel.set(heterogenous_inflow_config=heterogenous_inflow_config) +fmodel.set(heterogeneous_inflow_config=heterogeneous_inflow_config) # Run the FLORIS simulation fmodel.run() @@ -75,24 +75,24 @@ # Get the power output of the turbines turbine_powers = fmodel.get_turbine_powers() / 1000.0 -# Now repeat using the wind_data object and heterogenous_inflow_config_by_wd +# Now repeat using the wind_data object and heterogeneous_inflow_config_by_wd # First, create the speedups for the two wind directions speed_ups = [[1.0, 1.25, 1.0, 1.25], [1.0, 1.35, 1.0, 1.35]] -# Create the heterogenous_inflow_config_by_wd dictionary -heterogenous_inflow_config_by_wd = { +# Create the heterogeneous_inflow_config_by_wd dictionary +heterogeneous_inflow_config_by_wd = { "speed_multipliers": speed_ups, "x": x_locs, "y": y_locs, "wind_directions": [270.0, 280.0], } -# Now create a new TimeSeries object including the heterogenous_inflow_config_by_wd +# Now create a new TimeSeries object including the heterogeneous_inflow_config_by_wd time_series = TimeSeries( wind_directions=np.array([269.0, 270.0, 271.0, 282.0]), wind_speeds=8.0, turbulence_intensities=0.06, - heterogenous_inflow_config_by_wd=heterogenous_inflow_config_by_wd, + heterogeneous_inflow_config_by_wd=heterogeneous_inflow_config_by_wd, ) # Apply the time series to the FlorisModel diff --git a/examples/examples_heterogeneous/003_heterogeneous_2d_and_3d.py b/examples/examples_heterogeneous/003_heterogeneous_2d_and_3d.py index be36a6e60..1d1f3b791 100644 --- a/examples/examples_heterogeneous/003_heterogeneous_2d_and_3d.py +++ b/examples/examples_heterogeneous/003_heterogeneous_2d_and_3d.py @@ -16,8 +16,8 @@ 3. Vertical slice parallel to to the turbine disc plane Since the intention is for plotting, only a single condition is run and in -this case the heterogenous_inflow_config is more convenient to use than -heterogenous_inflow_config_by_wd. However, the latter is more convenient +this case the heterogeneous_inflow_config is more convenient to use than +heterogeneous_inflow_config_by_wd. However, the latter is more convenient when running multiple conditions. """ @@ -29,11 +29,11 @@ # Initialize FLORIS with the given input file via FlorisModel. -# Note that the heterogeneous flow is defined in the input file. The heterogenous_inflow_config +# Note that the heterogeneous flow is defined in the input file. The heterogeneous_inflow_config # dictionary is defined as below. The speed ups are multipliers of the ambient wind speed, # and the x and y are the locations of the speed ups. # -# heterogenous_inflow_config = { +# heterogeneous_inflow_config = { # 'speed_multipliers': [[2.0, 1.0, 2.0, 1.0]], # 'x': [-300.0, -300.0, 2600.0, 2600.0], # 'y': [ -300.0, 300.0, -300.0, 300.0], @@ -88,7 +88,7 @@ z_locs = [540.0, 540.0, 0.0, 0.0, 540.0, 540.0, 0.0, 0.0] # Create the configuration dictionary to be used for the heterogeneous inflow. -heterogenous_inflow_config = { +heterogeneous_inflow_config = { "speed_multipliers": speed_multipliers, "x": x_locs, "y": y_locs, @@ -99,7 +99,7 @@ # Note that we initialize FLORIS with a homogenous flow input file, but # then configure the heterogeneous inflow via the reinitialize method. fmodel_3d = FlorisModel("../inputs/gch.yaml") -fmodel_3d.set(heterogenous_inflow_config=heterogenous_inflow_config) +fmodel_3d.set(heterogeneous_inflow_config=heterogeneous_inflow_config) # Set shear to 0.0 to highlight the heterogeneous inflow fmodel_3d.set(wind_shear=0.0) diff --git a/examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py b/examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py index 616b60e68..1b54d8aaa 100644 --- a/examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py +++ b/examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py @@ -59,7 +59,7 @@ y_locs = [-D, -D, D, D] # Create the configuration dictionary to be used for the heterogeneous inflow. -heterogenous_inflow_config_by_wd = { +heterogeneous_inflow_config_by_wd = { 'speed_multipliers': speed_multipliers, 'wind_directions': wind_directions, 'x': x_locs, @@ -72,7 +72,7 @@ wind_speeds=wind_speeds, freq_table=freq_table, ti_table=0.06, - heterogenous_inflow_config_by_wd=heterogenous_inflow_config_by_wd + heterogeneous_inflow_config_by_wd=heterogeneous_inflow_config_by_wd ) diff --git a/examples/inputs/gch_heterogeneous_inflow.yaml b/examples/inputs/gch_heterogeneous_inflow.yaml index 86507e287..3c2010773 100644 --- a/examples/inputs/gch_heterogeneous_inflow.yaml +++ b/examples/inputs/gch_heterogeneous_inflow.yaml @@ -27,7 +27,7 @@ farm: flow_field: air_density: 1.225 - heterogenous_inflow_config: + heterogeneous_inflow_config: speed_multipliers: - - 2.0 - 1.0 diff --git a/floris/core/farm.py b/floris/core/farm.py index 26cec1bec..4e881a346 100644 --- a/floris/core/farm.py +++ b/floris/core/farm.py @@ -38,7 +38,7 @@ @define class Farm(BaseClass): """Farm is where wind power plants should be instantiated from a YAML configuration - file. The Farm will create a heterogenous set of turbines that compose a wind farm, + file. The Farm will create a heterogeneous set of turbines that compose a wind farm, validate the inputs, and then create a vectorized representation of the the turbine data. diff --git a/floris/core/flow_field.py b/floris/core/flow_field.py index 655f771a9..09228c2b7 100644 --- a/floris/core/flow_field.py +++ b/floris/core/flow_field.py @@ -29,7 +29,7 @@ class FlowField(BaseClass): turbulence_intensities: NDArrayFloat = field(converter=floris_array_converter) reference_wind_height: float = field(converter=float) time_series: bool = field(default=False) - heterogenous_inflow_config: dict = field(default=None) + heterogeneous_inflow_config: dict = field(default=None) multidim_conditions: dict = field(default=None) n_findex: int = field(init=False) @@ -97,19 +97,19 @@ def wind_speeds_validator(self, instance: attrs.Attribute, value: NDArrayFloat) f"wind_speeds (length = {len(self.wind_speeds)}) must have the same length" ) - @heterogenous_inflow_config.validator - def heterogenous_config_validator(self, instance: attrs.Attribute, value: dict | None) -> None: - """Using the validator method to check that the heterogenous_inflow_config dictionary has + @heterogeneous_inflow_config.validator + def heterogeneous_config_validator(self, instance: attrs.Attribute, value: dict | None) -> None: + """Using the validator method to check that the heterogeneous_inflow_config dictionary has the correct key-value pairs. """ if value is None: return - # Check that the correct keys are supplied for the heterogenous_inflow_config dict + # Check that the correct keys are supplied for the heterogeneous_inflow_config dict for k in ["speed_multipliers", "x", "y"]: if k not in value.keys(): raise ValueError( - "heterogenous_inflow_config must contain entries for 'speed_multipliers'," + "heterogeneous_inflow_config must contain entries for 'speed_multipliers'," f"'x', and 'y', with 'z' optional. Missing '{k}'." ) if "z" not in value: @@ -131,7 +131,7 @@ def het_map_validator(self, instance: attrs.Attribute, value: list | None) -> No def __attrs_post_init__(self) -> None: - if self.heterogenous_inflow_config is not None: + if self.heterogeneous_inflow_config is not None: self.generate_heterogeneous_wind_map() @@ -165,8 +165,8 @@ def initialize_velocity_field(self, grid: Grid) -> None: # grid locations are determined in either 2 or 3 dimensions. else: bounds = np.array(list(zip( - self.heterogenous_inflow_config['x'], - self.heterogenous_inflow_config['y'] + self.heterogeneous_inflow_config['x'], + self.heterogeneous_inflow_config['y'] ))) hull = ConvexHull(bounds) polygon = Polygon(bounds[hull.vertices]) @@ -273,7 +273,7 @@ def generate_heterogeneous_wind_map(self): map bounds. Args: - heterogenous_inflow_config (dict): The heterogeneous inflow configuration dictionary. + heterogeneous_inflow_config (dict): The heterogeneous inflow configuration dictionary. The configuration should have the following inputs specified. - **speed_multipliers** (list): A list of speed up factors that will multiply the specified freestream wind speed. This 2-dimensional array should have an @@ -282,10 +282,10 @@ def generate_heterogeneous_wind_map(self): - **y**: A list of y locations at which the speed up factors are defined. - **z** (optional): A list of z locations at which the speed up factors are defined. """ - speed_multipliers = self.heterogenous_inflow_config['speed_multipliers'] - x = self.heterogenous_inflow_config['x'] - y = self.heterogenous_inflow_config['y'] - z = self.heterogenous_inflow_config['z'] + speed_multipliers = self.heterogeneous_inflow_config['speed_multipliers'] + x = self.heterogeneous_inflow_config['x'] + y = self.heterogeneous_inflow_config['y'] + z = self.heterogeneous_inflow_config['z'] if z is not None: # Compute the 3-dimensional interpolants for each wind direction diff --git a/floris/floris_model.py b/floris/floris_model.py index 2b0f6cb9a..1e671cac2 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -121,7 +121,7 @@ def _reinitialize( turbine_type: list | None = None, turbine_library_path: str | Path | None = None, solver_settings: dict | None = None, - heterogenous_inflow_config=None, + heterogeneous_inflow_config=None, wind_data: type[WindDataBase] | None = None, ): """ @@ -150,8 +150,8 @@ def _reinitialize( turbine_library_path (str | Path | None, optional): Path to the turbine library. Defaults to None. solver_settings (dict | None, optional): Solver settings. Defaults to None. - heterogenous_inflow_config (None, optional): Heterogenous inflow configuration. Defaults - to None. + heterogeneous_inflow_config (None, optional): heterogeneous inflow configuration. + Defaults to None. wind_data (type[WindDataBase] | None, optional): Wind data. Defaults to None. """ # Export the floris object recursively as a dictionary @@ -170,18 +170,18 @@ def _reinitialize( (wind_directions is not None) or (wind_speeds is not None) or (turbulence_intensities is not None) - or (heterogenous_inflow_config is not None) + or (heterogeneous_inflow_config is not None) ): raise ValueError( "If wind_data is passed to reinitialize, then do not pass wind_directions, " "wind_speeds, turbulence_intensities or " - "heterogenous_inflow_config as this is redundant" + "heterogeneous_inflow_config as this is redundant" ) ( wind_directions, wind_speeds, turbulence_intensities, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_data.unpack_for_reinitialize() ## FlowField @@ -199,8 +199,8 @@ def _reinitialize( flow_field_dict["turbulence_intensities"] = turbulence_intensities if air_density is not None: flow_field_dict["air_density"] = air_density - if heterogenous_inflow_config is not None: - flow_field_dict["heterogenous_inflow_config"] = heterogenous_inflow_config + if heterogeneous_inflow_config is not None: + flow_field_dict["heterogeneous_inflow_config"] = heterogeneous_inflow_config ## Farm if layout_x is not None: @@ -294,7 +294,7 @@ def set( turbine_type: list | None = None, turbine_library_path: str | Path | None = None, solver_settings: dict | None = None, - heterogenous_inflow_config=None, + heterogeneous_inflow_config=None, wind_data: type[WindDataBase] | None = None, yaw_angles: NDArrayFloat | list[float] | None = None, power_setpoints: NDArrayFloat | list[float] | list[float, None] | None = None, @@ -322,8 +322,8 @@ def set( turbine_library_path (str | Path | None, optional): Path to the turbine library. Defaults to None. solver_settings (dict | None, optional): Solver settings. Defaults to None. - heterogenous_inflow_config (None, optional): Heterogenous inflow configuration. Defaults - to None. + heterogeneous_inflow_config (None, optional): heterogeneous inflow configuration. + Defaults to None. wind_data (type[WindDataBase] | None, optional): Wind data. Defaults to None. yaw_angles (NDArrayFloat | list[float] | None, optional): Turbine yaw angles. Defaults to None. @@ -349,7 +349,7 @@ def set( turbine_type=turbine_type, turbine_library_path=turbine_library_path, solver_settings=solver_settings, - heterogenous_inflow_config=heterogenous_inflow_config, + heterogeneous_inflow_config=heterogeneous_inflow_config, wind_data=wind_data, ) diff --git a/floris/flow_visualization.py b/floris/flow_visualization.py index c8d30d141..8152be3df 100644 --- a/floris/flow_visualization.py +++ b/floris/flow_visualization.py @@ -297,8 +297,8 @@ def visualize_heterogeneous_cut_plane( points = np.array( list( zip( - fmodel.core.flow_field.heterogenous_inflow_config['x'], - fmodel.core.flow_field.heterogenous_inflow_config['y'], + fmodel.core.flow_field.heterogeneous_inflow_config['x'], + fmodel.core.flow_field.heterogeneous_inflow_config['y'], ) ) ) diff --git a/floris/optimization/yaw_optimization/yaw_optimization_base.py b/floris/optimization/yaw_optimization/yaw_optimization_base.py index 5608f58f4..07a2f7e11 100644 --- a/floris/optimization/yaw_optimization/yaw_optimization_base.py +++ b/floris/optimization/yaw_optimization/yaw_optimization_base.py @@ -318,7 +318,7 @@ def _calculate_farm_power( turbine_weights (iterable, optional): Array or list of weights to apply to the turbine powers. Defaults to None. heterogeneous_speed_multipliers (iterable, optional): Array or list of speed up factors - for heterogenous inflow. Defaults to None. + for heterogeneous inflow. Defaults to None. Returns: @@ -338,7 +338,7 @@ def _calculate_farm_power( turbine_weights = self._turbine_weights_subset if heterogeneous_speed_multipliers is not None: fmodel_subset.core.flow_field.\ - heterogenous_inflow_config['speed_multipliers'] = heterogeneous_speed_multipliers + heterogeneous_inflow_config['speed_multipliers'] = heterogeneous_speed_multipliers # Ensure format [incompatible with _subset notation] yaw_angles = self._unpack_variable(yaw_angles, subset=True) diff --git a/floris/optimization/yaw_optimization/yaw_optimizer_scipy.py b/floris/optimization/yaw_optimization/yaw_optimizer_scipy.py index b62649117..cdde87656 100644 --- a/floris/optimization/yaw_optimization/yaw_optimizer_scipy.py +++ b/floris/optimization/yaw_optimization/yaw_optimizer_scipy.py @@ -98,10 +98,10 @@ def optimize(self): turbine_weights = np.tile(turbine_weights, (1, 1)) # Handle heterogeneous inflow, if there is one - if (hasattr(self.fmodel.core.flow_field, 'heterogenous_inflow_config') and - self.fmodel.core.flow_field.heterogenous_inflow_config is not None): + if (hasattr(self.fmodel.core.flow_field, 'heterogeneous_inflow_config') and + self.fmodel.core.flow_field.heterogeneous_inflow_config is not None): het_sm_orig = np.array( - self.fmodel.core.flow_field.heterogenous_inflow_config['speed_multipliers'] + self.fmodel.core.flow_field.heterogeneous_inflow_config['speed_multipliers'] ) het_sm = het_sm_orig[i, :].reshape(1, -1) else: diff --git a/floris/optimization/yaw_optimization/yaw_optimizer_sr.py b/floris/optimization/yaw_optimization/yaw_optimizer_sr.py index c6d76b04e..2b5b7ad1b 100644 --- a/floris/optimization/yaw_optimization/yaw_optimizer_sr.py +++ b/floris/optimization/yaw_optimization/yaw_optimizer_sr.py @@ -129,10 +129,10 @@ def _calc_powers_with_memory(self, yaw_angles_subset, use_memory=True): if not np.all(idx): # Now calculate farm powers for conditions we haven't yet evaluated previously start_time = timerpc() - if (hasattr(self.fmodel.core.flow_field, 'heterogenous_inflow_config') and - self.fmodel.core.flow_field.heterogenous_inflow_config is not None): + if (hasattr(self.fmodel.core.flow_field, 'heterogeneous_inflow_config') and + self.fmodel.core.flow_field.heterogeneous_inflow_config is not None): het_sm_orig = np.array( - self.fmodel.core.flow_field.heterogenous_inflow_config['speed_multipliers'] + self.fmodel.core.flow_field.heterogeneous_inflow_config['speed_multipliers'] ) het_sm = np.tile(het_sm_orig, (Ny, 1))[~idx, :] else: diff --git a/floris/wind_data.py b/floris/wind_data.py index ab202e670..ec480fd11 100644 --- a/floris/wind_data.py +++ b/floris/wind_data.py @@ -36,14 +36,14 @@ def unpack_for_reinitialize(self): ti_table_unpack, _, _, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = self.unpack() return ( wind_directions_unpack, wind_speeds_unpack, ti_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) def unpack_freq(self): @@ -60,63 +60,63 @@ def unpack_freq(self): return freq_table_unpack - def check_heterogenous_inflow_config_by_wd(self, heterogenous_inflow_config_by_wd): + def check_heterogeneous_inflow_config_by_wd(self, heterogeneous_inflow_config_by_wd): """ - Check that the heterogenous_inflow_config_by_wd dictionary is properly formatted + Check that the heterogeneous_inflow_config_by_wd dictionary is properly formatted Args: - heterogenous_inflow_config_by_wd (dict): A dictionary containing the following keys: + heterogeneous_inflow_config_by_wd (dict): A dictionary containing the following keys: * 'speed_multipliers': A 2D NumPy array (size num_wd x num_points) of speed multipliers. * 'wind_directions': A 1D NumPy array (size num_wd) of wind directions (degrees). * 'x': A 1D NumPy array (size num_points) of x-coordinates (meters). * 'y': A 1D NumPy array (size num_points) of y-coordinates (meters). """ - if heterogenous_inflow_config_by_wd is not None: - if not isinstance(heterogenous_inflow_config_by_wd, dict): - raise TypeError("heterogenous_inflow_config_by_wd must be a dictionary") - if "speed_multipliers" not in heterogenous_inflow_config_by_wd: + if heterogeneous_inflow_config_by_wd is not None: + if not isinstance(heterogeneous_inflow_config_by_wd, dict): + raise TypeError("heterogeneous_inflow_config_by_wd must be a dictionary") + if "speed_multipliers" not in heterogeneous_inflow_config_by_wd: raise ValueError( - "heterogenous_inflow_config_by_wd must contain a key 'speed_multipliers'" + "heterogeneous_inflow_config_by_wd must contain a key 'speed_multipliers'" ) - if "wind_directions" not in heterogenous_inflow_config_by_wd: + if "wind_directions" not in heterogeneous_inflow_config_by_wd: raise ValueError( - "heterogenous_inflow_config_by_wd must contain a key 'wind_directions'" + "heterogeneous_inflow_config_by_wd must contain a key 'wind_directions'" ) - if "x" not in heterogenous_inflow_config_by_wd: - raise ValueError("heterogenous_inflow_config_by_wd must contain a key 'x'") - if "y" not in heterogenous_inflow_config_by_wd: - raise ValueError("heterogenous_inflow_config_by_wd must contain a key 'y'") + if "x" not in heterogeneous_inflow_config_by_wd: + raise ValueError("heterogeneous_inflow_config_by_wd must contain a key 'x'") + if "y" not in heterogeneous_inflow_config_by_wd: + raise ValueError("heterogeneous_inflow_config_by_wd must contain a key 'y'") - def check_heterogenous_inflow_config(self, heterogenous_inflow_config): + def check_heterogeneous_inflow_config(self, heterogeneous_inflow_config): """ - Check that the heterogenous_inflow_config dictionary is properly formatted + Check that the heterogeneous_inflow_config dictionary is properly formatted Args: - heterogenous_inflow_config (dict): A dictionary containing the following keys: + heterogeneous_inflow_config (dict): A dictionary containing the following keys: * 'speed_multipliers': A 2D NumPy array (size n_findex x num_points) of speed multipliers. * 'x': A 1D NumPy array (size num_points) of x-coordinates (meters). * 'y': A 1D NumPy array (size num_points) of y-coordinates (meters). """ - if heterogenous_inflow_config is not None: - if not isinstance(heterogenous_inflow_config, dict): - raise TypeError("heterogenous_inflow_config_by_wd must be a dictionary") - if "speed_multipliers" not in heterogenous_inflow_config: + if heterogeneous_inflow_config is not None: + if not isinstance(heterogeneous_inflow_config, dict): + raise TypeError("heterogeneous_inflow_config_by_wd must be a dictionary") + if "speed_multipliers" not in heterogeneous_inflow_config: raise ValueError( - "heterogenous_inflow_config must contain a key 'speed_multipliers'" + "heterogeneous_inflow_config must contain a key 'speed_multipliers'" ) - if "x" not in heterogenous_inflow_config: - raise ValueError("heterogenous_inflow_config must contain a key 'x'") - if "y" not in heterogenous_inflow_config: - raise ValueError("heterogenous_inflow_config must contain a key 'y'") + if "x" not in heterogeneous_inflow_config: + raise ValueError("heterogeneous_inflow_config must contain a key 'x'") + if "y" not in heterogeneous_inflow_config: + raise ValueError("heterogeneous_inflow_config must contain a key 'y'") - def get_speed_multipliers_by_wd(self, heterogenous_inflow_config_by_wd, wind_directions): + def get_speed_multipliers_by_wd(self, heterogeneous_inflow_config_by_wd, wind_directions): """ - Processes heterogenous inflow configuration data to generate a speed multiplier array + Processes heterogeneous inflow configuration data to generate a speed multiplier array aligned with the wind directions. Accounts for the cyclical nature of wind directions. Args: - heterogenous_inflow_config_by_wd (dict): A dictionary containing the following keys: + heterogeneous_inflow_config_by_wd (dict): A dictionary containing the following keys: * 'speed_multipliers': A 2D NumPy array (size num_wd x num_points) of speed multipliers. * 'wind_directions': A 1D NumPy array (size num_wd) of wind directions (degrees). @@ -132,14 +132,14 @@ def get_speed_multipliers_by_wd(self, heterogenous_inflow_config_by_wd, wind_dir """ # Extract data from the configuration dictionary - speed_multipliers = np.array(heterogenous_inflow_config_by_wd["speed_multipliers"]) - het_wd = np.array(heterogenous_inflow_config_by_wd["wind_directions"]) + speed_multipliers = np.array(heterogeneous_inflow_config_by_wd["speed_multipliers"]) + het_wd = np.array(heterogeneous_inflow_config_by_wd["wind_directions"]) # Confirm 0th dimension of speed_multipliers == len(het_wd) if len(het_wd) != speed_multipliers.shape[0]: raise ValueError( "The legnth of het_wd must equal the number of rows speed_multipliers" - "Within the heterogenous_inflow_config_by_wd dictionary" + "Within the heterogeneous_inflow_config_by_wd dictionary" ) # Calculate closest wind direction indices (accounting for angles) @@ -150,21 +150,21 @@ def get_speed_multipliers_by_wd(self, heterogenous_inflow_config_by_wd, wind_dir # Construct the output array using the calculated indices return speed_multipliers[closest_wd_indices] - def get_heterogenous_inflow_config(self, heterogenous_inflow_config_by_wd, wind_directions): - # If heterogenous_inflow_config_by_wd is None, return None - if heterogenous_inflow_config_by_wd is None: + def get_heterogeneous_inflow_config(self, heterogeneous_inflow_config_by_wd, wind_directions): + # If heterogeneous_inflow_config_by_wd is None, return None + if heterogeneous_inflow_config_by_wd is None: return None - # If heterogenous_inflow_config_by_wd is not None, then process it + # If heterogeneous_inflow_config_by_wd is not None, then process it # Build the n-findex version of the het map speed_multipliers = self.get_speed_multipliers_by_wd( - heterogenous_inflow_config_by_wd, wind_directions + heterogeneous_inflow_config_by_wd, wind_directions ) - # Return heterogenous_inflow_config + # Return heterogeneous_inflow_config return { "speed_multipliers": speed_multipliers, - "x": heterogenous_inflow_config_by_wd["x"], - "y": heterogenous_inflow_config_by_wd["y"], + "x": heterogeneous_inflow_config_by_wd["x"], + "y": heterogeneous_inflow_config_by_wd["y"], } @@ -194,7 +194,7 @@ class WindRose(WindDataBase): each bin to compute the total value of the energy produced compute_zero_freq_occurrence: Flag indicating whether to compute zero frequency occurrences (bool, optional). Defaults to False. - heterogenous_inflow_config_by_wd (dict, optional): A dictionary containing the following + heterogeneous_inflow_config_by_wd (dict, optional): A dictionary containing the following keys. Defaults to None. * 'speed_multipliers': A 2D NumPy array (size num_wd x num_points) of speed multipliers. @@ -212,7 +212,7 @@ def __init__( freq_table: NDArrayFloat | None = None, value_table: NDArrayFloat | None = None, compute_zero_freq_occurrence: bool = False, - heterogenous_inflow_config_by_wd: dict | None = None, + heterogeneous_inflow_config_by_wd: dict | None = None, ): if not isinstance(wind_directions, np.ndarray): raise TypeError("wind_directions must be a NumPy array") @@ -272,12 +272,12 @@ def __init__( ) self.compute_zero_freq_occurrence = compute_zero_freq_occurrence - # Check that heterogenous_inflow_config_by_wd is a dictionary with keys: + # Check that heterogeneous_inflow_config_by_wd is a dictionary with keys: # speed_multipliers, wind_directions, x and y - self.check_heterogenous_inflow_config_by_wd(heterogenous_inflow_config_by_wd) + self.check_heterogeneous_inflow_config_by_wd(heterogeneous_inflow_config_by_wd) # Then save - self.heterogenous_inflow_config_by_wd = heterogenous_inflow_config_by_wd + self.heterogeneous_inflow_config_by_wd = heterogeneous_inflow_config_by_wd # Build the gridded and flatten versions self._build_gridded_and_flattened_version() @@ -343,14 +343,14 @@ def unpack(self): else: value_table_unpack = None - # If heterogenous_inflow_config_by_wd is not None, then update - # heterogenous_inflow_config to match wind_directions_unpack - if self.heterogenous_inflow_config_by_wd is not None: - heterogenous_inflow_config = self.get_heterogenous_inflow_config( - self.heterogenous_inflow_config_by_wd, wind_directions_unpack + # If heterogeneous_inflow_config_by_wd is not None, then update + # heterogeneous_inflow_config to match wind_directions_unpack + if self.heterogeneous_inflow_config_by_wd is not None: + heterogeneous_inflow_config = self.get_heterogeneous_inflow_config( + self.heterogeneous_inflow_config_by_wd, wind_directions_unpack ) else: - heterogenous_inflow_config = None + heterogeneous_inflow_config = None return ( wind_directions_unpack, @@ -358,7 +358,7 @@ def unpack(self): ti_table_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) def resample_wind_rose(self, wd_step=None, ws_step=None): @@ -394,7 +394,7 @@ def resample_wind_rose(self, wd_step=None, ws_step=None): self.ws_flat, self.ti_table_flat, self.value_table_flat, - self.heterogenous_inflow_config_by_wd, + self.heterogeneous_inflow_config_by_wd, ) # Now build a new wind rose using the new steps @@ -565,7 +565,7 @@ class WindTIRose(WindDataBase): to compute the total value of the energy produced. compute_zero_freq_occurrence: Flag indicating whether to compute zero frequency occurrences (bool, optional). Defaults to False. - heterogenous_inflow_config_by_wd (dict, optional): A dictionary containing the following + heterogeneous_inflow_config_by_wd (dict, optional): A dictionary containing the following keys. Defaults to None. * 'speed_multipliers': A 2D NumPy array (size num_wd x num_points) of speed multipliers. @@ -583,7 +583,7 @@ def __init__( freq_table: NDArrayFloat | None = None, value_table: NDArrayFloat | None = None, compute_zero_freq_occurrence: bool = False, - heterogenous_inflow_config_by_wd: dict | None = None, + heterogeneous_inflow_config_by_wd: dict | None = None, ): if not isinstance(wind_directions, np.ndarray): raise TypeError("wind_directions must be a NumPy array") @@ -632,12 +632,12 @@ def __init__( ) self.value_table = value_table - # Check that heterogenous_inflow_config_by_wd is a dictionary with keys: + # Check that heterogeneous_inflow_config_by_wd is a dictionary with keys: # speed_multipliers, wind_directions, x and y - self.check_heterogenous_inflow_config_by_wd(heterogenous_inflow_config_by_wd) + self.check_heterogeneous_inflow_config_by_wd(heterogeneous_inflow_config_by_wd) # Then save - self.heterogenous_inflow_config_by_wd = heterogenous_inflow_config_by_wd + self.heterogeneous_inflow_config_by_wd = heterogeneous_inflow_config_by_wd # Save whether zero occurrence cases should be computed self.compute_zero_freq_occurrence = compute_zero_freq_occurrence @@ -704,14 +704,14 @@ def unpack(self): else: value_table_unpack = None - # If heterogenous_inflow_config_by_wd is not None, then update - # heterogenous_inflow_config to match wind_directions_unpack - if self.heterogenous_inflow_config_by_wd is not None: - heterogenous_inflow_config = self.get_heterogenous_inflow_config( - self.heterogenous_inflow_config_by_wd, wind_directions_unpack + # If heterogeneous_inflow_config_by_wd is not None, then update + # heterogeneous_inflow_config to match wind_directions_unpack + if self.heterogeneous_inflow_config_by_wd is not None: + heterogeneous_inflow_config = self.get_heterogeneous_inflow_config( + self.heterogeneous_inflow_config_by_wd, wind_directions_unpack ) else: - heterogenous_inflow_config = None + heterogeneous_inflow_config = None return ( wind_directions_unpack, @@ -719,7 +719,7 @@ def unpack(self): turbulence_intensities_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) def resample_wind_rose(self, wd_step=None, ws_step=None, ti_step=None): @@ -763,7 +763,7 @@ def resample_wind_rose(self, wd_step=None, ws_step=None, ti_step=None): self.ws_flat, self.ti_flat, self.value_table_flat, - self.heterogenous_inflow_config_by_wd, + self.heterogeneous_inflow_config_by_wd, ) # Now build a new wind rose using the new steps @@ -923,14 +923,14 @@ class TimeSeries(WindDataBase): a single value or an array of values. values (NDArrayFloat, optional): Values associated with each wind direction, wind speed, and turbulence intensity. Defaults to None. - heterogenous_inflow_config_by_wd (dict, optional): A dictionary containing the following + heterogeneous_inflow_config_by_wd (dict, optional): A dictionary containing the following keys. Defaults to None. * 'speed_multipliers': A 2D NumPy array (size num_wd x num_points) of speed multipliers. * 'wind_directions': A 1D NumPy array (size num_wd) of wind directions (degrees). * 'x': A 1D NumPy array (size num_points) of x-coordinates (meters). * 'y': A 1D NumPy array (size num_points) of y-coordinates (meters). - heterogenous_inflow_config (dict, optional): A dictionary containing the following keys. + heterogeneous_inflow_config (dict, optional): A dictionary containing the following keys. Defaults to None. * 'speed_multipliers': A 2D NumPy array (size n_findex x num_points) of speed multipliers. @@ -944,8 +944,8 @@ def __init__( wind_speeds: float | NDArrayFloat, turbulence_intensities: float | NDArrayFloat, values: NDArrayFloat | None = None, - heterogenous_inflow_config_by_wd: dict | None = None, - heterogenous_inflow_config: dict | None = None, + heterogeneous_inflow_config_by_wd: dict | None = None, + heterogeneous_inflow_config: dict | None = None, ): # At least one of wind_directions, wind_speeds, or turbulence_intensities must be an array if ( @@ -1014,29 +1014,32 @@ def __init__( self.turbulence_intensities = turbulence_intensities self.values = values - # Only one of heterogenous_inflow_config_by_wd and - # heterogenous_inflow_config can be not None - if heterogenous_inflow_config_by_wd is not None and heterogenous_inflow_config is not None: + # Only one of heterogeneous_inflow_config_by_wd and + # heterogeneous_inflow_config can be not None + if ( + heterogeneous_inflow_config_by_wd is not None + and heterogeneous_inflow_config is not None + ): raise ValueError( - "Only one of heterogenous_inflow_config_by_wd and heterogenous_inflow_config " + "Only one of heterogeneous_inflow_config_by_wd and heterogeneous_inflow_config " "can be not None" ) - # if heterogenous_inflow_config is not None, then the speed_multipliers + # if heterogeneous_inflow_config is not None, then the speed_multipliers # must be the same length as wind_directions # in the 0th dimension - if heterogenous_inflow_config is not None: - if len(heterogenous_inflow_config["speed_multipliers"]) != len(wind_directions): + if heterogeneous_inflow_config is not None: + if len(heterogeneous_inflow_config["speed_multipliers"]) != len(wind_directions): raise ValueError("speed_multipliers must be the same length as wind_directions") - # Check that heterogenous_inflow_config_by_wd is a dictionary with keys: + # Check that heterogeneous_inflow_config_by_wd is a dictionary with keys: # speed_multipliers, wind_directions, x and y - self.check_heterogenous_inflow_config_by_wd(heterogenous_inflow_config_by_wd) - self.check_heterogenous_inflow_config(heterogenous_inflow_config) + self.check_heterogeneous_inflow_config_by_wd(heterogeneous_inflow_config_by_wd) + self.check_heterogeneous_inflow_config(heterogeneous_inflow_config) # Then save - self.heterogenous_inflow_config_by_wd = heterogenous_inflow_config_by_wd - self.heterogenous_inflow_config = heterogenous_inflow_config + self.heterogeneous_inflow_config_by_wd = heterogeneous_inflow_config_by_wd + self.heterogeneous_inflow_config = heterogeneous_inflow_config # Record findex self.n_findex = len(self.wind_directions) @@ -1050,14 +1053,14 @@ def unpack(self): uniform_frequency = np.ones_like(self.wind_directions) uniform_frequency = uniform_frequency / uniform_frequency.sum() - # If heterogenous_inflow_config_by_wd is not None, then update - # heterogenous_inflow_config to match wind_directions_unpack - if self.heterogenous_inflow_config_by_wd is not None: - heterogenous_inflow_config = self.get_heterogenous_inflow_config( - self.heterogenous_inflow_config_by_wd, self.wind_directions + # If heterogeneous_inflow_config_by_wd is not None, then update + # heterogeneous_inflow_config to match wind_directions_unpack + if self.heterogeneous_inflow_config_by_wd is not None: + heterogeneous_inflow_config = self.get_heterogeneous_inflow_config( + self.heterogeneous_inflow_config_by_wd, self.wind_directions ) else: - heterogenous_inflow_config = self.heterogenous_inflow_config + heterogeneous_inflow_config = self.heterogeneous_inflow_config return ( self.wind_directions, @@ -1065,7 +1068,7 @@ def unpack(self): self.turbulence_intensities, uniform_frequency, self.values, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) def _wrap_wind_directions_near_360(self, wind_directions, wd_step): @@ -1261,7 +1264,7 @@ def to_wind_rose( ti_table, freq_table, value_table, - self.heterogenous_inflow_config_by_wd, + self.heterogeneous_inflow_config_by_wd, ) def to_wind_ti_rose( @@ -1428,5 +1431,5 @@ def to_wind_ti_rose( ti_centers, freq_table, value_table, - self.heterogenous_inflow_config_by_wd, + self.heterogeneous_inflow_config_by_wd, ) diff --git a/tests/wind_data_integration_test.py b/tests/wind_data_integration_test.py index ecc8281b3..27da7bb01 100644 --- a/tests/wind_data_integration_test.py +++ b/tests/wind_data_integration_test.py @@ -130,7 +130,7 @@ def test_wind_rose_unpack(): ti_table_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack() # Given the above frequency table with zeros for a few elements, @@ -155,7 +155,7 @@ def test_wind_rose_unpack(): ti_table_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack() # Expect now to compute all combinations @@ -177,7 +177,7 @@ def test_unpack_for_reinitialize(): wind_directions_unpack, wind_speeds_unpack, ti_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack_for_reinitialize() # Given the above frequency table, would only expect the @@ -361,7 +361,7 @@ def test_wind_ti_rose_unpack(): turbulence_intensities_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack() # Given the above frequency table with zeros for a few elements, @@ -391,7 +391,7 @@ def test_wind_ti_rose_unpack(): turbulence_intensities_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack() # Expect now to compute all combinations @@ -423,7 +423,7 @@ def test_wind_ti_rose_unpack_for_reinitialize(): wind_directions_unpack, wind_speeds_unpack, turbulence_intensities_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack_for_reinitialize() # Given the above frequency table with zeros for a few elements, @@ -481,7 +481,7 @@ def test_time_series_to_wind_ti_rose(): def test_get_speed_multipliers_by_wd(): - heterogenous_inflow_config_by_wd = { + heterogeneous_inflow_config_by_wd = { 'speed_multipliers': np.array( [ [1.0, 1.1, 1.2], @@ -503,7 +503,7 @@ def test_get_speed_multipliers_by_wd(): ) wind_data = WindDataBase() result = wind_data.get_speed_multipliers_by_wd( - heterogenous_inflow_config_by_wd, + heterogeneous_inflow_config_by_wd, wind_directions ) assert np.allclose(result, expected_output) @@ -513,7 +513,7 @@ def test_get_speed_multipliers_by_wd(): expected_output = np.array([[1.0, 1.1, 1.2], [1.0, 1.1, 1.2]]) result = wind_data.get_speed_multipliers_by_wd( - heterogenous_inflow_config_by_wd, + heterogeneous_inflow_config_by_wd, wind_directions ) assert np.allclose(result, expected_output) @@ -521,17 +521,17 @@ def test_get_speed_multipliers_by_wd(): # Confirm can expand the result to match wind directions wind_directions = np.arange(0.0,360.0,10.0) num_wd = len(wind_directions) - result = wind_data.get_speed_multipliers_by_wd(heterogenous_inflow_config_by_wd, + result = wind_data.get_speed_multipliers_by_wd(heterogeneous_inflow_config_by_wd, wind_directions) assert result.shape[0] == num_wd -def test_gen_heterogenous_inflow_config(): +def test_gen_heterogeneous_inflow_config(): wind_directions = np.array([259.8, 260.2, 260.3, 260.1, 270.0]) wind_speeds = 8 turbulence_intensities = 0.06 - heterogenous_inflow_config_by_wd = { + heterogeneous_inflow_config_by_wd = { 'speed_multipliers': np.array( [ [0.9, 0.9], @@ -548,10 +548,10 @@ def test_gen_heterogenous_inflow_config(): wind_directions, wind_speeds, turbulence_intensities=turbulence_intensities, - heterogenous_inflow_config_by_wd=heterogenous_inflow_config_by_wd + heterogeneous_inflow_config_by_wd=heterogeneous_inflow_config_by_wd ) - (_, _, _, _, _, heterogenous_inflow_config) = time_series.unpack() + (_, _, _, _, _, heterogeneous_inflow_config) = time_series.unpack() expected_result = np.array( [ @@ -562,5 +562,5 @@ def test_gen_heterogenous_inflow_config(): [1.1, 1.2] ] ) - np.testing.assert_allclose(heterogenous_inflow_config['speed_multipliers'], expected_result) - np.testing.assert_allclose(heterogenous_inflow_config['x'],heterogenous_inflow_config_by_wd['x']) + np.testing.assert_allclose(heterogeneous_inflow_config['speed_multipliers'], expected_result) + np.testing.assert_allclose(heterogeneous_inflow_config['x'],heterogeneous_inflow_config_by_wd['x']) From 4d5524b26318d3e70886870827a857d6f808ff24 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 20 Mar 2024 14:04:52 -0600 Subject: [PATCH 048/120] Add place holder examples --- examples/examples_uncertain/002_approx_floris.py | 1 + examples/examples_uncertain/003_yaw_inertial_frame.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 examples/examples_uncertain/002_approx_floris.py create mode 100644 examples/examples_uncertain/003_yaw_inertial_frame.py diff --git a/examples/examples_uncertain/002_approx_floris.py b/examples/examples_uncertain/002_approx_floris.py new file mode 100644 index 000000000..9a87e5eb4 --- /dev/null +++ b/examples/examples_uncertain/002_approx_floris.py @@ -0,0 +1 @@ +#TODO: ADD EXAMPLE diff --git a/examples/examples_uncertain/003_yaw_inertial_frame.py b/examples/examples_uncertain/003_yaw_inertial_frame.py new file mode 100644 index 000000000..613c0348d --- /dev/null +++ b/examples/examples_uncertain/003_yaw_inertial_frame.py @@ -0,0 +1 @@ +#TODO add example here From 66f2991135a86273e544e9128563491686d62064 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 15:53:42 -0600 Subject: [PATCH 049/120] Spelling --- floris/floris_model.py | 2 +- tests/wind_data_integration_test.py | 32 ++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/floris/floris_model.py b/floris/floris_model.py index a353d44d9..3561832c9 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -188,7 +188,7 @@ def _reinitialize( wind_directions, wind_speeds, turbulence_intensities, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_data.unpack_for_reinitialize() self._wind_data = wind_data diff --git a/tests/wind_data_integration_test.py b/tests/wind_data_integration_test.py index 778c35403..ba5fde00b 100644 --- a/tests/wind_data_integration_test.py +++ b/tests/wind_data_integration_test.py @@ -130,7 +130,7 @@ def test_wind_rose_unpack(): ti_table_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack() # Given the above frequency table with zeros for a few elements, @@ -155,7 +155,7 @@ def test_wind_rose_unpack(): ti_table_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack() # Expect now to compute all combinations @@ -177,7 +177,7 @@ def test_unpack_for_reinitialize(): wind_directions_unpack, wind_speeds_unpack, ti_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack_for_reinitialize() # Given the above frequency table, would only expect the @@ -361,7 +361,7 @@ def test_wind_ti_rose_unpack(): turbulence_intensities_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack() # Given the above frequency table with zeros for a few elements, @@ -391,7 +391,7 @@ def test_wind_ti_rose_unpack(): turbulence_intensities_unpack, freq_table_unpack, value_table_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack() # Expect now to compute all combinations @@ -423,7 +423,7 @@ def test_wind_ti_rose_unpack_for_reinitialize(): wind_directions_unpack, wind_speeds_unpack, turbulence_intensities_unpack, - heterogenous_inflow_config, + heterogeneous_inflow_config, ) = wind_rose.unpack_for_reinitialize() # Given the above frequency table with zeros for a few elements, @@ -481,7 +481,7 @@ def test_time_series_to_wind_ti_rose(): def test_get_speed_multipliers_by_wd(): - heterogenous_inflow_config_by_wd = { + heterogeneous_inflow_config_by_wd = { "speed_multipliers": np.array( [ [1.0, 1.1, 1.2], @@ -497,7 +497,7 @@ def test_get_speed_multipliers_by_wd(): expected_output = np.array([[1.3, 1.4, 1.5], [1.1, 1.1, 1.1], [1.0, 1.1, 1.2]]) wind_data = WindDataBase() result = wind_data.get_speed_multipliers_by_wd( - heterogenous_inflow_config_by_wd, wind_directions + heterogeneous_inflow_config_by_wd, wind_directions ) assert np.allclose(result, expected_output) @@ -505,7 +505,7 @@ def test_get_speed_multipliers_by_wd(): wind_directions = np.array([350, 10]) expected_output = np.array([[1.0, 1.1, 1.2], [1.0, 1.1, 1.2]]) result = wind_data.get_speed_multipliers_by_wd( - heterogenous_inflow_config_by_wd, wind_directions + heterogeneous_inflow_config_by_wd, wind_directions ) assert np.allclose(result, expected_output) @@ -513,17 +513,17 @@ def test_get_speed_multipliers_by_wd(): wind_directions = np.arange(0.0, 360.0, 10.0) num_wd = len(wind_directions) result = wind_data.get_speed_multipliers_by_wd( - heterogenous_inflow_config_by_wd, wind_directions + heterogeneous_inflow_config_by_wd, wind_directions ) assert result.shape[0] == num_wd -def test_gen_heterogenous_inflow_config(): +def test_gen_heterogeneous_inflow_config(): wind_directions = np.array([259.8, 260.2, 260.3, 260.1, 270.0]) wind_speeds = 8 turbulence_intensities = 0.06 - heterogenous_inflow_config_by_wd = { + heterogeneous_inflow_config_by_wd = { "speed_multipliers": np.array( [ [0.9, 0.9], @@ -540,15 +540,15 @@ def test_gen_heterogenous_inflow_config(): wind_directions, wind_speeds, turbulence_intensities=turbulence_intensities, - heterogenous_inflow_config_by_wd=heterogenous_inflow_config_by_wd, + heterogeneous_inflow_config_by_wd=heterogeneous_inflow_config_by_wd, ) - (_, _, _, _, _, heterogenous_inflow_config) = time_series.unpack() + (_, _, _, _, _, heterogeneous_inflow_config) = time_series.unpack() expected_result = np.array([[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.1, 1.2]]) - np.testing.assert_allclose(heterogenous_inflow_config["speed_multipliers"], expected_result) + np.testing.assert_allclose(heterogeneous_inflow_config["speed_multipliers"], expected_result) np.testing.assert_allclose( - heterogenous_inflow_config["x"], heterogenous_inflow_config_by_wd["x"] + heterogeneous_inflow_config["x"], heterogeneous_inflow_config_by_wd["x"] ) From 99184f6de4961a493654f868a47dccee158e004f Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 15:55:02 -0600 Subject: [PATCH 050/120] update toWindRose --- examples/003_wind_data_objects.py | 2 +- examples/006_get_farm_aep.py | 2 +- tests/wind_data_integration_test.py | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index bc5f2a820..7f10da783 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -109,7 +109,7 @@ ################################################## # The TimeSeries class has a method to generate a wind rose from a time series based on binning -wind_rose = time_series.to_wind_rose( +wind_rose = time_series.to_WindRose( wd_edges=np.arange(0, 360, 3.0), ws_edges=np.arange(4, 20, 2.0) ) diff --git a/examples/006_get_farm_aep.py b/examples/006_get_farm_aep.py index a22626798..6b59e4a81 100644 --- a/examples/006_get_farm_aep.py +++ b/examples/006_get_farm_aep.py @@ -114,7 +114,7 @@ ) # Convert time series to wind rose using the frequencies as bin weights -wind_rose_from_time_series = time_series.to_wind_rose( +wind_rose_from_time_series = time_series.to_WindRose( wd_step=wd_step, ws_step=ws_step, bin_weights=freq_vals ) diff --git a/tests/wind_data_integration_test.py b/tests/wind_data_integration_test.py index ba5fde00b..a3d0b223c 100644 --- a/tests/wind_data_integration_test.py +++ b/tests/wind_data_integration_test.py @@ -218,12 +218,12 @@ def test_wrap_wind_directions_near_360(): assert np.allclose(wd_wrapped, expected_result) -def test_time_series_to_wind_rose(): +def test_time_series_to_WindRose(): # Test just 1 wind speed wind_directions = np.array([259.8, 260.2, 264.3]) wind_speeds = np.array([5.0, 5.0, 5.1]) time_series = TimeSeries(wind_directions, wind_speeds, 0.06) - wind_rose = time_series.to_wind_rose(wd_step=2.0, ws_step=1.0) + wind_rose = time_series.to_WindRose(wd_step=2.0, ws_step=1.0) # The wind directions should be 260, 262 and 264 because they're binned # to the nearest 2 deg increment @@ -243,7 +243,7 @@ def test_time_series_to_wind_rose(): wind_directions = np.array([259.8, 260.2, 264.3]) wind_speeds = np.array([5.0, 5.0, 6.1]) time_series = TimeSeries(wind_directions, wind_speeds, 0.06) - wind_rose = time_series.to_wind_rose(wd_step=2.0, ws_step=1.0) + wind_rose = time_series.to_WindRose(wd_step=2.0, ws_step=1.0) # The wind directions should be 260, 262 and 264 assert np.allclose(wind_rose.wind_directions, [260, 262, 264]) @@ -267,11 +267,11 @@ def test_time_series_to_wind_rose(): assert np.allclose(ti_table[~np.isnan(ti_table)], 0.06) -def test_time_series_to_wind_rose_wrapping(): +def test_time_series_to_WindRose_wrapping(): wind_directions = np.arange(0.0, 360.0, 0.25) wind_speeds = 8.0 * np.ones_like(wind_directions) time_series = TimeSeries(wind_directions, wind_speeds, 0.06) - wind_rose = time_series.to_wind_rose(wd_step=2.0, ws_step=1.0) + wind_rose = time_series.to_WindRose(wd_step=2.0, ws_step=1.0) # Expert for the first bin in this case to be 0, and the final to be 358 # and both to have equal numbers of points @@ -280,7 +280,7 @@ def test_time_series_to_wind_rose_wrapping(): np.testing.assert_almost_equal(wind_rose.freq_table[0, 0], wind_rose.freq_table[-1, 0]) -def test_time_series_to_wind_rose_with_ti(): +def test_time_series_to_WindRose_with_ti(): wind_directions = np.array([259.8, 260.2, 260.3, 260.1]) wind_speeds = np.array([5.0, 5.0, 5.1, 7.2]) turbulence_intensities = np.array([0.5, 1.0, 1.5, 2.0]) @@ -289,7 +289,7 @@ def test_time_series_to_wind_rose_with_ti(): wind_speeds, turbulence_intensities=turbulence_intensities, ) - wind_rose = time_series.to_wind_rose(wd_step=2.0, ws_step=1.0) + wind_rose = time_series.to_WindRose(wd_step=2.0, ws_step=1.0) # Turbulence intensity should average to 1 in the 5 m/s bin and 2 in the 7 m/s bin ti_table = wind_rose.ti_table From ae48bd1d2738a1a26f75f32a3453c62985bd18d0 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 15:57:26 -0600 Subject: [PATCH 051/120] Update function --- tests/wind_data_integration_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/wind_data_integration_test.py b/tests/wind_data_integration_test.py index a3d0b223c..4cec2eb0c 100644 --- a/tests/wind_data_integration_test.py +++ b/tests/wind_data_integration_test.py @@ -460,7 +460,7 @@ def test_wind_ti_rose_resample(): ) -def test_time_series_to_wind_ti_rose(): +def test_time_series_to_WindTIRose(): wind_directions = np.array([259.8, 260.2, 260.3, 260.1]) wind_speeds = np.array([5.0, 5.0, 5.1, 7.2]) turbulence_intensities = np.array([0.05, 0.1, 0.15, 0.2]) @@ -469,7 +469,7 @@ def test_time_series_to_wind_ti_rose(): wind_speeds, turbulence_intensities=turbulence_intensities, ) - wind_rose = time_series.to_wind_ti_rose(wd_step=2.0, ws_step=1.0, ti_step=0.1) + wind_rose = time_series.to_WindTIRose(wd_step=2.0, ws_step=1.0, ti_step=0.1) # The binning should result in turbulence intensity bins of 0.1 and 0.2 tis_windrose = wind_rose.turbulence_intensities From a7da89ec7b3fb48c9b596f9e1d9672266e516868 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:00:09 -0600 Subject: [PATCH 052/120] Update style --- examples/001_opening_floris_computing_power.py | 6 +++--- examples/002_visualizations.py | 15 ++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/examples/001_opening_floris_computing_power.py b/examples/001_opening_floris_computing_power.py index 41158dcc9..adab8f2c8 100644 --- a/examples/001_opening_floris_computing_power.py +++ b/examples/001_opening_floris_computing_power.py @@ -17,14 +17,14 @@ from floris import FlorisModel -# Initialize FLORIS with the given input file. -# The Floris class is the entry point for most usage. +# The FlorisModel class is the entry point for most usage. +# Initialize using an input yaml file fmodel = FlorisModel("inputs/gch.yaml") # Changing the wind farm layout uses FLORIS' set method to a two-turbine layout fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0]) -# Changing wind speed, wind direction, and turbulence intensity using the set method +# Changing wind speed, wind direction, and turbulence intensity uses the set method # as well. Note that the wind_speeds, wind_directions, and turbulence_intensities # are all specified as arrays of the same length. fmodel.set( diff --git a/examples/002_visualizations.py b/examples/002_visualizations.py index 27603c0a6..4d122d621 100644 --- a/examples/002_visualizations.py +++ b/examples/002_visualizations.py @@ -2,6 +2,12 @@ This example demonstrates the use of the flow and layout visualizations in FLORIS. +FLORIS includes two modules for visualization: + 1) flow_visualization: for visualizing the flow field + 2) layout_visualization: for visualizing the layout of the wind farm +The two modules can be used together to visualize the flow field and the layout +of the wind farm. + """ @@ -12,15 +18,6 @@ from floris.flow_visualization import visualize_cut_plane -# FLORIS includes two modules for visualization: -# 1) flow_visualization: for visualizing the flow field -# 2) layout_visualization: for visualizing the layout of the wind farm -# The two modules can be used together to visualize the flow field and the layout -# of the wind farm. - -# Initialize FLORIS with the given input file via FlorisModel. -# For basic usage, FlorisModel provides a simplified and expressive -# entry point to the simulation routines. fmodel = FlorisModel("inputs/gch.yaml") # Set the farm layout to have 8 turbines irregularly placed From 341ce7eeb5d02cdbda0170cc0ef9705ac4f6ac54 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:05:23 -0600 Subject: [PATCH 053/120] Update 003 example --- examples/003_wind_data_objects.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index 7f10da783..2737f407d 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -109,15 +109,21 @@ ################################################## # The TimeSeries class has a method to generate a wind rose from a time series based on binning -wind_rose = time_series.to_WindRose( - wd_edges=np.arange(0, 360, 3.0), ws_edges=np.arange(4, 20, 2.0) -) +wind_rose = time_series.to_WindRose(wd_edges=np.arange(0, 360, 3.0), ws_edges=np.arange(4, 20, 2.0)) ################################################## # Wind Rose from long CSV FILE ################################################## -#TODO +# The WindRose class can also be initialized from a long CSV file. By long what is meant is +# that the file has a column for each wind direction, wind speed combination. The file can +# also specify the mean TI per bin and the frequency of each bin as seperate columns. + +# If the TI is not provided, can specify a fixed TI for all bins using the ti_col_or_value +# input +wind_rose_from_csv = WindRose.read_csv_long( + "inputs/wind_rose.csv", wd_col="wd", ws_col="ws", freq_col="freq_val", ti_col_or_value=0.06 +) ################################################## # Setting turbulence intensity @@ -164,7 +170,8 @@ # Each of the wind data objects can be used to set the FLORIS model by passing # them in as is to the set method. The FLORIS model will then use the member functions # of the wind data to extract the wind conditions for the simulation. Frequency tables -# are also extracted for AEP calculations. +# are also extracted for expected power and AEP-like calculations. +# Similarly the value data is extracted and maintained. fmodel = FlorisModel("inputs/gch.yaml") From b732894927a3e88a2bb7f8185caa7cca2576fb83 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:06:41 -0600 Subject: [PATCH 054/120] Update example 004 --- examples/004_set.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/004_set.py b/examples/004_set.py index e1ce31c1a..c2b40e273 100644 --- a/examples/004_set.py +++ b/examples/004_set.py @@ -16,7 +16,6 @@ ) -# Initialize FLORIS with the given input file via FlorisModel fmodel = FlorisModel("inputs/gch.yaml") ###################################################### @@ -65,9 +64,6 @@ # Changing the wind farm layout uses FLORIS' set method to a two-turbine layout fmodel.set(layout_x=[0, 500.0], layout_y=[0.0, 0.0]) -# Change the turbine type for the 0th turbine -# TODO: Going to write this one when the param functions are available - ###################################################### # Controls Settings ###################################################### @@ -86,7 +82,8 @@ # which provides the same cosine loss model, and # additionally methods for specifying derating levels for power and disabling turbines. -#TODO: RESET OPERATION HERE? +# Use the reset operation method to clear out control signals +fmodel.reset_operation() # Change to the mixed model turbine fmodel.set_power_thrust_model("mixed") From b2762eeac8ab5eba21b1de0e43e5b088ac14a2d4 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:16:57 -0600 Subject: [PATCH 055/120] Update 005 --- examples/005_getting_power.py | 67 +++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/examples/005_getting_power.py b/examples/005_getting_power.py index 6ea5c1fb4..09ee4ceae 100644 --- a/examples/005_getting_power.py +++ b/examples/005_getting_power.py @@ -1,7 +1,16 @@ """Example 5: Getting Turbine and Farm Power After setting the FlorisModel and running, the next step is typically to get the power output -of the turbines. +of the turbines. FLORIS has several methods for getting power: + +1. `get_turbine_powers()`: Returns the power output of each turbine in the farm for each findex + (n_findex, n_turbines) +2. `get_farm_power()`: Returns the total power output of the farm for each findex (n_findex) +3. `get_expected_farm_power()`: Returns the combination of the farm power over each findex + with the frequency of each findex to get the expected farm power +4. `get_farm_AEP()`: Multiplies the expected farm power by the number of hours in a year to get + the expected annual energy production (AEP) of the farm + """ @@ -11,14 +20,14 @@ from floris import ( FlorisModel, TimeSeries, + WindRose, ) -# Initialize FLORIS with the given input file via FlorisModel fmodel = FlorisModel("inputs/gch.yaml") # Set to a 3-turbine layout -fmodel.set(layout_x=[0, 126*5, 126*10], layout_y=[0, 0, 0]) +fmodel.set(layout_x=[0, 126 * 5, 126 * 10], layout_y=[0, 0, 0]) ###################################################### # Using TimeSeries @@ -68,8 +77,8 @@ # Plot the farm power ax = axarr[1] -ax.plot(wind_directions, farm_power / 1e3, label='Farm Power With Wakes', color='k') -ax.plot(wind_directions, farm_power_no_wake / 1e3, label='Farm Power No Wakes', color='r') +ax.plot(wind_directions, farm_power / 1e3, label="Farm Power With Wakes", color="k") +ax.plot(wind_directions, farm_power_no_wake / 1e3, label="Farm Power No Wakes", color="r") ax.set_xlabel("Wind Direction (deg)") ax.set_ylabel("Power (kW)") ax.grid(True) @@ -79,7 +88,7 @@ # Plot the percent wake losses ax = axarr[2] percent_wake_losses = 100 * (farm_power_no_wake - farm_power) / farm_power_no_wake -ax.plot(wind_directions, percent_wake_losses, label='Percent Wake Losses', color='k') +ax.plot(wind_directions, percent_wake_losses, label="Percent Wake Losses", color="k") ax.set_xlabel("Wind Direction (deg)") ax.set_ylabel("Percent Wake Losses") ax.grid(True) @@ -91,15 +100,45 @@ # Using WindRose ###################################################### -# When running FLORIS using a wind rose, the wind data is held in a -# wind_directions x wind_speeds table -# form, which is unpacked into a 1D array within the FlorisModel. -# Additionally wind direction and -# wind speed combinations which have 0 frequency are not computed, unless the user specifies -# the `compute_zero_freq_occurrence=True` option in the WindRose constructor. +# When running FLORIS using a wind rose, that is when a WindRose or WindTIRose object is +# passed into the set function. The functions get_expected_farm_power and get_farm_AEP +# will operate the same as above, however the functions get_turbine_powers and get_farm_power +# will be reshaped from (n_findex, n_turbines) and +# (n_findex) to (n_wind_dir, n_wind_speed, n_turbines) +# and (n_wind_dir, n_wind_speed) respectively. This is make the powers align more easily with the +# provided wind rose. + +# Declare a WindRose object of 2 wind directions and 3 wind speeds and constant turbulence intensity +wind_rose = WindRose( + wind_directions=np.array([270.0, 280.0]), wind_speeds=np.array([8.0, 9.0, 10.0]), ti_table=0.06 +) + +fmodel.set(wind_data=wind_rose) + +print("==========Wind Rose==========") +print(f"Number of conditions to simulate (2 x 3): {fmodel.core.flow_field.n_findex}") -# When calculating AEP, the bins can be combined automatically +fmodel.run() + +turbine_powers = fmodel.get_turbine_powers() -#TODO: Revist this section after https://github.com/NREL/floris/pull/844 is merged +print(f"Shape of turbine powers: {turbine_powers.shape}") + +farm_power = fmodel.get_farm_power() + +print(f"Shape of farm power: {farm_power.shape}") + + +# Plot the farm power +fig, ax = plt.subplots() + +for w_idx, wd in enumerate(wind_rose.wind_directions): + ax.plot(wind_rose.wind_speeds, farm_power[w_idx, :] / 1e3, label=f"WD: {wd}") + +ax.set_xlabel("Wind Speed (m/s)") +ax.set_ylabel("Power (kW)") +ax.grid(True) +ax.legend() +ax.set_title("Farm Power (from Wind Rose)") plt.show() From 2709b238f86c56676422597110beaccb85306263 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:26:59 -0600 Subject: [PATCH 056/120] Update example 006 --- examples/006_get_farm_aep.py | 116 ++++++++++------------------------- 1 file changed, 32 insertions(+), 84 deletions(-) diff --git a/examples/006_get_farm_aep.py b/examples/006_get_farm_aep.py index 6b59e4a81..714eea822 100644 --- a/examples/006_get_farm_aep.py +++ b/examples/006_get_farm_aep.py @@ -1,7 +1,14 @@ -"""Example 6: Getting AEP +"""Example 6: Getting Expected Power and AEP + +The expected power of a farm is computed by multiplying the power output of the farm by the +frequency of each findex. This is done by the `get_expected_farm_power` method. The expected +AEP is annual energy production is computed by multiplying the expected power by the number of +hours in a year. + +If a wind_data object is provided to the model, the expected power and AEP + can be computed directly by the`get_farm_AEP_with_wind_data` using the frequency table + of the wind data object. If not, a frequency table must be passed into these functions -AEP is annual energy production and can is typically a weighted sum over farm power. This -example demonstrates how to calculate the AEP """ @@ -15,7 +22,6 @@ ) -# Initialize FLORIS with the given input file via FlorisModel fmodel = FlorisModel("inputs/gch.yaml") @@ -26,9 +32,6 @@ # Using TimeSeries -# In the case of time series data, although not required, the typical assumption is -# that each time step is equally likely. - # Randomly generated a time series with time steps = 365 * 24 N = 365 * 24 wind_directions = np.random.uniform(0, 360, N) @@ -39,105 +42,50 @@ wind_directions=wind_directions, wind_speeds=wind_speeds, turbulence_intensities=0.06 ) -# Note that the AEP functions run the model -# So it is not necessary to call run() +# Set the wind data fmodel.set(wind_data=time_series) -aep = fmodel.get_farm_AEP_with_wind_data(time_series) +# Run the model +fmodel.run() + +expected_farm_power = fmodel.get_expected_farm_power() +aep = fmodel.get_farm_AEP() # Note this is equivalent to the following -aep_b = fmodel.get_farm_AEP(time_series.unpack_freq()) +aep_b = fmodel.get_farm_AEP(freq=time_series.unpack_freq()) print(f"AEP from time series: {aep}, and re-computed AEP: {aep_b}") -# Using WindRose - -# Assume a provided wind rose of frequency by wind direction and wind speed -df_wr = pd.read_csv("inputs/wind_rose.csv") - -# Get the wind directions, wind speeds, and frequency table -wind_direction_values = df_wr["wd"].values -wind_speed_values = df_wr["ws"].values -wind_directions = df_wr["wd"].unique() -wind_speeds = df_wr["ws"].unique() -freq_vals = df_wr["freq_val"].values / df_wr["freq_val"].sum() - -n_row = df_wr.shape[0] -n_wd = len(wind_directions) -n_ws = len(wind_speeds) - -wd_step = wind_directions[1] - wind_directions[0] -ws_step = wind_speeds[1] - wind_speeds[0] - -print("The wind rose dataframe looks as follows:") -print(df_wr.head()) -print(f"There are {n_row} rows, {n_wd} unique wind directions, and {n_ws} unique wind speeds") -print(f"The wind direction has a step of {wd_step} and the wind speed has a step of {ws_step}") - -# Declare a frequency table of size (n_wd, n_ws) -freq_table = np.zeros((n_wd, n_ws)) - -# Populate the frequency table using the values of wind_direction_values, -# wind_speed_values, and freq_vals -for i in range(n_row): - wd = wind_direction_values[i] - ws = wind_speed_values[i] - freq = freq_vals[i] +# Using WindRose============================================== - # Find the index of the wind direction and wind speed - wd_idx = np.where(wind_directions == wd)[0][0] - ws_idx = np.where(wind_speeds == ws)[0][0] - - # Populate the frequency table - freq_table[wd_idx, ws_idx] = freq - -# Normalize the frequency table -freq_table = freq_table / freq_table.sum() - -print(f"The frequency table has shape {freq_table.shape}") - -# Set up a wind rose -wind_rose = WindRose( - wind_directions=wind_directions, - wind_speeds=wind_speeds, - freq_table=freq_table, - ti_table=0.06, # Assume contant TI -) - -# Note that the wind rose could have been computed directly -# by first building a TimeSeries and applying -# the provided frequencies as bin weights in resampling -time_series = TimeSeries( - wind_directions=wind_direction_values, - wind_speeds=wind_speed_values, - turbulence_intensities=0.06, +# Load the wind rose from csv as in example 003 +wind_rose = WindRose.read_csv_long( + "inputs/wind_rose.csv", wd_col="wd", ws_col="ws", freq_col="freq_val", ti_col_or_value=0.06 ) -# Convert time series to wind rose using the frequencies as bin weights -wind_rose_from_time_series = time_series.to_WindRose( - wd_step=wd_step, ws_step=ws_step, bin_weights=freq_vals -) +# Store some values +n_wd = len(wind_rose.wind_directions) +n_ws = len(wind_rose.wind_speeds) -print("Wind rose from wind_rose and wind_rose_from_time_series are equivalent:") -print( - " -- Directions: " - f"{np.allclose(wind_rose.wind_directions, wind_rose_from_time_series.wind_directions)}" -) -print(f" -- Speeds: {np.allclose(wind_rose.wind_speeds, wind_rose_from_time_series.wind_speeds)}") -print(f" -- Freq: {np.allclose(wind_rose.freq_table, wind_rose_from_time_series.freq_table)}") +# Store the number of elements of the freq_table which are 0 +n_zeros = np.sum(wind_rose.freq_table == 0) # Set the wind rose fmodel.set(wind_data=wind_rose) +# Run the model +fmodel.run() + # Note that the frequency table contains 0 frequency for some wind directions and wind speeds # and we've not selected to compute 0 frequency bins, therefore the n_findex will be less than # the total number of wind directions and wind speed combinations -print(f"Total number of rows in input wind rose: {n_row}") +print(f"Total number of wind direction and wind speed combination: {n_wd * n_ws}") +print(f"Number of 0 frequency bins: {n_zeros}") print(f"n_findex: {fmodel.core.flow_field.n_findex}") # Get the AEP -aep = fmodel.get_farm_AEP_with_wind_data(wind_rose) +aep = fmodel.get_farm_AEP() # Print the AEP print(f"AEP from wind rose: {aep/1E9:.3f} (GW-h)") From 92167809cc98122c9baf33476341c6d33a5a52f1 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:30:00 -0600 Subject: [PATCH 057/120] Update example 006 --- examples/006_get_farm_aep.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/006_get_farm_aep.py b/examples/006_get_farm_aep.py index 714eea822..5be3886cb 100644 --- a/examples/006_get_farm_aep.py +++ b/examples/006_get_farm_aep.py @@ -89,3 +89,15 @@ # Print the AEP print(f"AEP from wind rose: {aep/1E9:.3f} (GW-h)") + +# Run the model again, without wakes, and use the result to compute the wake losses +fmodel.run_no_wake() + +# Get the AEP without wake +aep_no_wake = fmodel.get_farm_AEP() + +# Compute the wake losses +wake_losses = 100 * (aep_no_wake - aep) / aep_no_wake + +# Print the wake losses +print(f"Wake losses: {wake_losses:.2f}%") From c36eb52a4e540a1e70dbb138038942acedc3e91a Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:30:53 -0600 Subject: [PATCH 058/120] Update example 07 and 007 --- examples/007_sweeping_variables.py | 2 - examples/07_calc_aep_from_rose.py | 69 ------------------------------ 2 files changed, 71 deletions(-) delete mode 100644 examples/07_calc_aep_from_rose.py diff --git a/examples/007_sweeping_variables.py b/examples/007_sweeping_variables.py index f285c2d33..52437c951 100644 --- a/examples/007_sweeping_variables.py +++ b/examples/007_sweeping_variables.py @@ -16,7 +16,6 @@ ) -# Initialize FLORIS with the given input file via FlorisModel fmodel = FlorisModel("inputs/gch.yaml") # Set to a 2 turbine layout @@ -124,7 +123,6 @@ ###################################################### # Since we're changing control modes, need to reset the operation -#TODO: Needed? fmodel.reset_operation() # To the de-rating need to change the power_thrust_mode to mixed or simple de-rating diff --git a/examples/07_calc_aep_from_rose.py b/examples/07_calc_aep_from_rose.py deleted file mode 100644 index 135a4c119..000000000 --- a/examples/07_calc_aep_from_rose.py +++ /dev/null @@ -1,69 +0,0 @@ - -import numpy as np -import pandas as pd -from scipy.interpolate import NearestNDInterpolator - -from floris import FlorisModel - - -""" -This example demonstrates how to calculate the Annual Energy Production (AEP) -of a wind farm using wind rose information stored in a .csv file. - -The wind rose information is first loaded, after which we initialize our FlorisModel. -A 3 turbine farm is generated, and then the turbine wakes and powers -are calculated across all the wind directions. Finally, the farm power is -converted to AEP and reported out. -""" - -# Read the windrose information file and display -df_wr = pd.read_csv("inputs/wind_rose.csv") -print("The wind rose dataframe looks as follows: \n\n {} \n".format(df_wr)) - -# Derive the wind directions and speeds we need to evaluate in FLORIS -wd_grid, ws_grid = np.meshgrid( - np.array(df_wr["wd"].unique(), dtype=float), # wind directions - np.array(df_wr["ws"].unique(), dtype=float), # wind speeds - indexing="ij" -) -wind_directions = wd_grid.flatten() -wind_speeds = ws_grid.flatten() -turbulence_intensities = np.ones_like(wind_directions) * 0.06 - -# Format the frequency array into the conventional FLORIS v3 format, which is -# an np.array with shape (n_wind_directions, n_wind_speeds). To avoid having -# to manually derive how the variables are sorted and how to reshape the -# one-dimensional frequency array, we use a nearest neighbor interpolant. This -# ensures the frequency values are mapped appropriately to the new 2D array. -freq_interp = NearestNDInterpolator(df_wr[["wd", "ws"]], df_wr["freq_val"]) -freq = freq_interp(wd_grid, ws_grid).flatten() - -# Normalize the frequency array to sum to exactly 1.0 -freq = freq / np.sum(freq) - -# Load the FLORIS object -fmodel = FlorisModel("inputs/gch.yaml") # GCH model -# fmodel = FlorisModel("inputs/cc.yaml") # CumulativeCurl model - -# Assume a three-turbine wind farm with 5D spacing. We reinitialize the -# floris object and assign the layout, wind speed and wind direction arrays. -D = fmodel.core.farm.rotor_diameters[0] # Rotor diameter for the NREL 5 MW -fmodel.set( - layout_x=[0.0, 5 * D, 10 * D], - layout_y=[0.0, 0.0, 0.0], - wind_directions=wind_directions, - wind_speeds=wind_speeds, - turbulence_intensities=turbulence_intensities, -) -fmodel.run() - -# Compute the AEP using the default settings -aep = fmodel.get_farm_AEP(freq=freq) -print("Farm AEP: {:.3f} GWh".format(aep / 1.0e9)) - -# Finally, we can also compute the AEP while ignoring all wake calculations. -# This can be useful to quantity the annual wake losses in the farm. Such -# calculations can be facilitated by first running with run_no_wake(). -fmodel.run_no_wake() -aep_no_wake = fmodel.get_farm_AEP(freq=freq) -print("Farm AEP (no wakes): {:.3f} GWh".format(aep_no_wake / 1.0e9)) From 504ea91de580b9e8019a2fb686ae35a08ef4d2b4 Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:35:11 -0600 Subject: [PATCH 059/120] Organize examples --- .../{layout_visualizations.py => 001_layout_visualizations.py} | 0 .../{visualize_y_cut_plane.py => 002_visualize_y_cut_plane.py} | 0 .../{visualize_cross_plane.py => 003_visualize_cross_plane.py} | 0 .../{visualize_rotor_values.py => 004_visualize_rotor_values.py} | 0 ...ing_turbines.py => 005_visualize_flow_by_sweeping_turbines.py} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename examples/examples_visualizations/{layout_visualizations.py => 001_layout_visualizations.py} (100%) rename examples/examples_visualizations/{visualize_y_cut_plane.py => 002_visualize_y_cut_plane.py} (100%) rename examples/examples_visualizations/{visualize_cross_plane.py => 003_visualize_cross_plane.py} (100%) rename examples/examples_visualizations/{visualize_rotor_values.py => 004_visualize_rotor_values.py} (100%) rename examples/examples_visualizations/{visualize_flow_by_sweeping_turbines.py => 005_visualize_flow_by_sweeping_turbines.py} (100%) diff --git a/examples/examples_visualizations/layout_visualizations.py b/examples/examples_visualizations/001_layout_visualizations.py similarity index 100% rename from examples/examples_visualizations/layout_visualizations.py rename to examples/examples_visualizations/001_layout_visualizations.py diff --git a/examples/examples_visualizations/visualize_y_cut_plane.py b/examples/examples_visualizations/002_visualize_y_cut_plane.py similarity index 100% rename from examples/examples_visualizations/visualize_y_cut_plane.py rename to examples/examples_visualizations/002_visualize_y_cut_plane.py diff --git a/examples/examples_visualizations/visualize_cross_plane.py b/examples/examples_visualizations/003_visualize_cross_plane.py similarity index 100% rename from examples/examples_visualizations/visualize_cross_plane.py rename to examples/examples_visualizations/003_visualize_cross_plane.py diff --git a/examples/examples_visualizations/visualize_rotor_values.py b/examples/examples_visualizations/004_visualize_rotor_values.py similarity index 100% rename from examples/examples_visualizations/visualize_rotor_values.py rename to examples/examples_visualizations/004_visualize_rotor_values.py diff --git a/examples/examples_visualizations/visualize_flow_by_sweeping_turbines.py b/examples/examples_visualizations/005_visualize_flow_by_sweeping_turbines.py similarity index 100% rename from examples/examples_visualizations/visualize_flow_by_sweeping_turbines.py rename to examples/examples_visualizations/005_visualize_flow_by_sweeping_turbines.py From ab10b6aba59e3f4f159fbcdd02d5f61fa88d160b Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 22 Mar 2024 16:36:52 -0600 Subject: [PATCH 060/120] Update first layout --- ...imize_layout.py => 001_optimize_layout.py} | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) rename examples/examples_layout_optimization/{15_optimize_layout.py => 001_optimize_layout.py} (97%) diff --git a/examples/examples_layout_optimization/15_optimize_layout.py b/examples/examples_layout_optimization/001_optimize_layout.py similarity index 97% rename from examples/examples_layout_optimization/15_optimize_layout.py rename to examples/examples_layout_optimization/001_optimize_layout.py index df0f1d460..66bf3109e 100644 --- a/examples/examples_layout_optimization/15_optimize_layout.py +++ b/examples/examples_layout_optimization/001_optimize_layout.py @@ -1,4 +1,15 @@ +"""Example: Optimize Layout +This example shows a simple layout optimization using the python module Scipy. + +A 4 turbine array is optimized such that the layout of the turbine produces the +highest annual energy production (AEP) based on the given wind resource. The turbines +are constrained to a square boundary and a random wind resource is supplied. The results +of the optimization show that the turbines are pushed to the outer corners of the boundary, +which makes sense in order to maximize the energy production by minimizing wake interactions. +""" + + import os import matplotlib.pyplot as plt @@ -10,19 +21,9 @@ ) -""" -This example shows a simple layout optimization using the python module Scipy. - -A 4 turbine array is optimized such that the layout of the turbine produces the -highest annual energy production (AEP) based on the given wind resource. The turbines -are constrained to a square boundary and a random wind resource is supplied. The results -of the optimization show that the turbines are pushed to the outer corners of the boundary, -which makes sense in order to maximize the energy production by minimizing wake interactions. -""" - # Initialize the FLORIS interface fi file_dir = os.path.dirname(os.path.abspath(__file__)) -fmodel = FlorisModel('inputs/gch.yaml') +fmodel = FlorisModel('../inputs/gch.yaml') # Setup 72 wind directions with a 1 wind speed and frequency distribution wind_directions = np.arange(0, 360.0, 5.0) From 867b2035f990b66dc8699760a09a6fdea7ed01a3 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 1 Apr 2024 13:32:29 -0600 Subject: [PATCH 061/120] move 09 to het examples --- .../09_compare_farm_power_with_neighbor.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{ => examples_heterogeneous}/09_compare_farm_power_with_neighbor.py (100%) diff --git a/examples/09_compare_farm_power_with_neighbor.py b/examples/examples_heterogeneous/09_compare_farm_power_with_neighbor.py similarity index 100% rename from examples/09_compare_farm_power_with_neighbor.py rename to examples/examples_heterogeneous/09_compare_farm_power_with_neighbor.py From df558808e6bee4f16f503db200ba5d8d3aafeb21 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 1 Apr 2024 17:10:24 -0600 Subject: [PATCH 062/120] Add optimize example 4 --- .../004_optimize_yaw_aep.py | 155 +++++++++ .../04_optimize_yaw_aep.py | 305 ------------------ 2 files changed, 155 insertions(+), 305 deletions(-) create mode 100644 examples/examples_control_optimization/004_optimize_yaw_aep.py delete mode 100644 examples/examples_control_optimization/04_optimize_yaw_aep.py diff --git a/examples/examples_control_optimization/004_optimize_yaw_aep.py b/examples/examples_control_optimization/004_optimize_yaw_aep.py new file mode 100644 index 000000000..6ab346046 --- /dev/null +++ b/examples/examples_control_optimization/004_optimize_yaw_aep.py @@ -0,0 +1,155 @@ +"""Example: Optimize yaw and compare AEP + +This example demonstrates how to perform a yaw optimization and evaluate the performance +over a full wind rose. + +The script performs the following steps: + 1. Load a wind rose from a csv file + 2. Calculates the optimal yaw angles for a wind speed of 8 m/s across the directions + 3. Applies the optimal yaw angles to the wind rose and calculates the AEP + +""" + +from time import perf_counter as timerpc + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import seaborn as sns + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, +) +from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR + + +# Load the wind rose from csv +wind_rose = WindRose.read_csv_long( + "../inputs/wind_rose.csv", wd_col="wd", ws_col="ws", freq_col="freq_val", ti_col_or_value=0.06 +) + +# Load FLORIS +fmodel = FlorisModel("../inputs/gch.yaml") + +# 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), +) +fmodel.set(layout_x=X.flatten(), layout_y=Y.flatten()) + +# Get the number of turbines +n_turbines = len(fmodel.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) + +# Get the optimal angles +start_time = timerpc() +yaw_opt = YawOptimizationSR( + fmodel=fmodel, + 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=True, +) +df_opt = yaw_opt.optimize() +end_time = timerpc() +t_tot = end_time - start_time +t_fmodel = yaw_opt.time_spent_in_floris +print("Optimization finished in {:.2f} seconds.".format(t_tot)) +print(" ") +print(df_opt) +print(" ") + + +# Calculate the AEP in the baseline case +fmodel.set(wind_data=wind_rose) +fmodel.run() +farm_power_baseline = fmodel.get_farm_power() +aep_baseline = fmodel.get_farm_AEP() +print("Baseline AEP: {:.2f} GWh.".format(aep_baseline)) + +# 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 +# and ramped down to 0 above and below this range + +# Grab wind speeds and wind directions from the fmodel. Note that we do this because the +# 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.core.flow_field.wind_directions +wind_speeds = fmodel.core.flow_field.wind_speeds +n_findex = fmodel.core.flow_field.n_findex + + +# Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds +yaw_angles_opt = np.vstack(df_opt["yaw_angles_opt"]) +yaw_angles_wind_rose = np.zeros((n_findex, n_turbines)) +for i in range(n_findex): + wind_speed = wind_speeds[i] + wind_direction = wind_directions[i] + + # Interpolate the optimal yaw angles for this wind direction from df_opt + id_opt = df_opt["wind_direction"] == wind_direction + yaw_opt_full = np.array(df_opt.loc[id_opt, "yaw_angles_opt"])[0] + + # Now decide what to do for different wind speeds + if (wind_speed < 4.0) | (wind_speed > 14.0): + yaw_opt = np.zeros(n_turbines) # do nothing for very low/high speeds + elif wind_speed < 6.0: + yaw_opt = yaw_opt_full * (6.0 - wind_speed) / 2.0 # Linear ramp up + elif wind_speed > 12.0: + yaw_opt = yaw_opt_full * (14.0 - wind_speed) / 2.0 # Linear ramp down + else: + yaw_opt = yaw_opt_full # Apply full offsets between 6.0 and 12.0 m/s + + # Save to collective array + yaw_angles_wind_rose[i, :] = yaw_opt + + +# Now apply the optimal yaw angles and get the AEP +fmodel.set(yaw_angles=yaw_angles_wind_rose) +fmodel.run() +aep_opt = fmodel.get_farm_AEP() +aep_uplift = 100.0 * (aep_opt / aep_baseline - 1) +farm_power_opt = fmodel.get_farm_power() +print("Optimal AEP: {:.2f} GWh.".format(aep_opt)) +print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift)) + +# Use farm_power_baseline, farm_power_opt and wind_data to make a heat map of uplift by +# wind direction and wind speed +wind_directions = wind_rose.wind_directions +wind_speeds = wind_rose.wind_speeds +relative_gain = farm_power_opt - farm_power_baseline + +# Plt the heatmap with wind speeds on x, wind directions ony and relative gain as the color +fig, ax = plt.subplots(figsize=(7, 12)) +sns.heatmap(relative_gain, cmap="viridis", cbar_kws={"label": "Relative gain (%)"}, ax=ax) +ax.set_yticks(np.arange(len(wind_directions)) + 0.5) +ax.set_yticklabels(wind_directions) +ax.set_xticks(np.arange(len(wind_speeds)) + 0.5) +ax.set_xticklabels(wind_speeds) +ax.set_ylabel("Wind direction (deg)") +ax.set_xlabel("Wind speed (m/s)") +plt.tight_layout() + +# Reduce y tick font size +for tick in ax.yaxis.get_major_ticks(): + tick.label.set_fontsize(8) + +# Set y ticks to be horizontal +for tick in ax.get_yticklabels(): + tick.set_rotation(0) + +ax.set_title("Uplift in farm power by wind direction and wind speed", fontsize=12) + +plt.show() diff --git a/examples/examples_control_optimization/04_optimize_yaw_aep.py b/examples/examples_control_optimization/04_optimize_yaw_aep.py deleted file mode 100644 index 1360db458..000000000 --- a/examples/examples_control_optimization/04_optimize_yaw_aep.py +++ /dev/null @@ -1,305 +0,0 @@ - -"""Example: Optimize yaw and compare AEP - -This example demonstrates how to perform a yaw optimization and evaluate the performance -over a full wind rose. - -The beginning of the file contains the definition of several functions used in the main part -of the script. - -Within the main part of the script, we first load the wind rose information. We then initialize -our Floris Interface object. We determine the baseline AEP using the wind rose information, and -then perform the yaw optimization over 72 wind directions with 1 wind speed per direction. The -optimal yaw angles are then used to determine yaw angles across all the wind speeds included in -the wind rose. Lastly, the final AEP is calculated and analysis of the results are -shown in several plots. -""" - -from time import perf_counter as timerpc - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd - -from floris import FlorisModel -from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR - - -def load_floris(): - # Load the default example floris object - fmodel = FlorisModel("../inputs/gch.yaml") # GCH model - # fmodel = FlorisModel("inputs/cc.yaml") # New CumulativeCurl model - - # Specify wind farm layout and update in the floris object - N = 5 # 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), - ) - fmodel.set(layout_x=X.flatten(), layout_y=Y.flatten()) - - return fmodel - - -def load_windrose(): - fn = "../inputs/wind_rose.csv" - df = pd.read_csv(fn) - df = df[(df["ws"] < 22)].reset_index(drop=True) # Reduce size - df["freq_val"] = df["freq_val"] / df["freq_val"].sum() # Normalize wind rose frequencies - - return df - - -def calculate_aep(fmodel, df_windrose, column_name="farm_power"): - from scipy.interpolate import NearestNDInterpolator - - # Define columns - nturbs = len(fmodel.layout_x) - yaw_cols = ["yaw_{:03d}".format(ti) for ti in range(nturbs)] - - if "yaw_000" not in df_windrose.columns: - df_windrose[yaw_cols] = 0.0 # Add zeros - - # Derive the wind directions and speeds we need to evaluate in FLORIS - wd_array = np.array(df_windrose["wd"], dtype=float) - ws_array = np.array(df_windrose["ws"], dtype=float) - turbulence_intensities = 0.06 * np.ones_like(wd_array) - yaw_angles = np.array(df_windrose[yaw_cols], dtype=float) - fmodel.set( - wind_directions=wd_array, - wind_speeds=ws_array, - turbulence_intensities=turbulence_intensities, - yaw_angles=yaw_angles - ) - - # Calculate FLORIS for every WD and WS combination and get the farm power - fmodel.run() - farm_power_array = fmodel.get_farm_power() - - # Now map FLORIS solutions to dataframe - interpolant = NearestNDInterpolator( - np.vstack([wd_array, ws_array]).T, - farm_power_array.flatten() - ) - df_windrose[column_name] = interpolant(df_windrose[["wd", "ws"]]) # Save to dataframe - df_windrose[column_name] = df_windrose[column_name].fillna(0.0) # Replace NaNs with 0.0 - - # Calculate AEP in GWh - aep = np.dot(df_windrose["freq_val"], df_windrose[column_name]) * 365 * 24 / 1e9 - - return aep - - -if __name__ == "__main__": - # Load a dataframe containing the wind rose information - df_windrose = load_windrose() - - # Load FLORIS - fmodel = load_floris() - ws_array = 8.0 * np.ones_like(fmodel.core.flow_field.wind_directions) - fmodel.set(wind_speeds=ws_array) - nturbs = len(fmodel.layout_x) - - # First, get baseline AEP, without wake steering - start_time = timerpc() - print(" ") - print("===========================================================") - print("Calculating baseline annual energy production (AEP)...") - aep_bl = calculate_aep(fmodel, df_windrose, "farm_power_baseline") - t = timerpc() - start_time - print("Baseline AEP: {:.3f} GWh. Time spent: {:.1f} s.".format(aep_bl, t)) - print("===========================================================") - print(" ") - - # Now optimize the yaw angles using the Serial Refine method - print("Now starting yaw optimization for the entire wind rose...") - start_time = timerpc() - wd_array = np.arange(0.0, 360.0, 5.0) - ws_array = 8.0 * np.ones_like(wd_array) - turbulence_intensities = 0.06 * np.ones_like(wd_array) - fmodel.set( - wind_directions=wd_array, - wind_speeds=ws_array, - turbulence_intensities=turbulence_intensities, - ) - yaw_opt = YawOptimizationSR( - fmodel=fmodel, - 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=True, - ) - - df_opt = yaw_opt.optimize() - end_time = timerpc() - t_tot = end_time - start_time - t_fmodel = yaw_opt.time_spent_in_floris - - print("Optimization finished in {:.2f} seconds.".format(t_tot)) - print(" ") - print(df_opt) - print(" ") - - # Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds - yaw_angles_opt = np.vstack(df_opt["yaw_angles_opt"]) - yaw_angles_wind_rose = np.zeros((df_windrose.shape[0], nturbs)) - for ii, idx in enumerate(df_windrose.index): - wind_speed = df_windrose.loc[idx, "ws"] - wind_direction = df_windrose.loc[idx, "wd"] - - # Interpolate the optimal yaw angles for this wind direction from df_opt - id_opt = df_opt["wind_direction"] == wind_direction - yaw_opt_full = np.array(df_opt.loc[id_opt, "yaw_angles_opt"])[0] - - # Now decide what to do for different wind speeds - if (wind_speed < 4.0) | (wind_speed > 14.0): - yaw_opt = np.zeros(nturbs) # do nothing for very low/high speeds - elif wind_speed < 6.0: - yaw_opt = yaw_opt_full * (6.0 - wind_speed) / 2.0 # Linear ramp up - elif wind_speed > 12.0: - yaw_opt = yaw_opt_full * (14.0 - wind_speed) / 2.0 # Linear ramp down - else: - yaw_opt = yaw_opt_full # Apply full offsets between 6.0 and 12.0 m/s - - # Save to collective array - yaw_angles_wind_rose[ii, :] = yaw_opt - - # Add optimal and interpolated angles to the wind rose dataframe - yaw_cols = ["yaw_{:03d}".format(ti) for ti in range(nturbs)] - df_windrose[yaw_cols] = yaw_angles_wind_rose - - # Now get AEP with optimized yaw angles - start_time = timerpc() - print("==================================================================") - print("Calculating annual energy production (AEP) with wake steering...") - aep_opt = calculate_aep(fmodel, df_windrose, "farm_power_opt") - aep_uplift = 100.0 * (aep_opt / aep_bl - 1) - t = timerpc() - start_time - print("Optimal AEP: {:.3f} GWh. Time spent: {:.1f} s.".format(aep_opt, t)) - print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift)) - print("==================================================================") - print(" ") - - # Now calculate helpful variables and then plot wind rose information - df = df_windrose.copy() - df["farm_power_relative"] = ( - df["farm_power_opt"] / df["farm_power_baseline"] - ) - df["farm_energy_baseline"] = df["freq_val"] * df["farm_power_baseline"] - df["farm_energy_opt"] = df["freq_val"] * df["farm_power_opt"] - df["energy_uplift"] = df["farm_energy_opt"] - df["farm_energy_baseline"] - df["rel_energy_uplift"] = df["energy_uplift"] / df["energy_uplift"].sum() - - # Plot power and AEP uplift across wind direction - fig, ax = plt.subplots(nrows=3, sharex=True) - - df_8ms = df[df["ws"] == 8.0].reset_index(drop=True) - pow_uplift = 100 * ( - df_8ms["farm_power_opt"] / df_8ms["farm_power_baseline"] - 1 - ) - ax[0].bar( - x=df_8ms["wd"], - height=pow_uplift, - color="darkgray", - edgecolor="black", - width=4.5, - ) - ax[0].set_ylabel("Power uplift \n at 8 m/s (%)") - ax[0].grid(True) - - dist = df.groupby("wd").sum().reset_index() - ax[1].bar( - x=dist["wd"], - height=100 * dist["rel_energy_uplift"], - color="darkgray", - edgecolor="black", - width=4.5, - ) - ax[1].set_ylabel("Contribution to \n AEP uplift (%)") - ax[1].grid(True) - - ax[2].bar( - x=dist["wd"], - height=dist["freq_val"], - color="darkgray", - edgecolor="black", - width=4.5, - ) - ax[2].set_xlabel("Wind direction (deg)") - ax[2].set_ylabel("Frequency of \n occurrence (-)") - ax[2].grid(True) - plt.tight_layout() - - # Plot power and AEP uplift across wind direction - fig, ax = plt.subplots(nrows=3, sharex=True) - - df_avg = df.groupby("ws").mean().reset_index(drop=False) - mean_power_uplift = 100.0 * (df_avg["farm_power_relative"] - 1.0) - ax[0].bar( - x=df_avg["ws"], - height=mean_power_uplift, - color="darkgray", - edgecolor="black", - width=0.95, - ) - ax[0].set_ylabel("Mean power \n uplift (%)") - ax[0].grid(True) - - dist = df.groupby("ws").sum().reset_index() - ax[1].bar( - x=dist["ws"], - height=100 * dist["rel_energy_uplift"], - color="darkgray", - edgecolor="black", - width=0.95, - ) - ax[1].set_ylabel("Contribution to \n AEP uplift (%)") - ax[1].grid(True) - - ax[2].bar( - x=dist["ws"], - height=dist["freq_val"], - color="darkgray", - edgecolor="black", - width=0.95, - ) - ax[2].set_xlabel("Wind speed (m/s)") - ax[2].set_ylabel("Frequency of \n occurrence (-)") - ax[2].grid(True) - plt.tight_layout() - - # Now plot yaw angle distributions over wind direction up to first three turbines - for ti in range(np.min([nturbs, 3])): - fig, ax = plt.subplots(figsize=(6, 3.5)) - ax.plot( - df_opt["wind_direction"], - yaw_angles_opt[:, ti], - "-o", - color="maroon", - markersize=3, - label="For wind speeds between 6 and 12 m/s", - ) - ax.plot( - df_opt["wind_direction"], - 0.5 * yaw_angles_opt[:, ti], - "-v", - color="dodgerblue", - markersize=3, - label="For wind speeds of 5 and 13 m/s", - ) - ax.plot( - df_opt["wind_direction"], - 0.0 * yaw_angles_opt[:, ti], - "-o", - color="grey", - markersize=3, - label="For wind speeds below 4 and above 14 m/s", - ) - ax.set_ylabel("Assigned yaw offsets (deg)") - ax.set_xlabel("Wind direction (deg)") - ax.set_title("Turbine {:d}".format(ti)) - ax.grid(True) - ax.legend() - plt.tight_layout() - - plt.show() From 8bade8bf4a538076b9fcbf1a6df94b61cc6be9d7 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 1 Apr 2024 17:36:42 -0600 Subject: [PATCH 063/120] small fixes --- examples/examples_control_optimization/004_optimize_yaw_aep.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/examples_control_optimization/004_optimize_yaw_aep.py b/examples/examples_control_optimization/004_optimize_yaw_aep.py index 6ab346046..934e60ebe 100644 --- a/examples/examples_control_optimization/004_optimize_yaw_aep.py +++ b/examples/examples_control_optimization/004_optimize_yaw_aep.py @@ -34,7 +34,7 @@ fmodel = FlorisModel("../inputs/gch.yaml") # Specify wind farm layout and update in the floris object -N = 2 # number of turbines per row and per column +N = 4 # 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), @@ -64,7 +64,6 @@ df_opt = yaw_opt.optimize() end_time = timerpc() t_tot = end_time - start_time -t_fmodel = yaw_opt.time_spent_in_floris print("Optimization finished in {:.2f} seconds.".format(t_tot)) print(" ") print(df_opt) From 2d895fda53ddb6e7e7eee7f791bbbf4433acc697 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 1 Apr 2024 17:43:52 -0600 Subject: [PATCH 064/120] Push up broken 005 example --- .../005_optimize_yaw_aep_parallel.py | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py diff --git a/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py b/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py new file mode 100644 index 000000000..4c47f712c --- /dev/null +++ b/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py @@ -0,0 +1,180 @@ +"""Example: Optimize yaw and compare AEP in parallel + +This example demonstrates how to perform a yaw optimization and evaluate the performance +over a full wind rose. The example repeats the steps in 04 except using parallel +optimization and evaluation. + +The script performs the following steps: + 1. Load a wind rose from a csv file + 2. Calculates the optimal yaw angles for a wind speed of 8 m/s across the directions + 3. Applies the optimal yaw angles to the wind rose and calculates the AEP + +""" + +from time import perf_counter as timerpc + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import seaborn as sns + +from floris import ( + FlorisModel, + ParallelFlorisModel, + TimeSeries, + WindRose, +) +from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR + + +# THIS IS IMPORTANT +if __name__ == "__main__": + + # Load the wind rose from csv + wind_rose = WindRose.read_csv_long( + "../inputs/wind_rose.csv", wd_col="wd", ws_col="ws", freq_col="freq_val", + ti_col_or_value=0.06 + ) + + # Load FLORIS + fmodel = FlorisModel("../inputs/gch.yaml") + + # 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), + ) + fmodel.set(layout_x=X.flatten(), layout_y=Y.flatten()) + + # Get the number of turbines + n_turbines = len(fmodel.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, + ) + + # 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, + Ny_passes=[5, 4], + exclude_downstream_turbines=False, + ) + end_time = timerpc() + t_tot = end_time - start_time + print("Optimization finished in {:.2f} seconds.".format(t_tot)) + print(" ") + print(df_opt) + print(" ") + + + # 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 + + farm_power_baseline = pfmodel.get_farm_power() + aep_baseline = pfmodel.get_farm_AEP() + print("Baseline AEP: {:.2f} GWh.".format(aep_baseline)) + + # # 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 + # # and ramped down to 0 above and below this range + + # # Grab wind speeds and wind directions from the fmodel. Note that we do this because the + # # 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.core.flow_field.wind_directions + # wind_speeds = fmodel.core.flow_field.wind_speeds + # n_findex = fmodel.core.flow_field.n_findex + + + # # Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds + # yaw_angles_opt = np.vstack(df_opt["yaw_angles_opt"]) + # yaw_angles_wind_rose = np.zeros((n_findex, n_turbines)) + # for i in range(n_findex): + # wind_speed = wind_speeds[i] + # wind_direction = wind_directions[i] + + # # Interpolate the optimal yaw angles for this wind direction from df_opt + # id_opt = df_opt["wind_direction"] == wind_direction + # yaw_opt_full = np.array(df_opt.loc[id_opt, "yaw_angles_opt"])[0] + + # # Now decide what to do for different wind speeds + # if (wind_speed < 4.0) | (wind_speed > 14.0): + # yaw_opt = np.zeros(n_turbines) # do nothing for very low/high speeds + # elif wind_speed < 6.0: + # yaw_opt = yaw_opt_full * (6.0 - wind_speed) / 2.0 # Linear ramp up + # elif wind_speed > 12.0: + # yaw_opt = yaw_opt_full * (14.0 - wind_speed) / 2.0 # Linear ramp down + # else: + # yaw_opt = yaw_opt_full # Apply full offsets between 6.0 and 12.0 m/s + + # # Save to collective array + # yaw_angles_wind_rose[i, :] = yaw_opt + + + # # Now apply the optimal yaw angles and get the AEP + # fmodel.set(yaw_angles=yaw_angles_wind_rose) + # fmodel.run() + # aep_opt = fmodel.get_farm_AEP() + # aep_uplift = 100.0 * (aep_opt / aep_baseline - 1) + # farm_power_opt = fmodel.get_farm_power() + # print("Optimal AEP: {:.2f} GWh.".format(aep_opt)) + # print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift)) + + # # Use farm_power_baseline, farm_power_opt and wind_data to make a heat map of uplift by + # # wind direction and wind speed + # wind_directions = wind_rose.wind_directions + # wind_speeds = wind_rose.wind_speeds + # relative_gain = farm_power_opt - farm_power_baseline + + # # Plt the heatmap with wind speeds on x, wind directions ony and relative gain as the color + # fig, ax = plt.subplots(figsize=(7, 12)) + # sns.heatmap(relative_gain, cmap="viridis", cbar_kws={"label": "Relative gain (%)"}, ax=ax) + # ax.set_yticks(np.arange(len(wind_directions)) + 0.5) + # ax.set_yticklabels(wind_directions) + # ax.set_xticks(np.arange(len(wind_speeds)) + 0.5) + # ax.set_xticklabels(wind_speeds) + # ax.set_ylabel("Wind direction (deg)") + # ax.set_xlabel("Wind speed (m/s)") + # plt.tight_layout() + + # # Reduce y tick font size + # for tick in ax.yaxis.get_major_ticks(): + # tick.label.set_fontsize(8) + + # # Set y ticks to be horizontal + # for tick in ax.get_yticklabels(): + # tick.set_rotation(0) + + # ax.set_title("Uplift in farm power by wind direction and wind speed", fontsize=12) + + # plt.show() From ba6e06b60461376a79d9f6e89df17570134c61af Mon Sep 17 00:00:00 2001 From: misi9170 Date: Mon, 1 Apr 2024 19:36:08 -0600 Subject: [PATCH 065/120] Bugfix for uneven splits. --- floris/parallel_floris_model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/floris/parallel_floris_model.py b/floris/parallel_floris_model.py index 86fc3ea08..cdf70afa6 100644 --- a/floris/parallel_floris_model.py +++ b/floris/parallel_floris_model.py @@ -245,8 +245,7 @@ def _postprocessing(self, output): flowfield_subsets = [p[1] for p in output] # Retrieve and merge turbine power productions - i, j, k = np.shape(power_subsets) - turbine_powers = np.reshape(power_subsets, (i*j, k)) + turbine_powers = np.concatenate(power_subsets, axis=0) # Optionally, also merge flow field dictionaries from individual floris solutions if self.propagate_flowfield_from_workers: From 1b817a1624ae5ddb12720e373d072ea344944b2c Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 1 Apr 2024 22:02:03 -0600 Subject: [PATCH 066/120] Update examples 4 and 5 --- .../004_optimize_yaw_aep.py | 2 +- .../005_optimize_yaw_aep_parallel.py | 142 ++++++++---------- 2 files changed, 60 insertions(+), 84 deletions(-) diff --git a/examples/examples_control_optimization/004_optimize_yaw_aep.py b/examples/examples_control_optimization/004_optimize_yaw_aep.py index 934e60ebe..04913d3a0 100644 --- a/examples/examples_control_optimization/004_optimize_yaw_aep.py +++ b/examples/examples_control_optimization/004_optimize_yaw_aep.py @@ -34,7 +34,7 @@ fmodel = FlorisModel("../inputs/gch.yaml") # Specify wind farm layout and update in the floris object -N = 4 # number of turbines per row and per column +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), diff --git a/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py b/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py index 4c47f712c..87c0b0e7e 100644 --- a/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py +++ b/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py @@ -4,10 +4,10 @@ over a full wind rose. The example repeats the steps in 04 except using parallel optimization and evaluation. -The script performs the following steps: - 1. Load a wind rose from a csv file - 2. Calculates the optimal yaw angles for a wind speed of 8 m/s across the directions - 3. Applies the optimal yaw angles to the wind rose and calculates the AEP +Note that constraints on parallelized operations mean that some syntax is different and +not all operations are possible. Also, rather passing the ParallelFlorisModel +object to a YawOptimizationSR object, the optimization is performed +directly by member functions """ @@ -24,10 +24,10 @@ TimeSeries, WindRose, ) -from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR -# THIS IS IMPORTANT +# When using parallel optimization it is importat the "root" script include this +# if __name__ == "__main__": block to avoid problems if __name__ == "__main__": # Load the wind rose from csv @@ -99,82 +99,58 @@ # 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 - farm_power_baseline = pfmodel.get_farm_power() - aep_baseline = pfmodel.get_farm_AEP() + aep_baseline = pfmodel.get_farm_AEP(freq=wind_rose.unpack_freq()) print("Baseline AEP: {:.2f} GWh.".format(aep_baseline)) - # # 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 - # # and ramped down to 0 above and below this range - - # # Grab wind speeds and wind directions from the fmodel. Note that we do this because the - # # 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.core.flow_field.wind_directions - # wind_speeds = fmodel.core.flow_field.wind_speeds - # n_findex = fmodel.core.flow_field.n_findex - - - # # Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds - # yaw_angles_opt = np.vstack(df_opt["yaw_angles_opt"]) - # yaw_angles_wind_rose = np.zeros((n_findex, n_turbines)) - # for i in range(n_findex): - # wind_speed = wind_speeds[i] - # wind_direction = wind_directions[i] - - # # Interpolate the optimal yaw angles for this wind direction from df_opt - # id_opt = df_opt["wind_direction"] == wind_direction - # yaw_opt_full = np.array(df_opt.loc[id_opt, "yaw_angles_opt"])[0] - - # # Now decide what to do for different wind speeds - # if (wind_speed < 4.0) | (wind_speed > 14.0): - # yaw_opt = np.zeros(n_turbines) # do nothing for very low/high speeds - # elif wind_speed < 6.0: - # yaw_opt = yaw_opt_full * (6.0 - wind_speed) / 2.0 # Linear ramp up - # elif wind_speed > 12.0: - # yaw_opt = yaw_opt_full * (14.0 - wind_speed) / 2.0 # Linear ramp down - # else: - # yaw_opt = yaw_opt_full # Apply full offsets between 6.0 and 12.0 m/s - - # # Save to collective array - # yaw_angles_wind_rose[i, :] = yaw_opt - - - # # Now apply the optimal yaw angles and get the AEP - # fmodel.set(yaw_angles=yaw_angles_wind_rose) - # fmodel.run() - # aep_opt = fmodel.get_farm_AEP() - # aep_uplift = 100.0 * (aep_opt / aep_baseline - 1) - # farm_power_opt = fmodel.get_farm_power() - # print("Optimal AEP: {:.2f} GWh.".format(aep_opt)) - # print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift)) - - # # Use farm_power_baseline, farm_power_opt and wind_data to make a heat map of uplift by - # # wind direction and wind speed - # wind_directions = wind_rose.wind_directions - # wind_speeds = wind_rose.wind_speeds - # relative_gain = farm_power_opt - farm_power_baseline - - # # Plt the heatmap with wind speeds on x, wind directions ony and relative gain as the color - # fig, ax = plt.subplots(figsize=(7, 12)) - # sns.heatmap(relative_gain, cmap="viridis", cbar_kws={"label": "Relative gain (%)"}, ax=ax) - # ax.set_yticks(np.arange(len(wind_directions)) + 0.5) - # ax.set_yticklabels(wind_directions) - # ax.set_xticks(np.arange(len(wind_speeds)) + 0.5) - # ax.set_xticklabels(wind_speeds) - # ax.set_ylabel("Wind direction (deg)") - # ax.set_xlabel("Wind speed (m/s)") - # plt.tight_layout() - - # # Reduce y tick font size - # for tick in ax.yaxis.get_major_ticks(): - # tick.label.set_fontsize(8) - - # # Set y ticks to be horizontal - # for tick in ax.get_yticklabels(): - # tick.set_rotation(0) - - # ax.set_title("Uplift in farm power by wind direction and wind speed", fontsize=12) - - # plt.show() + # 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 + # and ramped down to 0 above and below this range + + # Grab wind speeds and wind directions from the fmodel. Note that we do this because the + # 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.core.flow_field.wind_directions + wind_speeds = fmodel.core.flow_field.wind_speeds + n_findex = fmodel.core.flow_field.n_findex + + + # Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds + yaw_angles_opt = np.vstack(df_opt["yaw_angles_opt"]) + yaw_angles_wind_rose = np.zeros((n_findex, n_turbines)) + for i in range(n_findex): + wind_speed = wind_speeds[i] + wind_direction = wind_directions[i] + + # Interpolate the optimal yaw angles for this wind direction from df_opt + id_opt = df_opt["wind_direction"] == wind_direction + yaw_opt_full = np.array(df_opt.loc[id_opt, "yaw_angles_opt"])[0] + + # Now decide what to do for different wind speeds + if (wind_speed < 4.0) | (wind_speed > 14.0): + yaw_opt = np.zeros(n_turbines) # do nothing for very low/high speeds + elif wind_speed < 6.0: + yaw_opt = yaw_opt_full * (6.0 - wind_speed) / 2.0 # Linear ramp up + elif wind_speed > 12.0: + yaw_opt = yaw_opt_full * (14.0 - wind_speed) / 2.0 # Linear ramp down + else: + yaw_opt = yaw_opt_full # Apply full offsets between 6.0 and 12.0 m/s + + # Save to collective array + yaw_angles_wind_rose[i, :] = yaw_opt + + + # 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()) + aep_uplift = 100.0 * (aep_opt / aep_baseline - 1) + + print("Optimal AEP: {:.2f} GWh.".format(aep_opt)) + print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift)) From 631e282c91459562fba90f139d72fdd09d29fdca Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 1 Apr 2024 23:32:55 -0600 Subject: [PATCH 067/120] Update example 6 --- .../006_compare_yaw_optimizers.py | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 examples/examples_control_optimization/006_compare_yaw_optimizers.py diff --git a/examples/examples_control_optimization/006_compare_yaw_optimizers.py b/examples/examples_control_optimization/006_compare_yaw_optimizers.py new file mode 100644 index 000000000..e0c39bbba --- /dev/null +++ b/examples/examples_control_optimization/006_compare_yaw_optimizers.py @@ -0,0 +1,204 @@ + +"""Example: Compare yaw optimizers +This example compares the SciPy-based yaw optimizer with the Serial-Refine optimizer +and geometric optimizer. + +First, we initialize Floris, and then generate a 3 turbine wind farm. +Next, we create two yaw optimization objects, `yaw_opt_sr` and `yaw_opt_scipy` for the +Serial-Refine and SciPy methods, respectively. +We then perform the optimization using both methods. +Finally, we compare the time it took to find the optimal angles and plot the optimal yaw angles +and resulting wind farm powers. + +The example now also compares the Geometric Yaw optimizer, which is fast +a method to find approximately optimal yaw angles based on the wind farm geometry. Its +main use case is for coupled layout and yaw optimization. +see floris.optimization.yaw_optimization.yaw_optimizer_geometric.py and the paper online +at https://wes.copernicus.org/preprints/wes-2023-1/. See also example 16c. + +""" + +from time import perf_counter as timerpc + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel +from floris.optimization.yaw_optimization.yaw_optimizer_geometric import ( + YawOptimizationGeometric, +) +from floris.optimization.yaw_optimization.yaw_optimizer_scipy import YawOptimizationScipy +from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR + + +# Load the default example floris object +fmodel = FlorisModel("../inputs/gch.yaml") + +# Reinitialize as a 3-turbine farm with range of WDs and 1 WS +D = 126.0 # Rotor diameter for the NREL 5 MW +wd_array = np.arange(0.0, 360.0, 3.0) +ws_array = 8.0 * np.ones_like(wd_array) +turbulence_intensities = 0.06 * np.ones_like(wd_array) +fmodel.set( + layout_x=[0.0, 5 * D, 10 * D], + layout_y=[0.0, 0.0, 0.0], + wind_directions=wd_array, + wind_speeds=ws_array, + turbulence_intensities=turbulence_intensities, +) + +print("Performing optimizations with SciPy...") +start_time = timerpc() +yaw_opt_scipy = YawOptimizationScipy(fmodel) +df_opt_scipy = yaw_opt_scipy.optimize() +time_scipy = timerpc() - start_time + +print("Performing optimizations with Serial Refine...") +start_time = timerpc() +yaw_opt_sr = YawOptimizationSR(fmodel) +df_opt_sr = yaw_opt_sr.optimize() +time_sr = timerpc() - start_time + +print("Performing optimizations with Geometric Yaw...") +start_time = timerpc() +yaw_opt_geo = YawOptimizationGeometric(fmodel) +df_opt_geo = yaw_opt_geo.optimize() +time_geo = timerpc() - start_time + + + +# Print time spent +print("\n Time spent, Geometric Yaw: {:.2f} s.".format(time_geo)) +print(" Time spent, Serial Refine: {:.2f} s.".format(time_sr)) +print(" Time spent, SciPy (SLSQP): {:.2f} s.\n".format(time_scipy)) + +# Split out the turbine results +yaw_angles_opt_geo = np.vstack(df_opt_geo.yaw_angles_opt) +yaw_angles_opt_sr = np.vstack(df_opt_sr.yaw_angles_opt) +yaw_angles_opt_scipy = np.vstack(df_opt_scipy.yaw_angles_opt) + + +# Yaw results +for t in range(3): + fig, ax = plt.subplots() + ax.plot(df_opt_geo.wind_direction, yaw_angles_opt_geo[:, t],color='m',label='Geometric') + ax.plot(df_opt_sr.wind_direction, yaw_angles_opt_sr[:, t],color='r',label='Serial Refine') + ax.plot(df_opt_scipy.wind_direction, yaw_angles_opt_scipy[:, t],'--', color='g', label='SciPy') + ax.grid(True) + ax.set_ylabel('Yaw Offset (deg') + ax.legend() + ax.grid(True) + ax.set_title("Turbine {:d}".format(t)) + +# Power results ============== + +# Before plotting results, need to compute values for GEOOPT since it doesn't compute +# power within the optimization +fmodel.set(yaw_angles=yaw_angles_opt_geo) +fmodel.run() +geo_farm_power = fmodel.get_farm_power().squeeze() + + +fig, ax = plt.subplots() +ax.plot( + df_opt_sr.wind_direction, + df_opt_sr.farm_power_baseline, + color='k', + label='Baseline' +) +ax.plot( + df_opt_geo.wind_direction, + geo_farm_power, + color='m', + label='Optimized, Gemoetric' +) +ax.plot( + df_opt_sr.wind_direction, + df_opt_sr.farm_power_opt, + color='r', + label='Optimized, Serial Refine' +) +ax.plot( + df_opt_scipy.wind_direction, + df_opt_scipy.farm_power_opt, + '--', + color='g', + label='Optimized, SciPy' +) +ax.set_ylabel('Wind Farm Power (W)') +ax.set_xlabel('Wind Direction (deg)') +ax.legend() +ax.grid(True) + +# Finally, compare the overall the power gains + +fig, ax = plt.subplots() + +ax.plot( + df_opt_geo.wind_direction, + geo_farm_power - df_opt_sr.farm_power_baseline, + color='m', + label='Optimized, Gemoetric' +) +ax.plot( + df_opt_sr.wind_direction, + df_opt_sr.farm_power_opt - df_opt_sr.farm_power_baseline, + color='r', + label='Optimized, Serial Refine' +) +ax.plot( + df_opt_scipy.wind_direction, + df_opt_scipy.farm_power_opt - df_opt_scipy.farm_power_baseline, + '--', + color='g', + label='Optimized, SciPy' +) +ax.set_ylabel('Increase in Wind Farm Power (W)') +ax.set_xlabel('Wind Direction (deg)') +ax.legend() +ax.grid(True) + + +# Finally, make a quick bar plot comparing nomimal power and nomimal uplift +total_power_uplift_geo = np.sum(geo_farm_power - df_opt_sr.farm_power_baseline) +total_power_uplift_sr = np.sum(df_opt_sr.farm_power_opt - df_opt_sr.farm_power_baseline) +total_power_uplift_scipy = np.sum(df_opt_scipy.farm_power_opt - df_opt_scipy.farm_power_baseline) + +# Plot on the left subplot a barplot comparing the uplift normalized to scipy and on the right +# subplot a barplot of total time normalzed to scipy +fig, axarr = plt.subplots(1,2,figsize=(10,5)) + +ax = axarr[0] +ax.bar( + [0, 1, 2], + [ + total_power_uplift_geo / total_power_uplift_scipy, + total_power_uplift_sr / total_power_uplift_scipy, + 1.0, + ], + color=['m', 'r', 'g'], +) +ax.set_xticks([0, 1, 2]) +ax.set_xticklabels(['Geometric', 'Serial Refine', 'SciPy']) +ax.set_ylabel('Normalized Power Gain') +ax.grid(True) + +ax = axarr[1] +ax.bar( + [0, 1, 2], + [ + time_geo / time_scipy, + time_sr / time_scipy, + 1.0, + ], + color=['m', 'r', 'g'], +) +ax.set_xticks([0, 1, 2]) +ax.set_xticklabels(['Geometric', 'Serial Refine', 'SciPy']) +ax.set_ylabel('Normalized Computation Time') +ax.grid(True) + +# Change to semi-logy +axarr[1].set_yscale('log') + +plt.show() From 35335551b367bda951e0ee813196a67deadd0390 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 1 Apr 2024 23:33:07 -0600 Subject: [PATCH 068/120] Remove old examples --- .../12_optimize_yaw_in_parallel.py | 300 ------------------ .../14_compare_yaw_optimizers.py | 204 ------------ 2 files changed, 504 deletions(-) delete mode 100644 examples/examples_control_optimization/12_optimize_yaw_in_parallel.py delete mode 100644 examples/examples_control_optimization/14_compare_yaw_optimizers.py diff --git a/examples/examples_control_optimization/12_optimize_yaw_in_parallel.py b/examples/examples_control_optimization/12_optimize_yaw_in_parallel.py deleted file mode 100644 index 8050a8764..000000000 --- a/examples/examples_control_optimization/12_optimize_yaw_in_parallel.py +++ /dev/null @@ -1,300 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from scipy.interpolate import LinearNDInterpolator - -from floris import FlorisModel, ParallelFlorisModel - - -""" -This example demonstrates how to perform a yaw optimization using parallel computing. -... -""" - -def load_floris(): - # Load the default example floris object - fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 - # fmodel = FlorisModel("inputs/cc.yaml") # New CumulativeCurl model - - # Specify wind farm layout and update in the floris object - N = 4 # 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), - ) - fmodel.set(layout_x=X.flatten(), layout_y=Y.flatten()) - - return fmodel - - -def load_windrose(): - # Grab a linear interpolant from this wind rose - df = pd.read_csv("inputs/wind_rose.csv") - interp = LinearNDInterpolator(points=df[["wd", "ws"]], values=df["freq_val"], fill_value=0.0) - return df, interp - - -if __name__ == "__main__": - # Parallel options - max_workers = 16 - - # Load a dataframe containing the wind rose information - df_windrose, windrose_interpolant = load_windrose() - - # Load a FLORIS object for AEP calculations - fmodel_aep = load_floris() - - # Define arrays of wd/ws - wind_directions_to_expand = np.arange(0.0, 360.0, 1.0) - wind_speeds_to_expand = np.arange(1.0, 25.0, 1.0) - - # Create grids to make combinations of ws/wd - wind_directions_grid, wind_speeds_grid = np.meshgrid( - wind_directions_to_expand, - wind_speeds_to_expand, - ) - - # Flatten the grids back to 1D arrays - wd_array = wind_directions_grid.flatten() - ws_array = wind_speeds_grid.flatten() - turbulence_intensities = 0.08 * np.ones_like(wd_array) - - fmodel_aep.set( - wind_directions=wd_array, - wind_speeds=ws_array, - turbulence_intensities=turbulence_intensities, - ) - - # Pour this into a parallel computing interface - parallel_interface = "concurrent" - pfmodel_aep = ParallelFlorisModel( - fmodel=fmodel_aep, - max_workers=max_workers, - n_wind_condition_splits=max_workers, - interface=parallel_interface, - print_timings=True, - ) - - # Calculate frequency of occurrence for each bin and normalize sum to 1.0 - freq_grid = windrose_interpolant(wd_array, ws_array) - freq_grid = freq_grid / np.sum(freq_grid) # Normalize to 1.0 - - # Calculate farm power baseline - farm_power_bl = pfmodel_aep.get_farm_power() - aep_bl = np.sum(24 * 365 * np.multiply(farm_power_bl, freq_grid)) - - # Alternatively to above code, we could calculate AEP using - # 'pfmodel_aep.get_farm_AEP(...)' but then we would not have the - # farm power productions, which we use later on for plotting. - - # First, get baseline AEP, without wake steering - print(" ") - print("===========================================================") - print("Calculating baseline annual energy production (AEP)...") - print("Baseline AEP: {:.3f} GWh.".format(aep_bl / 1.0e9)) - print("===========================================================") - print(" ") - - # Load a FLORIS object for yaw optimization - fmodel_opt = load_floris() - - # Define arrays of wd/ws - wind_directions_to_expand = np.arange(0.0, 360.0, 3.0) - wind_speeds_to_expand = np.arange(6.0, 14.0, 2.0) - - # Create grids to make combinations of ws/wd - wind_directions_grid, wind_speeds_grid = np.meshgrid( - wind_directions_to_expand, - wind_speeds_to_expand, - ) - - # Flatten the grids back to 1D arrays - wd_array_opt = wind_directions_grid.flatten() - ws_array_opt = wind_speeds_grid.flatten() - turbulence_intensities = 0.08 * np.ones_like(wd_array_opt) - - fmodel_opt.set( - wind_directions=wd_array_opt, - wind_speeds=ws_array_opt, - turbulence_intensities=turbulence_intensities, - ) - - # Pour this into a parallel computing interface - pfmodel_opt = ParallelFlorisModel( - fmodel=fmodel_opt, - max_workers=max_workers, - n_wind_condition_splits=max_workers, - interface=parallel_interface, - print_timings=True, - ) - - # Now optimize the yaw angles using the Serial Refine method - df_opt = pfmodel_opt.optimize_yaw_angles( - minimum_yaw_angle=-25.0, - maximum_yaw_angle=25.0, - Ny_passes=[5, 4], - exclude_downstream_turbines=False, - ) - - - - # Assume linear ramp up at 5-6 m/s and ramp down at 13-14 m/s, - # add to table for linear interpolant - df_copy_lb = df_opt[df_opt["wind_speed"] == 6.0].copy() - df_copy_ub = df_opt[df_opt["wind_speed"] == 13.0].copy() - df_copy_lb["wind_speed"] = 5.0 - df_copy_ub["wind_speed"] = 14.0 - df_copy_lb["yaw_angles_opt"] *= 0.0 - df_copy_ub["yaw_angles_opt"] *= 0.0 - df_opt = pd.concat([df_copy_lb, df_opt, df_copy_ub], axis=0).reset_index(drop=True) - - # Deal with 360 deg wrapping: solutions at 0 deg are also solutions at 360 deg - df_copy_360deg = df_opt[df_opt["wind_direction"] == 0.0].copy() - df_copy_360deg["wind_direction"] = 360.0 - df_opt = pd.concat([df_opt, df_copy_360deg], axis=0).reset_index(drop=True) - - # Derive linear interpolant from solution space - yaw_angles_interpolant = LinearNDInterpolator( - points=df_opt[["wind_direction", "wind_speed"]], - values=np.vstack(df_opt["yaw_angles_opt"]), - fill_value=0.0, - ) - - # Get optimized AEP, with wake steering - yaw_grid = yaw_angles_interpolant(wd_array, ws_array) - farm_power_opt = pfmodel_aep.get_farm_power(yaw_angles=yaw_grid) - aep_opt = np.sum(24 * 365 * np.multiply(farm_power_opt, freq_grid)) - aep_uplift = 100.0 * (aep_opt / aep_bl - 1) - - # Alternatively to above code, we could calculate AEP using - # 'pfmodel_aep.get_farm_AEP(...)' but then we would not have the - # farm power productions, which we use later on for plotting. - - print(" ") - print("===========================================================") - print("Calculating optimized annual energy production (AEP)...") - print("Optimized AEP: {:.3f} GWh.".format(aep_opt / 1.0e9)) - print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift)) - print("===========================================================") - print(" ") - - # Now calculate helpful variables and then plot wind rose information - farm_energy_bl = np.multiply(freq_grid, farm_power_bl) - farm_energy_opt = np.multiply(freq_grid, farm_power_opt) - df = pd.DataFrame({ - "wd": wd_array.flatten(), - "ws": ws_array.flatten(), - "freq_val": freq_grid.flatten(), - "farm_power_baseline": farm_power_bl.flatten(), - "farm_power_opt": farm_power_opt.flatten(), - "farm_power_relative": farm_power_opt.flatten() / farm_power_bl.flatten(), - "farm_energy_baseline": farm_energy_bl.flatten(), - "farm_energy_opt": farm_energy_opt.flatten(), - "energy_uplift": (farm_energy_opt - farm_energy_bl).flatten(), - "rel_energy_uplift": farm_energy_opt.flatten() / np.sum(farm_energy_bl) - }) - - # Plot power and AEP uplift across wind direction - wd_step = np.diff(fmodel_aep.core.flow_field.wind_directions)[0] # Useful variable for plotting - fig, ax = plt.subplots(nrows=3, sharex=True) - - df_8ms = df[df["ws"] == 8.0].reset_index(drop=True) - pow_uplift = 100 * ( - df_8ms["farm_power_opt"] / df_8ms["farm_power_baseline"] - 1 - ) - ax[0].bar( - x=df_8ms["wd"], - height=pow_uplift, - color="darkgray", - edgecolor="black", - width=wd_step, - ) - ax[0].set_ylabel("Power uplift \n at 8 m/s (%)") - ax[0].grid(True) - - dist = df.groupby("wd").sum().reset_index() - ax[1].bar( - x=dist["wd"], - height=100 * dist["rel_energy_uplift"], - color="darkgray", - edgecolor="black", - width=wd_step, - ) - ax[1].set_ylabel("Contribution to \n AEP uplift (%)") - ax[1].grid(True) - - ax[2].bar( - x=dist["wd"], - height=dist["freq_val"], - color="darkgray", - edgecolor="black", - width=wd_step, - ) - ax[2].set_xlabel("Wind direction (deg)") - ax[2].set_ylabel("Frequency of \n occurrence (-)") - ax[2].grid(True) - plt.tight_layout() - - # Plot power and AEP uplift across wind direction - fig, ax = plt.subplots(nrows=3, sharex=True) - - df_avg = df.groupby("ws").mean().reset_index(drop=False) - mean_power_uplift = 100.0 * (df_avg["farm_power_relative"] - 1.0) - ax[0].bar( - x=df_avg["ws"], - height=mean_power_uplift, - color="darkgray", - edgecolor="black", - width=0.95, - ) - ax[0].set_ylabel("Mean power \n uplift (%)") - ax[0].grid(True) - - dist = df.groupby("ws").sum().reset_index() - ax[1].bar( - x=dist["ws"], - height=100 * dist["rel_energy_uplift"], - color="darkgray", - edgecolor="black", - width=0.95, - ) - ax[1].set_ylabel("Contribution to \n AEP uplift (%)") - ax[1].grid(True) - - ax[2].bar( - x=dist["ws"], - height=dist["freq_val"], - color="darkgray", - edgecolor="black", - width=0.95, - ) - ax[2].set_xlabel("Wind speed (m/s)") - ax[2].set_ylabel("Frequency of \n occurrence (-)") - ax[2].grid(True) - plt.tight_layout() - - # Now plot yaw angle distributions over wind direction up to first three turbines - wd_plot = np.arange(0.0, 360.001, 1.0) - for tindex in range(np.min([fmodel_aep.core.farm.n_turbines, 3])): - fig, ax = plt.subplots(figsize=(6, 3.5)) - ws_to_plot = [6.0, 9.0, 12.0] - colors = ["maroon", "dodgerblue", "grey"] - styles = ["-o", "-v", "-o"] - for ii, ws in enumerate(ws_to_plot): - ax.plot( - wd_plot, - yaw_angles_interpolant(wd_plot, ws * np.ones_like(wd_plot))[:, tindex], - styles[ii], - color=colors[ii], - markersize=3, - label="For wind speed of {:.1f} m/s".format(ws), - ) - ax.set_ylabel("Assigned yaw offsets (deg)") - ax.set_xlabel("Wind direction (deg)") - ax.set_title("Turbine {:d}".format(tindex)) - ax.grid(True) - ax.legend() - plt.tight_layout() - - plt.show() diff --git a/examples/examples_control_optimization/14_compare_yaw_optimizers.py b/examples/examples_control_optimization/14_compare_yaw_optimizers.py deleted file mode 100644 index 4e0fa1d99..000000000 --- a/examples/examples_control_optimization/14_compare_yaw_optimizers.py +++ /dev/null @@ -1,204 +0,0 @@ - -from time import perf_counter as timerpc - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel -from floris.optimization.yaw_optimization.yaw_optimizer_geometric import ( - YawOptimizationGeometric, -) -from floris.optimization.yaw_optimization.yaw_optimizer_scipy import YawOptimizationScipy -from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR - - -""" -This example compares the SciPy-based yaw optimizer with the new Serial-Refine optimizer. - -First, we initialize Floris, and then generate a 3 turbine wind farm. -Next, we create two yaw optimization objects, `yaw_opt_sr` and `yaw_opt_scipy` for the -Serial-Refine and SciPy methods, respectively. -We then perform the optimization using both methods. -Finally, we compare the time it took to find the optimal angles and plot the optimal yaw angles -and resulting wind farm powers. - -The example now also compares the Geometric Yaw optimizer, which is fast -a method to find approximately optimal yaw angles based on the wind farm geometry. Its -main use case is for coupled layout and yaw optimization. -see floris.optimization.yaw_optimization.yaw_optimizer_geometric.py and the paper online -at https://wes.copernicus.org/preprints/wes-2023-1/. See also example 16c. - -""" - -# Load the default example floris object -fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 -# fmodel = FlorisModel("inputs/cc.yaml") # New CumulativeCurl model - -# Reinitialize as a 3-turbine farm with range of WDs and 1 WS -D = 126.0 # Rotor diameter for the NREL 5 MW -wd_array = np.arange(0.0, 360.0, 3.0) -ws_array = 8.0 * np.ones_like(wd_array) -turbulence_intensities = 0.06 * np.ones_like(wd_array) -fmodel.set( - layout_x=[0.0, 5 * D, 10 * D], - layout_y=[0.0, 0.0, 0.0], - wind_directions=wd_array, - wind_speeds=ws_array, - turbulence_intensities=turbulence_intensities, -) - -print("Performing optimizations with SciPy...") -start_time = timerpc() -yaw_opt_scipy = YawOptimizationScipy(fmodel) -df_opt_scipy = yaw_opt_scipy.optimize() -time_scipy = timerpc() - start_time - -print("Performing optimizations with Serial Refine...") -start_time = timerpc() -yaw_opt_sr = YawOptimizationSR(fmodel) -df_opt_sr = yaw_opt_sr.optimize() -time_sr = timerpc() - start_time - -print("Performing optimizations with Geometric Yaw...") -start_time = timerpc() -yaw_opt_geo = YawOptimizationGeometric(fmodel) -df_opt_geo = yaw_opt_geo.optimize() -time_geo = timerpc() - start_time - - - -# Print time spent -print("\n Time spent, Geometric Yaw: {:.2f} s.".format(time_geo)) -print(" Time spent, Serial Refine: {:.2f} s.".format(time_sr)) -print(" Time spent, SciPy (SLSQP): {:.2f} s.\n".format(time_scipy)) - -# Split out the turbine results -yaw_angles_opt_geo = np.vstack(df_opt_geo.yaw_angles_opt) -yaw_angles_opt_sr = np.vstack(df_opt_sr.yaw_angles_opt) -yaw_angles_opt_scipy = np.vstack(df_opt_scipy.yaw_angles_opt) - - -# Yaw results -for t in range(3): - fig, ax = plt.subplots() - ax.plot(df_opt_geo.wind_direction, yaw_angles_opt_geo[:, t],color='m',label='Geometric') - ax.plot(df_opt_sr.wind_direction, yaw_angles_opt_sr[:, t],color='r',label='Serial Refine') - ax.plot(df_opt_scipy.wind_direction, yaw_angles_opt_scipy[:, t],'--', color='g', label='SciPy') - ax.grid(True) - ax.set_ylabel('Yaw Offset (deg') - ax.legend() - ax.grid(True) - ax.set_title("Turbine {:d}".format(t)) - -# Power results ============== - -# Before plotting results, need to compute values for GEOOPT since it doesn't compute -# power within the optimization -fmodel.set(yaw_angles=yaw_angles_opt_geo) -fmodel.run() -geo_farm_power = fmodel.get_farm_power().squeeze() - - -fig, ax = plt.subplots() -ax.plot( - df_opt_sr.wind_direction, - df_opt_sr.farm_power_baseline, - color='k', - label='Baseline' -) -ax.plot( - df_opt_geo.wind_direction, - geo_farm_power, - color='m', - label='Optimized, Gemoetric' -) -ax.plot( - df_opt_sr.wind_direction, - df_opt_sr.farm_power_opt, - color='r', - label='Optimized, Serial Refine' -) -ax.plot( - df_opt_scipy.wind_direction, - df_opt_scipy.farm_power_opt, - '--', - color='g', - label='Optimized, SciPy' -) -ax.set_ylabel('Wind Farm Power (W)') -ax.set_xlabel('Wind Direction (deg)') -ax.legend() -ax.grid(True) - -# Finally, compare the overall the power gains - -fig, ax = plt.subplots() - -ax.plot( - df_opt_geo.wind_direction, - geo_farm_power - df_opt_sr.farm_power_baseline, - color='m', - label='Optimized, Gemoetric' -) -ax.plot( - df_opt_sr.wind_direction, - df_opt_sr.farm_power_opt - df_opt_sr.farm_power_baseline, - color='r', - label='Optimized, Serial Refine' -) -ax.plot( - df_opt_scipy.wind_direction, - df_opt_scipy.farm_power_opt - df_opt_scipy.farm_power_baseline, - '--', - color='g', - label='Optimized, SciPy' -) -ax.set_ylabel('Increase in Wind Farm Power (W)') -ax.set_xlabel('Wind Direction (deg)') -ax.legend() -ax.grid(True) - - -# Finally, make a quick bar plot comparing nomimal power and nomimal uplift -total_power_uplift_geo = np.sum(geo_farm_power - df_opt_sr.farm_power_baseline) -total_power_uplift_sr = np.sum(df_opt_sr.farm_power_opt - df_opt_sr.farm_power_baseline) -total_power_uplift_scipy = np.sum(df_opt_scipy.farm_power_opt - df_opt_scipy.farm_power_baseline) - -# Plot on the left subplot a barplot comparing the uplift normalized to scipy and on the right -# subplot a barplot of total time normalzed to scipy -fig, axarr = plt.subplots(1,2,figsize=(10,5)) - -ax = axarr[0] -ax.bar( - [0, 1, 2], - [ - total_power_uplift_geo / total_power_uplift_scipy, - total_power_uplift_sr / total_power_uplift_scipy, - 1.0, - ], - color=['m', 'r', 'g'], -) -ax.set_xticks([0, 1, 2]) -ax.set_xticklabels(['Geometric', 'Serial Refine', 'SciPy']) -ax.set_ylabel('Normalized Power Gain') -ax.grid(True) - -ax = axarr[1] -ax.bar( - [0, 1, 2], - [ - time_geo / time_scipy, - time_sr / time_scipy, - 1.0, - ], - color=['m', 'r', 'g'], -) -ax.set_xticks([0, 1, 2]) -ax.set_xticklabels(['Geometric', 'Serial Refine', 'SciPy']) -ax.set_ylabel('Normalized Computation Time') -ax.grid(True) - -# Change to semi-logy -axarr[1].set_yscale('log') - -plt.show() From d9146b7de37217e78e48d492896d67f654d310f9 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 13:03:38 -0600 Subject: [PATCH 069/120] bugfix --- examples/004_set.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/004_set.py b/examples/004_set.py index c2b40e273..506d1cc17 100644 --- a/examples/004_set.py +++ b/examples/004_set.py @@ -86,7 +86,7 @@ fmodel.reset_operation() # Change to the mixed model turbine -fmodel.set_power_thrust_model("mixed") +fmodel.set_operation_model("mixed") # Shut down the front turbine for the first two findex disable_turbines = np.array([[True, False], [True, False], [False, False]]) From 8b536efd42dec9136b952daeb6f05013b3b45730 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 13:03:52 -0600 Subject: [PATCH 070/120] Update derating example --- .../000_derating_control.py | 95 +++++++++++++++ .../40_test_derating.py | 112 ------------------ 2 files changed, 95 insertions(+), 112 deletions(-) create mode 100644 examples/examples_control_types/000_derating_control.py delete mode 100644 examples/examples_control_types/40_test_derating.py diff --git a/examples/examples_control_types/000_derating_control.py b/examples/examples_control_types/000_derating_control.py new file mode 100644 index 000000000..41bf3ea2a --- /dev/null +++ b/examples/examples_control_types/000_derating_control.py @@ -0,0 +1,95 @@ +"""Example of using the simple-derating control model in FLORIS. + +This example demonstrates how to use the simple-derating control model in FLORIS. +The simple-derating control model allows the user to specify a power setpoint for each turbine +in the farm. The power setpoint is used to derate the turbine power output to be at most the +power setpoint. + +In this example: + +1. A simple two-turbine layout is created. +2. The wind conditions are set to be constant. +3. The power setpoint is varied, and set the same for each turbine +4. The power produced by each turbine is computed and plotted +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel + + +fmodel = FlorisModel("../inputs/gch.yaml") + +# Change to the simple-derating model turbine +# (Note this could also be done with the mixed model) +fmodel.set_operation_model("simple-derating") + +# Convert to a simple two turbine layout with derating turbines +fmodel.set(layout_x=[0, 1000.0], layout_y=[0.0, 0.0]) + +# For reference, load the turbine type +turbine_type = fmodel.core.farm.turbine_definitions[0] + +# Set the wind directions and speeds to be constant over n_findex = N time steps +N = 50 +fmodel.set( + wind_directions=270 * np.ones(N), + wind_speeds=10.0 * np.ones(N), + turbulence_intensities=0.06 * np.ones(N), +) +fmodel.run() +turbine_powers_orig = fmodel.get_turbine_powers() + +# Add derating level to both turbines +power_setpoints = np.tile(np.linspace(1, 6e6, N), 2).reshape(2, N).T +fmodel.set(power_setpoints=power_setpoints) +fmodel.run() +turbine_powers_derated = fmodel.get_turbine_powers() + +# Compute available power at downstream turbine +power_setpoints_2 = np.array([np.linspace(1, 6e6, N), np.full(N, None)]).T +fmodel.set(power_setpoints=power_setpoints_2) +fmodel.run() +turbine_powers_avail_ds = fmodel.get_turbine_powers()[:, 1] + +# Plot the results +fig, ax = plt.subplots(1, 1) +ax.plot( + power_setpoints[:, 0] / 1000, turbine_powers_derated[:, 0] / 1000, color="C0", label="Upstream" +) +ax.plot( + power_setpoints[:, 1] / 1000, + turbine_powers_derated[:, 1] / 1000, + color="C1", + label="Downstream", +) +ax.plot( + power_setpoints[:, 0] / 1000, + turbine_powers_orig[:, 0] / 1000, + color="C0", + linestyle="dotted", + label="Upstream available", +) +ax.plot( + power_setpoints[:, 1] / 1000, + turbine_powers_avail_ds / 1000, + color="C1", + linestyle="dotted", + label="Downstream available", +) +ax.plot( + power_setpoints[:, 1] / 1000, + np.ones(N) * np.max(turbine_type["power_thrust_table"]["power"]), + color="k", + linestyle="dashed", + label="Rated power", +) +ax.grid() +ax.legend() +ax.set_xlim([0, 6e3]) +ax.set_xlabel("Power setpoint (kW) [Applied to both turbines]") +ax.set_ylabel("Power produced (kW)") + + +plt.show() diff --git a/examples/examples_control_types/40_test_derating.py b/examples/examples_control_types/40_test_derating.py deleted file mode 100644 index 7d72252b6..000000000 --- a/examples/examples_control_types/40_test_derating.py +++ /dev/null @@ -1,112 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np -import yaml - -from floris import FlorisModel - - -""" -Example to test out derating of turbines and mixed derating and yawing. Will be refined before -release. TODO: Demonstrate shutting off turbines also, once developed. -""" - -# Grab model of FLORIS and update to deratable turbines -fmodel = FlorisModel("inputs/gch.yaml") - -with open(str( - fmodel.core.as_dict()["farm"]["turbine_library_path"] / - (fmodel.core.as_dict()["farm"]["turbine_type"][0] + ".yaml") -)) as t: - turbine_type = yaml.safe_load(t) -turbine_type["operation_model"] = "simple-derating" - -# Convert to a simple two turbine layout with derating turbines -fmodel.set(layout_x=[0, 1000.0], layout_y=[0.0, 0.0], turbine_type=[turbine_type]) - -# Set the wind directions and speeds to be constant over n_findex = N time steps -N = 50 -fmodel.set( - wind_directions=270 * np.ones(N), - wind_speeds=10.0 * np.ones(N), - turbulence_intensities=0.06 * np.ones(N) -) -fmodel.run() -turbine_powers_orig = fmodel.get_turbine_powers() - -# Add derating -power_setpoints = np.tile(np.linspace(1, 6e6, N), 2).reshape(2, N).T -fmodel.set(power_setpoints=power_setpoints) -fmodel.run() -turbine_powers_derated = fmodel.get_turbine_powers() - -# Compute available power at downstream turbine -power_setpoints_2 = np.array([np.linspace(1, 6e6, N), np.full(N, None)]).T -fmodel.set(power_setpoints=power_setpoints_2) -fmodel.run() -turbine_powers_avail_ds = fmodel.get_turbine_powers()[:,1] - -# Plot the results -fig, ax = plt.subplots(1, 1) -ax.plot(power_setpoints[:, 0]/1000, turbine_powers_derated[:, 0]/1000, color="C0", label="Upstream") -ax.plot( - power_setpoints[:, 1]/1000, - turbine_powers_derated[:, 1]/1000, - color="C1", - label="Downstream" -) -ax.plot( - power_setpoints[:, 0]/1000, - turbine_powers_orig[:, 0]/1000, - color="C0", - linestyle="dotted", - label="Upstream available" -) -ax.plot( - power_setpoints[:, 1]/1000, - turbine_powers_avail_ds/1000, - color="C1", - linestyle="dotted", label="Downstream available" -) -ax.plot( - power_setpoints[:, 1]/1000, - np.ones(N)*np.max(turbine_type["power_thrust_table"]["power"]), - color="k", - linestyle="dashed", - label="Rated power" -) -ax.grid() -ax.legend() -ax.set_xlim([0, 6e3]) -ax.set_xlabel("Power setpoint (kW)") -ax.set_ylabel("Power produced (kW)") - -# Second example showing mixed model use. -turbine_type["operation_model"] = "mixed" -yaw_angles = np.array([ - [0.0, 0.0], - [0.0, 0.0], - [20.0, 10.0], - [0.0, 10.0], - [20.0, 0.0] -]) -power_setpoints = np.array([ - [None, None], - [2e6, 1e6], - [None, None], - [2e6, None,], - [None, 1e6] -]) -fmodel.set( - wind_directions=270 * np.ones(len(yaw_angles)), - wind_speeds=10.0 * np.ones(len(yaw_angles)), - turbulence_intensities=0.06 * np.ones(len(yaw_angles)), - turbine_type=[turbine_type]*2, - yaw_angles=yaw_angles, - power_setpoints=power_setpoints, -) -fmodel.run() -turbine_powers = fmodel.get_turbine_powers() -print(turbine_powers) - -plt.show() From 6ffc40ad4a055b4cddb1c73a791ecacff6b7cc16 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 13:25:11 -0600 Subject: [PATCH 071/120] Add 001 example --- .../001_disable_turbines.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 examples/examples_control_types/001_disable_turbines.py diff --git a/examples/examples_control_types/001_disable_turbines.py b/examples/examples_control_types/001_disable_turbines.py new file mode 100644 index 000000000..e8cd4b94c --- /dev/null +++ b/examples/examples_control_types/001_disable_turbines.py @@ -0,0 +1,79 @@ +"""Example 001: Disable turbines + +This example is adapted from https://github.com/NREL/floris/pull/693 +contributed by Elie Kadoche. + +This example demonstrates the ability of FLORIS to shut down some turbines +during a simulation. +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel + + +# Initialize FLORIS +fmodel = FlorisModel("../inputs/gch.yaml") + +# Change to the mixed model turbine +# (Note this could also be done with the simple-derating model) +fmodel.set_operation_model("mixed") + +# Consider a wind farm of 3 aligned wind turbines +layout = np.array([[0.0, 0.0], [500.0, 0.0], [1000.0, 0.0]]) + +# Run the computations for 2 identical wind data +# (n_findex = 2) +wind_directions = np.array([270.0, 270.0]) +wind_speeds = np.array([8.0, 8.0]) +turbulence_intensities = np.array([0.06, 0.06]) + +# Shut down the first 2 turbines for the second findex +# 2 findex x 3 turbines +disable_turbines = np.array([[False, False, False], [True, True, False]]) + +# Simulation +# ------------------------------------------ + +# Reinitialize flow field +fmodel.set( + layout_x=layout[:, 0], + layout_y=layout[:, 1], + wind_directions=wind_directions, + wind_speeds=wind_speeds, + turbulence_intensities=turbulence_intensities, + disable_turbines=disable_turbines, +) + +# # Compute wakes +fmodel.run() + +# Results +# ------------------------------------------ + +# Get powers and effective wind speeds +turbine_powers = fmodel.get_turbine_powers() +turbine_powers = np.round(turbine_powers * 1e-3, decimals=2) +effective_wind_speeds = fmodel.turbine_average_velocities + + +# Plot the results +fig, axarr = plt.subplots(2, 1, sharex=True) + +# Plot the power +ax = axarr[0] +ax.plot(["T0", "T1", "T2"], turbine_powers[0, :], "ks-", label="All on") +ax.plot(["T0", "T1", "T2"], turbine_powers[1, :], "ro-", label="T0 & T1 disabled") +ax.set_ylabel("Power (kW)") +ax.grid(True) +ax.legend() + +ax = axarr[1] +ax.plot(["T0", "T1", "T2"], effective_wind_speeds[0, :], "ks-", label="All on") +ax.plot(["T0", "T1", "T2"], effective_wind_speeds[1, :], "ro-", label="T0 & T1 disabled") +ax.set_ylabel("Effective wind speeds (m/s)") +ax.grid(True) +ax.legend() + +plt.show() From 1d191450b8c5f0492523f39a2dcd8fa2eac7bf0f Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 13:25:25 -0600 Subject: [PATCH 072/120] Add 002 example --- .../002_setting_yaw_and_disabling.py | 83 ++++++++++++++++++ .../41_test_disable_turbines.py | 85 ------------------- .../setting_yaw_and_dertating.py | 3 - 3 files changed, 83 insertions(+), 88 deletions(-) create mode 100644 examples/examples_control_types/002_setting_yaw_and_disabling.py delete mode 100644 examples/examples_control_types/41_test_disable_turbines.py delete mode 100644 examples/examples_control_types/setting_yaw_and_dertating.py diff --git a/examples/examples_control_types/002_setting_yaw_and_disabling.py b/examples/examples_control_types/002_setting_yaw_and_disabling.py new file mode 100644 index 000000000..fb526009f --- /dev/null +++ b/examples/examples_control_types/002_setting_yaw_and_disabling.py @@ -0,0 +1,83 @@ +"""Example: Setting yaw angles and disabling turbine + +This example demonstrates how to set yaw angles and disable turbines in FLORIS. +The yaw angles are set to sweep from -20 to 20 degrees for the upstream-most turbine +and to 0 degrees for the downstream-most turbine(s). A two-turbine case is compared +to a three-turbine case where the middle turbine is disabled making the two cases +functionally equivalent. +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel, TimeSeries + + +# Initialize 2 FLORIS models, a two-turbine layout +# and three-turbine layout +fmodel_2 = FlorisModel("../inputs/gch.yaml") +fmodel_3 = FlorisModel("../inputs/gch.yaml") + +# Change to the mixed model turbine +# This example sets both yaw angle and power setpoints +fmodel_2.set_operation_model("mixed") +fmodel_3.set_operation_model("mixed") + +# Set the layouts, f_model_3 has an extra turbine in-between the two +# turbines of f_model_2 +fmodel_2.set(layout_x=[0, 1000.0], layout_y=[0.0, 0.0]) +fmodel_3.set(layout_x=[0, 500.0, 1000.0], layout_y=[0.0, 0.0, 0.0]) + +# Set bo + +# Set both to have constant wind conditions +N = 50 +time_series = TimeSeries( + wind_directions=270.0 * np.ones(N), + wind_speeds = 8., + turbulence_intensities=0.06 + ) +fmodel_2.set(wind_data=time_series) +fmodel_3.set(wind_data=time_series) + +# In both cases, set the yaw angles of the upstream-most turbine +# to sweep from -20 to 20 degrees, while other turbines are set to 0 +upstream_yaw_angles = np.linspace(-20, 20, N) +yaw_angles_2 = np.array([upstream_yaw_angles, np.zeros(N)]).T +yaw_angles_3 = np.array([upstream_yaw_angles, np.zeros(N), np.zeros(N)]).T + +# In the three turbine case, also disable the middle turbine +# Declare a np array of booleans that is Nx3 and whose middle column is True +disable_turbines = np.array([np.zeros(N), np.ones(N), np.zeros(N)]).T.astype(bool) + +# Set the yaw angles for both and disable the middle turbine for the +# three turbine case +fmodel_2.set(yaw_angles=yaw_angles_2) +fmodel_3.set(yaw_angles=yaw_angles_3, disable_turbines=disable_turbines) + +# Run both models +fmodel_2.run() +fmodel_3.run() + +# Collect the turbine powers from both +turbine_powers_2 = fmodel_2.get_turbine_powers() +turbine_powers_3 = fmodel_3.get_turbine_powers() + +# Make a 2-panel plot of the turbine powers. For the three-turbine case, +# only plot the first and last turbine +fig, axarr = plt.subplots(2, 1, sharex=True) +axarr[0].plot(upstream_yaw_angles, turbine_powers_2[:, 0] / 1000, label="Two-Turbine", marker='s') +axarr[0].plot(upstream_yaw_angles, turbine_powers_3[:, 0] / 1000, label="Three-Turbine", marker='.') +axarr[0].set_ylabel("Power (kW)") +axarr[0].legend() +axarr[0].grid(True) +axarr[0].set_title("Upstream Turbine") + +axarr[1].plot(upstream_yaw_angles, turbine_powers_2[:, 1] / 1000, label="Two-Turbine", marker='s') +axarr[1].plot(upstream_yaw_angles, turbine_powers_3[:, 2] / 1000, label="Three-Turbine", marker='.') +axarr[1].set_ylabel("Power (kW)") +axarr[1].legend() +axarr[1].grid(True) +axarr[1].set_title("Downstream-most Turbine") + +plt.show() diff --git a/examples/examples_control_types/41_test_disable_turbines.py b/examples/examples_control_types/41_test_disable_turbines.py deleted file mode 100644 index 9dfb2620b..000000000 --- a/examples/examples_control_types/41_test_disable_turbines.py +++ /dev/null @@ -1,85 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np -import yaml - -from floris import FlorisModel - - -""" -Adapted from https://github.com/NREL/floris/pull/693 contributed by Elie Kadoche -This example demonstrates the ability of FLORIS to shut down some turbines -during a simulation. -""" - -# Initialize FLORIS -fmodel = FlorisModel("inputs/gch.yaml") - -# Change to the mixed model turbine -with open( - str( - fmodel.core.as_dict()["farm"]["turbine_library_path"] - / (fmodel.core.as_dict()["farm"]["turbine_type"][0] + ".yaml") - ) -) as t: - turbine_type = yaml.safe_load(t) -turbine_type["operation_model"] = "mixed" -fmodel.set(turbine_type=[turbine_type]) - -# Consider a wind farm of 3 aligned wind turbines -layout = np.array([[0.0, 0.0], [500.0, 0.0], [1000.0, 0.0]]) - -# Run the computations for 2 identical wind data -# (n_findex = 2) -wind_directions = np.array([270.0, 270.0]) -wind_speeds = np.array([8.0, 8.0]) -turbulence_intensities = np.array([0.06, 0.06]) - -# Shut down the first 2 turbines for the second findex -# 2 findex x 3 turbines -disable_turbines = np.array([[False, False, False], [True, True, False]]) - -# Simulation -# ------------------------------------------ - -# Reinitialize flow field -fmodel.set( - layout_x=layout[:, 0], - layout_y=layout[:, 1], - wind_directions=wind_directions, - wind_speeds=wind_speeds, - turbulence_intensities=turbulence_intensities, - disable_turbines=disable_turbines, -) - -# # Compute wakes -fmodel.run() - -# Results -# ------------------------------------------ - -# Get powers and effective wind speeds -turbine_powers = fmodel.get_turbine_powers() -turbine_powers = np.round(turbine_powers * 1e-3, decimals=2) -effective_wind_speeds = fmodel.turbine_average_velocities - - -# Plot the results -fig, axarr = plt.subplots(2, 1, sharex=True) - -# Plot the power -ax = axarr[0] -ax.plot(["T0", "T1", "T2"], turbine_powers[0, :], "ks-", label="All on") -ax.plot(["T0", "T1", "T2"], turbine_powers[1, :], "ro-", label="T0 & T1 disabled") -ax.set_ylabel("Power (kW)") -ax.grid(True) -ax.legend() - -ax = axarr[1] -ax.plot(["T0", "T1", "T2"], effective_wind_speeds[0, :], "ks-", label="All on") -ax.plot(["T0", "T1", "T2"], effective_wind_speeds[1, :], "ro-", label="T0 & T1 disabled") -ax.set_ylabel("Effective wind speeds (m/s)") -ax.grid(True) -ax.legend() - -plt.show() diff --git a/examples/examples_control_types/setting_yaw_and_dertating.py b/examples/examples_control_types/setting_yaw_and_dertating.py deleted file mode 100644 index 58d878877..000000000 --- a/examples/examples_control_types/setting_yaw_and_dertating.py +++ /dev/null @@ -1,3 +0,0 @@ -# TO BE WRITTEN - -# Show that can use both but not at same turbine at same findex From b574c298d8b8688fbc69d14aa5b73978d42f84b6 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 13:30:24 -0600 Subject: [PATCH 073/120] Small bugfix --- floris/core/turbine/operation_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/floris/core/turbine/operation_models.py b/floris/core/turbine/operation_models.py index 88f0f4fac..d614b17e9 100644 --- a/floris/core/turbine/operation_models.py +++ b/floris/core/turbine/operation_models.py @@ -396,7 +396,8 @@ def power( power_setpoints: NDArrayFloat, **kwargs ): - yaw_angles_mask = yaw_angles > 0 + # Yaw angles mask all yaw_angles not equal to zero + yaw_angles_mask = yaw_angles != 0.0 power_setpoints_mask = power_setpoints < POWER_SETPOINT_DEFAULT neither_mask = np.logical_not(yaw_angles_mask) & np.logical_not(power_setpoints_mask) From 3c68038cdc0ea7c45c248078181aa4ceb85977bc Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 13:31:04 -0600 Subject: [PATCH 074/120] bugfix --- floris/core/turbine/operation_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/floris/core/turbine/operation_models.py b/floris/core/turbine/operation_models.py index d614b17e9..a4ddfddfe 100644 --- a/floris/core/turbine/operation_models.py +++ b/floris/core/turbine/operation_models.py @@ -428,7 +428,7 @@ def thrust_coefficient( power_setpoints: NDArrayFloat, **kwargs ): - yaw_angles_mask = yaw_angles > 0 + yaw_angles_mask = yaw_angles != 0.0 power_setpoints_mask = power_setpoints < POWER_SETPOINT_DEFAULT neither_mask = np.logical_not(yaw_angles_mask) & np.logical_not(power_setpoints_mask) @@ -459,7 +459,7 @@ def axial_induction( power_setpoints: NDArrayFloat, **kwargs ): - yaw_angles_mask = yaw_angles > 0 + yaw_angles_mask = yaw_angles != 0.0 power_setpoints_mask = power_setpoints < POWER_SETPOINT_DEFAULT neither_mask = np.logical_not(yaw_angles_mask) & np.logical_not(power_setpoints_mask) From e7389d1b2a03637fde0a5e7f0847cb3028367616 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 13:44:34 -0600 Subject: [PATCH 075/120] Update emgauss examples --- ...ical_gauss_velocity_deficit_parameters.py} | 126 +++++++++--------- ..._empirical_gauss_deflection_parameters.py} | 13 +- ...3_tilt_driven_vertical_wake_deflection.py} | 48 +++---- 3 files changed, 91 insertions(+), 96 deletions(-) rename examples/examples_emgauss/{26_empirical_gauss_velocity_deficit_parameters.py => 001_empirical_gauss_velocity_deficit_parameters.py} (57%) rename examples/examples_emgauss/{27_empirical_gauss_deflection_parameters.py => 002_empirical_gauss_deflection_parameters.py} (98%) rename examples/examples_emgauss/{25_tilt_driven_vertical_wake_deflection.py => 003_tilt_driven_vertical_wake_deflection.py} (70%) diff --git a/examples/examples_emgauss/26_empirical_gauss_velocity_deficit_parameters.py b/examples/examples_emgauss/001_empirical_gauss_velocity_deficit_parameters.py similarity index 57% rename from examples/examples_emgauss/26_empirical_gauss_velocity_deficit_parameters.py rename to examples/examples_emgauss/001_empirical_gauss_velocity_deficit_parameters.py index a3c43343a..4cdf37bea 100644 --- a/examples/examples_emgauss/26_empirical_gauss_velocity_deficit_parameters.py +++ b/examples/examples_emgauss/001_empirical_gauss_velocity_deficit_parameters.py @@ -1,3 +1,7 @@ +"""Example: Empirical Gaussian velocity deficit parameters +This example illustrates the main parameters of the Empirical Gaussian +velocity deficit model and their effects on the wind turbine wake. +""" import copy @@ -5,20 +9,16 @@ import numpy as np from floris import FlorisModel -from floris.flow_visualization import plot_rotor_values, visualize_cut_plane +from floris.flow_visualization import visualize_cut_plane -""" -This example illustrates the main parameters of the Empirical Gaussian -velocity deficit model and their effects on the wind turbine wake. -""" - # Options show_flow_cuts = True num_in_row = 5 yaw_angles = np.zeros((1, num_in_row)) + # Define function for visualizing wakes def generate_wake_visualization(fmodel: FlorisModel, title=None): # Using the FlorisModel functions, get 2D slices. @@ -38,7 +38,7 @@ def generate_wake_visualization(fmodel: FlorisModel, title=None): height=horizontal_plane_location, x_bounds=x_bounds, y_bounds=y_bounds, - yaw_angles=yaw_angles + yaw_angles=yaw_angles, ) y_plane = fmodel.calculate_y_plane( x_resolution=200, @@ -46,64 +46,67 @@ def generate_wake_visualization(fmodel: FlorisModel, title=None): crossstream_dist=streamwise_plane_location, x_bounds=x_bounds, z_bounds=z_bounds, - yaw_angles=yaw_angles + yaw_angles=yaw_angles, ) cross_planes = [] for cpl in cross_plane_locations: cross_planes.append( - fmodel.calculate_cross_plane( - y_resolution=100, - z_resolution=100, - downstream_dist=cpl - ) + fmodel.calculate_cross_plane(y_resolution=100, z_resolution=100, downstream_dist=cpl) ) # Create the plots # Cutplane settings - cp_ls = "solid" # line style - cp_lw = 0.5 # line width - cp_clr = "black" # line color + cp_ls = "solid" # line style + cp_lw = 0.5 # line width + cp_clr = "black" # line color fig = plt.figure() fig.set_size_inches(12, 12) # Horizontal profile ax = fig.add_subplot(311) - visualize_cut_plane(horizontal_plane, ax=ax, title="Top-down profile", - min_speed=min_ws, max_speed=max_ws) - ax.plot(x_bounds, [streamwise_plane_location]*2, color=cp_clr, - linewidth=cp_lw, linestyle=cp_ls) + visualize_cut_plane( + horizontal_plane, ax=ax, title="Top-down profile", min_speed=min_ws, max_speed=max_ws + ) + ax.plot( + x_bounds, [streamwise_plane_location] * 2, color=cp_clr, linewidth=cp_lw, linestyle=cp_ls + ) for cpl in cross_plane_locations: - ax.plot([cpl]*2, y_bounds, color=cp_clr, linewidth=cp_lw, - linestyle=cp_ls) + ax.plot([cpl] * 2, y_bounds, color=cp_clr, linewidth=cp_lw, linestyle=cp_ls) ax = fig.add_subplot(312) - visualize_cut_plane(y_plane, ax=ax, title="Streamwise profile", - min_speed=min_ws, max_speed=max_ws) - ax.plot(x_bounds, [horizontal_plane_location]*2, color=cp_clr, - linewidth=cp_lw, linestyle=cp_ls) + visualize_cut_plane( + y_plane, ax=ax, title="Streamwise profile", min_speed=min_ws, max_speed=max_ws + ) + ax.plot( + x_bounds, [horizontal_plane_location] * 2, color=cp_clr, linewidth=cp_lw, linestyle=cp_ls + ) for cpl in cross_plane_locations: - ax.plot([cpl, cpl], z_bounds, color=cp_clr, linewidth=cp_lw, - linestyle=cp_ls) + ax.plot([cpl, cpl], z_bounds, color=cp_clr, linewidth=cp_lw, linestyle=cp_ls) # Spanwise profiles for i, (cp, cpl) in enumerate(zip(cross_planes, cross_plane_locations)): - visualize_cut_plane(cp, ax=fig.add_subplot(3, len(cross_planes), i+7), - title="Loc: {:.0f}m".format(cpl), min_speed=min_ws, - max_speed=max_ws) + visualize_cut_plane( + cp, + ax=fig.add_subplot(3, len(cross_planes), i + 7), + title="Loc: {:.0f}m".format(cpl), + min_speed=min_ws, + max_speed=max_ws, + ) # Add overall figure title if title is not None: fig.suptitle(title, fontsize=16) + ## Main script # Load input yaml and define farm layout -fmodel = FlorisModel("inputs/emgauss.yaml") +fmodel = FlorisModel("../inputs/emgauss.yaml") D = fmodel.core.farm.rotor_diameters[0] fmodel.set( - layout_x=[x*5.0*D for x in range(num_in_row)], - layout_y=[0.0]*num_in_row, + layout_x=[x * 5.0 * D for x in range(num_in_row)], + layout_y=[0.0] * num_in_row, wind_speeds=[8.0], - wind_directions=[270.0] + wind_directions=[270.0], ) # Save dictionary to modify later @@ -113,12 +116,12 @@ def generate_wake_visualization(fmodel: FlorisModel, title=None): fmodel.run() # Look at the powers of each turbine -turbine_powers = fmodel.get_turbine_powers().flatten()/1e6 +turbine_powers = fmodel.get_turbine_powers().flatten() / 1e6 -fig0, ax0 = plt.subplots(1,1) +fig0, ax0 = plt.subplots(1, 1) width = 0.1 nw = -2 -x = np.array(range(num_in_row))+width*nw +x = np.array(range(num_in_row)) + width * nw nw += 1 title = "Original" @@ -131,18 +134,17 @@ def generate_wake_visualization(fmodel: FlorisModel, title=None): # Increase the base recovery rate fmodel_dict_mod = copy.deepcopy(fmodel_dict) -fmodel_dict_mod['wake']['wake_velocity_parameters']['empirical_gauss']\ - ['wake_expansion_rates'] = [0.03, 0.015] +fmodel_dict_mod["wake"]["wake_velocity_parameters"]["empirical_gauss"]["wake_expansion_rates"] = [ + 0.03, + 0.015, +] fmodel = FlorisModel(fmodel_dict_mod) -fmodel.set( - wind_speeds=[8.0], - wind_directions=[270.0] -) +fmodel.set(wind_speeds=[8.0], wind_directions=[270.0]) fmodel.run() -turbine_powers = fmodel.get_turbine_powers().flatten()/1e6 +turbine_powers = fmodel.get_turbine_powers().flatten() / 1e6 -x = np.array(range(num_in_row))+width*nw +x = np.array(range(num_in_row)) + width * nw nw += 1 title = "Increase base recovery" @@ -153,23 +155,19 @@ def generate_wake_visualization(fmodel: FlorisModel, title=None): # Add new expansion rate fmodel_dict_mod = copy.deepcopy(fmodel_dict) -fmodel_dict_mod['wake']['wake_velocity_parameters']['empirical_gauss']\ - ['wake_expansion_rates'] = \ - fmodel_dict['wake']['wake_velocity_parameters']['empirical_gauss']\ - ['wake_expansion_rates'] + [0.0] -fmodel_dict_mod['wake']['wake_velocity_parameters']['empirical_gauss']\ - ['breakpoints_D'] = [5, 10] +fmodel_dict_mod["wake"]["wake_velocity_parameters"]["empirical_gauss"]["wake_expansion_rates"] = ( + fmodel_dict["wake"]["wake_velocity_parameters"]["empirical_gauss"]["wake_expansion_rates"] + + [0.0] +) +fmodel_dict_mod["wake"]["wake_velocity_parameters"]["empirical_gauss"]["breakpoints_D"] = [5, 10] fmodel = FlorisModel(fmodel_dict_mod) -fmodel.set( - wind_speeds=[8.0], - wind_directions=[270.0] -) +fmodel.set(wind_speeds=[8.0], wind_directions=[270.0]) fmodel.run() -turbine_powers = fmodel.get_turbine_powers().flatten()/1e6 +turbine_powers = fmodel.get_turbine_powers().flatten() / 1e6 -x = np.array(range(num_in_row))+width*nw +x = np.array(range(num_in_row)) + width * nw nw += 1 title = "Add rate, change breakpoints" @@ -180,18 +178,14 @@ def generate_wake_visualization(fmodel: FlorisModel, title=None): # Increase the wake-induced mixing gain fmodel_dict_mod = copy.deepcopy(fmodel_dict) -fmodel_dict_mod['wake']['wake_velocity_parameters']['empirical_gauss']\ - ['mixing_gain_velocity'] = 3.0 +fmodel_dict_mod["wake"]["wake_velocity_parameters"]["empirical_gauss"]["mixing_gain_velocity"] = 3.0 fmodel = FlorisModel(fmodel_dict_mod) -fmodel.set( - wind_speeds=[8.0], - wind_directions=[270.0] -) +fmodel.set(wind_speeds=[8.0], wind_directions=[270.0]) fmodel.run() -turbine_powers = fmodel.get_turbine_powers().flatten()/1e6 +turbine_powers = fmodel.get_turbine_powers().flatten() / 1e6 -x = np.array(range(num_in_row))+width*nw +x = np.array(range(num_in_row)) + width * nw nw += 1 title = "Increase mixing gain" diff --git a/examples/examples_emgauss/27_empirical_gauss_deflection_parameters.py b/examples/examples_emgauss/002_empirical_gauss_deflection_parameters.py similarity index 98% rename from examples/examples_emgauss/27_empirical_gauss_deflection_parameters.py rename to examples/examples_emgauss/002_empirical_gauss_deflection_parameters.py index 79bdee9f8..b945ad8dc 100644 --- a/examples/examples_emgauss/27_empirical_gauss_deflection_parameters.py +++ b/examples/examples_emgauss/002_empirical_gauss_deflection_parameters.py @@ -1,3 +1,9 @@ +"""Example: Empirical Gaussian deflection parameters + +This example illustrates the main parameters of the Empirical Gaussian +deflection model and their effects on the wind turbine wake. +""" + import copy @@ -8,11 +14,6 @@ from floris.flow_visualization import plot_rotor_values, visualize_cut_plane -""" -This example illustrates the main parameters of the Empirical Gaussian -deflection model and their effects on the wind turbine wake. -""" - # Initialize FLORIS with the given input file. # For basic usage, FlorisModel provides a simplified and expressive # entry point to the simulation routines. @@ -105,7 +106,7 @@ def generate_wake_visualization(fmodel, title=None): ## Main script # Load input yaml and define farm layout -fmodel = FlorisModel("inputs/emgauss.yaml") +fmodel = FlorisModel("../inputs/emgauss.yaml") D = fmodel.core.farm.rotor_diameters[0] fmodel.set( layout_x=[x*5.0*D for x in range(num_in_row)], diff --git a/examples/examples_emgauss/25_tilt_driven_vertical_wake_deflection.py b/examples/examples_emgauss/003_tilt_driven_vertical_wake_deflection.py similarity index 70% rename from examples/examples_emgauss/25_tilt_driven_vertical_wake_deflection.py rename to examples/examples_emgauss/003_tilt_driven_vertical_wake_deflection.py index b8d6ffbf5..88049cc7f 100644 --- a/examples/examples_emgauss/25_tilt_driven_vertical_wake_deflection.py +++ b/examples/examples_emgauss/003_tilt_driven_vertical_wake_deflection.py @@ -1,3 +1,10 @@ +"""Example: Tilt-driven vertical wake deflection +This example demonstrates vertical wake deflections due to the tilt angle when running +with the Empirical Gauss model. Note that only the Empirical Gauss model implements +vertical deflections at this time. Also be aware that this example uses a potentially +unrealistic tilt angle, 15 degrees, to highlight the wake deflection. Moreover, the magnitude +of vertical deflections due to tilt has not been validated. +""" import matplotlib.pyplot as plt import numpy as np @@ -6,19 +13,11 @@ from floris.flow_visualization import visualize_cut_plane -""" -This example demonstrates vertical wake deflections due to the tilt angle when running -with the Empirical Gauss model. Note that only the Empirical Gauss model implements -vertical deflections at this time. Also be aware that this example uses a potentially -unrealistic tilt angle, 15 degrees, to highlight the wake deflection. Moreover, the magnitude -of vertical deflections due to tilt has not been validated. -""" - # Initialize two FLORIS objects: one with 5 degrees of tilt (fixed across all # wind speeds) and one with 15 degrees of tilt (fixed across all wind speeds). -fmodel_5 = FlorisModel("inputs_floating/emgauss_floating_fixedtilt5.yaml") -fmodel_15 = FlorisModel("inputs_floating/emgauss_floating_fixedtilt15.yaml") +fmodel_5 = FlorisModel("../inputs_floating/emgauss_floating_fixedtilt5.yaml") +fmodel_15 = FlorisModel("../inputs_floating/emgauss_floating_fixedtilt15.yaml") D = fmodel_5.core.farm.rotor_diameters[0] @@ -30,14 +29,14 @@ z_bounds = [0.001, 500] cross_plane_locations = [10, 1200, 2500] -horizontal_plane_location=90.0 -streamwise_plane_location=0.0 +horizontal_plane_location = 90.0 +streamwise_plane_location = 0.0 # Create the plots # Cutplane settings -cp_ls = "solid" # line style -cp_lw = 0.5 # line width -cp_clr = "black" # line color +cp_ls = "solid" # line style +cp_lw = 0.5 # line width +cp_clr = "black" # line color min_ws = 4 max_ws = 10 fig = plt.figure() @@ -47,18 +46,17 @@ # Calculate wakes, powers, plot for i, (fmodel, tilt) in enumerate(zip([fmodel_5, fmodel_15], [5, 15])): - # Farm layout and wind conditions fmodel.set( layout_x=[x * 5.0 * D for x in range(num_in_row)], - layout_y=[0.0]*num_in_row, + layout_y=[0.0] * num_in_row, wind_speeds=[8.0], - wind_directions=[270.0] + wind_directions=[270.0], ) # Flow solve and power computation fmodel.run() - powers[i,:] = fmodel.get_turbine_powers().flatten() + powers[i, :] = fmodel.get_turbine_powers().flatten() # Compute flow slices y_plane = fmodel.calculate_y_plane( @@ -66,13 +64,15 @@ z_resolution=100, crossstream_dist=streamwise_plane_location, x_bounds=x_bounds, - z_bounds=z_bounds + z_bounds=z_bounds, ) # Horizontal profile - ax = fig.add_subplot(2, 1, i+1) + ax = fig.add_subplot(2, 1, i + 1) visualize_cut_plane(y_plane, ax=ax, min_speed=min_ws, max_speed=max_ws) - ax.plot(x_bounds, [horizontal_plane_location]*2, color=cp_clr, linewidth=cp_lw, linestyle=cp_ls) + ax.plot( + x_bounds, [horizontal_plane_location] * 2, color=cp_clr, linewidth=cp_lw, linestyle=cp_ls + ) ax.set_title("Tilt angle: {0} degrees".format(tilt)) fig = plt.figure() @@ -80,8 +80,8 @@ ax = fig.add_subplot(1, 1, 1) x_locs = np.arange(num_in_row) width = 0.25 -ax.bar(x_locs-width/2, powers[0,:]/1000, width=width, label="5 degree tilt") -ax.bar(x_locs+width/2, powers[1,:]/1000, width=width, label="15 degree tilt") +ax.bar(x_locs - width / 2, powers[0, :] / 1000, width=width, label="5 degree tilt") +ax.bar(x_locs + width / 2, powers[1, :] / 1000, width=width, label="15 degree tilt") ax.set_xticks(x_locs) ax.set_xticklabels(["T{0}".format(i) for i in range(num_in_row)]) ax.set_xlabel("Turbine number in row") From f4a3b7dc086e06dab7508701ee0297a57021adf0 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 14:30:41 -0600 Subject: [PATCH 076/120] Update floating examples --- ...dels.py => 001_floating_turbine_models.py} | 123 ++++++++++-------- ...py => 002_floating_vs_fixedbottom_farm.py} | 50 ++++--- 2 files changed, 91 insertions(+), 82 deletions(-) rename examples/examples_floating/{24_floating_turbine_models.py => 001_floating_turbine_models.py} (53%) rename examples/examples_floating/{29_floating_vs_fixedbottom_farm.py => 002_floating_vs_fixedbottom_farm.py} (82%) diff --git a/examples/examples_floating/24_floating_turbine_models.py b/examples/examples_floating/001_floating_turbine_models.py similarity index 53% rename from examples/examples_floating/24_floating_turbine_models.py rename to examples/examples_floating/001_floating_turbine_models.py index 76822a76f..75936b09a 100644 --- a/examples/examples_floating/24_floating_turbine_models.py +++ b/examples/examples_floating/001_floating_turbine_models.py @@ -1,11 +1,4 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel - - -""" +"""Example: Floating turbines This example demonstrates the impact of floating on turbine power and thrust (not wake behavior). A floating turbine in FLORIS is defined by including a `floating_tilt_table` in the turbine input yaml which sets the steady tilt angle of the turbine based on wind speed. This tilt angle @@ -31,32 +24,36 @@ tilt does not scale cp/ct) """ + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel, TimeSeries + + # Create the Floris instances -fmodel_fixed = FlorisModel("inputs_floating/gch_fixed.yaml") -fmodel_floating = FlorisModel("inputs_floating/gch_floating.yaml") -fmodel_floating_defined_floating = FlorisModel("inputs_floating/gch_floating_defined_floating.yaml") - -# Calculate across wind speeds -ws_array = np.arange(3., 25., 1.) -wd_array = 270.0 * np.ones_like(ws_array) -ti_array = 0.06 * np.ones_like(ws_array) -fmodel_fixed.set(wind_speeds=ws_array, wind_directions=wd_array, turbulence_intensities=ti_array) -fmodel_floating.set(wind_speeds=ws_array, wind_directions=wd_array, turbulence_intensities=ti_array) -fmodel_floating_defined_floating.set( - wind_speeds=ws_array, - wind_directions=wd_array, - turbulence_intensities=ti_array +fmodel_fixed = FlorisModel("../inputs_floating/gch_fixed.yaml") +fmodel_floating = FlorisModel("../inputs_floating/gch_floating.yaml") +fmodel_floating_defined_floating = FlorisModel( + "../inputs_floating/gch_floating_defined_floating.yaml" ) +# Calculate across wind speeds, while holding win directions constant +ws_array = np.arange(3.0, 25.0, 1.0) +time_series = TimeSeries(wind_directions=270.0, wind_speeds=ws_array, turbulence_intensities=0.06) +fmodel_fixed.set(wind_data=time_series) +fmodel_floating.set(wind_data=time_series) +fmodel_floating_defined_floating.set(wind_data=time_series) + fmodel_fixed.run() fmodel_floating.run() fmodel_floating_defined_floating.run() # Grab power -power_fixed = fmodel_fixed.get_turbine_powers().flatten()/1000. -power_floating = fmodel_floating.get_turbine_powers().flatten()/1000. +power_fixed = fmodel_fixed.get_turbine_powers().flatten() / 1000.0 +power_floating = fmodel_floating.get_turbine_powers().flatten() / 1000.0 power_floating_defined_floating = ( - fmodel_floating_defined_floating.get_turbine_powers().flatten()/1000. + fmodel_floating_defined_floating.get_turbine_powers().flatten() / 1000.0 ) # Grab Ct @@ -68,62 +65,80 @@ # Grab turbine tilt angles eff_vels = fmodel_fixed.turbine_average_velocities -tilt_angles_fixed = np.squeeze( - fmodel_fixed.core.farm.calculate_tilt_for_eff_velocities(eff_vels) - ) +tilt_angles_fixed = np.squeeze(fmodel_fixed.core.farm.calculate_tilt_for_eff_velocities(eff_vels)) eff_vels = fmodel_floating.turbine_average_velocities tilt_angles_floating = np.squeeze( fmodel_floating.core.farm.calculate_tilt_for_eff_velocities(eff_vels) - ) +) eff_vels = fmodel_floating_defined_floating.turbine_average_velocities tilt_angles_floating_defined_floating = np.squeeze( fmodel_floating_defined_floating.core.farm.calculate_tilt_for_eff_velocities(eff_vels) - ) +) # Plot results -fig, axarr = plt.subplots(4,1, figsize=(8,10), sharex=True) +fig, axarr = plt.subplots(4, 1, figsize=(8, 10), sharex=True) ax = axarr[0] -ax.plot(ws_array, tilt_angles_fixed, color='k',lw=2,label='Fixed Bottom') -ax.plot(ws_array, tilt_angles_floating, color='b',label='Floating') -ax.plot(ws_array, tilt_angles_floating_defined_floating, color='m',ls='--', - label='Floating (cp/ct not scaled by tilt)') +ax.plot(ws_array, tilt_angles_fixed, color="k", lw=2, label="Fixed Bottom") +ax.plot(ws_array, tilt_angles_floating, color="b", label="Floating") +ax.plot( + ws_array, + tilt_angles_floating_defined_floating, + color="m", + ls="--", + label="Floating (cp/ct not scaled by tilt)", +) ax.grid(True) ax.legend() -ax.set_title('Tilt angle (deg)') -ax.set_ylabel('Tlit (deg)') +ax.set_title("Tilt angle (deg)") +ax.set_ylabel("Tlit (deg)") ax = axarr[1] -ax.plot(ws_array, power_fixed, color='k',lw=2,label='Fixed Bottom') -ax.plot(ws_array, power_floating, color='b',label='Floating') -ax.plot(ws_array, power_floating_defined_floating, color='m',ls='--', - label='Floating (cp/ct not scaled by tilt)') +ax.plot(ws_array, power_fixed, color="k", lw=2, label="Fixed Bottom") +ax.plot(ws_array, power_floating, color="b", label="Floating") +ax.plot( + ws_array, + power_floating_defined_floating, + color="m", + ls="--", + label="Floating (cp/ct not scaled by tilt)", +) ax.grid(True) ax.legend() -ax.set_title('Power') -ax.set_ylabel('Power (kW)') +ax.set_title("Power") +ax.set_ylabel("Power (kW)") ax = axarr[2] # ax.plot(ws_array, power_fixed, color='k',label='Fixed Bottom') -ax.plot(ws_array, power_floating - power_fixed, color='b',label='Floating') -ax.plot(ws_array, power_floating_defined_floating - power_fixed, color='m',ls='--', - label='Floating (cp/ct not scaled by tilt)') +ax.plot(ws_array, power_floating - power_fixed, color="b", label="Floating") +ax.plot( + ws_array, + power_floating_defined_floating - power_fixed, + color="m", + ls="--", + label="Floating (cp/ct not scaled by tilt)", +) ax.grid(True) ax.legend() -ax.set_title('Difference from fixed bottom power') -ax.set_ylabel('Power (kW)') +ax.set_title("Difference from fixed bottom power") +ax.set_ylabel("Power (kW)") ax = axarr[3] -ax.plot(ws_array, ct_fixed, color='k',lw=2,label='Fixed Bottom') -ax.plot(ws_array, ct_floating, color='b',label='Floating') -ax.plot(ws_array, ct_floating_defined_floating, color='m',ls='--', - label='Floating (cp/ct not scaled by tilt)') +ax.plot(ws_array, ct_fixed, color="k", lw=2, label="Fixed Bottom") +ax.plot(ws_array, ct_floating, color="b", label="Floating") +ax.plot( + ws_array, + ct_floating_defined_floating, + color="m", + ls="--", + label="Floating (cp/ct not scaled by tilt)", +) ax.grid(True) ax.legend() -ax.set_title('Coefficient of thrust') -ax.set_ylabel('Ct (-)') +ax.set_title("Coefficient of thrust") +ax.set_ylabel("Ct (-)") plt.show() diff --git a/examples/examples_floating/29_floating_vs_fixedbottom_farm.py b/examples/examples_floating/002_floating_vs_fixedbottom_farm.py similarity index 82% rename from examples/examples_floating/29_floating_vs_fixedbottom_farm.py rename to examples/examples_floating/002_floating_vs_fixedbottom_farm.py index ef9745621..0400ac7f1 100644 --- a/examples/examples_floating/29_floating_vs_fixedbottom_farm.py +++ b/examples/examples_floating/002_floating_vs_fixedbottom_farm.py @@ -1,15 +1,5 @@ - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from scipy.interpolate import NearestNDInterpolator - -import floris.flow_visualization as flowviz -from floris import FlorisModel - - -""" -This example demonstrates the impact of floating on turbine power and thurst +"""Example: Floating vs fixed-bottom farm +This example demonstrates the impact of floating on turbine power and thrust and wake behavior. A floating turbine in FLORIS is defined by including a `floating_tilt_table` in the turbine input yaml which sets the steady tilt angle of the turbine based on wind speed. This tilt angle is computed for each @@ -31,9 +21,19 @@ fmodel_floating: Floating turbine (tilt varies with wind speed) """ + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from scipy.interpolate import NearestNDInterpolator + +import floris.flow_visualization as flowviz +from floris import FlorisModel, WindRose + + # Declare the Floris Interface for fixed bottom, provide layout -fmodel_fixed = FlorisModel("inputs_floating/emgauss_fixed.yaml") -fmodel_floating = FlorisModel("inputs_floating/emgauss_floating.yaml") +fmodel_fixed = FlorisModel("../inputs_floating/emgauss_fixed.yaml") +fmodel_floating = FlorisModel("../inputs_floating/emgauss_floating.yaml") x, y = np.meshgrid(np.linspace(0, 4*630., 5), np.linspace(0, 3*630., 4)) x = x.flatten() y = y.flatten() @@ -107,28 +107,22 @@ flowviz.visualize_cut_plane(y_planes[1], ax=ax_list[1], title="Streamwise profile") fig.suptitle("Floating farm") -# Compute AEP (see 07_calc_aep_from_rose.py for details) -df_wr = pd.read_csv("inputs/wind_rose.csv") -wd_grid, ws_grid = np.meshgrid( - np.array(df_wr["wd"].unique(), dtype=float), - np.array(df_wr["ws"].unique(), dtype=float), - indexing="ij" +# Compute AEP +# Load the wind rose from csv as in example 003 +wind_rose = WindRose.read_csv_long( + "../inputs/wind_rose.csv", wd_col="wd", ws_col="ws", freq_col="freq_val", ti_col_or_value=0.06 ) -freq_interp = NearestNDInterpolator(df_wr[["wd", "ws"]], df_wr["freq_val"]) -freq = freq_interp(wd_grid, ws_grid).flatten() -freq = freq / np.sum(freq) + for fmodel in [fmodel_fixed, fmodel_floating]: fmodel.set( - wind_directions=wd_grid.flatten(), - wind_speeds= ws_grid.flatten(), - turbulence_intensities=0.06 * np.ones_like(wd_grid.flatten()) + wind_data=wind_rose, ) fmodel.run() # Compute the AEP -aep_fixed = fmodel_fixed.get_farm_AEP(freq=freq) -aep_floating = fmodel_floating.get_farm_AEP(freq=freq) +aep_fixed = fmodel_fixed.get_farm_AEP() +aep_floating = fmodel_floating.get_farm_AEP() print("Farm AEP (fixed bottom): {:.3f} GWh".format(aep_fixed / 1.0e9)) print("Farm AEP (floating): {:.3f} GWh".format(aep_floating / 1.0e9)) print( From 9c55655cdb739301759e2889809f4c961e89f0b2 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 14:37:11 -0600 Subject: [PATCH 077/120] Update get flow examples --- .../001_extract_wind_speed_at_turbines.py | 39 +++++++ ...py => 002_extract_wind_speed_at_points.py} | 34 +++--- ... => 003_plot_velocity_deficit_profiles.py} | 109 +++++++++--------- .../22_get_wind_speed_at_turbines.py | 33 ------ 4 files changed, 111 insertions(+), 104 deletions(-) create mode 100644 examples/examples_get_flow/001_extract_wind_speed_at_turbines.py rename examples/examples_get_flow/{28_extract_wind_speed_at_points.py => 002_extract_wind_speed_at_points.py} (84%) rename examples/examples_get_flow/{32_plot_velocity_deficit_profiles.py => 003_plot_velocity_deficit_profiles.py} (75%) delete mode 100644 examples/examples_get_flow/22_get_wind_speed_at_turbines.py diff --git a/examples/examples_get_flow/001_extract_wind_speed_at_turbines.py b/examples/examples_get_flow/001_extract_wind_speed_at_turbines.py new file mode 100644 index 000000000..1eed14e75 --- /dev/null +++ b/examples/examples_get_flow/001_extract_wind_speed_at_turbines.py @@ -0,0 +1,39 @@ +"""Example: Extract wind speed at turbines + +This example demonstrates how to extract the wind speed at the turbine points +from the FLORIS model. Both the u velocities and the turbine average +velocities are grabbed from the model, then the turbine average is +recalculated from the u velocities to show that they are equivalent. +""" + + +import numpy as np + +from floris import FlorisModel + + +# Initialize the FLORIS model +fmodel = FlorisModel("../inputs/gch.yaml") + +# Create a 4-turbine layouts +fmodel.set(layout_x=[0, 0.0, 500.0, 500.0], layout_y=[0.0, 300.0, 0.0, 300.0]) + +# Calculate wake +fmodel.run() + +# Collect the wind speed at all the turbine points +u_points = fmodel.core.flow_field.u + +print("U points is 1 findex x 4 turbines x 3 x 3 points (turbine_grid_points=3)") +print(u_points.shape) + +print("turbine_average_velocities is 1 findex x 4 turbines") +print(fmodel.turbine_average_velocities) + +# Show that one is equivalent to the other following averaging +print( + "turbine_average_velocities is determined by taking the cube root of mean " + "of the cubed value across the points " +) +print(f"turbine_average_velocities: {fmodel.turbine_average_velocities}") +print(f"Recomputed: {np.cbrt(np.mean(u_points**3, axis=(2,3)))}") diff --git a/examples/examples_get_flow/28_extract_wind_speed_at_points.py b/examples/examples_get_flow/002_extract_wind_speed_at_points.py similarity index 84% rename from examples/examples_get_flow/28_extract_wind_speed_at_points.py rename to examples/examples_get_flow/002_extract_wind_speed_at_points.py index 7c9b9adbc..aaf086f4b 100644 --- a/examples/examples_get_flow/28_extract_wind_speed_at_points.py +++ b/examples/examples_get_flow/002_extract_wind_speed_at_points.py @@ -1,11 +1,4 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel - - -""" +"""Example: Extract wind speed at points This example demonstrates the use of the sample_flow_at_points method of FlorisModel. sample_flow_at_points extracts the wind speed information at user-specified locations in the flow. @@ -19,21 +12,28 @@ met mast within the two-turbine farm. """ + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel + + # User options # FLORIS model to use (limited to Gauss/GCH, Jensen, and empirical Gauss) -floris_model = "gch" # Try "gch", "jensen", "emgauss" +floris_model = "gch" # Try "gch", "jensen", "emgauss" # Option to try different met mast locations -met_mast_option = 0 # Try 0, 1, 2, 3 +met_mast_option = 0 # Try 0, 1, 2, 3 # Instantiate FLORIS model -fmodel = FlorisModel("inputs/"+floris_model+".yaml") +fmodel = FlorisModel("../inputs/" + floris_model + ".yaml") # Set up a two-turbine farm D = 126 fmodel.set(layout_x=[0, 3 * D], layout_y=[0, 3 * D]) -fig, ax = plt.subplots(1,2) -fig.set_size_inches(10,4) +fig, ax = plt.subplots(1, 2) +fig.set_size_inches(10, 4) ax[0].scatter(fmodel.layout_x, fmodel.layout_y, color="black", label="Turbine") # Set the wind direction to run 360 degrees @@ -44,7 +44,7 @@ # Simulate a met mast in between the turbines if met_mast_option == 0: - points_x = 4 * [3*D] + points_x = 4 * [3 * D] points_y = 4 * [0] elif met_mast_option == 1: points_x = 4 * [200.0] @@ -69,10 +69,10 @@ # Plot the velocities for z_idx, z in enumerate(points_z): - ax[1].plot(wd_array, u_at_points[:, z_idx].flatten(), label=f'Speed at z={z} m') + ax[1].plot(wd_array, u_at_points[:, z_idx].flatten(), label=f"Speed at z={z} m") ax[1].grid() ax[1].legend() -ax[1].set_xlabel('Wind Direction (deg)') -ax[1].set_ylabel('Wind Speed (m/s)') +ax[1].set_xlabel("Wind Direction (deg)") +ax[1].set_ylabel("Wind Speed (m/s)") plt.show() diff --git a/examples/examples_get_flow/32_plot_velocity_deficit_profiles.py b/examples/examples_get_flow/003_plot_velocity_deficit_profiles.py similarity index 75% rename from examples/examples_get_flow/32_plot_velocity_deficit_profiles.py rename to examples/examples_get_flow/003_plot_velocity_deficit_profiles.py index a0b2949e0..546157032 100644 --- a/examples/examples_get_flow/32_plot_velocity_deficit_profiles.py +++ b/examples/examples_get_flow/003_plot_velocity_deficit_profiles.py @@ -1,22 +1,23 @@ +"""Example: Plot velocity deficit profiles + +This example illustrates how to plot velocity deficit profiles at several locations +downstream of a turbine. Here we use the following definition: + velocity_deficit = (homogeneous_wind_speed - u) / homogeneous_wind_speed + , where u is the wake velocity obtained when the incoming wind speed is the + same at all heights and equal to `homogeneous_wind_speed`. +""" + import matplotlib.pyplot as plt import numpy as np from matplotlib import ticker import floris.flow_visualization as flowviz -from floris import cut_plane, FlorisModel +from floris import FlorisModel from floris.flow_visualization import VelocityProfilesFigure from floris.utilities import reverse_rotate_coordinates_rel_west -""" -This example illustrates how to plot velocity deficit profiles at several locations -downstream of a turbine. Here we use the following definition: - velocity_deficit = (homogeneous_wind_speed - u) / homogeneous_wind_speed - , where u is the wake velocity obtained when the incoming wind speed is the - same at all heights and equal to `homogeneous_wind_speed`. -""" - # The first two functions are just used to plot the coordinate system in which the # profiles are sampled. Please go to the main function to begin the example. def plot_coordinate_system(x_origin, y_origin, wind_direction): @@ -27,34 +28,36 @@ def plot_coordinate_system(x_origin, y_origin, wind_direction): [quiver_length, quiver_length], [0, 0], angles=[270 - wind_direction, 360 - wind_direction], - scale_units='x', + scale_units="x", scale=1, ) annotate_coordinate_system(x_origin, y_origin, quiver_length) + def annotate_coordinate_system(x_origin, y_origin, quiver_length): x1 = np.array([quiver_length + 0.35 * D, 0.0]) x2 = np.array([0.0, quiver_length + 0.35 * D]) x3 = np.array([90.0, 90.0]) x, y, _ = reverse_rotate_coordinates_rel_west( - fmodel.core.flow_field.wind_directions, - x1[None, :], - x2[None, :], - x3[None, :], - x_center_of_rotation=0.0, - y_center_of_rotation=0.0, + fmodel.core.flow_field.wind_directions, + x1[None, :], + x2[None, :], + x3[None, :], + x_center_of_rotation=0.0, + y_center_of_rotation=0.0, ) x = np.squeeze(x, axis=0) + x_origin y = np.squeeze(y, axis=0) + y_origin - plt.text(x[0], y[0], '$x_1$', bbox={'facecolor': 'white'}) - plt.text(x[1], y[1], '$x_2$', bbox={'facecolor': 'white'}) + plt.text(x[0], y[0], "$x_1$", bbox={"facecolor": "white"}) + plt.text(x[1], y[1], "$x_2$", bbox={"facecolor": "white"}) -if __name__ == '__main__': - D = 125.88 # Turbine diameter + +if __name__ == "__main__": + D = 125.88 # Turbine diameter hub_height = 90.0 homogeneous_wind_speed = 8.0 - fmodel = FlorisModel("inputs/gch.yaml") + fmodel = FlorisModel("../inputs/gch.yaml") fmodel.set(layout_x=[0.0], layout_y=[0.0]) # ------------------------------ Single-turbine layout ------------------------------ @@ -64,7 +67,7 @@ def annotate_coordinate_system(x_origin, y_origin, quiver_length): # Sample three profiles along three corresponding lines that are all parallel to the y-axis # (cross-stream direction). The streamwise location of each line is given in `downstream_dists`. profiles = fmodel.sample_velocity_deficit_profiles( - direction='cross-stream', + direction="cross-stream", downstream_dists=downstream_dists, homogeneous_wind_speed=homogeneous_wind_speed, ) @@ -72,13 +75,13 @@ def annotate_coordinate_system(x_origin, y_origin, quiver_length): horizontal_plane = fmodel.calculate_horizontal_plane(height=hub_height) fig, ax = plt.subplots(figsize=(6.4, 3)) flowviz.visualize_cut_plane(horizontal_plane, ax) - colors = ['b', 'g', 'c'] + colors = ["b", "g", "c"] for i, profile in enumerate(profiles): # Plot profile coordinates on the horizontal plane - ax.plot(profile['x'], profile['y'], colors[i], label=f'x/D={downstream_dists[i] / D:.1f}') - ax.set_xlabel('x [m]') - ax.set_ylabel('y [m]') - ax.set_title('Streamwise velocity in a horizontal plane: gauss velocity model') + ax.plot(profile["x"], profile["y"], colors[i], label=f"x/D={downstream_dists[i] / D:.1f}") + ax.set_xlabel("x [m]") + ax.set_ylabel("y [m]") + ax.set_title("Streamwise velocity in a horizontal plane: gauss velocity model") fig.tight_layout(rect=[0, 0, 0.82, 1]) ax.legend(bbox_to_anchor=[1.29, 1.04]) @@ -86,34 +89,34 @@ def annotate_coordinate_system(x_origin, y_origin, quiver_length): # Initialize it, plot data, and then customize it further if needed. profiles_fig = VelocityProfilesFigure( downstream_dists_D=downstream_dists / D, - layout=['cross-stream'], - coordinate_labels=['x/D', 'y/D'], + layout=["cross-stream"], + coordinate_labels=["x/D", "y/D"], ) # Add profiles to the VelocityProfilesFigure. This method automatically matches the supplied # profiles to the initialized axes in the figure. - profiles_fig.add_profiles(profiles, color='k') + profiles_fig.add_profiles(profiles, color="k") # Change velocity model to jensen, get the velocity deficit profiles, # and add them to the figure. floris_dict = fmodel.core.as_dict() - floris_dict['wake']['model_strings']['velocity_model'] = 'jensen' + floris_dict["wake"]["model_strings"]["velocity_model"] = "jensen" fmodel = FlorisModel(floris_dict) profiles = fmodel.sample_velocity_deficit_profiles( - direction='cross-stream', + direction="cross-stream", downstream_dists=downstream_dists, homogeneous_wind_speed=homogeneous_wind_speed, resolution=400, ) - profiles_fig.add_profiles(profiles, color='r') + profiles_fig.add_profiles(profiles, color="r") # The dashed reference lines show the extent of the rotor profiles_fig.add_ref_lines_x2([-0.5, 0.5]) for ax in profiles_fig.axs[0]: ax.xaxis.set_major_locator(ticker.MultipleLocator(0.2)) - profiles_fig.axs[0,0].legend(['gauss', 'jensen'], fontsize=11) + profiles_fig.axs[0, 0].legend(["gauss", "jensen"], fontsize=11) profiles_fig.fig.suptitle( - 'Velocity deficit profiles from different velocity models', + "Velocity deficit profiles from different velocity models", fontsize=14, ) @@ -123,19 +126,19 @@ def annotate_coordinate_system(x_origin, y_origin, quiver_length): # sampling-coordinate-system (x1, x2, x3) that is rotated such that x1 is always in the # streamwise direction. The user may define the origin of this coordinate system # (i.e. where to start sampling the profiles). - wind_direction = 315.0 # Try to change this + wind_direction = 315.0 # Try to change this downstream_dists = D * np.array([3, 5]) floris_dict = fmodel.core.as_dict() - floris_dict['wake']['model_strings']['velocity_model'] = 'gauss' + floris_dict["wake"]["model_strings"]["velocity_model"] = "gauss" fmodel = FlorisModel(floris_dict) # Let (x_t1, y_t1) be the location of the second turbine - x_t1 = 2 * D + x_t1 = 2 * D y_t1 = -2 * D fmodel.set(wind_directions=[wind_direction], layout_x=[0.0, x_t1], layout_y=[0.0, y_t1]) # Extract profiles at a set of downstream distances from the starting point (x_start, y_start) cross_profiles = fmodel.sample_velocity_deficit_profiles( - direction='cross-stream', + direction="cross-stream", downstream_dists=downstream_dists, homogeneous_wind_speed=homogeneous_wind_speed, x_start=x_t1, @@ -143,21 +146,20 @@ def annotate_coordinate_system(x_origin, y_origin, quiver_length): ) horizontal_plane = fmodel.calculate_horizontal_plane( - height=hub_height, - x_bounds=[-2 * D, 9 * D] + height=hub_height, x_bounds=[-2 * D, 9 * D] ) ax = flowviz.visualize_cut_plane(horizontal_plane) - colors = ['b', 'g', 'c'] + colors = ["b", "g", "c"] for i, profile in enumerate(cross_profiles): ax.plot( - profile['x'], - profile['y'], + profile["x"], + profile["y"], colors[i], - label=f'$x_1/D={downstream_dists[i] / D:.1f}$', + label=f"$x_1/D={downstream_dists[i] / D:.1f}$", ) - ax.set_xlabel('x [m]') - ax.set_ylabel('y [m]') - ax.set_title('Streamwise velocity in a horizontal plane') + ax.set_xlabel("x [m]") + ax.set_ylabel("y [m]") + ax.set_title("Streamwise velocity in a horizontal plane") ax.legend() plot_coordinate_system(x_origin=x_t1, y_origin=y_t1, wind_direction=wind_direction) @@ -166,7 +168,7 @@ def annotate_coordinate_system(x_origin, y_origin, quiver_length): # profiles are almost identical to the cross-stream profiles. However, we now explicitly # set the profile range. The default range is [-2 * D, 2 * D]. vertical_profiles = fmodel.sample_velocity_deficit_profiles( - direction='vertical', + direction="vertical", profile_range=[-1.5 * D, 1.5 * D], downstream_dists=downstream_dists, homogeneous_wind_speed=homogeneous_wind_speed, @@ -176,19 +178,18 @@ def annotate_coordinate_system(x_origin, y_origin, quiver_length): profiles_fig = VelocityProfilesFigure( downstream_dists_D=downstream_dists / D, - layout=['cross-stream', 'vertical'], + layout=["cross-stream", "vertical"], ) - profiles_fig.add_profiles(cross_profiles + vertical_profiles, color='k') + profiles_fig.add_profiles(cross_profiles + vertical_profiles, color="k") profiles_fig.set_xlim([-0.05, 0.85]) - profiles_fig.axs[1,0].set_ylim([-2.2, 2.2]) + profiles_fig.axs[1, 0].set_ylim([-2.2, 2.2]) for ax in profiles_fig.axs[0]: ax.xaxis.set_major_locator(ticker.MultipleLocator(0.4)) profiles_fig.fig.suptitle( - 'Cross-stream profiles at hub-height, and\nvertical profiles at $x_2 = 0$', + "Cross-stream profiles at hub-height, and\nvertical profiles at $x_2 = 0$", fontsize=14, ) - plt.show() diff --git a/examples/examples_get_flow/22_get_wind_speed_at_turbines.py b/examples/examples_get_flow/22_get_wind_speed_at_turbines.py deleted file mode 100644 index 7f15a4100..000000000 --- a/examples/examples_get_flow/22_get_wind_speed_at_turbines.py +++ /dev/null @@ -1,33 +0,0 @@ - -import numpy as np - -from floris import FlorisModel - - -# Initialize FLORIS with the given input file. -# For basic usage, FlorisModel provides a simplified and expressive -# entry point to the simulation routines. -fmodel = FlorisModel("inputs/gch.yaml") - -# Create a 4-turbine layouts -fmodel.set(layout_x=[0, 0., 500., 500.], layout_y=[0., 300., 0., 300.]) - -# Calculate wake -fmodel.run() - -# Collect the wind speed at all the turbine points -u_points = fmodel.core.flow_field.u - -print('U points is 1 findex x 4 turbines x 3 x 3 points (turbine_grid_points=3)') -print(u_points.shape) - -print('turbine_average_velocities is 1 findex x 4 turbines') -print(fmodel.turbine_average_velocities) - -# Show that one is equivalent to the other following averaging -print( - 'turbine_average_velocities is determined by taking the cube root of mean ' - 'of the cubed value across the points ' -) -print(f'turbine_average_velocities: {fmodel.turbine_average_velocities}') -print(f'Recomputed: {np.cbrt(np.mean(u_points**3, axis=(2,3)))}') From 3b3ab9178e0458b33d9ff1b6ecb7160968244197 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 14:40:48 -0600 Subject: [PATCH 078/120] Update 09 example --- .../009_compare_farm_power_with_neighbor.py | 77 +++++++++++++++++ .../09_compare_farm_power_with_neighbor.py | 85 ------------------- .../xx_using_het_to_approx_farm.py | 1 - 3 files changed, 77 insertions(+), 86 deletions(-) create mode 100644 examples/009_compare_farm_power_with_neighbor.py delete mode 100644 examples/examples_heterogeneous/09_compare_farm_power_with_neighbor.py delete mode 100644 examples/examples_heterogeneous/xx_using_het_to_approx_farm.py diff --git a/examples/009_compare_farm_power_with_neighbor.py b/examples/009_compare_farm_power_with_neighbor.py new file mode 100644 index 000000000..50d334d17 --- /dev/null +++ b/examples/009_compare_farm_power_with_neighbor.py @@ -0,0 +1,77 @@ +"""Example: 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 via wake losses, but whose own +power production is not +considered in farm power / aep production + +""" + + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel + + +# Instantiate FLORIS using either the GCH or CC model +fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 + +# Define a 4 turbine farm turbine farm +D = 126.0 +layout_x = np.array([0, D * 6, 0, D * 6]) +layout_y = [0, 0, D * 3, D * 3] +fmodel.set(layout_x=layout_x, layout_y=layout_y) + +# Define a simple inflow with just 1 wind speed +wd_array = np.arange(0, 360, 4.0) +ws_array = 8.0 * np.ones_like(wd_array) +turbulence_intensities = 0.06 * np.ones_like(wd_array) +fmodel.set( + wind_directions=wd_array, wind_speeds=ws_array, turbulence_intensities=turbulence_intensities +) + + +# Calculate +fmodel.run() + +# Collect the farm power +farm_power_base = fmodel.get_farm_power() / 1e3 # In kW + +# Add a neighbor to the east +layout_x = np.array([0, D * 6, 0, D * 6, D * 12, D * 15, D * 12, D * 15]) +layout_y = np.array([0, 0, D * 3, D * 3, 0, 0, D * 3, D * 3]) +fmodel.set(layout_x=layout_x, layout_y=layout_y) + +# Define the weights to exclude the neighboring farm from calculations of power +turbine_weights = np.zeros(len(layout_x), dtype=int) +turbine_weights[0:4] = 1.0 + +# Calculate +fmodel.run() + +# Collect the farm power with the neighbor +farm_power_neighbor = fmodel.get_farm_power(turbine_weights=turbine_weights) / 1e3 # In kW + +# Show the farms +fig, ax = plt.subplots() +ax.scatter( + layout_x[turbine_weights == 1], layout_y[turbine_weights == 1], color="k", label="Base Farm" +) +ax.scatter( + layout_x[turbine_weights == 0], + layout_y[turbine_weights == 0], + color="r", + label="Neighboring Farm", +) +ax.legend() + +# Plot the power difference +fig, ax = plt.subplots() +ax.plot(wd_array, farm_power_base, color="k", label="Farm Power (no neighbor)") +ax.plot(wd_array, farm_power_neighbor, color="r", label="Farm Power (neighboring farm due east)") +ax.grid(True) +ax.legend() +ax.set_xlabel("Wind Direction (deg)") +ax.set_ylabel("Power (kW)") +plt.show() diff --git a/examples/examples_heterogeneous/09_compare_farm_power_with_neighbor.py b/examples/examples_heterogeneous/09_compare_farm_power_with_neighbor.py deleted file mode 100644 index 59e16f841..000000000 --- a/examples/examples_heterogeneous/09_compare_farm_power_with_neighbor.py +++ /dev/null @@ -1,85 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel - - -""" -This example demonstrates how to use turbine_wieghts to define a set of turbines belonging -to a neighboring farm which -impacts the power production of the farm under consideration via wake losses, but whose own -power production is not -considered in farm power / aep production - -The use of neighboring farms in the context of wake steering design is considered in example -examples/10_optimize_yaw_with_neighboring_farm.py -""" - - -# Instantiate FLORIS using either the GCH or CC model -fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 - -# Define a 4 turbine farm turbine farm -D = 126. -layout_x = np.array([0, D*6, 0, D*6]) -layout_y = [0, 0, D*3, D*3] -fmodel.set(layout_x=layout_x, layout_y=layout_y) - -# Define a simple wind rose with just 1 wind speed -wd_array = np.arange(0,360,4.) -ws_array = 8.0 * np.ones_like(wd_array) -turbulence_intensities = 0.06 * np.ones_like(wd_array) -fmodel.set( - wind_directions=wd_array, - wind_speeds=ws_array, - turbulence_intensities=turbulence_intensities -) - - -# Calculate -fmodel.run() - -# Collect the farm power -farm_power_base = fmodel.get_farm_power() / 1E3 # In kW - -# Add a neighbor to the east -layout_x = np.array([0, D*6, 0, D*6, D*12, D*15, D*12, D*15]) -layout_y = np.array([0, 0, D*3, D*3, 0, 0, D*3, D*3]) -fmodel.set(layout_x=layout_x, layout_y=layout_y) - -# Define the weights to exclude the neighboring farm from calcuations of power -turbine_weights = np.zeros(len(layout_x), dtype=int) -turbine_weights[0:4] = 1.0 - -# Calculate -fmodel.run() - -# Collect the farm power with the neightbor -farm_power_neighbor = fmodel.get_farm_power(turbine_weights=turbine_weights) / 1E3 # In kW - -# Show the farms -fig, ax = plt.subplots() -ax.scatter( - layout_x[turbine_weights==1], - layout_y[turbine_weights==1], - color='k', - label='Base Farm' -) -ax.scatter( - layout_x[turbine_weights==0], - layout_y[turbine_weights==0], - color='r', - label='Neighboring Farm' -) -ax.legend() - -# Plot the power difference -fig, ax = plt.subplots() -ax.plot(wd_array,farm_power_base,color='k',label='Farm Power (no neighbor)') -ax.plot(wd_array,farm_power_neighbor,color='r',label='Farm Power (neighboring farm due east)') -ax.grid(True) -ax.legend() -ax.set_xlabel('Wind Direction (deg)') -ax.set_ylabel('Power (kW)') -plt.show() diff --git a/examples/examples_heterogeneous/xx_using_het_to_approx_farm.py b/examples/examples_heterogeneous/xx_using_het_to_approx_farm.py deleted file mode 100644 index f9bf1abc1..000000000 --- a/examples/examples_heterogeneous/xx_using_het_to_approx_farm.py +++ /dev/null @@ -1 +0,0 @@ -#TODO From a6e8de9dc6e98ec0cd344040c0b147016c5c6e53 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 14:47:18 -0600 Subject: [PATCH 079/120] Update layout examples --- .../001_optimize_layout.py | 4 +- ...002_optimize_layout_with_heterogeneity.py} | 84 ++++++++----------- 2 files changed, 37 insertions(+), 51 deletions(-) rename examples/examples_layout_optimization/{16c_optimize_layout_with_heterogeneity.py => 002_optimize_layout_with_heterogeneity.py} (73%) diff --git a/examples/examples_layout_optimization/001_optimize_layout.py b/examples/examples_layout_optimization/001_optimize_layout.py index 66bf3109e..559b00558 100644 --- a/examples/examples_layout_optimization/001_optimize_layout.py +++ b/examples/examples_layout_optimization/001_optimize_layout.py @@ -35,7 +35,7 @@ freq_table[:,0] = (np.abs(np.sort(np.random.randn(len(wind_directions))))) freq_table = freq_table / freq_table.sum() -# Establish a TimeSeries object +# Establish a WindRose object wind_rose = WindRose( wind_directions=wind_directions, wind_speeds=wind_speeds, @@ -61,7 +61,7 @@ sol = layout_opt.optimize() # Get the resulting improvement in AEP -print('... calcuating improvement in AEP') +print('... calculating improvement in AEP') fmodel.run() base_aep = fmodel.get_farm_AEP() / 1e6 fmodel.set(layout_x=sol[0], layout_y=sol[1]) diff --git a/examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py b/examples/examples_layout_optimization/002_optimize_layout_with_heterogeneity.py similarity index 73% rename from examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py rename to examples/examples_layout_optimization/002_optimize_layout_with_heterogeneity.py index 1a77f39d0..e0879b38c 100644 --- a/examples/examples_layout_optimization/16c_optimize_layout_with_heterogeneity.py +++ b/examples/examples_layout_optimization/002_optimize_layout_with_heterogeneity.py @@ -1,3 +1,14 @@ +"""Example: Layout optimization with heterogeneous inflow +This example shows a layout optimization using the geometric yaw option. It +combines elements of layout optimization and heterogeneous +inflow for demonstrative purposes. + +Heterogeneity in the inflow provides the necessary driver for coupled yaw +and layout optimization to be worthwhile. First, a layout optimization is +run without coupled yaw optimization; then a coupled optimization is run to +show the benefits of coupled optimization when flows are heterogeneous. +""" + import os @@ -10,25 +21,13 @@ ) -""" -This example shows a layout optimization using the geometric yaw option. It -combines elements of examples 15 (layout optimization) and 16 (heterogeneous -inflow) for demonstrative purposes. If you haven't yet run those examples, -we recommend you try them first. - -Heterogeneity in the inflow provides the necessary driver for coupled yaw -and layout optimization to be worthwhile. First, a layout optimization is -run without coupled yaw optimization; then a coupled optimization is run to -show the benefits of coupled optimization when flows are heterogeneous. -""" - # Initialize FLORIS file_dir = os.path.dirname(os.path.abspath(__file__)) -fmodel = FlorisModel('inputs/gch.yaml') +fmodel = FlorisModel("../inputs/gch.yaml") # Setup 2 wind directions (due east and due west) # and 1 wind speed with uniform probability -wind_directions = np.array([270., 90.]) +wind_directions = np.array([270.0, 90.0]) n_wds = len(wind_directions) wind_speeds = [8.0] * np.ones_like(wind_directions) turbulence_intensities = 0.06 * np.ones_like(wind_directions) @@ -38,32 +37,26 @@ # The boundaries for the turbines, specified as vertices -D = 126.0 # rotor diameter for the NREL 5MW +D = 126.0 # rotor diameter for the NREL 5MW size_D = 12 -boundaries = [ - (0.0, 0.0), - (size_D * D, 0.0), - (size_D * D, 0.1), - (0.0, 0.1), - (0.0, 0.0) -] +boundaries = [(0.0, 0.0), (size_D * D, 0.0), (size_D * D, 0.1), (0.0, 0.1), (0.0, 0.0)] # Set turbine locations to 4 turbines at corners of the rectangle # (optimal without flow heterogeneity) -layout_x = [0.1, 0.3*size_D*D, 0.6*size_D*D] +layout_x = [0.1, 0.3 * size_D * D, 0.6 * size_D * D] layout_y = [0, 0, 0] # Generate exaggerated heterogeneous inflow (same for all wind directions) -speed_multipliers = np.repeat(np.array([0.5, 1.0, 0.5, 1.0])[None,:], n_wds, axis=0) +speed_multipliers = np.repeat(np.array([0.5, 1.0, 0.5, 1.0])[None, :], n_wds, axis=0) x_locs = [0, size_D * D, 0, size_D * D] y_locs = [-D, -D, D, D] # Create the configuration dictionary to be used for the heterogeneous inflow. heterogeneous_inflow_config_by_wd = { - 'speed_multipliers': speed_multipliers, - 'wind_directions': wind_directions, - 'x': x_locs, - 'y': y_locs, + "speed_multipliers": speed_multipliers, + "wind_directions": wind_directions, + "x": x_locs, + "y": y_locs, } # Establish a WindRose object @@ -72,7 +65,7 @@ wind_speeds=wind_speeds, freq_table=freq_table, ti_table=0.06, - heterogeneous_inflow_config_by_wd=heterogeneous_inflow_config_by_wd + heterogeneous_inflow_config_by_wd=heterogeneous_inflow_config_by_wd, ) @@ -85,10 +78,7 @@ # Setup and solve the layout optimization problem without heterogeneity maxiter = 100 layout_opt = LayoutOptimizationScipy( - fmodel, - boundaries, - min_dist=2*D, - optOptions={"maxiter":maxiter} + fmodel, boundaries, min_dist=2 * D, optOptions={"maxiter": maxiter} ) # Run the optimization @@ -96,7 +86,7 @@ sol = layout_opt.optimize() # Get the resulting improvement in AEP -print('... calcuating improvement in AEP') +print("... calcuating improvement in AEP") fmodel.run() base_aep = fmodel.get_farm_AEP() / 1e6 @@ -107,10 +97,10 @@ percent_gain = 100 * (opt_aep - base_aep) / base_aep # Print and plot the results -print(f'Optimal layout: {sol}') +print(f"Optimal layout: {sol}") print( - f'Optimal layout improves AEP by {percent_gain:.1f}% ' - f'from {base_aep:.1f} MWh to {opt_aep:.1f} MWh' + f"Optimal layout improves AEP by {percent_gain:.1f}% " + f"from {base_aep:.1f} MWh to {opt_aep:.1f} MWh" ) layout_opt.plot_layout_opt_results() ax = plt.gca() @@ -125,11 +115,7 @@ print("\nReoptimizing with geometric yaw enabled.") fmodel.set(layout_x=layout_x, layout_y=layout_y) layout_opt = LayoutOptimizationScipy( - fmodel, - boundaries, - min_dist=2*D, - enable_geometric_yaw=True, - optOptions={"maxiter":maxiter} + fmodel, boundaries, min_dist=2 * D, enable_geometric_yaw=True, optOptions={"maxiter": maxiter} ) # Run the optimization @@ -137,7 +123,7 @@ sol = layout_opt.optimize() # Get the resulting improvement in AEP -print('... calcuating improvement in AEP') +print("... calcuating improvement in AEP") fmodel.set(yaw_angles=np.zeros_like(layout_opt.yaw_angles)) fmodel.run() @@ -149,10 +135,10 @@ percent_gain = 100 * (opt_aep - base_aep) / base_aep # Print and plot the results -print(f'Optimal layout: {sol}') +print(f"Optimal layout: {sol}") print( - f'Optimal layout improves AEP by {percent_gain:.1f}% ' - f'from {base_aep:.1f} MWh to {opt_aep:.1f} MWh' + f"Optimal layout improves AEP by {percent_gain:.1f}% " + f"from {base_aep:.1f} MWh to {opt_aep:.1f} MWh" ) layout_opt.plot_layout_opt_results() ax = plt.gca() @@ -163,9 +149,9 @@ ax.set_title("Geometric yaw enabled") print( - 'Turbine geometric yaw angles for wind direction {0:.2f}'.format(wind_directions[1])\ - +' and wind speed {0:.2f} m/s:'.format(wind_speeds[0]), - f'{layout_opt.yaw_angles[1, :]}' + "Turbine geometric yaw angles for wind direction {0:.2f}".format(wind_directions[1]) + + " and wind speed {0:.2f} m/s:".format(wind_speeds[0]), + f"{layout_opt.yaw_angles[1, :]}", ) plt.show() From 8edc0c6e47027bbd84fc4238243ea640ace64d39 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 14:51:56 -0600 Subject: [PATCH 080/120] Update multidim examples --- ...p_ct.py => 001_multi_dimensional_cp_ct.py} | 43 ++++++----- .../002_multi_dimensional_cp_ct_2Hs.py | 65 +++++++++++++++++ .../31_multi_dimensional_cp_ct_2Hs.py | 72 ------------------- 3 files changed, 86 insertions(+), 94 deletions(-) rename examples/examples_multidim/{30_multi_dimensional_cp_ct.py => 001_multi_dimensional_cp_ct.py} (79%) create mode 100644 examples/examples_multidim/002_multi_dimensional_cp_ct_2Hs.py delete mode 100644 examples/examples_multidim/31_multi_dimensional_cp_ct_2Hs.py diff --git a/examples/examples_multidim/30_multi_dimensional_cp_ct.py b/examples/examples_multidim/001_multi_dimensional_cp_ct.py similarity index 79% rename from examples/examples_multidim/30_multi_dimensional_cp_ct.py rename to examples/examples_multidim/001_multi_dimensional_cp_ct.py index e33ca31d2..b1bf0441b 100644 --- a/examples/examples_multidim/30_multi_dimensional_cp_ct.py +++ b/examples/examples_multidim/001_multi_dimensional_cp_ct.py @@ -1,14 +1,8 @@ - -import numpy as np - -from floris import FlorisModel - - -""" -This example follows the same setup as example 01 to createa a FLORIS instance and: +"""Example: Multi-dimensional Cp/Ct data +This example creates a FLORIS instance and: 1) Makes a two-turbine layout 2) Demonstrates single ws/wd simulations -3) Demonstrates mulitple ws/wd simulations +3) Demonstrates multiple ws/wd simulations with the modification of using a turbine definition that has a multi-dimensional Cp/Ct table. @@ -19,7 +13,7 @@ height. For every combination of Tp and Hs defined, a Cp/Ct/Wind speed table of values is also defined. It is required for this .csv file to have the last 3 columns be ws, Cp, and Ct. In order for this table to be used, the flag 'multi_dimensional_cp_ct' must be present and set to true in -the turbine definition. With this flag enabled, the solver will downselect to use the +the turbine definition. With this flag enabled, the solver will down-select to use the interpolant defined at the closest conditions. The user must supply these conditions in the main input file under the 'flow_field' section, e.g.: @@ -40,20 +34,25 @@ 'get_turbine_powers_multidim'. The normal 'get_turbine_powers' method will not work. """ +import numpy as np + +from floris import FlorisModel + + # Initialize FLORIS with the given input file. -fmodel = FlorisModel("inputs/gch_multi_dim_cp_ct.yaml") +fmodel = FlorisModel("../inputs/gch_multi_dim_cp_ct.yaml") # Convert to a simple two turbine layout -fmodel.set(layout_x=[0., 500.], layout_y=[0., 0.]) +fmodel.set(layout_x=[0.0, 500.0], layout_y=[0.0, 0.0]) # Single wind speed and wind direction -print('\n========================= Single Wind Direction and Wind Speed =========================') +print("\n========================= Single Wind Direction and Wind Speed =========================") # Get the turbine powers assuming 1 wind speed and 1 wind direction fmodel.set(wind_directions=[270.0], wind_speeds=[8.0], turbulence_intensities=[0.06]) # Set the yaw angles to 0 -yaw_angles = np.zeros([1, 2]) # 1 wind direction and wind speed, 2 turbines +yaw_angles = np.zeros([1, 2]) # 1 wind direction and wind speed, 2 turbines fmodel.set(yaw_angles=yaw_angles) # Calculate @@ -63,10 +62,10 @@ turbine_powers = fmodel.get_turbine_powers() / 1000.0 print("The turbine power matrix should be of dimensions 1 findex X 2 Turbines") print(turbine_powers) -print("Shape: ",turbine_powers.shape) +print("Shape: ", turbine_powers.shape) # Single wind speed and multiple wind directions -print('\n========================= Single Wind Direction and Multiple Wind Speeds ===============') +print("\n========================= Single Wind Direction and Multiple Wind Speeds ===============") wind_speeds = np.array([8.0, 9.0, 10.0]) wind_directions = np.array([270.0, 270.0, 270.0]) @@ -77,16 +76,16 @@ wind_speeds=wind_speeds, wind_directions=wind_directions, turbulence_intensities=turbulence_intensities, - yaw_angles=yaw_angles + yaw_angles=yaw_angles, ) fmodel.run() turbine_powers = fmodel.get_turbine_powers() / 1000.0 print("The turbine power matrix should be of dimensions 3 findex X 2 Turbines") print(turbine_powers) -print("Shape: ",turbine_powers.shape) +print("Shape: ", turbine_powers.shape) # Multiple wind speeds and multiple wind directions -print('\n========================= Multiple Wind Directions and Multiple Wind Speeds ============') +print("\n========================= Multiple Wind Directions and Multiple Wind Speeds ============") wind_speeds = np.tile([8.0, 9.0, 10.0], 3) wind_directions = np.repeat([260.0, 270.0, 280.0], 3) @@ -97,10 +96,10 @@ wind_directions=wind_directions, wind_speeds=wind_speeds, turbulence_intensities=turbulence_intensities, - yaw_angles=yaw_angles + yaw_angles=yaw_angles, ) fmodel.run() -turbine_powers = fmodel.get_turbine_powers()/1000. +turbine_powers = fmodel.get_turbine_powers() / 1000.0 print("The turbine power matrix should be of dimensions 9 WD/WS X 2 Turbines") print(turbine_powers) -print("Shape: ",turbine_powers.shape) +print("Shape: ", turbine_powers.shape) diff --git a/examples/examples_multidim/002_multi_dimensional_cp_ct_2Hs.py b/examples/examples_multidim/002_multi_dimensional_cp_ct_2Hs.py new file mode 100644 index 000000000..8cf206f07 --- /dev/null +++ b/examples/examples_multidim/002_multi_dimensional_cp_ct_2Hs.py @@ -0,0 +1,65 @@ +"""Example: Multi-dimensional Cp/Ct with 2 Hs values +This example follows the previous example but shows the effect of changing the Hs setting. + +NOTE: The multi-dimensional Cp/Ct data used in this example is fictional for the purposes of +facilitating this example. The Cp/Ct values for the different wave conditions are scaled +values of the original Cp/Ct data for the IEA 15MW turbine. +""" + + +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel, TimeSeries + + +# Initialize FLORIS with the given input file. +fmodel = FlorisModel("../inputs/gch_multi_dim_cp_ct.yaml") + +# Make a second Floris instance with a different setting for Hs. +# Note the multi-cp-ct file (iea_15MW_multi_dim_Tp_Hs.csv) +# for the turbine model iea_15MW_floating_multi_dim_cp_ct.yaml +# Defines Hs at 1 and 5. +# The value in gch_multi_dim_cp_ct.yaml is 3.01 which will map +# to 5 as the nearer value, so we set the other case to 1 +# for contrast. +fmodel_dict_mod = fmodel.core.as_dict() +fmodel_dict_mod["flow_field"]["multidim_conditions"]["Hs"] = 1.0 +fmodel_hs_1 = FlorisModel(fmodel_dict_mod) + +# Set both cases to 3 turbine layout +fmodel.set(layout_x=[0.0, 500.0, 1000.0], layout_y=[0.0, 0.0, 0.0]) +fmodel_hs_1.set(layout_x=[0.0, 500.0, 1000.0], layout_y=[0.0, 0.0, 0.0]) + +# Use a sweep of wind speeds +wind_speeds = np.arange(5, 20, 1.0) +time_series = TimeSeries( + wind_directions=270.0, wind_speeds=wind_speeds, turbulence_intensities=0.06 +) +fmodel.set(wind_data=time_series) +fmodel_hs_1.set(wind_data=time_series) + +# Calculate wakes with baseline yaw +fmodel.run() +fmodel_hs_1.run() + +# Collect the turbine powers in kW +turbine_powers = fmodel.get_turbine_powers() / 1000.0 +turbine_powers_hs_1 = fmodel_hs_1.get_turbine_powers() / 1000.0 + +# Plot the power in each case and the difference in power +fig, axarr = plt.subplots(1, 3, sharex=True, figsize=(12, 4)) + +for t_idx in range(3): + ax = axarr[t_idx] + ax.plot(wind_speeds, turbine_powers[:, t_idx], color="k", label="Hs=3.1 (5)") + ax.plot(wind_speeds, turbine_powers_hs_1[:, t_idx], color="r", label="Hs=1.0") + ax.grid(True) + ax.set_xlabel("Wind Speed (m/s)") + ax.set_title(f"Turbine {t_idx}") + +axarr[0].set_ylabel("Power (kW)") +axarr[0].legend() +fig.suptitle("Power of each turbine") + +plt.show() diff --git a/examples/examples_multidim/31_multi_dimensional_cp_ct_2Hs.py b/examples/examples_multidim/31_multi_dimensional_cp_ct_2Hs.py deleted file mode 100644 index 56bb6fc20..000000000 --- a/examples/examples_multidim/31_multi_dimensional_cp_ct_2Hs.py +++ /dev/null @@ -1,72 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel - - -""" -This example follows after example 30 but shows the effect of changing the Hs setting. - -NOTE: The multi-dimensional Cp/Ct data used in this example is fictional for the purposes of -facilitating this example. The Cp/Ct values for the different wave conditions are scaled -values of the original Cp/Ct data for the IEA 15MW turbine. -""" - -# Initialize FLORIS with the given input file. -fmodel = FlorisModel("inputs/gch_multi_dim_cp_ct.yaml") - -# Make a second Floris instance with a different setting for Hs. -# Note the multi-cp-ct file (iea_15MW_multi_dim_Tp_Hs.csv) -# for the turbine model iea_15MW_floating_multi_dim_cp_ct.yaml -# Defines Hs at 1 and 5. -# The value in gch_multi_dim_cp_ct.yaml is 3.01 which will map -# to 5 as the nearer value, so we set the other case to 1 -# for contrast. -fmodel_dict_mod = fmodel.core.as_dict() -fmodel_dict_mod['flow_field']['multidim_conditions']['Hs'] = 1.0 -fmodel_hs_1 = FlorisModel(fmodel_dict_mod) - -# Set both cases to 3 turbine layout -fmodel.set(layout_x=[0., 500., 1000.], layout_y=[0., 0., 0.]) -fmodel_hs_1.set(layout_x=[0., 500., 1000.], layout_y=[0., 0., 0.]) - -# Use a sweep of wind speeds -wind_speeds = np.arange(5, 20, 1.0) -wind_directions = 270.0 * np.ones_like(wind_speeds) -turbulence_intensities = 0.06 * np.ones_like(wind_speeds) -fmodel.set( - wind_directions=wind_directions, - wind_speeds=wind_speeds, - turbulence_intensities=turbulence_intensities -) -fmodel_hs_1.set( - wind_directions=wind_directions, - wind_speeds=wind_speeds, - turbulence_intensities=turbulence_intensities -) - -# Calculate wakes with baseline yaw -fmodel.run() -fmodel_hs_1.run() - -# Collect the turbine powers in kW -turbine_powers = fmodel.get_turbine_powers()/1000. -turbine_powers_hs_1 = fmodel_hs_1.get_turbine_powers()/1000. - -# Plot the power in each case and the difference in power -fig, axarr = plt.subplots(1,3,sharex=True,figsize=(12,4)) - -for t_idx in range(3): - ax = axarr[t_idx] - ax.plot(wind_speeds, turbine_powers[:,t_idx], color='k', label='Hs=3.1 (5)') - ax.plot(wind_speeds, turbine_powers_hs_1[:,t_idx], color='r', label='Hs=1.0') - ax.grid(True) - ax.set_xlabel('Wind Speed (m/s)') - ax.set_title(f'Turbine {t_idx}') - -axarr[0].set_ylabel('Power (kW)') -axarr[0].legend() -fig.suptitle('Power of each turbine') - -plt.show() From 953c2341881770385ab99b0ff5d2553951b6e7d9 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 14:56:03 -0600 Subject: [PATCH 081/120] Update turbine examples --- ..._check_turbine.py => 001_check_turbine.py} | 62 +++++++++---------- ...types.py => 002_multiple_turbine_types.py} | 17 +++-- ....py => 003_specify_turbine_power_curve.py} | 50 ++++++++------- 3 files changed, 65 insertions(+), 64 deletions(-) rename examples/examples_turbine/{18_check_turbine.py => 001_check_turbine.py} (66%) rename examples/examples_turbine/{17_multiple_turbine_types.py => 002_multiple_turbine_types.py} (87%) rename examples/examples_turbine/{33_specify_turbine_power_curve.py => 003_specify_turbine_power_curve.py} (66%) diff --git a/examples/examples_turbine/18_check_turbine.py b/examples/examples_turbine/001_check_turbine.py similarity index 66% rename from examples/examples_turbine/18_check_turbine.py rename to examples/examples_turbine/001_check_turbine.py index 258525340..7291ca60c 100644 --- a/examples/examples_turbine/18_check_turbine.py +++ b/examples/examples_turbine/001_check_turbine.py @@ -1,5 +1,9 @@ +"""Example: Check turbine power curves + +For each turbine in the turbine library, make a small figure showing that its power +curve and power loss to yaw are reasonable and reasonably smooth +""" -from pathlib import Path import matplotlib.pyplot as plt import numpy as np @@ -7,27 +11,21 @@ from floris import FlorisModel -""" -For each turbine in the turbine library, make a small figure showing that its power -curve and power loss to yaw are reasonable and reasonably smooth -""" -ws_array = np.arange(0.1,30,0.2) +ws_array = np.arange(0.1, 30, 0.2) wd_array = 270.0 * np.ones_like(ws_array) turbulence_intensities = 0.06 * np.ones_like(ws_array) -yaw_angles = np.linspace(-30,30,60) +yaw_angles = np.linspace(-30, 30, 60) wind_speed_to_test_yaw = 11 # Grab the gch model -fmodel = FlorisModel("inputs/gch.yaml") +fmodel = FlorisModel("../inputs/gch.yaml") # Make one turbine simulation fmodel.set(layout_x=[0], layout_y=[0]) # Apply wind directions and wind speeds fmodel.set( - wind_speeds=ws_array, - wind_directions=wd_array, - turbulence_intensities=turbulence_intensities + wind_speeds=ws_array, wind_directions=wd_array, turbulence_intensities=turbulence_intensities ) # Get a list of available turbine models provided through FLORIS, and remove @@ -39,11 +37,10 @@ ] # Declare a set of figures for comparing cp and ct across models -fig_pow_ct, axarr_pow_ct = plt.subplots(2,1,sharex=True,figsize=(10,10)) +fig_pow_ct, axarr_pow_ct = plt.subplots(2, 1, sharex=True, figsize=(10, 10)) # For each turbine model available plot the basic info for t in turbines: - # Set t as the turbine fmodel.set(turbine_type=[t]) @@ -53,26 +50,27 @@ # Plot power and ct onto the fig_pow_ct plot axarr_pow_ct[0].plot( fmodel.core.farm.turbine_map[0].power_thrust_table["wind_speed"], - fmodel.core.farm.turbine_map[0].power_thrust_table["power"],label=t + fmodel.core.farm.turbine_map[0].power_thrust_table["power"], + label=t, ) axarr_pow_ct[0].grid(True) axarr_pow_ct[0].legend() - axarr_pow_ct[0].set_ylabel('Power (kW)') + axarr_pow_ct[0].set_ylabel("Power (kW)") axarr_pow_ct[1].plot( fmodel.core.farm.turbine_map[0].power_thrust_table["wind_speed"], - fmodel.core.farm.turbine_map[0].power_thrust_table["thrust_coefficient"],label=t + fmodel.core.farm.turbine_map[0].power_thrust_table["thrust_coefficient"], + label=t, ) axarr_pow_ct[1].grid(True) axarr_pow_ct[1].legend() - axarr_pow_ct[1].set_ylabel('Ct (-)') - axarr_pow_ct[1].set_xlabel('Wind Speed (m/s)') + axarr_pow_ct[1].set_ylabel("Ct (-)") + axarr_pow_ct[1].set_xlabel("Wind Speed (m/s)") # Create a figure - fig, axarr = plt.subplots(1,2,figsize=(10,5)) + fig, axarr = plt.subplots(1, 2, figsize=(10, 5)) # Try a few density - for density in [1.15,1.225,1.3]: - + for density in [1.15, 1.225, 1.3]: fmodel.set(air_density=density) # POWER CURVE @@ -80,18 +78,18 @@ fmodel.set( wind_speeds=ws_array, wind_directions=wd_array, - turbulence_intensities=turbulence_intensities + turbulence_intensities=turbulence_intensities, ) fmodel.run() turbine_powers = fmodel.get_turbine_powers().flatten() / 1e3 if density == 1.225: - ax.plot(ws_array,turbine_powers,label='Air Density = %.3f' % density, lw=2, color='k') + ax.plot(ws_array, turbine_powers, label="Air Density = %.3f" % density, lw=2, color="k") else: - ax.plot(ws_array,turbine_powers,label='Air Density = %.3f' % density, lw=1) + ax.plot(ws_array, turbine_powers, label="Air Density = %.3f" % density, lw=1) ax.grid(True) ax.legend() - ax.set_xlabel('Wind Speed (m/s)') - ax.set_ylabel('Power (kW)') + ax.set_xlabel("Wind Speed (m/s)") + ax.set_ylabel("Power (kW)") # Power loss to yaw, try a range of yaw angles ax = axarr[1] @@ -99,7 +97,7 @@ fmodel.set( wind_speeds=[wind_speed_to_test_yaw], wind_directions=[270.0], - turbulence_intensities=[0.06] + turbulence_intensities=[0.06], ) yaw_result = [] for yaw in yaw_angles: @@ -108,15 +106,15 @@ turbine_powers = fmodel.get_turbine_powers().flatten() / 1e3 yaw_result.append(turbine_powers[0]) if density == 1.225: - ax.plot(yaw_angles,yaw_result,label='Air Density = %.3f' % density, lw=2, color='k') + ax.plot(yaw_angles, yaw_result, label="Air Density = %.3f" % density, lw=2, color="k") else: - ax.plot(yaw_angles,yaw_result,label='Air Density = %.3f' % density, lw=1) + ax.plot(yaw_angles, yaw_result, label="Air Density = %.3f" % density, lw=1) # ax.plot(yaw_angles,yaw_result,label='Air Density = %.3f' % density) ax.grid(True) ax.legend() - ax.set_xlabel('Yaw Error (deg)') - ax.set_ylabel('Power (kW)') - ax.set_title('Wind Speed = %.1f' % wind_speed_to_test_yaw ) + ax.set_xlabel("Yaw Error (deg)") + ax.set_ylabel("Power (kW)") + ax.set_title("Wind Speed = %.1f" % wind_speed_to_test_yaw) # Give a suptitle fig.suptitle(t) diff --git a/examples/examples_turbine/17_multiple_turbine_types.py b/examples/examples_turbine/002_multiple_turbine_types.py similarity index 87% rename from examples/examples_turbine/17_multiple_turbine_types.py rename to examples/examples_turbine/002_multiple_turbine_types.py index b7d1c4173..b945d5a0a 100644 --- a/examples/examples_turbine/17_multiple_turbine_types.py +++ b/examples/examples_turbine/002_multiple_turbine_types.py @@ -1,3 +1,9 @@ +"""Example: Multiple turbine types + +This example uses an input file where multiple turbine types are defined. +The first two turbines are the NREL 5MW, and the third turbine is the IEA 10MW. +""" + import matplotlib.pyplot as plt @@ -5,24 +11,17 @@ from floris import FlorisModel -""" -This example uses an input file where multiple turbine types are defined. -The first two turbines are the NREL 5MW, and the third turbine is the IEA 10MW. -""" - # Initialize FLORIS with the given input file. # For basic usage, FlorisModel provides a simplified and expressive # entry point to the simulation routines. -fmodel = FlorisModel("inputs/gch_multiple_turbine_types.yaml") +fmodel = FlorisModel("../inputs/gch_multiple_turbine_types.yaml") # Using the FlorisModel functions for generating plots, run FLORIS # and extract 2D planes of data. horizontal_plane = fmodel.calculate_horizontal_plane(x_resolution=200, y_resolution=100, height=90) y_plane = fmodel.calculate_y_plane(x_resolution=200, z_resolution=100, crossstream_dist=0.0) cross_plane = fmodel.calculate_cross_plane( - y_resolution=100, - z_resolution=100, - downstream_dist=500.0 + y_resolution=100, z_resolution=100, downstream_dist=500.0 ) # Create the plots diff --git a/examples/examples_turbine/33_specify_turbine_power_curve.py b/examples/examples_turbine/003_specify_turbine_power_curve.py similarity index 66% rename from examples/examples_turbine/33_specify_turbine_power_curve.py rename to examples/examples_turbine/003_specify_turbine_power_curve.py index 420f5aeab..1c1b59707 100644 --- a/examples/examples_turbine/33_specify_turbine_power_curve.py +++ b/examples/examples_turbine/003_specify_turbine_power_curve.py @@ -1,12 +1,5 @@ +"""Example: Specify turbine power curve -import matplotlib.pyplot as plt -import numpy as np - -from floris import FlorisModel -from floris.turbine_library import build_cosine_loss_turbine_dict - - -""" This example demonstrates how to specify a turbine model based on a power and thrust curve for the wind turbine, as well as possible physical parameters (which default to the parameters of the NREL 5MW reference turbine). @@ -15,14 +8,21 @@ argument to build_turbine_dict is set. """ +import matplotlib.pyplot as plt +import numpy as np + +from floris import FlorisModel +from floris.turbine_library import build_cosine_loss_turbine_dict + + # Generate an example turbine power and thrust curve for use in the FLORIS model powers_orig = np.array([0, 30, 200, 500, 1000, 2000, 4000, 4000, 4000, 4000, 4000]) wind_speeds = np.array([0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]) -power_coeffs = powers_orig[1:]/(0.5*126.**2*np.pi/4*1.225*wind_speeds[1:]**3) +power_coeffs = powers_orig[1:] / (0.5 * 126.0**2 * np.pi / 4 * 1.225 * wind_speeds[1:] ** 3) turbine_data_dict = { - "wind_speed":list(wind_speeds), - "power_coefficient":[0]+list(power_coeffs), - "thrust_coefficient":[0, 0.9, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.25, 0.2] + "wind_speed": list(wind_speeds), + "power_coefficient": [0] + list(power_coeffs), + "thrust_coefficient": [0, 0.9, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.25, 0.2], } turbine_dict = build_cosine_loss_turbine_dict( @@ -36,10 +36,10 @@ rotor_diameter=126, TSR=8, ref_air_density=1.225, - ref_tilt=5 + ref_tilt=5, ) -fmodel = FlorisModel("inputs/gch.yaml") +fmodel = FlorisModel("../inputs/gch.yaml") wind_speeds = np.linspace(1, 15, 100) wind_directions = 270 * np.ones_like(wind_speeds) turbulence_intensities = 0.06 * np.ones_like(wind_speeds) @@ -50,7 +50,7 @@ wind_directions=wind_directions, wind_speeds=wind_speeds, turbulence_intensities=turbulence_intensities, - turbine_type=[turbine_dict] + turbine_type=[turbine_dict], ) fmodel.run() @@ -58,16 +58,20 @@ specified_powers = ( np.array(turbine_data_dict["power_coefficient"]) - *0.5*turbine_dict["power_thrust_table"]["ref_air_density"] - *turbine_dict["rotor_diameter"]**2*np.pi/4 - *np.array(turbine_data_dict["wind_speed"])**3 -)/1000 + * 0.5 + * turbine_dict["power_thrust_table"]["ref_air_density"] + * turbine_dict["rotor_diameter"] ** 2 + * np.pi + / 4 + * np.array(turbine_data_dict["wind_speed"]) ** 3 +) / 1000 -fig, ax = plt.subplots(1,1,sharex=True) +fig, ax = plt.subplots(1, 1, sharex=True) -ax.scatter(wind_speeds, powers/1000, color="C0", s=5, label="Test points") -ax.scatter(turbine_data_dict["wind_speed"], specified_powers, - color="red", s=20, label="Specified points") +ax.scatter(wind_speeds, powers / 1000, color="C0", s=5, label="Test points") +ax.scatter( + turbine_data_dict["wind_speed"], specified_powers, color="red", s=20, label="Specified points" +) ax.grid() ax.set_xlabel("Wind speed [m/s]") From d19b05feef24b7fdc5b2939a8c29d38c25c79e2e Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 14:57:47 -0600 Subject: [PATCH 082/120] bugfix --- examples/007_sweeping_variables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/007_sweeping_variables.py b/examples/007_sweeping_variables.py index 52437c951..67103867d 100644 --- a/examples/007_sweeping_variables.py +++ b/examples/007_sweeping_variables.py @@ -126,7 +126,7 @@ fmodel.reset_operation() # To the de-rating need to change the power_thrust_mode to mixed or simple de-rating -fmodel.set_power_thrust_model("simple-derating") +fmodel.set_operation_model("simple-derating") # Sweep the de-rating levels RATED_POWER = 5e6 # For NREL 5MW From a8c65994dd9c65813af4e5b94cef6d5d581b203e Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 15:16:21 -0600 Subject: [PATCH 083/120] Convert parallel model to nansum --- floris/parallel_floris_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/floris/parallel_floris_model.py b/floris/parallel_floris_model.py index cdf70afa6..a98b28053 100644 --- a/floris/parallel_floris_model.py +++ b/floris/parallel_floris_model.py @@ -442,7 +442,7 @@ def get_farm_AEP( ) # Finally, calculate AEP in GWh - aep = np.sum(np.multiply(freq, farm_power) * 365 * 24) + aep = np.nansum(np.multiply(freq, farm_power) * 365 * 24) # Reset the FLORIS object to the full wind speed array self.fmodel.set( From 9b9a4a2c4c5fcf02b30b45a26469ab28db70706b Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 15:16:52 -0600 Subject: [PATCH 084/120] add ws/wd/ti/n_findex properties --- floris/floris_model.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/floris/floris_model.py b/floris/floris_model.py index 2ac376ae7..447217ab1 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -1394,6 +1394,46 @@ def layout_y(self): """ return self.core.farm.layout_y + @property + def wind_directions(self): + """ + Wind direction information. + + Returns: + np.array: Wind direction. + """ + return self.core.flow_field.wind_directions + + @property + def wind_speeds(self): + """ + Wind speed information. + + Returns: + np.array: Wind speed. + """ + return self.core.flow_field.wind_speeds + + @property + def turbulence_intensities(self): + """ + Turbulence intensity information. + + Returns: + np.array: Turbulence intensity. + """ + return self.core.flow_field.turbulence_intensities + + @property + def n_findex(self): + """ + Number of floris indices (findex). + + Returns: + int: Number of flow indices. + """ + return self.core.flow_field.n_findex + @property def turbine_average_velocities(self) -> NDArrayFloat: return average_velocity( From 7d4365428d95704578ee608e31ba955e407e7ccd Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 15:17:07 -0600 Subject: [PATCH 085/120] Clean up 004 --- .../004_optimize_yaw_aep.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/examples/examples_control_optimization/004_optimize_yaw_aep.py b/examples/examples_control_optimization/004_optimize_yaw_aep.py index 04913d3a0..1172adbfd 100644 --- a/examples/examples_control_optimization/004_optimize_yaw_aep.py +++ b/examples/examples_control_optimization/004_optimize_yaw_aep.py @@ -14,7 +14,6 @@ import matplotlib.pyplot as plt import numpy as np -import pandas as pd import seaborn as sns from floris import ( @@ -65,9 +64,6 @@ end_time = timerpc() t_tot = end_time - start_time print("Optimization finished in {:.2f} seconds.".format(t_tot)) -print(" ") -print(df_opt) -print(" ") # Calculate the AEP in the baseline case @@ -75,7 +71,7 @@ fmodel.run() farm_power_baseline = fmodel.get_farm_power() aep_baseline = fmodel.get_farm_AEP() -print("Baseline AEP: {:.2f} GWh.".format(aep_baseline)) + # 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 @@ -85,9 +81,9 @@ # 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.core.flow_field.wind_directions -wind_speeds = fmodel.core.flow_field.wind_speeds -n_findex = fmodel.core.flow_field.n_findex +wind_directions = fmodel.wind_directions +wind_speeds = fmodel.wind_speeds +n_findex = fmodel.n_findex # Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds @@ -121,7 +117,9 @@ aep_opt = fmodel.get_farm_AEP() aep_uplift = 100.0 * (aep_opt / aep_baseline - 1) farm_power_opt = fmodel.get_farm_power() -print("Optimal AEP: {:.2f} GWh.".format(aep_opt)) + +print("Baseline AEP: {:.2f} GWh.".format(aep_baseline/1E9)) +print("Optimal AEP: {:.2f} GWh.".format(aep_opt/1E9)) print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift)) # Use farm_power_baseline, farm_power_opt and wind_data to make a heat map of uplift by From 01e4479196decd18ab9f0432145237235683c40a Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 15:17:19 -0600 Subject: [PATCH 086/120] Clean up 005 --- .../005_optimize_yaw_aep_parallel.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py b/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py index 87c0b0e7e..17e02412b 100644 --- a/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py +++ b/examples/examples_control_optimization/005_optimize_yaw_aep_parallel.py @@ -13,10 +13,7 @@ from time import perf_counter as timerpc -import matplotlib.pyplot as plt import numpy as np -import pandas as pd -import seaborn as sns from floris import ( FlorisModel, @@ -81,9 +78,6 @@ end_time = timerpc() t_tot = end_time - start_time print("Optimization finished in {:.2f} seconds.".format(t_tot)) - print(" ") - print(df_opt) - print(" ") # Calculate the AEP in the baseline case, using the parallel interface @@ -98,9 +92,7 @@ # 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()) - print("Baseline AEP: {:.2f} GWh.".format(aep_baseline)) # 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 @@ -110,9 +102,9 @@ # 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.core.flow_field.wind_directions - wind_speeds = fmodel.core.flow_field.wind_speeds - n_findex = fmodel.core.flow_field.n_findex + wind_directions = fmodel.wind_directions + wind_speeds = fmodel.wind_speeds + n_findex = fmodel.n_findex # Now define how the optimal yaw angles for 8 m/s are applied over the other wind speeds @@ -149,8 +141,9 @@ interface=parallel_interface, print_timings=True, ) - aep_opt = pfmodel.get_farm_AEP(freq=wind_rose.unpack_freq()) + aep_opt = pfmodel.get_farm_AEP(freq=wind_rose.unpack_freq(), yaw_angles=yaw_angles_wind_rose) aep_uplift = 100.0 * (aep_opt / aep_baseline - 1) - print("Optimal AEP: {:.2f} GWh.".format(aep_opt)) + print("Baseline AEP: {:.2f} GWh.".format(aep_baseline/1E9)) + print("Optimal AEP: {:.2f} GWh.".format(aep_opt/1E9)) print("Relative AEP uplift by wake steering: {:.3f} %.".format(aep_uplift)) From 274de8d3a9fa6d59f06cfb0245b61cc642b3b2ab Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 15:17:35 -0600 Subject: [PATCH 087/120] Use property --- .../examples_get_flow/003_plot_velocity_deficit_profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples_get_flow/003_plot_velocity_deficit_profiles.py b/examples/examples_get_flow/003_plot_velocity_deficit_profiles.py index 546157032..1b8cabc77 100644 --- a/examples/examples_get_flow/003_plot_velocity_deficit_profiles.py +++ b/examples/examples_get_flow/003_plot_velocity_deficit_profiles.py @@ -39,7 +39,7 @@ def annotate_coordinate_system(x_origin, y_origin, quiver_length): x2 = np.array([0.0, quiver_length + 0.35 * D]) x3 = np.array([90.0, 90.0]) x, y, _ = reverse_rotate_coordinates_rel_west( - fmodel.core.flow_field.wind_directions, + fmodel.wind_directions, x1[None, :], x2[None, :], x3[None, :], From 275c63fd75a04cedc7f039e57cfdcd20af781df2 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 15:17:46 -0600 Subject: [PATCH 088/120] use property --- .../examples_heterogeneous/002_heterogeneous_inflow_multi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py b/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py index fe95a8f8a..fa8b9cfe4 100644 --- a/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py +++ b/examples/examples_heterogeneous/002_heterogeneous_inflow_multi.py @@ -105,7 +105,7 @@ turbine_powers_by_wd = fmodel.get_turbine_powers() / 1000.0 # Plot the results -wind_directions = fmodel.core.flow_field.wind_directions +wind_directions = fmodel.wind_directions fig, axarr = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(10, 10)) axarr = axarr.flatten() From 840eb244d7076469aa2f76cb012bf9c5fc971cdb Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 21:31:26 -0600 Subject: [PATCH 089/120] Update example 7 --- .../007_Optimize_Yaw_with_neighbor_farm.py | 316 +++++++++++++++++ .../13_optimize_yaw_with_neighboring_farm.py | 318 ------------------ 2 files changed, 316 insertions(+), 318 deletions(-) create mode 100644 examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py delete mode 100644 examples/examples_control_optimization/13_optimize_yaw_with_neighboring_farm.py diff --git a/examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py b/examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py new file mode 100644 index 000000000..f9e80a59d --- /dev/null +++ b/examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py @@ -0,0 +1,316 @@ +"""Example: Optimize yaw with neighbor farm + +This example demonstrates how to optimize the yaw angles of a subset of turbines +in order to maximize the annual energy production (AEP) of a wind farm. In this +case, the wind farm is part of a larger collection of turbines, some of which are +part of a neighboring farm. The optimization is performed in two ways: first by +accounting for the wakes of the neighboring farm (while not including those turbines) +in the optimization as a target of yaw angle changes or including their power +in the objective function. In th second method the neighboring farms are removed +from FLORIS for the optimization. The AEP is then calculated for the optimized +yaw angles (accounting for and not accounting for the neighboring farm) and compared +to the baseline AEP. +""" + +import matplotlib.pyplot as plt +import numpy as np + +from floris import ( + FlorisModel, + TimeSeries, + WindRose, +) +from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR + + +# Load the wind rose from csv +wind_rose = WindRose.read_csv_long( + "../inputs/wind_rose.csv", wd_col="wd", ws_col="ws", freq_col="freq_val", ti_col_or_value=0.06 +) + +# Load FLORIS +fmodel = FlorisModel("../inputs/gch.yaml") + +# Specify a layout of turbines in which only the first 10 turbines are part +# of the farm to be optimized, while the others belong to a neighboring farm +X = ( + np.array( + [ + 0.0, + 756.0, + 1512.0, + 2268.0, + 3024.0, + 0.0, + 756.0, + 1512.0, + 2268.0, + 3024.0, + 0.0, + 756.0, + 1512.0, + 2268.0, + 3024.0, + 0.0, + 756.0, + 1512.0, + 2268.0, + 3024.0, + 4500.0, + 5264.0, + 6028.0, + 4878.0, + 0.0, + 756.0, + 1512.0, + 2268.0, + 3024.0, + ] + ) + / 1.5 +) +Y = ( + np.array( + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 504.0, + 504.0, + 504.0, + 504.0, + 504.0, + 1008.0, + 1008.0, + 1008.0, + 1008.0, + 1008.0, + 1512.0, + 1512.0, + 1512.0, + 1512.0, + 1512.0, + 4500.0, + 4059.0, + 3618.0, + 5155.0, + -504.0, + -504.0, + -504.0, + -504.0, + -504.0, + ] + ) + / 1.5 +) + +# Turbine weights: we want to only optimize for the first 10 turbines +turbine_weights = np.zeros(len(X), dtype=int) +turbine_weights[0:10] = 1.0 + +# Now reinitialize FLORIS layout +fmodel.set(layout_x=X, layout_y=Y) + +# And visualize the floris layout +fig, ax = plt.subplots() +ax.plot(X[turbine_weights == 0], Y[turbine_weights == 0], "ro", label="Neighboring farms") +ax.plot(X[turbine_weights == 1], Y[turbine_weights == 1], "go", label="Farm subset") +ax.grid(True) +ax.set_xlabel("x coordinate (m)") +ax.set_ylabel("y coordinate (m)") +ax.legend() + +# Indicate turbine 0 in the plot above with an annotation arrow +ax.annotate( + "Turbine 0", + (X[0], Y[0]), + xytext=(X[0] + 100, Y[0] + 100), + arrowprops={'facecolor':"black", 'shrink':0.05}, +) + + +# 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) + +# CASE 1: Optimize the yaw angles of the included farm while accounting for the +# wake effects of the neighboring farm by using turbine weights + +# It's important here to do two things: +# 1. Exclude the downstream turbines from the power optimization goal via +# turbine_weights +# 2. Prevent the optimizer from changing the yaw angles of the turbines in the +# neighboring farm by limiting the yaw angles min max both to 0 + +# Set the yaw angles max min according to point(2) above +minimum_yaw_angle = np.zeros( + ( + fmodel.n_findex, + fmodel.n_turbines, + ) +) +maximum_yaw_angle = np.zeros( + ( + fmodel.n_findex, + fmodel.n_turbines, + ) +) +maximum_yaw_angle[:, :10] = 30.0 + + +yaw_opt = YawOptimizationSR( + fmodel=fmodel, + minimum_yaw_angle=minimum_yaw_angle, # Allowable yaw angles lower bound + maximum_yaw_angle=maximum_yaw_angle, # Allowable yaw angles upper bound + Ny_passes=[5, 4], + exclude_downstream_turbines=True, + turbine_weights=turbine_weights, +) +df_opt_with_neighbor = yaw_opt.optimize() + +# CASE 2: Repeat the optimization, this time ignoring the wakes of the neighboring farm +# by limiting the FLORIS model to only the turbines in the farm to be optimized +f_model_subset = fmodel.copy() +f_model_subset.set( + layout_x=X[:10], + layout_y=Y[:10], +) +yaw_opt = YawOptimizationSR( + fmodel=f_model_subset, + minimum_yaw_angle=0, # Allowable yaw angles lower bound + maximum_yaw_angle=30, # Allowable yaw angles upper bound + Ny_passes=[5, 4], + exclude_downstream_turbines=True, +) +df_opt_without_neighbor = yaw_opt.optimize() + + +# Calculate the AEP in the baseline case +# Use turbine weights again to only consider the first 10 turbines power +fmodel.set(wind_data=wind_rose) +fmodel.run() +farm_power_baseline = fmodel.get_farm_power(turbine_weights=turbine_weights) +aep_baseline = fmodel.get_farm_AEP(turbine_weights=turbine_weights) + + +# 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 +# and ramped down to 0 above and below this range + +# Grab wind speeds and wind directions from the fmodel. Note that we do this because the +# 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 + +yaw_angles_wind_rose_with_neighbor = np.zeros((n_findex, fmodel.n_turbines)) +yaw_angles_wind_rose_without_neighbor = np.zeros((n_findex, fmodel.n_turbines)) +for i in range(n_findex): + wind_speed = wind_speeds[i] + wind_direction = wind_directions[i] + + # Interpolate the optimal yaw angles for this wind direction from df_opt + id_opt_with_neighbor = df_opt_with_neighbor["wind_direction"] == wind_direction + id_opt_without_neighbor = df_opt_without_neighbor["wind_direction"] == wind_direction + + # Get the yaw angles for this wind direction + yaw_opt_full_with_neighbor = np.array( + df_opt_with_neighbor.loc[id_opt_with_neighbor, "yaw_angles_opt"] + )[0] + yaw_opt_full_without_neighbor = np.array( + df_opt_without_neighbor.loc[id_opt_without_neighbor, "yaw_angles_opt"] + )[0] + + # Extend the yaw angles from 10 turbine to n_turbine by filling with 0s + # in the case of the removed neighboring farms + yaw_opt_full_without_neighbor = np.concatenate( + (yaw_opt_full_without_neighbor, np.zeros(fmodel.n_turbines - 10)) + ) + + # Now decide what to do for different wind speeds + if (wind_speed < 4.0) | (wind_speed > 14.0): + yaw_opt_with_neighbor = np.zeros(fmodel.n_turbines) # do nothing for very low/high speeds + yaw_opt_without_neighbor = np.zeros( + fmodel.n_turbines + ) # do nothing for very low/high speeds + elif wind_speed < 6.0: + yaw_opt_with_neighbor = ( + yaw_opt_full_with_neighbor * (6.0 - wind_speed) / 2.0 + ) # Linear ramp up + yaw_opt_without_neighbor = ( + yaw_opt_full_without_neighbor * (6.0 - wind_speed) / 2.0 + ) # Linear ramp up + elif wind_speed > 12.0: + yaw_opt_with_neighbor = ( + yaw_opt_full_with_neighbor * (14.0 - wind_speed) / 2.0 + ) # Linear ramp down + yaw_opt_without_neighbor = ( + yaw_opt_full_without_neighbor * (14.0 - wind_speed) / 2.0 + ) # Linear ramp down + else: + yaw_opt_with_neighbor = ( + yaw_opt_full_with_neighbor # Apply full offsets between 6.0 and 12.0 m/s + ) + yaw_opt_without_neighbor = ( + yaw_opt_full_without_neighbor # Apply full offsets between 6.0 and 12.0 m/s + ) + + # Save to collective array + yaw_angles_wind_rose_with_neighbor[i, :] = yaw_opt_with_neighbor + yaw_angles_wind_rose_without_neighbor[i, :] = yaw_opt_without_neighbor + + +# Now apply the optimal yaw angles and get the AEP, first accounting for the neighboring farm +fmodel.set(yaw_angles=yaw_angles_wind_rose_with_neighbor) +fmodel.run() +aep_opt_with_neighbor = fmodel.get_farm_AEP(turbine_weights=turbine_weights) +aep_uplift_with_neighbor = 100.0 * (aep_opt_with_neighbor / aep_baseline - 1) +farm_power_opt_with_neighbor = fmodel.get_farm_power(turbine_weights=turbine_weights) + +# Repeat without accounting for neighboring farm +fmodel.set(yaw_angles=yaw_angles_wind_rose_without_neighbor) +fmodel.run() +aep_opt_without_neighbor = fmodel.get_farm_AEP(turbine_weights=turbine_weights) +aep_uplift_without_neighbor = 100.0 * (aep_opt_without_neighbor / aep_baseline - 1) +farm_power_opt_without_neighbor = fmodel.get_farm_power(turbine_weights=turbine_weights) + +print("Baseline AEP: {:.2f} GWh.".format(aep_baseline / 1e9)) +print( + "Optimal AEP (Not accounting for neighboring farm): {:.2f} GWh.".format( + aep_opt_without_neighbor / 1e9 + ) +) +print( + "Optimal AEP (Accounting for neighboring farm): {:.2f} GWh.".format(aep_opt_with_neighbor / 1e9) +) + +# Plot the optimal yaw angles for turbine 0 with and without accounting for the neighboring farm +yaw_angles_0_with_neighbor = np.vstack(df_opt_with_neighbor["yaw_angles_opt"])[:, 0] +yaw_angles_0_without_neighbor = np.vstack(df_opt_without_neighbor["yaw_angles_opt"])[:, 0] + +fig, ax = plt.subplots() +ax.plot( + df_opt_with_neighbor["wind_direction"], + yaw_angles_0_with_neighbor, + label="Accounting for neighboring farm", +) +ax.plot( + df_opt_without_neighbor["wind_direction"], + yaw_angles_0_without_neighbor, + label="Not accounting for neighboring farm", +) +ax.set_xlabel("Wind direction (deg)") +ax.set_ylabel("Yaw angle (deg)") +ax.legend() +ax.grid(True) +ax.set_title("Optimal yaw angles for turbine 0") + +plt.show() diff --git a/examples/examples_control_optimization/13_optimize_yaw_with_neighboring_farm.py b/examples/examples_control_optimization/13_optimize_yaw_with_neighboring_farm.py deleted file mode 100644 index 300748341..000000000 --- a/examples/examples_control_optimization/13_optimize_yaw_with_neighboring_farm.py +++ /dev/null @@ -1,318 +0,0 @@ - -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -from scipy.interpolate import NearestNDInterpolator - -from floris import FlorisModel -from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR - - -""" -This example demonstrates how to perform a yaw optimization and evaluate the performance over a -full wind rose. - -The beginning of the file contains the definition of several functions used in the main part of -the script. - -Within the main part of the script, we first load the wind rose information. -We then initialize our Floris Interface object. We determine the baseline AEP using the -wind rose information, and then perform the yaw optimization over 72 wind directions with 1 -wind speed per direction. The optimal yaw angles are then used to determine yaw angles across -all the wind speeds included in the wind rose. Lastly, the final AEP is calculated and analysis -of the results are shown in several plots. -""" - -def load_floris(): - # Load the default example floris object - fmodel = FlorisModel("inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 - # fmodel = FlorisModel("inputs/cc.yaml") # New CumulativeCurl model - - # Specify the full wind farm layout: nominal and neighboring wind farms - X = np.array( - [ - 0., 756., 1512., 2268., 3024., 0., 756., 1512., - 2268., 3024., 0., 756., 1512., 2268., 3024., 0., - 756., 1512., 2268., 3024., 4500., 5264., 6028., 4878., - 0., 756., 1512., 2268., 3024., - ] - ) / 1.5 - Y = np.array( - [ - 0., 0., 0., 0., 0., 504., 504., 504., - 504., 504., 1008., 1008., 1008., 1008., 1008., 1512., - 1512., 1512., 1512., 1512., 4500., 4059., 3618., 5155., - -504., -504., -504., -504., -504., - ] - ) / 1.5 - - # Turbine weights: we want to only optimize for the first 10 turbines - turbine_weights = np.zeros(len(X), dtype=int) - turbine_weights[0:10] = 1.0 - - # Now reinitialize FLORIS layout - fmodel.set(layout_x = X, layout_y = Y) - - # And visualize the floris layout - fig, ax = plt.subplots() - ax.plot(X[turbine_weights == 0], Y[turbine_weights == 0], 'ro', label="Neighboring farms") - ax.plot(X[turbine_weights == 1], Y[turbine_weights == 1], 'go', label='Farm subset') - ax.grid(True) - ax.set_xlabel("x coordinate (m)") - ax.set_ylabel("y coordinate (m)") - ax.legend() - - return fmodel, turbine_weights - - -def load_windrose(): - # Load the wind rose information from an external file - df = pd.read_csv("inputs/wind_rose.csv") - df = df[(df["ws"] < 22)].reset_index(drop=True) # Reduce size - df["freq_val"] = df["freq_val"] / df["freq_val"].sum() # Normalize wind rose frequencies - - # Now put the wind rose information in FLORIS format - ws_windrose = df["ws"].unique() - wd_windrose = df["wd"].unique() - - # Use an interpolant to shape the 'freq_val' vector appropriately. You can - # also use np.reshape(), but NearestNDInterpolator is more fool-proof. - freq_interpolant = NearestNDInterpolator( - df[["ws", "wd"]], df["freq_val"] - ) - freq = freq_interpolant(df["wd"], df["ws"]) - freq_windrose = freq / freq.sum() # Normalize to sum to 1.0 - - ws_windrose = df["ws"] - wd_windrose = df["wd"] - - return ws_windrose, wd_windrose, freq_windrose - - -def optimize_yaw_angles(fmodel_opt): - # Specify turbines to optimize - turbs_to_opt = np.zeros(len(fmodel_opt.layout_x), dtype=bool) - turbs_to_opt[0:10] = True - - # Specify turbine weights - turbine_weights = np.zeros(len(fmodel_opt.layout_x)) - turbine_weights[turbs_to_opt] = 1.0 - - # Specify minimum and maximum allowable yaw angle limits - minimum_yaw_angle = np.zeros( - ( - fmodel_opt.core.flow_field.n_findex, - fmodel_opt.core.farm.n_turbines, - ) - ) - maximum_yaw_angle = np.zeros( - ( - fmodel_opt.core.flow_field.n_findex, - fmodel_opt.core.farm.n_turbines, - ) - ) - maximum_yaw_angle[:, turbs_to_opt] = 30.0 - - yaw_opt = YawOptimizationSR( - fmodel=fmodel_opt, - minimum_yaw_angle=minimum_yaw_angle, - maximum_yaw_angle=maximum_yaw_angle, - turbine_weights=turbine_weights, - Ny_passes=[5], - exclude_downstream_turbines=True, - ) - - df_opt = yaw_opt.optimize() - yaw_angles_opt = yaw_opt.yaw_angles_opt - print("Optimization finished.") - print(" ") - print(df_opt) - print(" ") - - # Now create an interpolant from the optimal yaw angles - def yaw_opt_interpolant(wd, ws): - # Format the wind directions and wind speeds accordingly - wd = np.array(wd, dtype=float) - ws = np.array(ws, dtype=float) - - # Interpolate optimal yaw angles - x = yaw_opt.fmodel.core.flow_field.wind_directions - nturbs = fmodel_opt.core.farm.n_turbines - y = np.stack( - [np.interp(wd, x, yaw_angles_opt[:, ti]) for ti in range(nturbs)], - axis=np.ndim(wd) - ) - - # Now, we want to apply a ramp-up region near cut-in and ramp-down - # region near cut-out wind speed for the yaw offsets. - lim = np.ones(np.shape(wd), dtype=float) # Introduce a multiplication factor - - # Dont do wake steering under 4 m/s or above 14 m/s - lim[(ws <= 4.0) | (ws >= 14.0)] = 0.0 - - # Linear ramp up for the maximum yaw offset between 4.0 and 6.0 m/s - ids = (ws > 4.0) & (ws < 6.0) - lim[ids] = (ws[ids] - 4.0) / 2.0 - - # Linear ramp down for the maximum yaw offset between 12.0 and 14.0 m/s - ids = (ws > 12.0) & (ws < 14.0) - lim[ids] = (ws[ids] - 12.0) / 2.0 - - # Copy over multiplication factor to every turbine - lim = np.expand_dims(lim, axis=np.ndim(wd)).repeat(nturbs, axis=np.ndim(wd)) - lim = lim * 30.0 # These are the limits - - # Finally, Return clipped yaw offsets to the limits - return np.clip(a=y, a_min=0.0, a_max=lim) - - # Return the yaw interpolant - return yaw_opt_interpolant - - -if __name__ == "__main__": - # Load FLORIS: full farm including neighboring wind farms - fmodel, turbine_weights = load_floris() - nturbs = len(fmodel.layout_x) - - # Load a dataframe containing the wind rose information - ws_windrose, wd_windrose, freq_windrose = load_windrose() - ws_windrose = ws_windrose + 0.001 # Deal with 0.0 m/s discrepancy - turbulence_intensities_windrose = 0.06 * np.ones_like(wd_windrose) - - # Create a FLORIS object for AEP calculations - fmodel_aep = fmodel.copy() - fmodel_aep.set( - wind_speeds=ws_windrose, - wind_directions=wd_windrose, - turbulence_intensities=turbulence_intensities_windrose - ) - - # And create a separate FLORIS object for optimization - fmodel_opt = fmodel.copy() - wd_array = np.arange(0.0, 360.0, 3.0) - ws_array = 8.0 * np.ones_like(wd_array) - turbulence_intensities = 0.06 * np.ones_like(wd_array) - fmodel_opt.set( - wind_directions=wd_array, - wind_speeds=ws_array, - turbulence_intensities=turbulence_intensities, - ) - - # First, get baseline AEP, without wake steering - print(" ") - print("===========================================================") - print("Calculating baseline annual energy production (AEP)...") - fmodel_aep.run() - aep_bl_subset = 1.0e-9 * fmodel_aep.get_farm_AEP( - freq=freq_windrose, - turbine_weights=turbine_weights - ) - print("Baseline AEP for subset farm: {:.3f} GWh.".format(aep_bl_subset)) - print("===========================================================") - print(" ") - - # Now optimize the yaw angles using the Serial Refine method. We first - # create a copy of the floris object for optimization purposes and assign - # it the atmospheric conditions for which we want to optimize. Typically, - # the optimal yaw angles are very insensitive to the actual wind speed, - # and hence we only optimize for a single wind speed of 8.0 m/s. We assume - # that the optimal yaw angles at 8.0 m/s are also optimal at other wind - # speeds between 4 and 12 m/s. - print("Now starting yaw optimization for the entire wind rose for farm subset...") - - # In this hypothetical case, we can only control the yaw angles of the - # turbines of the wind farm subset (i.e., the first 10 wind turbines). - # Hence, we constrain the yaw angles of the neighboring wind farms to 0.0. - turbs_to_opt = (turbine_weights > 0.0001) - - # Optimize yaw angles while including neighboring farm - yaw_opt_interpolant = optimize_yaw_angles(fmodel_opt=fmodel_opt) - - # Optimize yaw angles while ignoring neighboring farm - fmodel_opt_subset = fmodel_opt.copy() - fmodel_opt_subset.set( - layout_x = fmodel.layout_x[turbs_to_opt], - layout_y = fmodel.layout_y[turbs_to_opt] - ) - yaw_opt_interpolant_nonb = optimize_yaw_angles(fmodel_opt=fmodel_opt_subset) - - # Use interpolant to get optimal yaw angles for fmodel_aep object - wd = fmodel_aep.core.flow_field.wind_directions - ws = fmodel_aep.core.flow_field.wind_speeds - yaw_angles_opt_AEP = yaw_opt_interpolant(wd, ws) - yaw_angles_opt_nonb_AEP = np.zeros_like(yaw_angles_opt_AEP) # nonb = no neighbor - yaw_angles_opt_nonb_AEP[:, turbs_to_opt] = yaw_opt_interpolant_nonb(wd, ws) - - # Now get AEP with optimized yaw angles - print(" ") - print("===========================================================") - print("Calculating annual energy production with wake steering (AEP)...") - fmodel_aep.set(yaw_angles=yaw_angles_opt_nonb_AEP) - fmodel_aep.run() - aep_opt_subset_nonb = 1.0e-9 * fmodel_aep.get_farm_AEP( - freq=freq_windrose, - turbine_weights=turbine_weights, - ) - fmodel_aep.set(yaw_angles=yaw_angles_opt_AEP) - fmodel_aep.run() - aep_opt_subset = 1.0e-9 * fmodel_aep.get_farm_AEP( - freq=freq_windrose, - turbine_weights=turbine_weights, - ) - uplift_subset_nonb = 100.0 * (aep_opt_subset_nonb - aep_bl_subset) / aep_bl_subset - uplift_subset = 100.0 * (aep_opt_subset - aep_bl_subset) / aep_bl_subset - print( - "Optimized AEP for subset farm (including neighbor farms' wakes): " - f"{aep_opt_subset_nonb:.3f} GWh (+{uplift_subset_nonb:.2f}%)." - ) - print( - "Optimized AEP for subset farm (ignoring neighbor farms' wakes): " - f"{aep_opt_subset:.3f} GWh (+{uplift_subset:.2f}%)." - ) - print("===========================================================") - print(" ") - - # Plot power and AEP uplift across wind direction at wind_speed of 8 m/s - wd = fmodel_opt.core.flow_field.wind_directions - ws = fmodel_opt.core.flow_field.wind_speeds - yaw_angles_opt = yaw_opt_interpolant(wd, ws) - - yaw_angles_opt_nonb = np.zeros_like(yaw_angles_opt) # nonb = no neighbor - yaw_angles_opt_nonb[:, turbs_to_opt] = yaw_opt_interpolant_nonb(wd, ws) - - fmodel_opt = fmodel_opt.copy() - fmodel_opt.set(yaw_angles=np.zeros_like(yaw_angles_opt)) - fmodel_opt.run() - farm_power_bl_subset = fmodel_opt.get_farm_power(turbine_weights).flatten() - - fmodel_opt = fmodel_opt.copy() - fmodel_opt.set(yaw_angles=yaw_angles_opt) - fmodel_opt.run() - farm_power_opt_subset = fmodel_opt.get_farm_power(turbine_weights).flatten() - - fmodel_opt = fmodel_opt.copy() - fmodel_opt.set(yaw_angles=yaw_angles_opt_nonb) - fmodel_opt.run() - farm_power_opt_subset_nonb = fmodel_opt.get_farm_power(turbine_weights).flatten() - - fig, ax = plt.subplots() - ax.bar( - x=fmodel_opt.core.flow_field.wind_directions - 0.65, - height=100.0 * (farm_power_opt_subset / farm_power_bl_subset - 1.0), - edgecolor="black", - width=1.3, - label="Including wake effects of neighboring farms" - ) - ax.bar( - x=fmodel_opt.core.flow_field.wind_directions + 0.65, - height=100.0 * (farm_power_opt_subset_nonb / farm_power_bl_subset - 1.0), - edgecolor="black", - width=1.3, - label="Ignoring neighboring farms" - ) - ax.set_ylabel("Power uplift \n at 8 m/s (%)") - ax.legend() - ax.grid(True) - ax.set_xlabel("Wind direction (deg)") - - plt.show() From 12e4f7ea61cdcdb19337f2280dc88c72e13a9120 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 21:31:44 -0600 Subject: [PATCH 090/120] add n_turbines property --- floris/floris_model.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/floris/floris_model.py b/floris/floris_model.py index 447217ab1..08225b720 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -1434,6 +1434,16 @@ def n_findex(self): """ return self.core.flow_field.n_findex + @property + def n_turbines(self): + """ + Number of turbines. + + Returns: + int: Number of turbines. + """ + return self.core.farm.n_turbines + @property def turbine_average_velocities(self) -> NDArrayFloat: return average_velocity( From b6bce5e0a2576193035896ac356191f2a0cdb513 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 21:43:58 -0600 Subject: [PATCH 091/120] Add properties to uncertain model --- floris/parallel_floris_model.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/floris/parallel_floris_model.py b/floris/parallel_floris_model.py index a98b28053..4de5015df 100644 --- a/floris/parallel_floris_model.py +++ b/floris/parallel_floris_model.py @@ -531,6 +531,27 @@ def layout_x(self): def layout_y(self): return self.fmodel.layout_y + @property + def wind_speeds(self): + return self.fmodel.wind_speeds + + @property + def wind_directions(self): + return self.fmodel.wind_directions + + @property + def turbulence_intensities(self): + return self.fmodel.turbulence_intensities + + @property + def n_findex(self): + return self.fmodel.n_findex + + @property + def n_turbines(self): + return self.fmodel.n_turbines + + # @property # def floris(self): # return self.fmodel.core From 065834bd58ef494cbfef340483492d60ae44351c Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 21:44:10 -0600 Subject: [PATCH 092/120] Use the properties --- examples/003_wind_data_objects.py | 4 ++-- examples/005_getting_power.py | 2 +- examples/006_get_farm_aep.py | 2 +- .../examples_control_optimization/003_opt_yaw_multiple_ws.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index 2737f407d..d482dfc6c 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -194,7 +194,7 @@ print( f"Number of conditions to simulate with compute_zero_freq_occurrence" - f"False: {fmodel.core.flow_field.n_findex}" + f"False: {fmodel.n_findex}" ) wind_rose = WindRose( @@ -208,7 +208,7 @@ print( f"Number of conditions to simulate with compute_zero_freq_occurrence" - f"True: {fmodel.core.flow_field.n_findex}" + f"True: {fmodel.n_findex}" ) # Set the wind conditions using the WindTIRose object diff --git a/examples/005_getting_power.py b/examples/005_getting_power.py index 09ee4ceae..2f4ddd9d2 100644 --- a/examples/005_getting_power.py +++ b/examples/005_getting_power.py @@ -116,7 +116,7 @@ fmodel.set(wind_data=wind_rose) print("==========Wind Rose==========") -print(f"Number of conditions to simulate (2 x 3): {fmodel.core.flow_field.n_findex}") +print(f"Number of conditions to simulate (2 x 3): {fmodel.n_findex}") fmodel.run() diff --git a/examples/006_get_farm_aep.py b/examples/006_get_farm_aep.py index 5be3886cb..045175c53 100644 --- a/examples/006_get_farm_aep.py +++ b/examples/006_get_farm_aep.py @@ -82,7 +82,7 @@ # the total number of wind directions and wind speed combinations print(f"Total number of wind direction and wind speed combination: {n_wd * n_ws}") print(f"Number of 0 frequency bins: {n_zeros}") -print(f"n_findex: {fmodel.core.flow_field.n_findex}") +print(f"n_findex: {fmodel.n_findex}") # Get the AEP aep = fmodel.get_farm_AEP() diff --git a/examples/examples_control_optimization/003_opt_yaw_multiple_ws.py b/examples/examples_control_optimization/003_opt_yaw_multiple_ws.py index d5d63c20e..1a2d7e0a0 100644 --- a/examples/examples_control_optimization/003_opt_yaw_multiple_ws.py +++ b/examples/examples_control_optimization/003_opt_yaw_multiple_ws.py @@ -66,7 +66,7 @@ figsize=(10, 8) ) jj = 0 -for ii, ws in enumerate(np.unique(fmodel.core.flow_field.wind_speeds)): +for ii, ws in enumerate(np.unique(fmodel.wind_speeds)): xi = np.remainder(ii, 4) if ((ii > 0) & (xi == 0)): jj += 1 @@ -96,7 +96,7 @@ figsize=(10, 8) ) jj = 0 -for ii, ws in enumerate(np.unique(fmodel.core.flow_field.wind_speeds)): +for ii, ws in enumerate(np.unique(fmodel.wind_speeds)): xi = np.remainder(ii, 4) if ((ii > 0) & (xi == 0)): jj += 1 From 3555f9e88fcf4c6c77692a6bbd0d5249c7a78f37 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 21:50:26 -0600 Subject: [PATCH 093/120] Update ci to run examples in subdirectories --- .github/workflows/check-working-examples.yaml | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check-working-examples.yaml b/.github/workflows/check-working-examples.yaml index 26483a4d6..0b3801b8f 100644 --- a/.github/workflows/check-working-examples.yaml +++ b/.github/workflows/check-working-examples.yaml @@ -36,21 +36,29 @@ jobs: error_found=0 # 0 is false error_results="Error in example:" - # Run each Python script example + # Run all the examples in the root directory + echo "Running examples in the root directory" for i in *.py; do - - # Skip these examples until the wind rose, optimization package, and - # uncertainty interface are update to v4 - if [[ $i == *20* ]]; then - continue - fi - if ! python $i; then error_results="${error_results}"$'\n'" - ${i}" error_found=1 fi done + # Now run the examples in the subdirectories + echo "Running examples in the subdirectories" + for d in $(find . -type d); do + cd $d + echo "Running examples in $d" + for i in *.py; do + if ! python $i; then + error_results="${error_results}"$'\n'" - ${i}" + error_found=1 + fi + done + cd .. + done + if [[ $error_found ]]; then echo "${error_results}" fi From a907a2bb1ddbe2264b4eac0a5541a15eac2c3c49 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:03:40 -0600 Subject: [PATCH 094/120] Update example ci --- .github/workflows/check-working-examples.yaml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/check-working-examples.yaml b/.github/workflows/check-working-examples.yaml index 0b3801b8f..b85a5ef24 100644 --- a/.github/workflows/check-working-examples.yaml +++ b/.github/workflows/check-working-examples.yaml @@ -36,19 +36,13 @@ jobs: error_found=0 # 0 is false error_results="Error in example:" - # Run all the examples in the root directory - echo "Running examples in the root directory" - for i in *.py; do - if ! python $i; then - error_results="${error_results}"$'\n'" - ${i}" - error_found=1 - fi - done - - # Now run the examples in the subdirectories - echo "Running examples in the subdirectories" + # Now run the examples in root and subdirectories + echo "Running examples" for d in $(find . -type d); do + echo "Current directory- $(pwd)" + echo "Trying to change to- $d" cd $d + echo "New directory- $(pwd)" echo "Running examples in $d" for i in *.py; do if ! python $i; then From f01f4ce2159fae526a72a2ce42cae18d42fda445 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:06:19 -0600 Subject: [PATCH 095/120] bugfix --- .github/workflows/check-working-examples.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-working-examples.yaml b/.github/workflows/check-working-examples.yaml index b85a5ef24..ba429ff98 100644 --- a/.github/workflows/check-working-examples.yaml +++ b/.github/workflows/check-working-examples.yaml @@ -50,7 +50,7 @@ jobs: error_found=1 fi done - cd .. + cd temp1/temp2/temp3/examples/ done if [[ $error_found ]]; then From 298338e8deca081891c91b5e1a7d1ee7924aa97f Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:11:55 -0600 Subject: [PATCH 096/120] bugfix --- .github/workflows/check-working-examples.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-working-examples.yaml b/.github/workflows/check-working-examples.yaml index ba429ff98..334785ae2 100644 --- a/.github/workflows/check-working-examples.yaml +++ b/.github/workflows/check-working-examples.yaml @@ -40,7 +40,6 @@ jobs: echo "Running examples" for d in $(find . -type d); do echo "Current directory- $(pwd)" - echo "Trying to change to- $d" cd $d echo "New directory- $(pwd)" echo "Running examples in $d" @@ -50,7 +49,9 @@ jobs: error_found=1 fi done - cd temp1/temp2/temp3/examples/ + if [ "$d" != "." ]; then + cd .. + fi done if [[ $error_found ]]; then From 1a0edee25a130acf3614efbd5fe6fb02d3492317 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:18:34 -0600 Subject: [PATCH 097/120] bugfix --- .github/workflows/check-working-examples.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-working-examples.yaml b/.github/workflows/check-working-examples.yaml index 334785ae2..b4881f094 100644 --- a/.github/workflows/check-working-examples.yaml +++ b/.github/workflows/check-working-examples.yaml @@ -38,7 +38,7 @@ jobs: # Now run the examples in root and subdirectories echo "Running examples" - for d in $(find . -type d); do + for d in . $(find . -type d -name "*examples*"); do echo "Current directory- $(pwd)" cd $d echo "New directory- $(pwd)" From 1d815d1d7c60bffe77a937b322a8995b1ea2fdd1 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:34:26 -0600 Subject: [PATCH 098/120] remove seaborn --- .../004_optimize_yaw_aep.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/examples_control_optimization/004_optimize_yaw_aep.py b/examples/examples_control_optimization/004_optimize_yaw_aep.py index 1172adbfd..d9fae6c82 100644 --- a/examples/examples_control_optimization/004_optimize_yaw_aep.py +++ b/examples/examples_control_optimization/004_optimize_yaw_aep.py @@ -14,7 +14,6 @@ import matplotlib.pyplot as plt import numpy as np -import seaborn as sns from floris import ( FlorisModel, @@ -128,25 +127,30 @@ wind_speeds = wind_rose.wind_speeds relative_gain = farm_power_opt - farm_power_baseline -# Plt the heatmap with wind speeds on x, wind directions ony and relative gain as the color -fig, ax = plt.subplots(figsize=(7, 12)) -sns.heatmap(relative_gain, cmap="viridis", cbar_kws={"label": "Relative gain (%)"}, ax=ax) -ax.set_yticks(np.arange(len(wind_directions)) + 0.5) +# Plot the heatmap with wind speeds on x, wind directions on y and relative gain as the color +fig, ax = plt.subplots(figsize=(10, 12)) +cax = ax.imshow(relative_gain, cmap='viridis', aspect='auto') +fig.colorbar(cax, ax=ax, label="Relative gain (%)") + +ax.set_yticks(np.arange(len(wind_directions))) ax.set_yticklabels(wind_directions) -ax.set_xticks(np.arange(len(wind_speeds)) + 0.5) +ax.set_xticks(np.arange(len(wind_speeds))) ax.set_xticklabels(wind_speeds) ax.set_ylabel("Wind direction (deg)") ax.set_xlabel("Wind speed (m/s)") -plt.tight_layout() -# Reduce y tick font size +# Reduce x and y tick font size for tick in ax.yaxis.get_major_ticks(): tick.label.set_fontsize(8) +for tick in ax.xaxis.get_major_ticks(): + tick.label.set_fontsize(8) + # Set y ticks to be horizontal for tick in ax.get_yticklabels(): tick.set_rotation(0) ax.set_title("Uplift in farm power by wind direction and wind speed", fontsize=12) +plt.tight_layout() plt.show() From ca43d053642bf4c4b5770c9c0c8969878c5a264d Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:35:30 -0600 Subject: [PATCH 099/120] switch to parallel running of examples --- .github/workflows/check-working-examples.yaml | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-working-examples.yaml b/.github/workflows/check-working-examples.yaml index b4881f094..67132bd17 100644 --- a/.github/workflows/check-working-examples.yaml +++ b/.github/workflows/check-working-examples.yaml @@ -36,15 +36,30 @@ jobs: error_found=0 # 0 is false error_results="Error in example:" - # Now run the examples in root and subdirectories - echo "Running examples" + # Run all the examples in the root directory and subdirectories + echo "Running examples in the background" for d in . $(find . -type d -name "*examples*"); do - echo "Current directory- $(pwd)" cd $d - echo "New directory- $(pwd)" echo "Running examples in $d" for i in *.py; do - if ! python $i; then + # Run the script in the background and redirect its output to a log file + python $i > "${i}.log" 2>&1 & + done + if [ "$d" != "." ]; then + cd .. + fi + done + + # Wait for all background processes to finish + wait + echo "All jobs completed. Checking for errors." + + # Check the log files for errors + for d in . $(find . -type d -name "*examples*"); do + cd $d + for i in *.py; do + # If the log file contains an error, print the name of the script and the error + if grep -q "Error" "${i}.log"; then error_results="${error_results}"$'\n'" - ${i}" error_found=1 fi From eeddb306e8e06ca42792bc1eec559cbd988f75c2 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:44:27 -0600 Subject: [PATCH 100/120] back to serial --- .github/workflows/check-working-examples.yaml | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/.github/workflows/check-working-examples.yaml b/.github/workflows/check-working-examples.yaml index 67132bd17..111b21170 100644 --- a/.github/workflows/check-working-examples.yaml +++ b/.github/workflows/check-working-examples.yaml @@ -36,30 +36,13 @@ jobs: error_found=0 # 0 is false error_results="Error in example:" - # Run all the examples in the root directory and subdirectories - echo "Running examples in the background" + # Now run the examples in root and subdirectories + echo "Running examples" for d in . $(find . -type d -name "*examples*"); do cd $d - echo "Running examples in $d" + echo "========================= Example directory- $d" for i in *.py; do - # Run the script in the background and redirect its output to a log file - python $i > "${i}.log" 2>&1 & - done - if [ "$d" != "." ]; then - cd .. - fi - done - - # Wait for all background processes to finish - wait - echo "All jobs completed. Checking for errors." - - # Check the log files for errors - for d in . $(find . -type d -name "*examples*"); do - cd $d - for i in *.py; do - # If the log file contains an error, print the name of the script and the error - if grep -q "Error" "${i}.log"; then + if ! python $i; then error_results="${error_results}"$'\n'" - ${i}" error_found=1 fi @@ -67,6 +50,12 @@ jobs: if [ "$d" != "." ]; then cd .. fi + + # if error found break the loop + if [[ $error_found ]]; then + break + fi + done if [[ $error_found ]]; then From c3f8ec579d97584890df5fa5a1ba2ec79054f606 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:48:55 -0600 Subject: [PATCH 101/120] bugfix --- .github/workflows/check-working-examples.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/check-working-examples.yaml b/.github/workflows/check-working-examples.yaml index 111b21170..138e70de8 100644 --- a/.github/workflows/check-working-examples.yaml +++ b/.github/workflows/check-working-examples.yaml @@ -42,6 +42,7 @@ jobs: cd $d echo "========================= Example directory- $d" for i in *.py; do + echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Running example- $i" if ! python $i; then error_results="${error_results}"$'\n'" - ${i}" error_found=1 @@ -51,11 +52,6 @@ jobs: cd .. fi - # if error found break the loop - if [[ $error_found ]]; then - break - fi - done if [[ $error_found ]]; then From 9b38cdde7dcbf8096adf5a000e61ebb6840b74a7 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 2 Apr 2024 22:57:05 -0600 Subject: [PATCH 102/120] fix deprecated pyplot code --- .../examples_control_optimization/004_optimize_yaw_aep.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/examples_control_optimization/004_optimize_yaw_aep.py b/examples/examples_control_optimization/004_optimize_yaw_aep.py index d9fae6c82..00269e6fe 100644 --- a/examples/examples_control_optimization/004_optimize_yaw_aep.py +++ b/examples/examples_control_optimization/004_optimize_yaw_aep.py @@ -141,10 +141,10 @@ # Reduce x and y tick font size for tick in ax.yaxis.get_major_ticks(): - tick.label.set_fontsize(8) + tick.label1.set_fontsize(8) for tick in ax.xaxis.get_major_ticks(): - tick.label.set_fontsize(8) + tick.label1.set_fontsize(8) # Set y ticks to be horizontal for tick in ax.get_yticklabels(): From f50ca53d4d2934215ed58c1d16a9c2b2f4520d98 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:31:33 -0600 Subject: [PATCH 103/120] rename 007 --- .../007_Optimize_Yaw_with_neighbor_farm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py b/examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py index f9e80a59d..04b6b65ba 100644 --- a/examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py +++ b/examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py @@ -12,6 +12,7 @@ to the baseline AEP. """ + import matplotlib.pyplot as plt import numpy as np From 30b3d6a0a833a712e36f32ab87a3c16c4b0a2055 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:33:15 -0600 Subject: [PATCH 104/120] Add comments --- examples/002_visualizations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/002_visualizations.py b/examples/002_visualizations.py index 4d122d621..f8c946324 100644 --- a/examples/002_visualizations.py +++ b/examples/002_visualizations.py @@ -1,6 +1,9 @@ """Example 2: Visualizations This example demonstrates the use of the flow and layout visualizations in FLORIS. +First, an example wind farm layout is plotted, with the turbine names and the directions +and distances between turbines shown in different configurations by subplot. +Next, the horizontal flow field at hub height is plotted for a single wind condition. FLORIS includes two modules for visualization: 1) flow_visualization: for visualizing the flow field From 0441150c76c879dd4746d725a0b97cad3792e1bb Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:34:30 -0600 Subject: [PATCH 105/120] Fix printout --- examples/003_wind_data_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index d482dfc6c..a1d3ae137 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -193,8 +193,8 @@ fmodel.set(wind_data=wind_rose) print( - f"Number of conditions to simulate with compute_zero_freq_occurrence" - f"False: {fmodel.n_findex}" + f"Number of conditions to simulate with compute_zero_freq_occurrence = False" + f"{fmodel.n_findex}" ) wind_rose = WindRose( From 787ef828d06a5ffd45d88c5a4d6d110554a7a619 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:39:54 -0600 Subject: [PATCH 106/120] format --- examples/006_get_farm_aep.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/006_get_farm_aep.py b/examples/006_get_farm_aep.py index 045175c53..2d9121be9 100644 --- a/examples/006_get_farm_aep.py +++ b/examples/006_get_farm_aep.py @@ -88,7 +88,7 @@ aep = fmodel.get_farm_AEP() # Print the AEP -print(f"AEP from wind rose: {aep/1E9:.3f} (GW-h)") +print(f"AEP from wind rose: {aep/1E9:.3f} (GWh)") # Run the model again, without wakes, and use the result to compute the wake losses fmodel.run_no_wake() From c721b4a14e136a930a30b2a67b3c162e57c878d2 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:41:19 -0600 Subject: [PATCH 107/120] fix file names --- .../{000_derating_control.py => 001_derating_control.py} | 0 .../{001_disable_turbines.py => 002_disable_turbines.py} | 0 ...ting_yaw_and_disabling.py => 003_setting_yaw_and_disabling.py} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename examples/examples_control_types/{000_derating_control.py => 001_derating_control.py} (100%) rename examples/examples_control_types/{001_disable_turbines.py => 002_disable_turbines.py} (100%) rename examples/examples_control_types/{002_setting_yaw_and_disabling.py => 003_setting_yaw_and_disabling.py} (100%) diff --git a/examples/examples_control_types/000_derating_control.py b/examples/examples_control_types/001_derating_control.py similarity index 100% rename from examples/examples_control_types/000_derating_control.py rename to examples/examples_control_types/001_derating_control.py diff --git a/examples/examples_control_types/001_disable_turbines.py b/examples/examples_control_types/002_disable_turbines.py similarity index 100% rename from examples/examples_control_types/001_disable_turbines.py rename to examples/examples_control_types/002_disable_turbines.py diff --git a/examples/examples_control_types/002_setting_yaw_and_disabling.py b/examples/examples_control_types/003_setting_yaw_and_disabling.py similarity index 100% rename from examples/examples_control_types/002_setting_yaw_and_disabling.py rename to examples/examples_control_types/003_setting_yaw_and_disabling.py From df63f149b0ba1cdcb1da221c09c957e40a8f81d5 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:41:33 -0600 Subject: [PATCH 108/120] fix file names --- ...h_neighbor_farm.py => 007_optimize_yaw_with_neighbor_farms.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/examples_control_optimization/{007_Optimize_Yaw_with_neighbor_farm.py => 007_optimize_yaw_with_neighbor_farms.py} (100%) diff --git a/examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py b/examples/examples_control_optimization/007_optimize_yaw_with_neighbor_farms.py similarity index 100% rename from examples/examples_control_optimization/007_Optimize_Yaw_with_neighbor_farm.py rename to examples/examples_control_optimization/007_optimize_yaw_with_neighbor_farms.py From 4e0a2ede8f485b2a852fab0ff6995b4c943586bd Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:45:04 -0600 Subject: [PATCH 109/120] Remove empty approx example --- examples/examples_uncertain/002_approx_floris.py | 1 - .../{003_yaw_inertial_frame.py => 002_yaw_inertial_frame.py} | 0 2 files changed, 1 deletion(-) delete mode 100644 examples/examples_uncertain/002_approx_floris.py rename examples/examples_uncertain/{003_yaw_inertial_frame.py => 002_yaw_inertial_frame.py} (100%) diff --git a/examples/examples_uncertain/002_approx_floris.py b/examples/examples_uncertain/002_approx_floris.py deleted file mode 100644 index 9a87e5eb4..000000000 --- a/examples/examples_uncertain/002_approx_floris.py +++ /dev/null @@ -1 +0,0 @@ -#TODO: ADD EXAMPLE diff --git a/examples/examples_uncertain/003_yaw_inertial_frame.py b/examples/examples_uncertain/002_yaw_inertial_frame.py similarity index 100% rename from examples/examples_uncertain/003_yaw_inertial_frame.py rename to examples/examples_uncertain/002_yaw_inertial_frame.py From 545fbd3e977c706df601c1a0af827bd80d03da9b Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:52:29 -0600 Subject: [PATCH 110/120] Improve docstrings --- examples/001_opening_floris_computing_power.py | 4 ++-- examples/003_wind_data_objects.py | 13 +++++++++---- examples/004_set.py | 5 +++++ examples/007_sweeping_variables.py | 14 +++++++++++--- examples/009_compare_farm_power_with_neighbor.py | 7 +++---- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/examples/001_opening_floris_computing_power.py b/examples/001_opening_floris_computing_power.py index adab8f2c8..52950c922 100644 --- a/examples/001_opening_floris_computing_power.py +++ b/examples/001_opening_floris_computing_power.py @@ -1,8 +1,8 @@ """Example 1: Opening FLORIS and Computing Power -This example illustrates several of the key concepts in FLORIS. It: +This example illustrates several of the key concepts in FLORIS. It demonstrates: - 1) Initializing FLORIS + 1) Initializing a FLORIS model 2) Changing the wind farm layout 3) Changing the incoming wind speed, wind direction and turbulence intensity 4) Running the FLORIS simulation diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index a1d3ae137..20e2b59c8 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -1,12 +1,17 @@ """Example 3: Wind Data Objects This example demonstrates the use of wind data objects in FLORIS: - TimeSeries, - WindRose, and WindTIRose. + TimeSeries, WindRose, and WindTIRose. + For each of the WindData objects, examples are shown of: + + 1) Initializing the object + 2) Broadcasting values + 3) Converting between objects + 3) Plotting + 4) Assigning value + 5) Setting the FLORIS model using the object -Main concept is introduce FLORIS and illustrate essential structure -of most-used FLORIS calls """ diff --git a/examples/004_set.py b/examples/004_set.py index 506d1cc17..ab103098a 100644 --- a/examples/004_set.py +++ b/examples/004_set.py @@ -4,6 +4,11 @@ change the wind conditions, the wind farm layout, the turbine type, and the controls settings. +This example demonstrates setting each of the following: + 1) Wind conditions + 2) Wind farm layout + 3) Controls settings + """ diff --git a/examples/007_sweeping_variables.py b/examples/007_sweeping_variables.py index 67103867d..502d961a4 100644 --- a/examples/007_sweeping_variables.py +++ b/examples/007_sweeping_variables.py @@ -1,9 +1,17 @@ """Example 7: Sweeping Variables Demonstrate methods for sweeping across variables. Wind directions, wind speeds, -turbulence intensities, -as well as control inputs are passed to set() as arrays and so can be swept -and run in one call to run(). +turbulence intensities, as well as control inputs are passed to set() as arrays +and so can be swept and run in one call to run(). + +The example includes demonstrations of sweeping: + + 1) Wind speeds + 2) Wind directions + 3) Turbulence intensities + 4) Yaw angles + 5) Power setpoints + 6) Disabling turbines """ diff --git a/examples/009_compare_farm_power_with_neighbor.py b/examples/009_compare_farm_power_with_neighbor.py index 50d334d17..1845d2288 100644 --- a/examples/009_compare_farm_power_with_neighbor.py +++ b/examples/009_compare_farm_power_with_neighbor.py @@ -1,9 +1,8 @@ """Example: 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 via wake losses, but whose own -power production is not -considered in farm power / aep production +to a neighboring farm which impacts the power production of the farm under consideration +via wake losses, but whose own power production is not considered in farm power / aep production """ From 5ec20ab651521f682a40c4f095eaa3cdc252d325 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 3 Apr 2024 21:53:19 -0600 Subject: [PATCH 111/120] Add number --- examples/009_compare_farm_power_with_neighbor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/009_compare_farm_power_with_neighbor.py b/examples/009_compare_farm_power_with_neighbor.py index 1845d2288..c67465f31 100644 --- a/examples/009_compare_farm_power_with_neighbor.py +++ b/examples/009_compare_farm_power_with_neighbor.py @@ -1,4 +1,4 @@ -"""Example: Compare farm power with neighboring farm +"""Example 9: 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 From be310044e1802f092dd93db96f24780fd8c8999e Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 4 Apr 2024 14:15:08 -0600 Subject: [PATCH 112/120] Add properties to the uncertain model to match floris model --- floris/uncertain_floris_model.py | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/floris/uncertain_floris_model.py b/floris/uncertain_floris_model.py index 2242f4075..217dab2e5 100644 --- a/floris/uncertain_floris_model.py +++ b/floris/uncertain_floris_model.py @@ -725,6 +725,56 @@ def layout_y(self): """ return self.fmodel_unexpanded.core.farm.layout_y + @property + def wind_directions(self): + """ + Wind direction information. + + Returns: + np.array: Wind direction. + """ + return self.fmodel_unexpanded.core.flow_field.wind_directions + + @property + def wind_speeds(self): + """ + Wind speed information. + + Returns: + np.array: Wind speed. + """ + return self.fmodel_unexpanded.core.flow_field.wind_speeds + + @property + def turbulence_intensities(self): + """ + Turbulence intensity information. + + Returns: + np.array: Turbulence intensity. + """ + return self.fmodel_unexpanded.core.flow_field.turbulence_intensities + + @property + def n_findex(self): + """ + Number of unique wind conditions. + + Returns: + int: Number of unique wind conditions. + """ + return self.fmodel_unexpanded.core.flow_field.n_findex + + @property + def n_turbines(self): + """ + Number of turbines in the wind farm. + + Returns: + int: Number of turbines in the wind farm. + """ + return self.fmodel_unexpanded.core.farm.n_turbines + @property def core(self): """ From 7e1e552a01d62a1bdfbfb9f5d1d4ab162f271d3a Mon Sep 17 00:00:00 2001 From: Eric Simley Date: Thu, 4 Apr 2024 14:42:51 -0600 Subject: [PATCH 113/120] including a value layout optimization example --- .../001_optimize_layout.py | 76 ++++++++++++++++--- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/examples/examples_layout_optimization/001_optimize_layout.py b/examples/examples_layout_optimization/001_optimize_layout.py index 559b00558..809c346d7 100644 --- a/examples/examples_layout_optimization/001_optimize_layout.py +++ b/examples/examples_layout_optimization/001_optimize_layout.py @@ -1,12 +1,24 @@ """Example: Optimize Layout -This example shows a simple layout optimization using the python module Scipy. +This example shows a simple layout optimization using the python module Scipy, optimizing for both +annual energy production (AEP) and annual value production (AVP). -A 4 turbine array is optimized such that the layout of the turbine produces the -highest annual energy production (AEP) based on the given wind resource. The turbines +First, a 4 turbine array is optimized such that the layout of the turbine produces the +highest AEP based on the given wind resource. The turbines are constrained to a square boundary and a random wind resource is supplied. The results -of the optimization show that the turbines are pushed to the outer corners of the boundary, -which makes sense in order to maximize the energy production by minimizing wake interactions. +of the optimization show that the turbines are pushed to near the outer corners of the boundary, +which, given the generally uniform wind rose, makes sense in order to maximize the energy +production by minimizing wake interactions. + +Next, with the same boundary, the same 4 turbine array is optimized to maximize AVP instead of AEP, +using the value table defined in the WindRose object, where value represents the value of the +energy produced for a given wind condition (e.g., the price of electricity). In this example, value +is defined to be significantly higher for northerly and southerly wind directions, and zero when +the wind is from the east or west. Because the value is much higher when the wind is from the north +or south, the turbines are spaced apart roughly evenly in the x direction while being relatively +close in the y direction to avoid wake interactions for northerly and southerly winds. Although the +layout results in large wake losses when the wind is from the east or west, these losses do not +significantly impact the objective function because of the low value for those wind directions. """ @@ -21,6 +33,15 @@ ) +# Define scipy optimization parameters +opt_options = { + "maxiter": 20, + "disp": True, + "iprint": 2, + "ftol": 1e-12, + "eps": 0.05, +} + # Initialize the FLORIS interface fi file_dir = os.path.dirname(os.path.abspath(__file__)) fmodel = FlorisModel('../inputs/gch.yaml') @@ -29,18 +50,27 @@ wind_directions = np.arange(0, 360.0, 5.0) wind_speeds = np.array([8.0]) -# Shape frequency distribution to match number of wind directions and wind speeds +# Shape random frequency distribution to match number of wind directions and wind speeds freq_table = np.zeros((len(wind_directions), len(wind_speeds))) np.random.seed(1) freq_table[:,0] = (np.abs(np.sort(np.random.randn(len(wind_directions))))) freq_table = freq_table / freq_table.sum() +# Define the value table such that the value of the energy produced is +# significantly higher when the wind direction is close to the north or +# south, and zero when the wind is from the east or west. Here, value is +# given a mean value of 25 USD/MWh. +value_table = (0.5 + 0.5*np.cos(2*np.radians(wind_directions)))**10 +value_table = 25*value_table/np.mean(value_table) +value_table = value_table.reshape((len(wind_directions),1)) + # Establish a WindRose object wind_rose = WindRose( wind_directions=wind_directions, wind_speeds=wind_speeds, freq_table=freq_table, - ti_table=0.06 + ti_table=0.06, + value_table=value_table ) fmodel.set(wind_data=wind_rose) @@ -54,8 +84,8 @@ layout_y = [0, 4 * D, 0, 4 * D] fmodel.set(layout_x=layout_x, layout_y=layout_y) -# Setup the optimization problem -layout_opt = LayoutOptimizationScipy(fmodel, boundaries) +# Setup the optimization problem to maximize AEP instead of value +layout_opt = LayoutOptimizationScipy(fmodel, boundaries, optOptions=opt_options) # Run the optimization sol = layout_opt.optimize() @@ -78,4 +108,32 @@ ) layout_opt.plot_layout_opt_results() +# reset to the original layout +fmodel.set(layout_x=layout_x, layout_y=layout_y) + +# Now set up the optimization problem to maximize annual value production (AVP) +# using the value table provided in the WindRose object. +layout_opt = LayoutOptimizationScipy(fmodel, boundaries, optOptions=opt_options, use_value=True) + +# Run the optimization +sol = layout_opt.optimize() + +# Get the resulting improvement in AVP +print('... calculating improvement in annual value production (AVP)') +fmodel.run() +base_avp = fmodel.get_farm_AVP() / 1e6 +fmodel.set(layout_x=sol[0], layout_y=sol[1]) +fmodel.run() +opt_avp = fmodel.get_farm_AVP() / 1e6 + +percent_gain = 100 * (opt_avp - base_avp) / base_avp + +# Print and plot the results +print(f'Optimal layout: {sol}') +print( + f'Optimal layout improves AVP by {percent_gain:.1f}% ' + f'from {base_avp:.1f} dollars to {opt_avp:.1f} dollars' +) +layout_opt.plot_layout_opt_results() + plt.show() From 0dde281e3a343a9d2fc2ed75e3a9211915da1cf4 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 4 Apr 2024 16:00:59 -0600 Subject: [PATCH 114/120] remove scipy regression from tests (scipy not stable in solution) --- ...yout_opt_regression_test.py => scipy_layout_opt_regression.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/reg_tests/{scipy_layout_opt_regression_test.py => scipy_layout_opt_regression.py} (100%) diff --git a/tests/reg_tests/scipy_layout_opt_regression_test.py b/tests/reg_tests/scipy_layout_opt_regression.py similarity index 100% rename from tests/reg_tests/scipy_layout_opt_regression_test.py rename to tests/reg_tests/scipy_layout_opt_regression.py From f80e82dd683a1d08423147d2aa9f73f34732cbe0 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 4 Apr 2024 16:02:11 -0600 Subject: [PATCH 115/120] Remove reference to time_series mode --- floris/core/core.py | 5 ----- floris/core/flow_field.py | 1 - floris/core/grid.py | 13 ------------- floris/core/solver.py | 3 --- tests/conftest.py | 3 --- 5 files changed, 25 deletions(-) diff --git a/floris/core/core.py b/floris/core/core.py index a31583567..084f0a717 100644 --- a/floris/core/core.py +++ b/floris/core/core.py @@ -93,14 +93,12 @@ def __attrs_post_init__(self) -> None: turbine_diameters=self.farm.rotor_diameters, wind_directions=self.flow_field.wind_directions, grid_resolution=self.solver["turbine_grid_points"], - time_series=self.flow_field.time_series, ) elif self.solver["type"] == "turbine_cubature_grid": self.grid = TurbineCubatureGrid( turbine_coordinates=self.farm.coordinates, turbine_diameters=self.farm.rotor_diameters, wind_directions=self.flow_field.wind_directions, - time_series=self.flow_field.time_series, grid_resolution=self.solver["turbine_grid_points"], ) elif self.solver["type"] == "flow_field_grid": @@ -109,7 +107,6 @@ def __attrs_post_init__(self) -> None: turbine_diameters=self.farm.rotor_diameters, wind_directions=self.flow_field.wind_directions, grid_resolution=self.solver["flow_field_grid_points"], - time_series=self.flow_field.time_series, ) elif self.solver["type"] == "flow_field_planar_grid": self.grid = FlowFieldPlanarGrid( @@ -119,7 +116,6 @@ def __attrs_post_init__(self) -> None: normal_vector=self.solver["normal_vector"], planar_coordinate=self.solver["planar_coordinate"], grid_resolution=self.solver["flow_field_grid_points"], - time_series=self.flow_field.time_series, x1_bounds=self.solver["flow_field_bounds"][0], x2_bounds=self.solver["flow_field_bounds"][1], ) @@ -230,7 +226,6 @@ def solve_for_points(self, x, y, z): turbine_diameters=self.farm.rotor_diameters, wind_directions=self.flow_field.wind_directions, grid_resolution=1, - time_series=self.flow_field.time_series, x_center_of_rotation=self.grid.x_center_of_rotation, y_center_of_rotation=self.grid.y_center_of_rotation ) diff --git a/floris/core/flow_field.py b/floris/core/flow_field.py index 09228c2b7..d28c47f27 100644 --- a/floris/core/flow_field.py +++ b/floris/core/flow_field.py @@ -28,7 +28,6 @@ class FlowField(BaseClass): air_density: float = field(converter=float) turbulence_intensities: NDArrayFloat = field(converter=floris_array_converter) reference_wind_height: float = field(converter=float) - time_series: bool = field(default=False) heterogeneous_inflow_config: dict = field(default=None) multidim_conditions: dict = field(default=None) diff --git a/floris/core/grid.py b/floris/core/grid.py index 3dc6280ae..9076e01e2 100644 --- a/floris/core/grid.py +++ b/floris/core/grid.py @@ -45,15 +45,12 @@ class Grid(ABC, BaseClass): arrays with shape (N coordinates, 3). turbine_diameters (:py:obj:`NDArrayFloat`): The rotor diameters of each turbine. wind_directions (:py:obj:`NDArrayFloat`): Wind directions supplied by the user. - time_series (:py:obj:`bool`): Flag to indicate whether the supplied wind data is a time - series. grid_resolution (:py:obj:`int` | :py:obj:`Iterable(int,)`): Grid resolution with values specific to each grid type. """ turbine_coordinates: NDArrayFloat = field(converter=floris_array_converter) turbine_diameters: NDArrayFloat = field(converter=floris_array_converter) wind_directions: NDArrayFloat = field(converter=floris_array_converter) - time_series: bool = field() grid_resolution: int | Iterable = field() n_turbines: int = field(init=False) @@ -116,8 +113,6 @@ class TurbineGrid(Grid): arrays with shape (N coordinates, 3). turbine_diameters (:py:obj:`NDArrayFloat`): The rotor diameters of each turbine. wind_directions (:py:obj:`NDArrayFloat`): Wind directions supplied by the user. - time_series (:py:obj:`bool`): Flag to indicate whether the supplied wind data is a time - series. grid_resolution (:py:obj:`int`): The number of points in each direction of the square grid on the rotor plane. For example, grid_resolution=3 creates a 3x3 grid within the rotor swept area. @@ -275,8 +270,6 @@ class TurbineCubatureGrid(Grid): arrays with shape (N coordinates, 3). turbine_diameters (:py:obj:`NDArrayFloat`): The rotor diameters of each turbine. wind_directions (:py:obj:`NDArrayFloat`): Wind directions supplied by the user. - time_series (:py:obj:`bool`): Flag to indicate whether the supplied wind data is a time - series. grid_resolution (:py:obj:`int`): The number of points to include in the cubature method. This value must be in the range [1, 10], and the corresponding cubature weights are set automatically. @@ -438,8 +431,6 @@ class FlowFieldGrid(Grid): arrays with shape (N coordinates, 3). turbine_diameters (:py:obj:`NDArrayFloat`): The rotor diameters of each turbine. wind_directions (:py:obj:`NDArrayFloat`): Wind directions supplied by the user. - time_series (:py:obj:`bool`): Flag to indicate whether the supplied wind data is a time - series. grid_resolution (:py:obj:`Iterable(int,)`): The number of grid points to create in each planar direction. Must be 3 components for resolution in the x, y, and z directions. """ @@ -509,8 +500,6 @@ class FlowFieldPlanarGrid(Grid): arrays with shape (N coordinates, 3). turbine_diameters (:py:obj:`NDArrayFloat`): The rotor diameters of each turbine. wind_directions (:py:obj:`NDArrayFloat`): Wind directions supplied by the user. - time_series (:py:obj:`bool`): Flag to indicate whether the supplied wind data is a time - series. grid_resolution (:py:obj:`Iterable(int,)`): The number of grid points to create in each planar direction. Must be 2 components for resolution in the x and y directions. The z direction is set to 3 planes at -10.0, 0.0, and +10.0 relative to the @@ -626,8 +615,6 @@ class PointsGrid(Grid): turbine_diameters (:py:obj:`NDArrayFloat`): Not used for PointsGrid, but required for the `Grid` super-class. wind_directions (:py:obj:`NDArrayFloat`): Wind directions supplied by the user. - time_series (:py:obj:`bool`): Not used for PointsGrid, but - required for the `Grid` super-class. grid_resolution (:py:obj:`int` | :py:obj:`Iterable(int,)`): Not used for PointsGrid, but required for the `Grid` super-class. diff --git a/floris/core/solver.py b/floris/core/solver.py index 00abcc129..a21978156 100644 --- a/floris/core/solver.py +++ b/floris/core/solver.py @@ -281,7 +281,6 @@ def full_flow_sequential_solver( turbine_diameters=turbine_grid_farm.rotor_diameters, wind_directions=turbine_grid_flow_field.wind_directions, grid_resolution=3, - time_series=turbine_grid_flow_field.time_series, ) turbine_grid_farm.expand_farm_properties( turbine_grid_flow_field.n_findex, @@ -703,7 +702,6 @@ def full_flow_cc_solver( turbine_diameters=turbine_grid_farm.rotor_diameters, wind_directions=turbine_grid_flow_field.wind_directions, grid_resolution=3, - time_series=turbine_grid_flow_field.time_series, ) turbine_grid_farm.expand_farm_properties( turbine_grid_flow_field.n_findex, @@ -1326,7 +1324,6 @@ def full_flow_empirical_gauss_solver( turbine_diameters=turbine_grid_farm.rotor_diameters, wind_directions=turbine_grid_flow_field.wind_directions, grid_resolution=3, - time_series=turbine_grid_flow_field.time_series, ) turbine_grid_farm.expand_farm_properties( turbine_grid_flow_field.n_findex, diff --git a/tests/conftest.py b/tests/conftest.py index 26210c963..4b2e6704f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -137,7 +137,6 @@ def print_test_values( N_TURBINES = len(X_COORDS) ROTOR_DIAMETER = 126.0 TURBINE_GRID_RESOLUTION = 2 -TIME_SERIES = False ## Unit test fixtures @@ -156,7 +155,6 @@ def turbine_grid_fixture(sample_inputs_fixture) -> TurbineGrid: turbine_diameters=rotor_diameters, wind_directions=np.array(WIND_DIRECTIONS), grid_resolution=TURBINE_GRID_RESOLUTION, - time_series=TIME_SERIES ) @pytest.fixture @@ -182,7 +180,6 @@ def points_grid_fixture(sample_inputs_fixture) -> PointsGrid: turbine_diameters=rotor_diameters, wind_directions=np.array(WIND_DIRECTIONS), grid_resolution=None, - time_series=False, points_x=points_x, points_y=points_y, points_z=points_z, From 0656261ded68ae56028a15b8ac75d698b6386331 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 4 Apr 2024 16:06:45 -0600 Subject: [PATCH 116/120] v3->v4 --- examples/inputs/cc.yaml | 2 +- examples/inputs/emgauss.yaml | 2 +- examples/inputs/gch.yaml | 2 +- examples/inputs/gch_heterogeneous_inflow.yaml | 2 +- examples/inputs/gch_multi_dim_cp_ct.yaml | 2 +- examples/inputs/gch_multiple_turbine_types.yaml | 2 +- examples/inputs/jensen.yaml | 2 +- examples/inputs/turbopark.yaml | 2 +- examples/inputs_floating/emgauss_fixed.yaml | 2 +- examples/inputs_floating/emgauss_floating.yaml | 2 +- examples/inputs_floating/emgauss_floating_fixedtilt15.yaml | 2 +- examples/inputs_floating/emgauss_floating_fixedtilt5.yaml | 2 +- examples/inputs_floating/gch_fixed.yaml | 2 +- examples/inputs_floating/gch_floating.yaml | 2 +- examples/inputs_floating/gch_floating_defined_floating.yaml | 2 +- tests/data/input_full.yaml | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/inputs/cc.yaml b/examples/inputs/cc.yaml index af62b0021..23bac8292 100644 --- a/examples/inputs/cc.yaml +++ b/examples/inputs/cc.yaml @@ -1,7 +1,7 @@ name: CC description: Three turbines using Cumulative Gauss Curl model -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/examples/inputs/emgauss.yaml b/examples/inputs/emgauss.yaml index 73344d5ea..2dbd0fe76 100644 --- a/examples/inputs/emgauss.yaml +++ b/examples/inputs/emgauss.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian description: Three turbines using emperical Gaussian model -floris_version: v3.x +floris_version: v4.x logging: console: diff --git a/examples/inputs/gch.yaml b/examples/inputs/gch.yaml index 3397839da..220cfcef6 100644 --- a/examples/inputs/gch.yaml +++ b/examples/inputs/gch.yaml @@ -12,7 +12,7 @@ description: Three turbines using Gauss Curl Hybrid model ### # The earliest verion of FLORIS this input file supports. # This is not currently only for the user's reference. -floris_version: v3.0.0 +floris_version: v4.0.0 ### # Configure the logging level and where to show the logs. diff --git a/examples/inputs/gch_heterogeneous_inflow.yaml b/examples/inputs/gch_heterogeneous_inflow.yaml index 3c2010773..750012c23 100644 --- a/examples/inputs/gch_heterogeneous_inflow.yaml +++ b/examples/inputs/gch_heterogeneous_inflow.yaml @@ -1,6 +1,6 @@ name: GCH description: Three turbines using Gauss Curl Hybrid model -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/examples/inputs/gch_multi_dim_cp_ct.yaml b/examples/inputs/gch_multi_dim_cp_ct.yaml index 581dd1f37..702cf07b6 100644 --- a/examples/inputs/gch_multi_dim_cp_ct.yaml +++ b/examples/inputs/gch_multi_dim_cp_ct.yaml @@ -1,7 +1,7 @@ name: GCH multi dimensional Cp/Ct description: Three turbines using GCH model -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/examples/inputs/gch_multiple_turbine_types.yaml b/examples/inputs/gch_multiple_turbine_types.yaml index 0ead479a1..685d6250b 100644 --- a/examples/inputs/gch_multiple_turbine_types.yaml +++ b/examples/inputs/gch_multiple_turbine_types.yaml @@ -1,7 +1,7 @@ name: GCH description: Three turbines using Gauss Curl Hybrid model -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/examples/inputs/jensen.yaml b/examples/inputs/jensen.yaml index 6b4ac0dd6..af0f03353 100644 --- a/examples/inputs/jensen.yaml +++ b/examples/inputs/jensen.yaml @@ -1,7 +1,7 @@ name: Jensen-Jimenez description: Three turbines using Jensen / Jimenez models -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/examples/inputs/turbopark.yaml b/examples/inputs/turbopark.yaml index 682b1e801..4c5d1dfdf 100644 --- a/examples/inputs/turbopark.yaml +++ b/examples/inputs/turbopark.yaml @@ -1,7 +1,7 @@ name: TurbOPark description: Three turbines using TurbOPark model -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/examples/inputs_floating/emgauss_fixed.yaml b/examples/inputs_floating/emgauss_fixed.yaml index 76c3c4513..9c1baa7d6 100644 --- a/examples/inputs_floating/emgauss_fixed.yaml +++ b/examples/inputs_floating/emgauss_fixed.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian description: Example of single fixed-bottom turbine -floris_version: v3.x +floris_version: v4.x logging: console: diff --git a/examples/inputs_floating/emgauss_floating.yaml b/examples/inputs_floating/emgauss_floating.yaml index 965ef7549..a53cef85e 100644 --- a/examples/inputs_floating/emgauss_floating.yaml +++ b/examples/inputs_floating/emgauss_floating.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian description: Example of single floating turbine -floris_version: v3.x +floris_version: v4.x logging: console: diff --git a/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml b/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml index e8a452325..a90dad5b8 100644 --- a/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml +++ b/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian floating description: Single turbine using emperical Gaussian model for floating -floris_version: v3.x +floris_version: v4.x logging: console: diff --git a/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml b/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml index 7732b6213..e0e846c88 100644 --- a/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml +++ b/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian floating description: Single turbine using emperical Gaussian model for floating -floris_version: v3.x +floris_version: v4.x logging: console: diff --git a/examples/inputs_floating/gch_fixed.yaml b/examples/inputs_floating/gch_fixed.yaml index be03460e1..9fe7eff0e 100644 --- a/examples/inputs_floating/gch_fixed.yaml +++ b/examples/inputs_floating/gch_fixed.yaml @@ -1,7 +1,7 @@ name: GCH description: Example of single fixed-bottom turbine -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/examples/inputs_floating/gch_floating.yaml b/examples/inputs_floating/gch_floating.yaml index 09aaa5604..d3b6b224e 100644 --- a/examples/inputs_floating/gch_floating.yaml +++ b/examples/inputs_floating/gch_floating.yaml @@ -2,7 +2,7 @@ name: GCH description: Example of single floating turbine -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/examples/inputs_floating/gch_floating_defined_floating.yaml b/examples/inputs_floating/gch_floating_defined_floating.yaml index d540c8d47..d56dcb92a 100644 --- a/examples/inputs_floating/gch_floating_defined_floating.yaml +++ b/examples/inputs_floating/gch_floating_defined_floating.yaml @@ -1,7 +1,7 @@ name: GCH description: Example of single floating turbine where the cp/ct is calculated with floating tilt included -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: diff --git a/tests/data/input_full.yaml b/tests/data/input_full.yaml index 36a150bdd..0833958d2 100644 --- a/tests/data/input_full.yaml +++ b/tests/data/input_full.yaml @@ -1,7 +1,7 @@ name: test_input description: Single turbine for testing -floris_version: v3.0.0 +floris_version: v4.0.0 logging: console: From c0799a833c9bfc789425e3130caa6e65105b65c1 Mon Sep 17 00:00:00 2001 From: Eric Simley Date: Thu, 4 Apr 2024 16:15:21 -0600 Subject: [PATCH 117/120] including more value examples in wind data example --- examples/003_wind_data_objects.py | 63 ++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index 20e2b59c8..22bd25dec 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -37,26 +37,36 @@ # The TimeSeries class is used to hold time series data, such as wind speed, wind direction, # and turbulence intensity. -# Generate wind speeds, directions, and turbulence intensities via random signals +# There is also a "value" wind data variable, which represents the value of the power +# generated at each time step or wind condition (e.g., the price of electricity). This can +# then be used in later optimization methods to optimize for quantities besides AEP. + +# Generate wind speeds, directions, turbulence intensities, and values via random signals N = 100 wind_speeds = 8 + 2 * np.random.randn(N) wind_directions = 270 + 30 * np.random.randn(N) turbulence_intensities = 0.06 + 0.02 * np.random.randn(N) +values = 25 + 10 * np.random.randn(N) time_series = TimeSeries( wind_directions=wind_directions, wind_speeds=wind_speeds, turbulence_intensities=turbulence_intensities, + values=values, ) # The WindRose class is used to hold wind rose data, such as wind speed, wind direction, -# and frequency. TI is represented as a bin average per wind direction and speed bin. +# and frequency. TI and value are represented as bin averages per wind direction and +# speed bin. wind_directions = np.arange(0, 360, 3.0) wind_speeds = np.arange(4, 20, 2.0) # Make TI table 6% TI for all wind directions and speeds ti_table = 0.06 * np.ones((len(wind_directions), len(wind_speeds))) +# Make value table 25 for all wind directions and speeds +value_table =25 * np.ones((len(wind_directions), len(wind_speeds))) + # Uniform frequency freq_table = np.ones((len(wind_directions), len(wind_speeds))) freq_table = freq_table / np.sum(freq_table) @@ -66,6 +76,7 @@ wind_speeds=wind_speeds, ti_table=ti_table, freq_table=freq_table, + value_table=value_table, ) # The WindTIRose class is similar to the WindRose table except that TI is also binned @@ -75,11 +86,15 @@ # Uniform frequency freq_table = np.ones((len(wind_directions), len(wind_speeds), len(turbulence_intensities))) +# Uniform value +value_table = 25* np.ones((len(wind_directions), len(wind_speeds), len(turbulence_intensities))) + wind_ti_rose = WindTIRose( wind_directions=wind_directions, wind_speeds=wind_speeds, turbulence_intensities=turbulence_intensities, freq_table=freq_table, + value_table=value_table, ) ################################################## @@ -114,7 +129,7 @@ ################################################## # The TimeSeries class has a method to generate a wind rose from a time series based on binning -wind_rose = time_series.to_WindRose(wd_edges=np.arange(0, 360, 3.0), ws_edges=np.arange(4, 20, 2.0)) +wind_rose = time_series.to_WindRose(wd_edges=np.arange(0, 360, 3.0), ws_edges=np.arange(2, 20, 2.0)) ################################################## # Wind Rose from long CSV FILE @@ -135,11 +150,26 @@ ################################################## # Each of the wind data objects also has the ability to set the turbulence intensity -# according to a function of wind speed and direction. This can be done using -# a custom function by using the function assign_ti_using_IEC_method which assigns -# TI based on the IEC 61400-1 standard +# according to a function of wind speed and direction. This can be done using a custom +# function by using the assign_ti_using_wd_ws_function method. There is also a method +# called assign_ti_using_IEC_method which assigns TI based on the IEC 61400-1 standard. wind_rose.assign_ti_using_IEC_method() # Assign using default settings for Iref and offset +################################################## +# Setting value +################################################## + +# Similarly, each of the wind data objects also has the ability to set the value according to +# a function of wind speed and direction. This can be done using a custom function by using +# the assign_value_using_wd_ws_function method. There is also a method called +# assign_value_piecewise_linear which assigns value based on a linear piecewise function of +# wind speed. + +# Assign value using default settings. This produces a value vs. wind speed that approximates +# the normalized mean electricity price vs. wind speed curve for the SPP market in the U.S. +# for years 2018-2020 from figure 7 in "The value of wake steering wind farm flow control in +# US energy markets," Wind Energy Science, 2024. https://doi.org/10.5194/wes-9-219-2024. +wind_rose.assign_value_piecewise_linear() ################################################## # Plotting Wind Data Objects @@ -152,21 +182,8 @@ # Showing TI over wind speed for a WindRose wind_rose.plot_ti_over_ws() -################################################## -# Assigning value to wind data objects -################################################## - -# Wind data objects can also hold value information, such as the price of electricity for different -# time periods or wind conditions. These can then be used in later optimization methods to optimize -# for quantities besides AEP. - -N = 100 -wind_speeds = 8 + 2 * np.random.randn(N) -values = 1 / wind_speeds # Assume Value is inversely proportional to wind speed - -time_series = TimeSeries( - wind_directions=270.0, wind_speeds=wind_speeds, turbulence_intensities=0.06, values=values -) +# Showing value over wind speed for a WindRose +wind_rose.plot_value_over_ws() ################################################## # Setting the FLORIS model via wind data @@ -198,7 +215,7 @@ fmodel.set(wind_data=wind_rose) print( - f"Number of conditions to simulate with compute_zero_freq_occurrence = False" + f"Number of conditions to simulate with compute_zero_freq_occurrence = False: " f"{fmodel.n_findex}" ) @@ -212,7 +229,7 @@ fmodel.set(wind_data=wind_rose) print( - f"Number of conditions to simulate with compute_zero_freq_occurrence" + f"Number of conditions to simulate with compute_zero_freq_occurrence = " f"True: {fmodel.n_findex}" ) From ee26496c3214a75aeae8e545264592fda5a38661 Mon Sep 17 00:00:00 2001 From: ejsimley <40040961+ejsimley@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:17:11 -0600 Subject: [PATCH 118/120] updating documentation in example 003 --- examples/003_wind_data_objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/003_wind_data_objects.py b/examples/003_wind_data_objects.py index 22bd25dec..d382d9a29 100644 --- a/examples/003_wind_data_objects.py +++ b/examples/003_wind_data_objects.py @@ -8,9 +8,9 @@ 1) Initializing the object 2) Broadcasting values 3) Converting between objects - 3) Plotting - 4) Assigning value - 5) Setting the FLORIS model using the object + 4) Setting TI and value + 5) Plotting + 6) Setting the FLORIS model using the object """ From 12883823f40c7ad564e17203e68208a81464363a Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 4 Apr 2024 22:01:50 -0600 Subject: [PATCH 119/120] standardize on v4 --- examples/inputs/cc.yaml | 2 +- examples/inputs/emgauss.yaml | 2 +- examples/inputs/gch.yaml | 2 +- examples/inputs/gch_heterogeneous_inflow.yaml | 2 +- examples/inputs/gch_multi_dim_cp_ct.yaml | 2 +- examples/inputs/gch_multiple_turbine_types.yaml | 2 +- examples/inputs/jensen.yaml | 2 +- examples/inputs/turbopark.yaml | 2 +- examples/inputs_floating/emgauss_fixed.yaml | 2 +- examples/inputs_floating/emgauss_floating.yaml | 2 +- examples/inputs_floating/emgauss_floating_fixedtilt15.yaml | 2 +- examples/inputs_floating/emgauss_floating_fixedtilt5.yaml | 2 +- examples/inputs_floating/gch_fixed.yaml | 2 +- examples/inputs_floating/gch_floating.yaml | 2 +- examples/inputs_floating/gch_floating_defined_floating.yaml | 2 +- tests/conftest.py | 2 +- tests/data/input_full.yaml | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/inputs/cc.yaml b/examples/inputs/cc.yaml index 23bac8292..de626ff8f 100644 --- a/examples/inputs/cc.yaml +++ b/examples/inputs/cc.yaml @@ -1,7 +1,7 @@ name: CC description: Three turbines using Cumulative Gauss Curl model -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/examples/inputs/emgauss.yaml b/examples/inputs/emgauss.yaml index 2dbd0fe76..89caef95b 100644 --- a/examples/inputs/emgauss.yaml +++ b/examples/inputs/emgauss.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian description: Three turbines using emperical Gaussian model -floris_version: v4.x +floris_version: v4 logging: console: diff --git a/examples/inputs/gch.yaml b/examples/inputs/gch.yaml index 220cfcef6..79b0b8629 100644 --- a/examples/inputs/gch.yaml +++ b/examples/inputs/gch.yaml @@ -12,7 +12,7 @@ description: Three turbines using Gauss Curl Hybrid model ### # The earliest verion of FLORIS this input file supports. # This is not currently only for the user's reference. -floris_version: v4.0.0 +floris_version: v4 ### # Configure the logging level and where to show the logs. diff --git a/examples/inputs/gch_heterogeneous_inflow.yaml b/examples/inputs/gch_heterogeneous_inflow.yaml index 750012c23..121457f15 100644 --- a/examples/inputs/gch_heterogeneous_inflow.yaml +++ b/examples/inputs/gch_heterogeneous_inflow.yaml @@ -1,6 +1,6 @@ name: GCH description: Three turbines using Gauss Curl Hybrid model -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/examples/inputs/gch_multi_dim_cp_ct.yaml b/examples/inputs/gch_multi_dim_cp_ct.yaml index 702cf07b6..236bb63f8 100644 --- a/examples/inputs/gch_multi_dim_cp_ct.yaml +++ b/examples/inputs/gch_multi_dim_cp_ct.yaml @@ -1,7 +1,7 @@ name: GCH multi dimensional Cp/Ct description: Three turbines using GCH model -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/examples/inputs/gch_multiple_turbine_types.yaml b/examples/inputs/gch_multiple_turbine_types.yaml index 685d6250b..366f4e9c0 100644 --- a/examples/inputs/gch_multiple_turbine_types.yaml +++ b/examples/inputs/gch_multiple_turbine_types.yaml @@ -1,7 +1,7 @@ name: GCH description: Three turbines using Gauss Curl Hybrid model -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/examples/inputs/jensen.yaml b/examples/inputs/jensen.yaml index af0f03353..c0f95de6e 100644 --- a/examples/inputs/jensen.yaml +++ b/examples/inputs/jensen.yaml @@ -1,7 +1,7 @@ name: Jensen-Jimenez description: Three turbines using Jensen / Jimenez models -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/examples/inputs/turbopark.yaml b/examples/inputs/turbopark.yaml index 4c5d1dfdf..598ed87a0 100644 --- a/examples/inputs/turbopark.yaml +++ b/examples/inputs/turbopark.yaml @@ -1,7 +1,7 @@ name: TurbOPark description: Three turbines using TurbOPark model -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/examples/inputs_floating/emgauss_fixed.yaml b/examples/inputs_floating/emgauss_fixed.yaml index 9c1baa7d6..026710481 100644 --- a/examples/inputs_floating/emgauss_fixed.yaml +++ b/examples/inputs_floating/emgauss_fixed.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian description: Example of single fixed-bottom turbine -floris_version: v4.x +floris_version: v4 logging: console: diff --git a/examples/inputs_floating/emgauss_floating.yaml b/examples/inputs_floating/emgauss_floating.yaml index a53cef85e..253944aaf 100644 --- a/examples/inputs_floating/emgauss_floating.yaml +++ b/examples/inputs_floating/emgauss_floating.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian description: Example of single floating turbine -floris_version: v4.x +floris_version: v4 logging: console: diff --git a/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml b/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml index a90dad5b8..c34b38250 100644 --- a/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml +++ b/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian floating description: Single turbine using emperical Gaussian model for floating -floris_version: v4.x +floris_version: v4 logging: console: diff --git a/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml b/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml index e0e846c88..398c6eb29 100644 --- a/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml +++ b/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml @@ -1,7 +1,7 @@ name: Emperical Gaussian floating description: Single turbine using emperical Gaussian model for floating -floris_version: v4.x +floris_version: v4 logging: console: diff --git a/examples/inputs_floating/gch_fixed.yaml b/examples/inputs_floating/gch_fixed.yaml index 9fe7eff0e..3290d6fa1 100644 --- a/examples/inputs_floating/gch_fixed.yaml +++ b/examples/inputs_floating/gch_fixed.yaml @@ -1,7 +1,7 @@ name: GCH description: Example of single fixed-bottom turbine -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/examples/inputs_floating/gch_floating.yaml b/examples/inputs_floating/gch_floating.yaml index d3b6b224e..c342473f6 100644 --- a/examples/inputs_floating/gch_floating.yaml +++ b/examples/inputs_floating/gch_floating.yaml @@ -2,7 +2,7 @@ name: GCH description: Example of single floating turbine -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/examples/inputs_floating/gch_floating_defined_floating.yaml b/examples/inputs_floating/gch_floating_defined_floating.yaml index d56dcb92a..47288c718 100644 --- a/examples/inputs_floating/gch_floating_defined_floating.yaml +++ b/examples/inputs_floating/gch_floating_defined_floating.yaml @@ -1,7 +1,7 @@ name: GCH description: Example of single floating turbine where the cp/ct is calculated with floating tilt included -floris_version: v4.0.0 +floris_version: v4 logging: console: diff --git a/tests/conftest.py b/tests/conftest.py index 4b2e6704f..b8b70dc7d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -521,7 +521,7 @@ def __init__(self): }, "name": "conftest", "description": "Inputs used for testing", - "floris_version": "v3.0.0", + "floris_version": "v4", } self.v3type_turbine = { diff --git a/tests/data/input_full.yaml b/tests/data/input_full.yaml index 0833958d2..d9415db1f 100644 --- a/tests/data/input_full.yaml +++ b/tests/data/input_full.yaml @@ -1,7 +1,7 @@ name: test_input description: Single turbine for testing -floris_version: v4.0.0 +floris_version: v4 logging: console: From 6a9ee31068e507991f8c2fe32763fad58640a6c8 Mon Sep 17 00:00:00 2001 From: Eric Simley Date: Thu, 4 Apr 2024 22:28:44 -0600 Subject: [PATCH 120/120] more value examples in wind data example folder --- .../001_wind_data_comparisons.py | 46 +++++++---- .../examples_wind_data/003_generate_value.py | 81 +++++++++++++++++++ 2 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 examples/examples_wind_data/003_generate_value.py diff --git a/examples/examples_wind_data/001_wind_data_comparisons.py b/examples/examples_wind_data/001_wind_data_comparisons.py index 615c26cae..9dbbe07c7 100644 --- a/examples/examples_wind_data/001_wind_data_comparisons.py +++ b/examples/examples_wind_data/001_wind_data_comparisons.py @@ -1,13 +1,14 @@ """Example: Wind Data Comparisons -In this example, a random time series of wind speeds, wind directions - and turbulence intensities is generated. -This time series is then used to instantiate a TimeSeries object. - The TimeSeries object is then used to -instantiate a WindRose object and WindTIRose object based on the same data. - The three objects are then each used -to drive a FLORIS model of a simple two-turbine wind farm. The AEP output is - then compared and printed to the console. +In this example, a random time series of wind speeds, wind directions, turbulence +intensities, and values is generated. Value represents the value of the power +generated at each time step or wind condition (e.g., the price of electricity). This +can then be used in later optimization methods to optimize for total value instead of +energy. This time series is then used to instantiate a TimeSeries object. The TimeSeries +object is then used to instantiate a WindRose object and WindTIRose object based on the +same data. The three objects are then each used to drive a FLORIS model of a simple +two-turbine wind farm. The annual energy production (AEP) and annual value production +(AVP) outputs are then compared and printed to the console. """ @@ -24,13 +25,15 @@ from floris.utilities import wrap_360 -# Generate a random time series of wind speeds, wind directions and turbulence intensities +# Generate a random time series of wind speeds, wind directions, turbulence +# intensities, and values. In this case let's treat value as the dollars per MWh. N = 500 wd_array = wrap_360(270 * np.ones(N) + np.random.randn(N) * 20) ws_array = np.clip(8 * np.ones(N) + np.random.randn(N) * 8, 3, 50) ti_array = np.clip(0.1 * np.ones(N) + np.random.randn(N) * 0.05, 0, 0.25) +value_array = np.clip(25 * np.ones(N) + np.random.randn(N) * 10, 0, 100) -fig, axarr = plt.subplots(3, 1, sharex=True, figsize=(7, 4)) +fig, axarr = plt.subplots(4, 1, sharex=True, figsize=(7, 6)) ax = axarr[0] ax.plot(wd_array, marker=".", ls="None") ax.set_ylabel("Wind Direction") @@ -40,10 +43,13 @@ ax = axarr[2] ax.plot(ti_array, marker=".", ls="None") ax.set_ylabel("Turbulence Intensity") +ax = axarr[3] +ax.plot(value_array, marker=".", ls="None") +ax.set_ylabel("Value") # Build the time series -time_series = TimeSeries(wd_array, ws_array, turbulence_intensities=ti_array) +time_series = TimeSeries(wd_array, ws_array, turbulence_intensities=ti_array, values=value_array) # Now build the wind rose wind_rose = time_series.to_WindRose() @@ -81,9 +87,9 @@ fmodel_wind_rose.run() fmodel_wind_ti_rose.run() -time_series_power = fmodel_time_series.get_farm_power() -wind_rose_power = fmodel_wind_rose.get_farm_power() -wind_ti_rose_power = fmodel_wind_ti_rose.get_farm_power() +# Now, compute AEP using the FLORIS models initialized with the three types of +# WindData objects. The AEP values are very similar but not exactly the same +# because of the effects of binning in the wind roses. time_series_aep = fmodel_time_series.get_farm_AEP() wind_rose_aep = fmodel_wind_rose.get_farm_AEP() @@ -93,4 +99,16 @@ print(f"AEP from WindRose {wind_rose_aep / 1e9:.2f} GWh") print(f"AEP from WindTIRose {wind_ti_rose_aep / 1e9:.2f} GWh") +# Now, compute annual value production (AVP) using the FLORIS models initialized +# with the three types of WindData objects. The AVP values are very similar but +# not exactly the same because of the effects of binning in the wind roses. + +time_series_avp = fmodel_time_series.get_farm_AVP() +wind_rose_avp = fmodel_wind_rose.get_farm_AVP() +wind_ti_rose_avp = fmodel_wind_ti_rose.get_farm_AVP() + +print(f"Annual Value Production (AVP) from TimeSeries {time_series_avp / 1e6:.2f} dollars") +print(f"AVP from WindRose {wind_rose_avp / 1e6:.2f} dollars") +print(f"AVP from WindTIRose {wind_ti_rose_avp / 1e6:.2f} dollars") + plt.show() diff --git a/examples/examples_wind_data/003_generate_value.py b/examples/examples_wind_data/003_generate_value.py new file mode 100644 index 000000000..af23c5522 --- /dev/null +++ b/examples/examples_wind_data/003_generate_value.py @@ -0,0 +1,81 @@ +"""Example: Generate value + +Demonstrate usage of value generating and plotting functionality in the WindRose +and TimeSeries classes. Value represents the value of the power or energy generated +at each time step or wind condition (e.g., the price of electricity in dollars/MWh). +This can then be used to compute the annual value production (AVP) instead of AEP, +or in later optimization methods to optimize for total value instead of energy. + +""" + + +import matplotlib.pyplot as plt +import numpy as np + +from floris import ( + TimeSeries, + WindRose, +) + + +# Generate a random time series of wind speeds, wind directions and turbulence intensities +wind_directions = np.array([250, 260, 270]) +wind_speeds = np.arange(3.0, 11.0, 1.0) +ti_table = 0.06 + +# Declare a WindRose object +wind_rose = WindRose(wind_directions=wind_directions, wind_speeds=wind_speeds, ti_table=ti_table) + + +# Define a custom function where value = 100 / wind_speed +def custom_value_func(wind_directions, wind_speeds): + return 100 / wind_speeds + + +wind_rose.assign_value_using_wd_ws_function(custom_value_func) + +fig, ax = plt.subplots() +wind_rose.plot_value_over_ws(ax) +ax.set_title("Value defined by custom function") + +# Now assign value using the provided assign_value_piecewise_linear method with the default +# settings. This method assigns value based on a linear piecewise function of wind speed +# (with two line segments). The default arguments produce a value vs. wind speed that +# approximates the normalized mean electricity price vs. wind speed curve for the SPP market +# in the U.S. for years 2018-2020 from figure 7 in "The value of wake steering wind farm flow +# control in US energy markets," Wind Energy Science, 2024. https://doi.org/10.5194/wes-9-219-2024. +wind_rose.assign_value_piecewise_linear( + value_zero_ws=1.425, + ws_knee=4.5, + slope_1=0.0, + slope_2=-0.135 +) +fig, ax = plt.subplots() +wind_rose.plot_value_over_ws(ax) +ax.set_title("Value defined by default piecewise linear function") + +# Demonstrate equivalent usage in time series +N = 100 +wind_directions = 270 * np.ones(N) +wind_speeds = np.linspace(3, 15, N) +turbulence_intensities = 0.06 * np.ones(N) +time_series = TimeSeries( + wind_directions=wind_directions, + wind_speeds=wind_speeds, + turbulence_intensities=turbulence_intensities +) +time_series.assign_value_piecewise_linear() + +fig, axarr = plt.subplots(2, 1, sharex=True, figsize=(7, 8)) +ax = axarr[0] +ax.plot(wind_speeds) +ax.set_ylabel("Wind Speeds (m/s)") +ax.grid(True) +ax = axarr[1] +ax.plot(time_series.values) +ax.set_ylabel("Value (normalized price/MWh)") +ax.grid(True) +fig.suptitle("Generating value in TimeSeries") + + +plt.show()