From 9418794705378ebb15dbadd993da420598e323b3 Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Tue, 27 Sep 2022 12:42:39 -0500 Subject: [PATCH 1/8] Add a method to show mpl plots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This means users don’t have to also import matplotlib.pyplot as plt only to run plt.show() --- floris/tools/visualization.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/floris/tools/visualization.py b/floris/tools/visualization.py index 95cf85098..5a04aeec7 100644 --- a/floris/tools/visualization.py +++ b/floris/tools/visualization.py @@ -23,6 +23,9 @@ from matplotlib import rcParams +def show_plots(): + plt.show() + def plot_turbines(ax, layout_x, layout_y, yaw_angles, rotor_diameters, color=None, wind_direction=270.0): """ Plot wind plant layout from turbine locations. From 4cb4d96b9a41fa747c26075a259d0ecb050f0346 Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Tue, 27 Sep 2022 12:44:07 -0500 Subject: [PATCH 2/8] API improvements --- floris/tools/floris_interface.py | 8 +++----- floris/tools/visualization.py | 35 ++++++++++++++++++-------------- floris/tools/wind_rose.py | 6 +++--- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/floris/tools/floris_interface.py b/floris/tools/floris_interface.py index ae6d7bf97..98b438e32 100644 --- a/floris/tools/floris_interface.py +++ b/floris/tools/floris_interface.py @@ -601,11 +601,9 @@ def get_turbine_ais(self) -> NDArrayFloat: ) return turbine_ais - def get_turbine_average_velocities(self) -> NDArrayFloat: - turbine_avg_vels = average_velocity( - velocities=self.floris.flow_field.u, - ) - return turbine_avg_vels + @property + def turbine_average_velocities(self) -> NDArrayFloat: + return average_velocity(velocities=self.floris.flow_field.u) def get_turbine_TIs(self) -> NDArrayFloat: return self.floris.flow_field.turbulence_intensity_field diff --git a/floris/tools/visualization.py b/floris/tools/visualization.py index 5a04aeec7..621b98496 100644 --- a/floris/tools/visualization.py +++ b/floris/tools/visualization.py @@ -54,23 +54,26 @@ def plot_turbines(ax, layout_x, layout_y, yaw_angles, rotor_diameters, color=Non ax.plot([x_0, x_1], [y_0, y_1], color=color) -def plot_turbines_with_fi(ax, fi, color=None): +def plot_turbines_with_fi(fi, ax=None, color=None, yaw_angles=None): """ Wrapper function to plot turbines which extracts the data from a FLORIS interface object Args: - ax (:py:class:`matplotlib.pyplot.axes`): figure axes. Defaults - to None. fi (:py:class:`floris.tools.flow_data.FlowData`): FlowData object. + ax (:py:class:`matplotlib.pyplot.axes`): figure axes. color (str, optional): Color to plot turbines """ + if not ax: + fig, ax = plt.subplots() + if yaw_angles is None: + yaw_angles = fi.floris.farm.yaw_angles plot_turbines( ax, fi.layout_x, fi.layout_y, - fi.floris.farm.yaw_angles[0, 0], + yaw_angles[0, 0], fi.floris.farm.rotor_diameters[0, 0], color=color, wind_direction=fi.floris.flow_field.wind_directions[0], @@ -103,8 +106,10 @@ def line_contour_cut_plane(cut_plane, ax=None, levels=None, colors=None, **kwarg Zm = np.ma.masked_where(np.isnan(u_mesh), u_mesh) rcParams["contour.negative_linestyle"] = "solid" - # # Plot the cut-through - ax.contour(x1_mesh, x2_mesh, Zm, levels=levels, colors=colors, **kwargs) + # Plot the cut-through + contours = ax.contour(x1_mesh, x2_mesh, Zm, levels=levels, colors=colors, **kwargs) + + ax.clabel(contours, contours.levels, inline=True, fontsize=10, colors="black") # Make equal axis ax.set_aspect("equal") @@ -119,7 +124,8 @@ def visualize_cut_plane( cmap="coolwarm", levels=None, color_bar=False, - title="" + title="", + kwargs={} ): """ Generate pseudocolor mesh plot of the cut_plane. @@ -170,7 +176,7 @@ def visualize_cut_plane( im = ax.pcolormesh(x1_mesh, x2_mesh, Zm, cmap=cmap, vmin=minSpeed, vmax=maxSpeed, shading="nearest") # Add line contour - line_contour_cut_plane(cut_plane, ax=ax, levels=levels, colors="w", linewidths=0.8, alpha=0.3) + line_contour_cut_plane(cut_plane, ax=ax, levels=levels, colors="b", linewidths=0.8, alpha=0.3, **kwargs) if cut_plane.normal_vector == "x": ax.invert_xaxis() @@ -267,6 +273,7 @@ def plot_rotor_values( return_fig_objects (bool): Indicator to return the primary figure objects for further editing, default False. save_path (str | None): Where to save the figure, if a value is provided. + t_range is turbine range; i.e. the turbine index to loop over Returns: None | tuple[plt.figure, plt.axes, plt.axis, plt.colorbar]: If @@ -275,9 +282,9 @@ def plot_rotor_values( Example: from floris.tools.visualization import plot_rotor_values - plot_rotor_values(floris.flow_field.u, wd_range=range(0,1), ws_range=range(0,1)) - plot_rotor_values(floris.flow_field.v, wd_range=range(0,1), ws_range=range(0,1)) - plot_rotor_values(floris.flow_field.w, wd_range=range(0,1), ws_range=range(0,1), show=True) + plot_rotor_values(floris.flow_field.u, wd_index=0, ws_index=0) + plot_rotor_values(floris.flow_field.v, wd_index=0, ws_index=0) + plot_rotor_values(floris.flow_field.w, wd_index=0, ws_index=0, show=True) """ cmap = plt.cm.get_cmap(name=cmap) @@ -288,15 +295,13 @@ def plot_rotor_values( fig = plt.figure() axes = fig.subplots(n_rows, n_cols) - indices = t_range - titles = np.array([f"T{i}" for i in indices]) + titles = np.array([f"T{i}" for i in t_range]) - for ax, t, i in zip(axes.flatten(), titles, indices): + for ax, t, i in zip(axes.flatten(), titles, t_range): vmin = np.min(values[wd_index, ws_index]) vmax = np.max(values[wd_index, ws_index]) - bounds = np.linspace(vmin, vmax, 31) norm = mplcolors.Normalize(vmin, vmax) ax.imshow(values[wd_index, ws_index, i].T, cmap=cmap, norm=norm, origin="lower") diff --git a/floris/tools/wind_rose.py b/floris/tools/wind_rose.py index e1e1ebe37..8809079da 100644 --- a/floris/tools/wind_rose.py +++ b/floris/tools/wind_rose.py @@ -1384,12 +1384,12 @@ def plot_wind_rose( # Set up figure if ax is None: - _, ax = plt.subplots(subplot_kw=dict(polar=True)) + _, ax = plt.subplots(subplot_kw={"polar": True}) # Get a color array color_array = cm.get_cmap(color_map, len(ws_right_edges)) - for wd_idx, wd in enumerate(wd_bins): + for wd in wd_bins: rects = list() df_plot_sub = df_plot[df_plot.wd == wd] for ws_idx, ws in enumerate(ws_right_edges[::-1]): @@ -1464,7 +1464,7 @@ def plot_wind_rose_ti( # Get a color array color_array = cm.get_cmap(color_map, len(ti_right_edges)) - for wd_idx, wd in enumerate(wd_bins): + for wd in wd_bins: rects = list() df_plot_sub = df_plot[df_plot.wd == wd] for ti_idx, ti in enumerate(ti_right_edges[::-1]): From d41226b28a195ae9baffdeed17e45039de589e3f Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Tue, 27 Sep 2022 12:45:17 -0500 Subject: [PATCH 3/8] Add a subtract overload to CutPlane Also replaces another method CutPlane.subtract to do something similar. That one made assumptions about the order and expected operations. The new subtract method expects all preprocessing to be down outside of the class. --- floris/tools/cut_plane.py | 59 +++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/floris/tools/cut_plane.py b/floris/tools/cut_plane.py index 633066630..16590c38f 100644 --- a/floris/tools/cut_plane.py +++ b/floris/tools/cut_plane.py @@ -113,9 +113,33 @@ def __init__(self, df, x1_resolution, x2_resolution, normal_vector): df (pandas.DataFrame): Pandas DataFrame of data with columns x1, x2, u, v, w. """ - self.df = df - self.normal_vector = normal_vector + self.df: pd.DataFrame = df + self.normal_vector: str = normal_vector self.resolution = (x1_resolution, x2_resolution) + self.df.set_index(["x1", "x2"]) + + def __sub__(self, other): + + if self.normal_vector != other.normal_vector: + raise ValueError("Operands must have consistent normal vectors.") + + # if self.normal_vector.df. + # DF must be of the same size + # resolution must be of the same size + + df: pd.DataFrame = self.df.copy() + other_df: pd.DataFrame = other.df.copy() + + df['u'] = self.df['u'] - other_df['u'] + df['v'] = self.df['v'] - other_df['v'] + df['w'] = self.df['w'] - other_df['w'] + + return CutPlane( + df, + self.resolution[0], + self.resolution[1], + self.normal_vector + ) # Modification functions @@ -323,37 +347,6 @@ def project_onto(cut_plane_a, cut_plane_b): ) -def subtract(cut_plane_a_in, cut_plane_b_in): - """ - Subtract u,v,w terms of cut_plane_b from cut_plane_a - - Args: - cut_plane_a_in (:py:class:`~.tools.cut_plane.CutPlane`): - Plane of data to subtract from. - cut_plane_b_in (:py:class:`~.tools.cut_plane.CutPlane`): - Plane of data to subtract b. - - Returns: - cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): - Difference of cut_plane_a_in minus cut_plane_b_in. - """ - - # First make copies of original - cut_plane_a = copy.deepcopy(cut_plane_a_in) - cut_plane_b = copy.deepcopy(cut_plane_b_in) - - # Sort x1 and x2 and make the index - cut_plane_a.df = cut_plane_a.df.set_index(["x1", "x2"]) - cut_plane_b.df = cut_plane_b.df.set_index(["x1", "x2"]) - - # Do subtraction - cut_plane_a.df = cut_plane_a.df.subtract( - cut_plane_b.df - ).reset_index() # .sort_values(['x2','x1'])# .dropna() - # cut_plane_a.df = cut_plane_a.df.sort_values(['x1','x2']) - return cut_plane_a - - def calculate_wind_speed(cross_plane, x1_loc, x2_loc, R): """ Calculate effective wind speed within specified range of a point. From 96b2327b330dd558cb624b21f1958e01155f74dc Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Tue, 18 Oct 2022 12:55:26 -0500 Subject: [PATCH 4/8] Fix incorrect yaw angles in fi.calculate_wake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In subsequent function calls, the yaw angles are not reset when they are all zeros. This means that if initially the yaw angles are non-zero and then they’re all-zero, the non-zero yaw settings are retained. --- floris/tools/floris_interface.py | 17 ++-- tests/floris_interface_test.py | 130 ++++--------------------------- 2 files changed, 24 insertions(+), 123 deletions(-) diff --git a/floris/tools/floris_interface.py b/floris/tools/floris_interface.py index 98b438e32..02b84c010 100644 --- a/floris/tools/floris_interface.py +++ b/floris/tools/floris_interface.py @@ -117,15 +117,16 @@ def calculate_wake( track_n_upstream_wakes (bool, optional): When *True*, will keep track of the number of upstream wakes a turbine is experiencing. Defaults to *False*. """ - # self.floris.flow_field.calculate_wake( - # no_wake=no_wake, - # points=points, - # track_n_upstream_wakes=track_n_upstream_wakes, - # ) - # TODO decide where to handle this sign issue - if (yaw_angles is not None) and not (np.all(yaw_angles==0.)): - self.floris.farm.yaw_angles = yaw_angles + if yaw_angles is None: + yaw_angles = np.zeros( + ( + self.floris.flow_field.n_wind_directions, + self.floris.flow_field.n_wind_speeds, + self.floris.farm.n_turbines + ) + ) + self.floris.farm.yaw_angles = yaw_angles # Initialize solution space self.floris.initialize_domain() diff --git a/tests/floris_interface_test.py b/tests/floris_interface_test.py index ae28b2ac7..54e5b05c5 100644 --- a/tests/floris_interface_test.py +++ b/tests/floris_interface_test.py @@ -1,3 +1,5 @@ + +import numpy as np from pathlib import Path from floris.tools.floris_interface import FlorisInterface @@ -14,124 +16,22 @@ def test_read_yaml(): def test_calculate_wake(): - pass - - -def test_reinitialize_flow_field(): - pass - - -def test_get_plane_of_points(): - pass - - -def test_get_set_of_points(): - pass - - -def test_get_hor_plane(): - pass - - -def test_get_cross_plane(): - pass - - -def test_get_y_plane(): - pass - - -def test_get_flow_data(): - pass - - -def test_get_farm_power(): - pass - - -def test_get_turbine_layout(): - pass - - -def test_get_power_curve(): - pass - - -def test_get_turbine_ti(): - pass - - -def test_get_farm_power_for_yaw_angle(): - pass - - -def test_get_farm_AEP(): - pass - - -def test_calc_one_AEP_case(): - pass - - -def test_get_farm_AEP_parallel(): - pass - - -def test_calc_AEP_wind_limit(): - pass - - -def test_calc_change_turbine(): - pass - - -def test_set_use_points_on_perimeter(): - pass - - -def test_set_gch(): - pass - - -def test_set_gch_yaw_added_recovery(): - pass - - -def test_set_gch_secondary_steering(): - pass - - -def test_layout_x(): # TODO - pass - - -def test_layout_y(): # TODO - pass - - -def test_TKE_to_TI(): - pass - - -def test_set_rotor_diameter(): # TODO - pass - - -def test_show_model_parameters(): # TODO - pass - - -def test_get_model_parameters(): # TODO - pass + """ + In FLORIS v3.2, running calculate_wake twice incorrectly set the yaw angles when the first time + has non-zero yaw settings but the second run had all-zero yaw settings. The test below asserts + that the yaw angles are correctly set in subsequent calls to calculate_wake. + """ + fi = FlorisInterface(configuration=YAML_INPUT) + yaw_angles = 20 * np.ones((fi.floris.flow_field.n_wind_directions, fi.floris.flow_field.n_wind_speeds, fi.floris.farm.n_turbines)) + fi.calculate_wake(yaw_angles=yaw_angles) + assert fi.floris.farm.yaw_angles == yaw_angles -def test_set_model_parameters(): # TODO - pass + yaw_angles = np.zeros((fi.floris.flow_field.n_wind_directions, fi.floris.flow_field.n_wind_speeds, fi.floris.farm.n_turbines)) + fi.calculate_wake(yaw_angles=yaw_angles) + assert fi.floris.farm.yaw_angles == yaw_angles -def test_vis_layout(): +def test_reinitialize(): pass - -def test_show_flow_field(): - pass From 8e0f52658754c192a93b44a4e83cd46fad8f3f41 Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Tue, 18 Oct 2022 13:25:16 -0500 Subject: [PATCH 5/8] Update API in examples And clean up examples imports --- examples/01_opening_floris_computing_power.py | 1 - examples/02_visualizations.py | 15 +++++++-------- examples/04_sweep_wind_directions.py | 3 +-- examples/05_sweep_wind_speeds.py | 1 - examples/06_sweep_wind_conditions.py | 1 - examples/08_calc_aep_from_rose_use_class.py | 8 ++------ examples/09_compare_farm_power_with_neighbor.py | 1 - examples/17_multiple_turbine_types.py | 11 +++++------ examples/18_check_turbine.py | 5 ----- examples/19_streamlit_demo.py | 1 - examples/22_get_wind_speed_at_turbines.py | 12 ++++-------- 11 files changed, 19 insertions(+), 40 deletions(-) diff --git a/examples/01_opening_floris_computing_power.py b/examples/01_opening_floris_computing_power.py index f7c0871b8..2de55ba0b 100644 --- a/examples/01_opening_floris_computing_power.py +++ b/examples/01_opening_floris_computing_power.py @@ -13,7 +13,6 @@ # See https://floris.readthedocs.io for documentation -import matplotlib.pyplot as plt import numpy as np from floris.tools import FlorisInterface diff --git a/examples/02_visualizations.py b/examples/02_visualizations.py index 3998e0bf9..38eed269f 100644 --- a/examples/02_visualizations.py +++ b/examples/02_visualizations.py @@ -16,8 +16,7 @@ import matplotlib.pyplot as plt from floris.tools import FlorisInterface -from floris.tools.visualization import visualize_cut_plane -from floris.tools.visualization import plot_rotor_values +import floris.tools.visualization as wakeviz """ This example initializes the FLORIS software, and then uses internal @@ -52,9 +51,9 @@ # Create the plots fig, ax_list = plt.subplots(3, 1, figsize=(10, 8)) ax_list = ax_list.flatten() -visualize_cut_plane(horizontal_plane, ax=ax_list[0], title="Horizontal") -visualize_cut_plane(y_plane, ax=ax_list[1], title="Streamwise profile") -visualize_cut_plane(cross_plane, ax=ax_list[2], title="Spanwise profile") +wakeviz.visualize_cut_plane(horizontal_plane, ax=ax_list[0], title="Horizontal") +wakeviz.visualize_cut_plane(y_plane, ax=ax_list[1], title="Streamwise profile") +wakeviz.visualize_cut_plane(cross_plane, ax=ax_list[2], title="Spanwise profile") # FLORIS further includes visualization methods for visualing the rotor plane of each # Turbine in the simulation @@ -64,7 +63,7 @@ fi.calculate_wake() # Plot the values at each rotor -fig, axes, _ , _ = plot_rotor_values(fi.floris.flow_field.u, wd_index=0, ws_index=0, n_rows=1, n_cols=3, return_fig_objects=True) +fig, axes, _ , _ = wakeviz.plot_rotor_values(fi.floris.flow_field.u, wd_index=0, ws_index=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 @@ -86,7 +85,7 @@ fi.calculate_wake() # Plot the values at each rotor -fig, axes, _ , _ = plot_rotor_values(fi.floris.flow_field.u, wd_index=0, ws_index=0, n_rows=1, n_cols=3, return_fig_objects=True) +fig, axes, _ , _ = wakeviz.plot_rotor_values(fi.floris.flow_field.u, wd_index=0, ws_index=0, n_rows=1, n_cols=3, return_fig_objects=True) fig.suptitle("Rotor Plane Visualization, 10x10 Resolution") -plt.show() +wakeviz.show_plots() diff --git a/examples/04_sweep_wind_directions.py b/examples/04_sweep_wind_directions.py index 338769c2b..cebdd5cd0 100644 --- a/examples/04_sweep_wind_directions.py +++ b/examples/04_sweep_wind_directions.py @@ -17,8 +17,6 @@ import numpy as np from floris.tools import FlorisInterface -from floris.tools.visualization import visualize_cut_plane - """ 04_sweep_wind_directions @@ -72,4 +70,5 @@ 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 index b23ef74b6..d116cdc69 100644 --- a/examples/05_sweep_wind_speeds.py +++ b/examples/05_sweep_wind_speeds.py @@ -17,7 +17,6 @@ import numpy as np from floris.tools import FlorisInterface -from floris.tools.visualization import visualize_cut_plane """ 05_sweep_wind_speeds diff --git a/examples/06_sweep_wind_conditions.py b/examples/06_sweep_wind_conditions.py index ab2db3ba7..e83a80c24 100644 --- a/examples/06_sweep_wind_conditions.py +++ b/examples/06_sweep_wind_conditions.py @@ -13,7 +13,6 @@ # See https://floris.readthedocs.io for documentation -import enum import matplotlib.pyplot as plt import numpy as np diff --git a/examples/08_calc_aep_from_rose_use_class.py b/examples/08_calc_aep_from_rose_use_class.py index 358fbc19e..360035afb 100644 --- a/examples/08_calc_aep_from_rose_use_class.py +++ b/examples/08_calc_aep_from_rose_use_class.py @@ -13,11 +13,8 @@ # See https://floris.readthedocs.io for documentation -import numpy as np -import pandas as pd -import matplotlib.pyplot as plt -from scipy.interpolate import NearestNDInterpolator from floris.tools import FlorisInterface, WindRose, wind_rose +import floris.tools.visualization as wakeviz """ This example demonstrates how to calculate the Annual Energy Production (AEP) @@ -70,5 +67,4 @@ aep_no_wake = fi.get_farm_AEP_wind_rose_class(wind_rose=wind_rose, no_wake=True) print("Farm AEP (no_wake=True): {:.3f} GWh".format(aep_no_wake / 1.0e9)) - -plt.show() \ No newline at end of file +wakeviz.show_plots() diff --git a/examples/09_compare_farm_power_with_neighbor.py b/examples/09_compare_farm_power_with_neighbor.py index 9dc0f845b..3c2c570a9 100644 --- a/examples/09_compare_farm_power_with_neighbor.py +++ b/examples/09_compare_farm_power_with_neighbor.py @@ -14,7 +14,6 @@ import numpy as np -import pandas as pd from floris.tools import FlorisInterface import matplotlib.pyplot as plt diff --git a/examples/17_multiple_turbine_types.py b/examples/17_multiple_turbine_types.py index feb23bf7c..5e6654794 100644 --- a/examples/17_multiple_turbine_types.py +++ b/examples/17_multiple_turbine_types.py @@ -14,10 +14,9 @@ import matplotlib.pyplot as plt -import numpy as np from floris.tools import FlorisInterface -from floris.tools.visualization import visualize_cut_plane +import floris.tools.visualization as wakeviz """ This example uses an input file where multiple turbine types are defined. @@ -38,8 +37,8 @@ # Create the plots fig, ax_list = plt.subplots(3, 1, figsize=(10, 8)) ax_list = ax_list.flatten() -visualize_cut_plane(horizontal_plane, ax=ax_list[0], title="Horizontal") -visualize_cut_plane(y_plane, ax=ax_list[1], title="Streamwise profile") -visualize_cut_plane(cross_plane, ax=ax_list[2], title="Spanwise profile") +wakeviz.visualize_cut_plane(horizontal_plane, ax=ax_list[0], title="Horizontal") +wakeviz.visualize_cut_plane(y_plane, ax=ax_list[1], title="Streamwise profile") +wakeviz.visualize_cut_plane(cross_plane, ax=ax_list[2], title="Spanwise profile") -plt.show() +wakeviz.show_plots() diff --git a/examples/18_check_turbine.py b/examples/18_check_turbine.py index aa135a757..f0d479f8e 100644 --- a/examples/18_check_turbine.py +++ b/examples/18_check_turbine.py @@ -18,7 +18,6 @@ import os from floris.tools import FlorisInterface -from floris.tools import visualize_cut_plane #, plot_turbines_with_fi """ For each turbine in the turbine library, make a small figure showing that its power curve and power loss to yaw are reasonable and @@ -109,8 +108,4 @@ # Give a suptitle fig.suptitle(t) - - plt.show() - - diff --git a/examples/19_streamlit_demo.py b/examples/19_streamlit_demo.py index 2fb5d5f0b..806086f94 100644 --- a/examples/19_streamlit_demo.py +++ b/examples/19_streamlit_demo.py @@ -16,7 +16,6 @@ import matplotlib.pyplot as plt import streamlit as st import numpy as np -import pandas as pd # import seaborn as sns from floris.tools import FlorisInterface diff --git a/examples/22_get_wind_speed_at_turbines.py b/examples/22_get_wind_speed_at_turbines.py index b9f68f0ee..705146cee 100644 --- a/examples/22_get_wind_speed_at_turbines.py +++ b/examples/22_get_wind_speed_at_turbines.py @@ -13,7 +13,6 @@ # See https://floris.readthedocs.io for documentation -import matplotlib.pyplot as plt import numpy as np from floris.tools import FlorisInterface @@ -35,13 +34,10 @@ print('U points is 1 wd x 1 ws x 4 turbines x 3 x 3 points (turbine_grid_points=3)') print(u_points.shape) -# Collect the average wind speeds from each turbine -avg_vel = fi.get_turbine_average_velocities() - -print('Avg vel is 1 wd x 1 ws x 4 turbines') -print(avg_vel.shape) +print('turbine_average_velocities is 1 wd x 1 ws x 4 turbines') +print(fi.turbine_average_velocities) # Show that one is equivalent to the other following averaging -print('Avg Vel is determined by taking the cube root of mean of the cubed value across the points') -print('Average velocity: ', avg_vel) +print('turbine_average_velocities is determined by taking the cube root of mean of the cubed value across the points') +print('turbine_average_velocities: ', fi.turbine_average_velocities) print('Recomputed: ', np.cbrt(np.mean(u_points**3, axis=(3,4)))) From bb6a132eef8a8c213779b604222eb29fb294381d Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Tue, 18 Oct 2022 13:43:42 -0500 Subject: [PATCH 6/8] Use standard conventions in argument case minSpeed, maxSpeed => min_speed, max_speed --- examples/03_making_adjustments.py | 12 +++++----- floris/tools/visualization.py | 40 +++++++++++++++---------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/examples/03_making_adjustments.py b/examples/03_making_adjustments.py index 42c8b6f3b..5a3d6cf69 100644 --- a/examples/03_making_adjustments.py +++ b/examples/03_making_adjustments.py @@ -38,18 +38,18 @@ # Plot a horizatonal slice of the initial configuration horizontal_plane = fi.calculate_horizontal_plane(height=90.0) -visualize_cut_plane(horizontal_plane, ax=axarr[0], title="Initial setup", minSpeed=MIN_WS, maxSpeed=MAX_WS) +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 = fi.calculate_horizontal_plane(ws=[7.0], height=90.0) -visualize_cut_plane(horizontal_plane, ax=axarr[1], title="Wind speed at 7 m/s", minSpeed=MIN_WS, maxSpeed=MAX_WS) +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 fi.reinitialize( wind_shear=0.2, wind_speeds=[8.0] ) y_plane = fi.calculate_y_plane(crossstream_dist=0.0) -visualize_cut_plane(y_plane, ax=axarr[2], title="Wind shear at 0.2", minSpeed=MIN_WS, maxSpeed=MAX_WS) +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 @@ -60,7 +60,7 @@ ) fi.reinitialize(layout_x=X.flatten(), layout_y=Y.flatten()) horizontal_plane = fi.calculate_horizontal_plane(height=90.0) -visualize_cut_plane(horizontal_plane, ax=axarr[3], title="3x3 Farm", minSpeed=MIN_WS, maxSpeed=MAX_WS) +visualize_cut_plane(horizontal_plane, ax=axarr[3], title="3x3 Farm", min_speed=MIN_WS, max_speed=MAX_WS) # Change the yaw angles and configure the plot differently @@ -77,13 +77,13 @@ yaw_angles[:,:,5] = -30.0 horizontal_plane = fi.calculate_horizontal_plane(yaw_angles=yaw_angles, height=90.0) -visualize_cut_plane(horizontal_plane, ax=axarr[4], title="Yawesome art", cmap="PuOr", minSpeed=MIN_WS, maxSpeed=MAX_WS) +visualize_cut_plane(horizontal_plane, ax=axarr[4], title="Yawesome art", cmap="PuOr", min_speed=MIN_WS, max_speed=MAX_WS) # plot_turbines_with_fi(axarr[8], fi) # Plot the cross-plane of the 3x3 configuration cross_plane = fi.calculate_cross_plane(yaw_angles=yaw_angles, downstream_dist=610.0) -visualize_cut_plane(cross_plane, ax=axarr[5], title="Cross section at 610 m", minSpeed=MIN_WS, maxSpeed=MAX_WS) +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() diff --git a/floris/tools/visualization.py b/floris/tools/visualization.py index 621b98496..bf57e93a0 100644 --- a/floris/tools/visualization.py +++ b/floris/tools/visualization.py @@ -119,8 +119,8 @@ def visualize_cut_plane( cut_plane, ax=None, vel_component='u', - minSpeed=None, - maxSpeed=None, + min_speed=None, + max_speed=None, cmap="coolwarm", levels=None, color_bar=False, @@ -135,9 +135,9 @@ def visualize_cut_plane( plane through wind plant. ax (:py:class:`matplotlib.pyplot.axes`): Figure axes. Defaults to None. - minSpeed (float, optional): Minimum value of wind speed for + min_speed (float, optional): Minimum value of wind speed for contours. Defaults to None. - maxSpeed (float, optional): Maximum value of wind speed for + max_speed (float, optional): Maximum value of wind speed for contours. Defaults to None. cmap (str, optional): Colormap specifier. Defaults to 'coolwarm'. @@ -150,22 +150,22 @@ def visualize_cut_plane( fig, ax = plt.subplots() if vel_component=='u': vel_mesh = cut_plane.df.u.values.reshape(cut_plane.resolution[1], cut_plane.resolution[0]) - if minSpeed is None: - minSpeed = cut_plane.df.u.min() - if maxSpeed is None: - maxSpeed = cut_plane.df.u.max() + if min_speed is None: + min_speed = cut_plane.df.u.min() + if max_speed is None: + max_speed = cut_plane.df.u.max() elif vel_component=='v': vel_mesh = cut_plane.df.v.values.reshape(cut_plane.resolution[1], cut_plane.resolution[0]) - if minSpeed is None: - minSpeed = cut_plane.df.v.min() - if maxSpeed is None: - maxSpeed = cut_plane.df.v.max() + if min_speed is None: + min_speed = cut_plane.df.v.min() + if max_speed is None: + max_speed = cut_plane.df.v.max() elif vel_component=='w': vel_mesh = cut_plane.df.w.values.reshape(cut_plane.resolution[1], cut_plane.resolution[0]) - if minSpeed is None: - minSpeed = cut_plane.df.w.min() - if maxSpeed is None: - maxSpeed = cut_plane.df.w.max() + if min_speed is None: + min_speed = cut_plane.df.w.min() + if max_speed is None: + max_speed = cut_plane.df.w.max() # Reshape to 2d for plotting x1_mesh = cut_plane.df.x1.values.reshape(cut_plane.resolution[1], cut_plane.resolution[0]) @@ -173,7 +173,7 @@ def visualize_cut_plane( Zm = np.ma.masked_where(np.isnan(vel_mesh), vel_mesh) # Plot the cut-through - im = ax.pcolormesh(x1_mesh, x2_mesh, Zm, cmap=cmap, vmin=minSpeed, vmax=maxSpeed, shading="nearest") + im = ax.pcolormesh(x1_mesh, x2_mesh, Zm, cmap=cmap, vmin=min_speed, vmax=max_speed, shading="nearest") # Add line contour line_contour_cut_plane(cut_plane, ax=ax, levels=levels, colors="b", linewidths=0.8, alpha=0.3, **kwargs) @@ -194,7 +194,7 @@ def visualize_cut_plane( return im -def visualize_quiver(cut_plane, ax=None, minSpeed=None, maxSpeed=None, downSamp=1, **kwargs): +def visualize_quiver(cut_plane, ax=None, min_speed=None, max_speed=None, downSamp=1, **kwargs): """ Visualize the in-plane flows in a cut_plane using quiver. @@ -203,9 +203,9 @@ def visualize_quiver(cut_plane, ax=None, minSpeed=None, maxSpeed=None, downSamp= plane through wind plant. ax (:py:class:`matplotlib.pyplot.axes`): Figure axes. Defaults to None. - minSpeed (float, optional): Minimum value of wind speed for + min_speed (float, optional): Minimum value of wind speed for contours. Defaults to None. - maxSpeed (float, optional): Maximum value of wind speed for + max_speed (float, optional): Maximum value of wind speed for contours. Defaults to None. downSamp (int, optional): Down sample the number of quiver arrows from underlying grid. From a73eb46f1cf8abfebfc3918a7522170b0c9df699 Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Mon, 14 Nov 2022 13:56:53 -0600 Subject: [PATCH 7/8] Add function to label turbines on CutPlane plots --- examples/03_making_adjustments.py | 30 +++++++++++++++--------------- floris/tools/visualization.py | 21 ++++++++++++++++++--- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/examples/03_making_adjustments.py b/examples/03_making_adjustments.py index 5a3d6cf69..54941ede6 100644 --- a/examples/03_making_adjustments.py +++ b/examples/03_making_adjustments.py @@ -17,7 +17,7 @@ import numpy as np from floris.tools import FlorisInterface -from floris.tools import visualize_cut_plane #, plot_turbines_with_fi +import floris.tools.visualization as wakeviz """ This example makes changes to the given input file through the script. @@ -38,21 +38,20 @@ # Plot a horizatonal slice of the initial configuration horizontal_plane = fi.calculate_horizontal_plane(height=90.0) -visualize_cut_plane(horizontal_plane, ax=axarr[0], title="Initial setup", min_speed=MIN_WS, max_speed=MAX_WS) - +wakeviz.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 = fi.calculate_horizontal_plane(ws=[7.0], height=90.0) -visualize_cut_plane(horizontal_plane, ax=axarr[1], title="Wind speed at 7 m/s", min_speed=MIN_WS, max_speed=MAX_WS) +wakeviz.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 fi.reinitialize( wind_shear=0.2, wind_speeds=[8.0] ) y_plane = fi.calculate_y_plane(crossstream_dist=0.0) -visualize_cut_plane(y_plane, ax=axarr[2], title="Wind shear at 0.2", min_speed=MIN_WS, max_speed=MAX_WS) +wakeviz.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 +# # Change the farm layout N = 3 # Number of turbines per row and per column X, Y = np.meshgrid( 5.0 * fi.floris.farm.rotor_diameters[0][0][0] * np.arange(0, N, 1), @@ -60,7 +59,8 @@ ) fi.reinitialize(layout_x=X.flatten(), layout_y=Y.flatten()) horizontal_plane = fi.calculate_horizontal_plane(height=90.0) -visualize_cut_plane(horizontal_plane, ax=axarr[3], title="3x3 Farm", min_speed=MIN_WS, max_speed=MAX_WS) +wakeviz.visualize_cut_plane(horizontal_plane, ax=axarr[3], title="3x3 Farm", min_speed=MIN_WS, max_speed=MAX_WS) +wakeviz.add_turbine_id_labels(fi, axarr[3], color="w", backgroundcolor="k") # Change the yaw angles and configure the plot differently @@ -68,23 +68,23 @@ ## First row yaw_angles[:,:,0] = 30.0 -yaw_angles[:,:,1] = -30.0 -yaw_angles[:,:,2] = 30.0 +yaw_angles[:,:,3] = -30.0 +yaw_angles[:,:,6] = 30.0 ## Second row -yaw_angles[:,:,3] = -30.0 +yaw_angles[:,:,1] = -30.0 yaw_angles[:,:,4] = 30.0 -yaw_angles[:,:,5] = -30.0 +yaw_angles[:,:,7] = -30.0 horizontal_plane = fi.calculate_horizontal_plane(yaw_angles=yaw_angles, height=90.0) -visualize_cut_plane(horizontal_plane, ax=axarr[4], title="Yawesome art", cmap="PuOr", min_speed=MIN_WS, max_speed=MAX_WS) -# plot_turbines_with_fi(axarr[8], fi) +wakeviz.visualize_cut_plane(horizontal_plane, ax=axarr[4], title="Yawesome art", cmap="PuOr", min_speed=MIN_WS, max_speed=MAX_WS) +wakeviz.plot_turbines_with_fi(fi, axarr[4], yaw_angles=yaw_angles, color="c") # Plot the cross-plane of the 3x3 configuration cross_plane = fi.calculate_cross_plane(yaw_angles=yaw_angles, downstream_dist=610.0) -visualize_cut_plane(cross_plane, ax=axarr[5], title="Cross section at 610 m", min_speed=MIN_WS, max_speed=MAX_WS) +wakeviz.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() \ No newline at end of file +wakeviz.show_plots() diff --git a/floris/tools/visualization.py b/floris/tools/visualization.py index bf57e93a0..608fb5944 100644 --- a/floris/tools/visualization.py +++ b/floris/tools/visualization.py @@ -14,7 +14,6 @@ from __future__ import annotations from typing import Union -from itertools import product import numpy as np import matplotlib as mpl @@ -22,6 +21,7 @@ import matplotlib.pyplot as plt from matplotlib import rcParams +from floris.tools.floris_interface import FlorisInterface def show_plots(): plt.show() @@ -54,7 +54,7 @@ def plot_turbines(ax, layout_x, layout_y, yaw_angles, rotor_diameters, color=Non ax.plot([x_0, x_1], [y_0, y_1], color=color) -def plot_turbines_with_fi(fi, ax=None, color=None, yaw_angles=None): +def plot_turbines_with_fi(fi: FlorisInterface, ax=None, color=None, yaw_angles=None): """ Wrapper function to plot turbines which extracts the data from a FLORIS interface object @@ -80,6 +80,21 @@ def plot_turbines_with_fi(fi, ax=None, color=None, yaw_angles=None): ) +def add_turbine_id_labels(fi: FlorisInterface, ax: plt.Axes, **kwargs): + """ + Adds index labels to a plot based on the given FlorisInterface. + See the pyplot.annotate docs for more info: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.annotate.html. + kwargs are passed to Text (https://matplotlib.org/stable/api/text_api.html#matplotlib.text.Text). + + Args: + fi (FlorisInterface): Simulation object to get the layout and index information. + ax (plt.Axes): Axes object to add the labels. + """ + + for i in range(fi.floris.farm.n_turbines): + ax.annotate(i, (fi.layout_x[i], fi.layout_y[i]), xytext=(0,10), textcoords="offset points", **kwargs) + + def line_contour_cut_plane(cut_plane, ax=None, levels=None, colors=None, **kwargs): """ Visualize a cut_plane as a line contour plot. @@ -125,7 +140,7 @@ def visualize_cut_plane( levels=None, color_bar=False, title="", - kwargs={} + **kwargs ): """ Generate pseudocolor mesh plot of the cut_plane. From 93cee3120b79bed77e01a9ef847116ffc68f54a9 Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Fri, 11 Nov 2022 16:42:39 -0600 Subject: [PATCH 8/8] Rotate turbine lines and ID annotations with wind --- examples/03_making_adjustments.py | 4 ++-- floris/tools/visualization.py | 26 +++++++++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/examples/03_making_adjustments.py b/examples/03_making_adjustments.py index 54941ede6..c93011fa5 100644 --- a/examples/03_making_adjustments.py +++ b/examples/03_making_adjustments.py @@ -57,11 +57,11 @@ 5.0 * fi.floris.farm.rotor_diameters[0][0][0] * np.arange(0, N, 1), 5.0 * fi.floris.farm.rotor_diameters[0][0][0] * np.arange(0, N, 1), ) -fi.reinitialize(layout_x=X.flatten(), layout_y=Y.flatten()) +fi.reinitialize(layout_x=X.flatten(), layout_y=Y.flatten(), wind_directions=[360.0]) horizontal_plane = fi.calculate_horizontal_plane(height=90.0) wakeviz.visualize_cut_plane(horizontal_plane, ax=axarr[3], title="3x3 Farm", min_speed=MIN_WS, max_speed=MAX_WS) wakeviz.add_turbine_id_labels(fi, axarr[3], color="w", backgroundcolor="k") - +wakeviz.plot_turbines_with_fi(fi, axarr[3]) # Change the yaw angles and configure the plot differently yaw_angles = np.zeros((1, 1, N * N)) diff --git a/floris/tools/visualization.py b/floris/tools/visualization.py index 608fb5944..ed47e2cf4 100644 --- a/floris/tools/visualization.py +++ b/floris/tools/visualization.py @@ -22,11 +22,20 @@ from matplotlib import rcParams from floris.tools.floris_interface import FlorisInterface +from floris.utilities import rotate_coordinates_rel_west def show_plots(): plt.show() -def plot_turbines(ax, layout_x, layout_y, yaw_angles, rotor_diameters, color=None, wind_direction=270.0): +def plot_turbines( + ax, + layout_x, + layout_y, + yaw_angles, + rotor_diameters, + color: str | None = None, + wind_direction: float = 270.0 +): """ Plot wind plant layout from turbine locations. @@ -39,13 +48,13 @@ def plot_turbines(ax, layout_x, layout_y, yaw_angles, rotor_diameters, color=Non color (str): Pyplot color option to plot the turbines. wind_direction (float): Wind direction (rotates farm) """ - - # Correct for the wind direction - yaw_angles = np.array(yaw_angles) # - wind_direction - 270 - if color is None: color = "k" - for x, y, yaw, d in zip(layout_x, layout_y, yaw_angles, rotor_diameters): + + coordinates_array = np.array([[x, y, 0.0] for x, y in list(zip(layout_x, layout_y))]) + layout_x, layout_y, _ = rotate_coordinates_rel_west(np.array([wind_direction]), coordinates_array) + + for x, y, yaw, d in zip(layout_x[0,0], layout_y[0,0], yaw_angles, rotor_diameters): R = d / 2.0 x_0 = x + np.sin(np.deg2rad(yaw)) * R x_1 = x - np.sin(np.deg2rad(yaw)) * R @@ -90,9 +99,12 @@ def add_turbine_id_labels(fi: FlorisInterface, ax: plt.Axes, **kwargs): fi (FlorisInterface): Simulation object to get the layout and index information. ax (plt.Axes): Axes object to add the labels. """ + coordinates_array = np.array([[x, y, 0.0] for x, y in list(zip(fi.layout_x, fi.layout_y))]) + wind_direction = fi.floris.flow_field.wind_directions[0] + layout_x, layout_y, _ = rotate_coordinates_rel_west(np.array([wind_direction]), coordinates_array) for i in range(fi.floris.farm.n_turbines): - ax.annotate(i, (fi.layout_x[i], fi.layout_y[i]), xytext=(0,10), textcoords="offset points", **kwargs) + ax.annotate(i, (layout_x[0,0,i], layout_y[0,0,i]), xytext=(0,10), textcoords="offset points", **kwargs) def line_contour_cut_plane(cut_plane, ax=None, levels=None, colors=None, **kwargs):