From 9ee9cf21b6868dcf23fca4a3fc0a60a2d8ba4a65 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 4 Apr 2024 20:01:17 -0600 Subject: [PATCH 01/10] remove yaw angles argument; switch to deepcopies. --- examples/02_visualizations.py | 6 +-- floris/floris_model.py | 97 +++++++++-------------------------- floris/flow_visualization.py | 40 +++++++-------- 3 files changed, 47 insertions(+), 96 deletions(-) diff --git a/examples/02_visualizations.py b/examples/02_visualizations.py index de526328f..5b399e90c 100644 --- a/examples/02_visualizations.py +++ b/examples/02_visualizations.py @@ -37,24 +37,22 @@ # to what existed previously at the end of the function # Using the FlorisModel functions, get 2D slices. +fmodel.set(yaw_angles=np.array([[25.,0.,0.]])) 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 @@ -85,7 +83,7 @@ fmodel, x_resolution=20, y_resolution=10, - yaw_angles=np.array([[25.,0.,0.]]), + #yaw_angles=np.array([[0.,0.,0.]]), ) fig, ax = plt.subplots() diff --git a/floris/floris_model.py b/floris/floris_model.py index d9a7ba7e3..acd7d0db8 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -1,6 +1,7 @@ from __future__ import annotations +import copy import inspect from pathlib import Path from typing import ( @@ -895,9 +896,6 @@ def calculate_cross_plane( wd=None, ws=None, ti=None, - yaw_angles=None, - power_setpoints=None, - disable_turbines=None, ): """ Shortcut method to instantiate a :py:class:`~.tools.cut_plane.CutPlane` @@ -905,16 +903,18 @@ def calculate_cross_plane( the simulation domain at a specific height. Args: - height (float): Height of cut plane. Defaults to Hub-height. - x_resolution (float, optional): Output array resolution. - Defaults to 200 points. + downstream_dist (float): Distance downstream of turbines to compute. y_resolution (float, optional): Output array resolution. Defaults to 200 points. - x_bounds (tuple, optional): Limits of output array (in m). - Defaults to None. + z_resolution (float, optional): Output array resolution. + Defaults to 200 points. y_bounds (tuple, optional): Limits of output array (in m). Defaults to None. - + z_bounds (tuple, optional): Limits of output array (in m). + Defaults to None. + wd (float, optional): Wind direction. Defaults to None. + ws (float, optional): Wind speed. Defaults to None. + ti (float, optional): Turbulence intensity. Defaults to None. Returns: :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w @@ -929,7 +929,7 @@ def calculate_cross_plane( self.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) # Store the current state for reinitialization - floris_dict = self.core.as_dict() + fmodel_viz = copy.deepcopy(self) # Set the solver to a flow field planar grid solver_settings = { @@ -939,23 +939,20 @@ def calculate_cross_plane( "flow_field_grid_points": [y_resolution, z_resolution], "flow_field_bounds": [y_bounds, z_bounds], } - self.set( + fmodel_viz.set( wind_directions=wd, wind_speeds=ws, turbulence_intensities=ti, solver_settings=solver_settings, - yaw_angles=yaw_angles, - power_setpoints=power_setpoints, - disable_turbines=disable_turbines, ) # Calculate wake - self.core.solve_for_viz() + fmodel_viz.core.solve_for_viz() # Get the points of data in a dataframe # TODO this just seems to be flattening and storing the data in a df; is this necessary? # It seems the biggest depenedcy is on CutPlane and the subsequent visualization tools. - df = self.get_plane_of_points( + df = fmodel_viz.get_plane_of_points( normal_vector="x", planar_coordinate=downstream_dist, ) @@ -963,12 +960,6 @@ def calculate_cross_plane( # Compute the cutplane cross_plane = CutPlane(df, y_resolution, z_resolution, "x") - # Reset the fmodel object back to the turbine grid configuration - self.core = Core.from_dict(floris_dict) - - # Run the simulation again for futher postprocessing (i.e. now we can get farm power) - self.run() - return cross_plane def calculate_horizontal_plane( @@ -981,9 +972,6 @@ def calculate_horizontal_plane( wd=None, ws=None, ti=None, - yaw_angles=None, - power_setpoints=None, - disable_turbines=None, ): """ Shortcut method to instantiate a :py:class:`~.tools.cut_plane.CutPlane` @@ -1003,12 +991,6 @@ def calculate_horizontal_plane( wd (float, optional): Wind direction. Defaults to None. ws (float, optional): Wind speed. Defaults to None. ti (float, optional): Turbulence intensity. Defaults to None. - yaw_angles (NDArrayFloat, optional): Turbine yaw angles. Defaults - to None. - power_setpoints (NDArrayFloat, optional): - Turbine power setpoints. Defaults to None. - disable_turbines (NDArrayBool, optional): Boolean array on whether - to disable turbines. Defaults to None. Returns: :py:class:`~.tools.cut_plane.CutPlane`: containing values @@ -1024,7 +1006,8 @@ def calculate_horizontal_plane( self.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) # Store the current state for reinitialization - floris_dict = self.core.as_dict() + fmodel_viz = copy.deepcopy(self) + # Set the solver to a flow field planar grid solver_settings = { "type": "flow_field_planar_grid", @@ -1033,23 +1016,20 @@ def calculate_horizontal_plane( "flow_field_grid_points": [x_resolution, y_resolution], "flow_field_bounds": [x_bounds, y_bounds], } - self.set( + fmodel_viz.set( wind_directions=wd, wind_speeds=ws, turbulence_intensities=ti, solver_settings=solver_settings, - yaw_angles=yaw_angles, - power_setpoints=power_setpoints, - disable_turbines=disable_turbines, ) # Calculate wake - self.core.solve_for_viz() + fmodel_viz.core.solve_for_viz() # Get the points of data in a dataframe # TODO this just seems to be flattening and storing the data in a df; is this necessary? # It seems the biggest depenedcy is on CutPlane and the subsequent visualization tools. - df = self.get_plane_of_points( + df = fmodel_viz.get_plane_of_points( normal_vector="z", planar_coordinate=height, ) @@ -1057,17 +1037,11 @@ def calculate_horizontal_plane( # Compute the cutplane horizontal_plane = CutPlane( df, - self.core.grid.grid_resolution[0], - self.core.grid.grid_resolution[1], + fmodel_viz.core.grid.grid_resolution[0], + fmodel_viz.core.grid.grid_resolution[1], "z", ) - # Reset the fmodel object back to the turbine grid configuration - self.core = Core.from_dict(floris_dict) - - # Run the simulation again for futher postprocessing (i.e. now we can get farm power) - self.run() - return horizontal_plane def calculate_y_plane( @@ -1080,9 +1054,6 @@ def calculate_y_plane( wd=None, ws=None, ti=None, - yaw_angles=None, - power_setpoints=None, - disable_turbines=None, ): """ Shortcut method to instantiate a :py:class:`~.tools.cut_plane.CutPlane` @@ -1093,24 +1064,15 @@ def calculate_y_plane( height (float): Height of cut plane. Defaults to Hub-height. x_resolution (float, optional): Output array resolution. Defaults to 200 points. - y_resolution (float, optional): Output array resolution. + z_resolution (float, optional): Output array resolution. Defaults to 200 points. x_bounds (tuple, optional): Limits of output array (in m). Defaults to None. - y_bounds (tuple, optional): Limits of output array (in m). - Defaults to None. z_bounds (tuple, optional): Limits of output array (in m). Defaults to None. wd (float, optional): Wind direction. Defaults to None. ws (float, optional): Wind speed. Defaults to None. ti (float, optional): Turbulence intensity. Defaults to None. - yaw_angles (NDArrayFloat, optional): Turbine yaw angles. Defaults - to None. - power_setpoints (NDArrayFloat, optional): - Turbine power setpoints. Defaults to None. - disable_turbines (NDArrayBool, optional): Boolean array on whether - to disable turbines. Defaults to None. - Returns: @@ -1127,7 +1089,7 @@ def calculate_y_plane( self.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) # Store the current state for reinitialization - floris_dict = self.core.as_dict() + fmodel_viz = copy.deepcopy(self) # Set the solver to a flow field planar grid solver_settings = { @@ -1137,23 +1099,20 @@ def calculate_y_plane( "flow_field_grid_points": [x_resolution, z_resolution], "flow_field_bounds": [x_bounds, z_bounds], } - self.set( + fmodel_viz.set( wind_directions=wd, wind_speeds=ws, turbulence_intensities=ti, solver_settings=solver_settings, - yaw_angles=yaw_angles, - power_setpoints=power_setpoints, - disable_turbines=disable_turbines, ) # Calculate wake - self.core.solve_for_viz() + fmodel_viz.core.solve_for_viz() # Get the points of data in a dataframe # TODO this just seems to be flattening and storing the data in a df; is this necessary? # It seems the biggest depenedcy is on CutPlane and the subsequent visualization tools. - df = self.get_plane_of_points( + df = fmodel_viz.get_plane_of_points( normal_vector="y", planar_coordinate=crossstream_dist, ) @@ -1161,12 +1120,6 @@ def calculate_y_plane( # Compute the cutplane y_plane = CutPlane(df, x_resolution, z_resolution, "y") - # Reset the fmodel object back to the turbine grid configuration - self.core = Core.from_dict(floris_dict) - - # Run the simulation again for futher postprocessing (i.e. now we can get farm power) - self.run() - return y_plane def check_wind_condition_for_viz(self, wd=None, ws=None, ti=None): diff --git a/floris/flow_visualization.py b/floris/flow_visualization.py index 3afaf1a38..82a2f7823 100644 --- a/floris/flow_visualization.py +++ b/floris/flow_visualization.py @@ -472,7 +472,7 @@ def plot_rotor_values( plt.show() def calculate_horizontal_plane_with_turbines( - fmodel_in, + fmodel, x_resolution=200, y_resolution=200, x_bounds=None, @@ -498,7 +498,7 @@ def calculate_horizontal_plane_with_turbines( for models where the visualization capability is not yet available. Args: - fmodel_in (:py:class:`floris.floris_model.FlorisModel`): + fmodel (:py:class:`floris.floris_model.FlorisModel`): Preinitialized FlorisModel object. x_resolution (float, optional): Output array resolution. Defaults to 200 points. y_resolution (float, optional): Output array resolution. Defaults to 200 points. @@ -516,33 +516,33 @@ def calculate_horizontal_plane_with_turbines( """ # Make a local copy of fmodel to avoid editing passed in fmodel - fmodel = copy.deepcopy(fmodel_in) + fmodel_viz = copy.deepcopy(fmodel) # If wd/ws not provided, use what is set in fmodel if wd is None: - wd = fmodel.core.flow_field.wind_directions + wd = fmodel_viz.core.flow_field.wind_directions if ws is None: - ws = fmodel.core.flow_field.wind_speeds + ws = fmodel_viz.core.flow_field.wind_speeds if ti is None: - ti = fmodel.core.flow_field.turbulence_intensities - fmodel.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) + ti = fmodel_viz.core.flow_field.turbulence_intensities + fmodel_viz.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) # Set the ws and wd - fmodel.set( + fmodel_viz.set( wind_directions=wd, wind_speeds=ws, yaw_angles=yaw_angles, power_setpoints=power_setpoints, disable_turbines=disable_turbines ) - yaw_angles = fmodel.core.farm.yaw_angles - power_setpoints = fmodel.core.farm.power_setpoints + yaw_angles = fmodel_viz.core.farm.yaw_angles + power_setpoints = fmodel_viz.core.farm.power_setpoints # Grab the turbine layout - layout_x = copy.deepcopy(fmodel.layout_x) - layout_y = copy.deepcopy(fmodel.layout_y) - turbine_types = copy.deepcopy(fmodel.core.farm.turbine_type) - D = fmodel.core.farm.rotor_diameters_sorted[0, 0] + layout_x = copy.deepcopy(fmodel_viz.layout_x) + layout_y = copy.deepcopy(fmodel_viz.layout_y) + turbine_types = copy.deepcopy(fmodel_viz.core.farm.turbine_type) + D = fmodel_viz.core.farm.rotor_diameters_sorted[0, 0] # Declare a new layout array with an extra turbine layout_x_test = np.append(layout_x,[0]) @@ -554,10 +554,10 @@ def calculate_horizontal_plane_with_turbines( turbine_types_test = [turbine_types[0] for i in range(len(layout_x))] + ['nrel_5MW'] else: turbine_types_test = np.append(turbine_types, 'nrel_5MW').tolist() - yaw_angles = np.append(yaw_angles, np.zeros([fmodel.core.flow_field.n_findex, 1]), axis=1) + yaw_angles = np.append(yaw_angles, np.zeros([fmodel_viz.core.flow_field.n_findex, 1]), axis=1) power_setpoints = np.append( power_setpoints, - POWER_SETPOINT_DEFAULT * np.ones([fmodel.core.flow_field.n_findex, 1]), + POWER_SETPOINT_DEFAULT * np.ones([fmodel_viz.core.flow_field.n_findex, 1]), axis=1 ) @@ -591,7 +591,7 @@ def calculate_horizontal_plane_with_turbines( # Place the test turbine at this location and calculate wake layout_x_test[-1] = x layout_y_test[-1] = y - fmodel.set( + fmodel_viz.set( layout_x=layout_x_test, layout_y=layout_y_test, yaw_angles=yaw_angles, @@ -599,11 +599,11 @@ def calculate_horizontal_plane_with_turbines( disable_turbines=disable_turbines, turbine_type=turbine_types_test ) - fmodel.run() + fmodel_viz.run() # Get the velocity of that test turbines central point - center_point = int(np.floor(fmodel.core.flow_field.u[0,-1].shape[0] / 2.0)) - u_results[idx] = fmodel.core.flow_field.u[0,-1,center_point,center_point] + center_point = int(np.floor(fmodel_viz.core.flow_field.u[0,-1].shape[0] / 2.0)) + u_results[idx] = fmodel_viz.core.flow_field.u[0,-1,center_point,center_point] # Increment index idx = idx + 1 From d6170012b43101390fea6eaaf1c522a0e34486b5 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 4 Apr 2024 20:28:49 -0600 Subject: [PATCH 02/10] No longer allowed to pass wd, ws, ti---instead, specify a findex (or set a single ws, wd, ti). --- floris/floris_model.py | 107 +++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 58 deletions(-) diff --git a/floris/floris_model.py b/floris/floris_model.py index acd7d0db8..5d0db9649 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -886,6 +886,25 @@ def get_turbine_TIs(self) -> NDArrayFloat: ### Methods for sampling and visualization + def set_for_viz(self, findex: int, solver_settings: dict) -> None: + """ + Set the floris object to a single findex for visualization. + + Args: + findex (int): The findex to set the floris object to. + solver_settings (dict): The solver settings to use for visualization. + """ + # TODO: can get from properties once #843 is merged + self.set( + wind_speeds=self.core.flow_field.wind_speeds[findex:findex+1], + wind_directions=self.core.flow_field.wind_directions[findex:findex+1], + turbulence_intensities=self.core.flow_field.turbulence_intensities[findex:findex+1], + yaw_angles=self.core.farm.yaw_angles[findex:findex+1,:], + power_setpoints=self.core.farm.power_setpoints[findex:findex+1,:], + # TODO: add Helix here. + solver_settings=solver_settings, + ) + def calculate_cross_plane( self, downstream_dist, @@ -893,9 +912,7 @@ def calculate_cross_plane( z_resolution=200, y_bounds=None, z_bounds=None, - wd=None, - ws=None, - ti=None, + findex_for_viz=None, ): """ Shortcut method to instantiate a :py:class:`~.tools.cut_plane.CutPlane` @@ -912,21 +929,18 @@ def calculate_cross_plane( Defaults to None. z_bounds (tuple, optional): Limits of output array (in m). Defaults to None. - wd (float, optional): Wind direction. Defaults to None. - ws (float, optional): Wind speed. Defaults to None. - ti (float, optional): Turbulence intensity. Defaults to None. + finder_for_viz (int, optional): Index of the condition to visualize. Returns: :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w """ # TODO update docstring - if wd is None: - wd = self.core.flow_field.wind_directions - if ws is None: - ws = self.core.flow_field.wind_speeds - if ti is None: - ti = self.core.flow_field.turbulence_intensities - self.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) + if self.core.flow_field.n_findex > 1 and findex_for_viz is None: + self.logger.warning( + "Multiple findices detected. Using first findex for visualization." + ) + if findex_for_viz is None: + findex_for_viz = 0 # Store the current state for reinitialization fmodel_viz = copy.deepcopy(self) @@ -939,12 +953,7 @@ def calculate_cross_plane( "flow_field_grid_points": [y_resolution, z_resolution], "flow_field_bounds": [y_bounds, z_bounds], } - fmodel_viz.set( - wind_directions=wd, - wind_speeds=ws, - turbulence_intensities=ti, - solver_settings=solver_settings, - ) + fmodel_viz.set_for_viz(findex_for_viz, solver_settings) # Calculate wake fmodel_viz.core.solve_for_viz() @@ -969,9 +978,7 @@ def calculate_horizontal_plane( y_resolution=200, x_bounds=None, y_bounds=None, - wd=None, - ws=None, - ti=None, + findex_for_viz=None, ): """ Shortcut method to instantiate a :py:class:`~.tools.cut_plane.CutPlane` @@ -988,22 +995,19 @@ def calculate_horizontal_plane( Defaults to None. y_bounds (tuple, optional): Limits of output array (in m). Defaults to None. - wd (float, optional): Wind direction. Defaults to None. - ws (float, optional): Wind speed. Defaults to None. - ti (float, optional): Turbulence intensity. Defaults to None. + finder_for_viz (int, optional): Index of the condition to visualize. Returns: :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w """ # TODO update docstring - if wd is None: - wd = self.core.flow_field.wind_directions - if ws is None: - ws = self.core.flow_field.wind_speeds - if ti is None: - ti = self.core.flow_field.turbulence_intensities - self.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) + if self.core.flow_field.n_findex > 1 and findex_for_viz is None: + self.logger.warning( + "Multiple findices detected. Using first findex for visualization." + ) + if findex_for_viz is None: + findex_for_viz = 0 # Store the current state for reinitialization fmodel_viz = copy.deepcopy(self) @@ -1016,12 +1020,7 @@ def calculate_horizontal_plane( "flow_field_grid_points": [x_resolution, y_resolution], "flow_field_bounds": [x_bounds, y_bounds], } - fmodel_viz.set( - wind_directions=wd, - wind_speeds=ws, - turbulence_intensities=ti, - solver_settings=solver_settings, - ) + fmodel_viz.set_for_viz(findex_for_viz, solver_settings) # Calculate wake fmodel_viz.core.solve_for_viz() @@ -1051,9 +1050,7 @@ def calculate_y_plane( z_resolution=200, x_bounds=None, z_bounds=None, - wd=None, - ws=None, - ti=None, + findex_for_viz=None, ): """ Shortcut method to instantiate a :py:class:`~.tools.cut_plane.CutPlane` @@ -1070,23 +1067,22 @@ def calculate_y_plane( Defaults to None. z_bounds (tuple, optional): Limits of output array (in m). Defaults to None. - wd (float, optional): Wind direction. Defaults to None. - ws (float, optional): Wind speed. Defaults to None. - ti (float, optional): Turbulence intensity. Defaults to None. - + findex_for_viz (int, optional): Index of the condition to visualize. + Defaults to 0. Returns: :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w """ # TODO update docstring - if wd is None: - wd = self.core.flow_field.wind_directions - if ws is None: - ws = self.core.flow_field.wind_speeds - if ti is None: - ti = self.core.flow_field.turbulence_intensities - self.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) + # TODO: can get from properties once #843 is merged. Do the same on + # other calculate_xx_plane methods + if self.core.flow_field.n_findex > 1 and findex_for_viz is None: + self.logger.warning( + "Multiple findices detected. Using first findex for visualization." + ) + if findex_for_viz is None: + findex_for_viz = 0 # Store the current state for reinitialization fmodel_viz = copy.deepcopy(self) @@ -1099,12 +1095,7 @@ def calculate_y_plane( "flow_field_grid_points": [x_resolution, z_resolution], "flow_field_bounds": [x_bounds, z_bounds], } - fmodel_viz.set( - wind_directions=wd, - wind_speeds=ws, - turbulence_intensities=ti, - solver_settings=solver_settings, - ) + fmodel_viz.set_for_viz(findex_for_viz, solver_settings) # Calculate wake fmodel_viz.core.solve_for_viz() From c9837df29971de674f803bf7bc3d3389058cf31b Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 4 Apr 2024 20:30:00 -0600 Subject: [PATCH 03/10] Remove erroneous type hints. --- floris/floris_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/floris/floris_model.py b/floris/floris_model.py index 5d0db9649..2ecfceea1 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -1113,7 +1113,7 @@ def calculate_y_plane( return y_plane - def check_wind_condition_for_viz(self, wd=None, ws=None, ti=None): + def check_wind_condition_for_viz(self, wd, ws, ti): if len(wd) > 1 or len(wd) < 1: raise ValueError( "Wind direction input must be of length 1 for visualization. " From fdf212d8abd0725c3d65ffde222f2719810506ab Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 4 Apr 2024 21:50:29 -0600 Subject: [PATCH 04/10] update tests. --- tests/floris_model_integration_test.py | 49 ++++++++++++-------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/tests/floris_model_integration_test.py b/tests/floris_model_integration_test.py index fb5871939..10c665543 100644 --- a/tests/floris_model_integration_test.py +++ b/tests/floris_model_integration_test.py @@ -475,7 +475,7 @@ def test_set_ti(): with pytest.raises(TypeError): fmodel.set(turbulence_intensities=0.12) -def test_calculate_planes(): +def test_calculate_planes(caplog): fmodel = FlorisModel(configuration=YAML_INPUT) # The calculate_plane functions should run directly with the inputs as given @@ -483,42 +483,37 @@ def test_calculate_planes(): fmodel.calculate_y_plane(0.0) fmodel.calculate_cross_plane(500.0) - # They should also support setting new wind conditions, but they all have to set at once - wind_speeds = [8.0, 8.0, 8.0] - wind_directions = [270.0, 270.0, 270.0] - turbulence_intensities = [0.1, 0.1, 0.1] + # No longer support setting new wind conditions, must be done with set() + fmodel.set( + wind_speeds = [8.0, 8.0, 8.0], + wind_directions = [270.0, 270.0, 270.0], + turbulence_intensities = [0.1, 0.1, 0.1], + ) fmodel.calculate_horizontal_plane( 90.0, - ws=[wind_speeds[0]], - wd=[wind_directions[0]], - ti=[turbulence_intensities[0]] + findex_for_viz=1 ) fmodel.calculate_y_plane( 0.0, - ws=[wind_speeds[0]], - wd=[wind_directions[0]], - ti=[turbulence_intensities[0]] + findex_for_viz=1 ) fmodel.calculate_cross_plane( 500.0, - ws=[wind_speeds[0]], - wd=[wind_directions[0]], - ti=[turbulence_intensities[0]] + findex_for_viz=1 ) - # If Floris is configured with multiple wind conditions prior to this, then all of the - # components must be changed together. - fmodel.set( - wind_speeds=wind_speeds, - wind_directions=wind_directions, - turbulence_intensities=turbulence_intensities - ) - with pytest.raises(ValueError): - fmodel.calculate_horizontal_plane(90.0, ws=[wind_speeds[0]], wd=[wind_directions[0]]) - with pytest.raises(ValueError): - fmodel.calculate_y_plane(0.0, ws=[wind_speeds[0]], wd=[wind_directions[0]]) - with pytest.raises(ValueError): - fmodel.calculate_cross_plane(500.0, ws=[wind_speeds[0]], wd=[wind_directions[0]]) + # Without specifying findex_for_viz should raise a logger warning. + with caplog.at_level(logging.WARNING): + fmodel.calculate_horizontal_plane(90.0) + assert caplog.text != "" # Checking not empty + caplog.clear() + with caplog.at_level(logging.WARNING): + fmodel.calculate_y_plane(0.0) + assert caplog.text != "" # Checking not empty + caplog.clear() + with caplog.at_level(logging.WARNING): + fmodel.calculate_cross_plane(500.0) + assert caplog.text != "" # Checking not empty def test_get_turbine_powers_with_WindRose(): fmodel = FlorisModel(configuration=YAML_INPUT) From e4f28bd0c51c764031f806f1d136a9facfd447a7 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 4 Apr 2024 22:04:20 -0600 Subject: [PATCH 05/10] Ruff formatting. --- floris/floris_model.py | 2 +- floris/flow_visualization.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/floris/floris_model.py b/floris/floris_model.py index 2ecfceea1..91133ef82 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -1075,7 +1075,7 @@ def calculate_y_plane( of x, y, u, v, w """ # TODO update docstring - # TODO: can get from properties once #843 is merged. Do the same on + # TODO: can get from properties once #843 is merged. Do the same on # other calculate_xx_plane methods if self.core.flow_field.n_findex > 1 and findex_for_viz is None: self.logger.warning( diff --git a/floris/flow_visualization.py b/floris/flow_visualization.py index 82a2f7823..32bdd6611 100644 --- a/floris/flow_visualization.py +++ b/floris/flow_visualization.py @@ -554,7 +554,11 @@ def calculate_horizontal_plane_with_turbines( turbine_types_test = [turbine_types[0] for i in range(len(layout_x))] + ['nrel_5MW'] else: turbine_types_test = np.append(turbine_types, 'nrel_5MW').tolist() - yaw_angles = np.append(yaw_angles, np.zeros([fmodel_viz.core.flow_field.n_findex, 1]), axis=1) + yaw_angles = np.append( + yaw_angles, + np.zeros([fmodel_viz.core.flow_field.n_findex, 1]), + axis=1 + ) power_setpoints = np.append( power_setpoints, POWER_SETPOINT_DEFAULT * np.ones([fmodel_viz.core.flow_field.n_findex, 1]), From 545768aa8ecb61933e4c95cd2e8023677ad9cd50 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 4 Apr 2024 22:05:03 -0600 Subject: [PATCH 06/10] Update examples. Note that previous calculate_XX_planes altered the rotor_diameters field on fmodel. --- examples/03_making_adjustments.py | 14 ++++++++------ examples/23_layout_visualizations.py | 6 +++--- ..._empirical_gauss_velocity_deficit_parameters.py | 4 ---- .../27_empirical_gauss_deflection_parameters.py | 2 -- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/examples/03_making_adjustments.py b/examples/03_making_adjustments.py index 0bac6e98b..d4bf91df7 100644 --- a/examples/03_making_adjustments.py +++ b/examples/03_making_adjustments.py @@ -35,7 +35,8 @@ ) # Change the wind speed -horizontal_plane = fmodel.calculate_horizontal_plane(ws=[7.0], height=90.0) +fmodel.set(wind_speeds=[7.0]) +horizontal_plane = fmodel.calculate_horizontal_plane(height=90.0) flowviz.visualize_cut_plane( horizontal_plane, ax=axarr[1], @@ -59,8 +60,8 @@ # # 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), + 5.0 * fmodel.core.farm.rotor_diameters[0] * np.arange(0, N, 1), + 5.0 * fmodel.core.farm.rotor_diameters[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) @@ -87,7 +88,8 @@ yaw_angles[:,4] = 30.0 yaw_angles[:,7] = -30.0 -horizontal_plane = fmodel.calculate_horizontal_plane(yaw_angles=yaw_angles, height=90.0) +fmodel.set(yaw_angles=yaw_angles) +horizontal_plane = fmodel.calculate_horizontal_plane(height=90.0) flowviz.visualize_cut_plane( horizontal_plane, ax=axarr[4], @@ -97,10 +99,10 @@ max_speed=MAX_WS ) -layoutviz.plot_turbine_rotors(fmodel, axarr[4], yaw_angles=yaw_angles, color="c") +layoutviz.plot_turbine_rotors(fmodel, axarr[4], color="c") # Plot the cross-plane of the 3x3 configuration -cross_plane = fmodel.calculate_cross_plane(yaw_angles=yaw_angles, downstream_dist=610.0) +cross_plane = fmodel.calculate_cross_plane(downstream_dist=610.0) flowviz.visualize_cut_plane( cross_plane, ax=axarr[5], diff --git a/examples/23_layout_visualizations.py b/examples/23_layout_visualizations.py index 465490e6e..878c6f152 100644 --- a/examples/23_layout_visualizations.py +++ b/examples/23_layout_visualizations.py @@ -54,15 +54,15 @@ # 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.]])) +fmodel.set(yaw_angles=np.array([[0., 30., 0., 0., 0.]])) +horizontal_plane = fmodel.calculate_horizontal_plane(height=90.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., 30., 0., 0., 0.]])) +layoutviz.plot_turbine_rotors(fmodel, ax=ax, yaw_angles=np.array([[0., 30., 0., 0., 0.]])) ax.set_title("Flow visualization with yawed turbine") # Plot 3: Show the layout, including wake directions diff --git a/examples/26_empirical_gauss_velocity_deficit_parameters.py b/examples/26_empirical_gauss_velocity_deficit_parameters.py index a3c43343a..33571aec5 100644 --- a/examples/26_empirical_gauss_velocity_deficit_parameters.py +++ b/examples/26_empirical_gauss_velocity_deficit_parameters.py @@ -17,8 +17,6 @@ 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 +36,6 @@ def generate_wake_visualization(fmodel: FlorisModel, title=None): height=horizontal_plane_location, x_bounds=x_bounds, y_bounds=y_bounds, - yaw_angles=yaw_angles ) y_plane = fmodel.calculate_y_plane( x_resolution=200, @@ -46,7 +43,6 @@ 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 ) cross_planes = [] for cpl in cross_plane_locations: diff --git a/examples/27_empirical_gauss_deflection_parameters.py b/examples/27_empirical_gauss_deflection_parameters.py index 79bdee9f8..e5de27033 100644 --- a/examples/27_empirical_gauss_deflection_parameters.py +++ b/examples/27_empirical_gauss_deflection_parameters.py @@ -45,7 +45,6 @@ def generate_wake_visualization(fmodel, title=None): height=horizontal_plane_location, x_bounds=x_bounds, y_bounds=y_bounds, - yaw_angles=yaw_angles ) y_plane = fmodel.calculate_y_plane( x_resolution=200, @@ -53,7 +52,6 @@ def generate_wake_visualization(fmodel, title=None): crossstream_dist=streamwise_plane_location, x_bounds=x_bounds, z_bounds=z_bounds, - yaw_angles=yaw_angles ) cross_planes = [] for cpl in cross_plane_locations: From 378777c14d729b6341406b39874d7089d48657e4 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Sun, 7 Apr 2024 15:39:32 -0600 Subject: [PATCH 07/10] Update Helix viz. --- .../004_helix_active_wake_mixing.py | 8 -------- floris/floris_model.py | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/examples/examples_control_types/004_helix_active_wake_mixing.py b/examples/examples_control_types/004_helix_active_wake_mixing.py index aae41a4b0..7738c079c 100644 --- a/examples/examples_control_types/004_helix_active_wake_mixing.py +++ b/examples/examples_control_types/004_helix_active_wake_mixing.py @@ -41,31 +41,23 @@ x_resolution=200, y_resolution=100, height=150.0, - awc_modes=awc_modes, - awc_amplitudes=awc_amplitudes ) y_plane_baseline = fmodel.calculate_y_plane( x_resolution=200, z_resolution=100, crossstream_dist=0.0, - awc_modes=awc_modes, - awc_amplitudes=awc_amplitudes ) y_plane_helix = fmodel.calculate_y_plane( x_resolution=200, z_resolution=100, crossstream_dist=-3*D, - awc_modes=awc_modes, - awc_amplitudes=awc_amplitudes ) cross_plane = fmodel.calculate_cross_plane( y_resolution=100, z_resolution=100, downstream_dist=720.0, - awc_modes=awc_modes, - awc_amplitudes=awc_amplitudes ) # Create the plots diff --git a/floris/floris_model.py b/floris/floris_model.py index 7799f2faf..cf84ff387 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -958,7 +958,8 @@ def set_for_viz(self, findex: int, solver_settings: dict) -> None: turbulence_intensities=self.core.flow_field.turbulence_intensities[findex:findex+1], yaw_angles=self.core.farm.yaw_angles[findex:findex+1,:], power_setpoints=self.core.farm.power_setpoints[findex:findex+1,:], - # TODO: add Helix here. + awc_modes=self.core.farm.awc_modes[findex:findex+1,:], + awc_amplitudes=self.core.farm.awc_amplitudes[findex:findex+1,:], solver_settings=solver_settings, ) @@ -1058,7 +1059,6 @@ def calculate_horizontal_plane( :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w """ - # TODO update docstring if self.core.flow_field.n_findex > 1 and findex_for_viz is None: self.logger.warning( "Multiple findices detected. Using first findex for visualization." From 6ccb27d6f31ef38ec2349969c71dea3d76d6dd87 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Sun, 7 Apr 2024 15:48:03 -0600 Subject: [PATCH 08/10] calculate_horizontal_plane_with_turbines now consistent with calculate_horizontal_plane. --- floris/flow_visualization.py | 60 +++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/floris/flow_visualization.py b/floris/flow_visualization.py index be20f9a66..73670154b 100644 --- a/floris/flow_visualization.py +++ b/floris/flow_visualization.py @@ -477,12 +477,7 @@ def calculate_horizontal_plane_with_turbines( y_resolution=200, x_bounds=None, y_bounds=None, - wd=None, - ws=None, - ti=None, - yaw_angles=None, - power_setpoints=None, - disable_turbines=None, + findex_for_viz=None, ) -> CutPlane: """ This function creates a :py:class:`~.tools.cut_plane.CutPlane` by @@ -504,39 +499,29 @@ def calculate_horizontal_plane_with_turbines( y_resolution (float, optional): Output array resolution. Defaults to 200 points. x_bounds (tuple, optional): Limits of output array (in m). Defaults to None. y_bounds (tuple, optional): Limits of output array (in m). Defaults to None. - wd (float, optional): Wind direction setting. Defaults to None. - ws (float, optional): Wind speed setting. Defaults to None. - ti (float, optional): Turbulence intensity. Defaults to None. - yaw_angles (np.ndarray, optional): Yaw angles settings. Defaults to None. - power_setpoints (np.ndarray, optional): Power setpoints settings. Defaults to None. - disable_turbines (np.ndarray, optional): Disable turbines settings. Defaults to None. + finder_for_viz (int, optional): Index of the condition to visualize. Returns: :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w """ + if fmodel.core.flow_field.n_findex > 1 and findex_for_viz is None: + print( + "Multiple findices detected. Using first findex for visualization." + ) + if findex_for_viz is None: + findex_for_viz = 0 # Make a local copy of fmodel to avoid editing passed in fmodel fmodel_viz = copy.deepcopy(fmodel) - # If wd/ws not provided, use what is set in fmodel - if wd is None: - wd = fmodel_viz.core.flow_field.wind_directions - if ws is None: - ws = fmodel_viz.core.flow_field.wind_speeds - if ti is None: - ti = fmodel_viz.core.flow_field.turbulence_intensities - fmodel_viz.check_wind_condition_for_viz(wd=wd, ws=ws, ti=ti) - # Set the ws and wd - fmodel_viz.set( - wind_directions=wd, - wind_speeds=ws, - yaw_angles=yaw_angles, - power_setpoints=power_setpoints, - disable_turbines=disable_turbines - ) + fmodel_viz.set_for_viz(findex_for_viz, None) + yaw_angles = fmodel_viz.core.farm.yaw_angles power_setpoints = fmodel_viz.core.farm.power_setpoints + awc_modes = fmodel_viz.core.farm.awc_modes + awc_amplitudes = fmodel_viz.core.farm.awc_amplitudes + awc_frequencies = fmodel_viz.core.farm.awc_frequencies # Grab the turbine layout layout_x = copy.deepcopy(fmodel_viz.layout_x) @@ -564,6 +549,21 @@ def calculate_horizontal_plane_with_turbines( POWER_SETPOINT_DEFAULT * np.ones([fmodel_viz.core.flow_field.n_findex, 1]), axis=1 ) + awc_modes = np.append( + awc_modes, + np.full((fmodel_viz.core.flow_field.n_findex, 1), "baseline"), + axis=1 + ) + awc_amplitudes = np.append( + awc_amplitudes, + np.zeros([fmodel_viz.core.flow_field.n_findex, 1]), + axis=1 + ) + awc_frequencies = np.append( + awc_frequencies, + np.zeros([fmodel_viz.core.flow_field.n_findex, 1]), + axis=1 + ) # Get a grid of points test test if x_bounds is None: @@ -600,7 +600,9 @@ def calculate_horizontal_plane_with_turbines( layout_y=layout_y_test, yaw_angles=yaw_angles, power_setpoints=power_setpoints, - disable_turbines=disable_turbines, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes, + awc_frequencies=awc_frequencies, turbine_type=turbine_types_test ) fmodel_viz.run() From f99914c3052d5e2f929b8ee08f0e816872047aca Mon Sep 17 00:00:00 2001 From: misi9170 Date: Sun, 7 Apr 2024 15:49:21 -0600 Subject: [PATCH 09/10] remove now-redundant check_wind_condition_for_viz --- floris/floris_model.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/floris/floris_model.py b/floris/floris_model.py index cf84ff387..2428433fa 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -1170,25 +1170,6 @@ def calculate_y_plane( return y_plane - def check_wind_condition_for_viz(self, wd, ws, ti): - if len(wd) > 1 or len(wd) < 1: - raise ValueError( - "Wind direction input must be of length 1 for visualization. " - f"Current length is {len(wd)}." - ) - - if len(ws) > 1 or len(ws) < 1: - raise ValueError( - "Wind speed input must be of length 1 for visualization. " - f"Current length is {len(ws)}." - ) - - if len(ti) != 1: - raise ValueError( - "Turbulence intensity input must be of length 1 for visualization. " - f"Current length is {len(ti)}." - ) - def get_plane_of_points( self, normal_vector="z", From 94d2c2bbf41aaa9367df3424bc216abafb3fa288 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Mon, 8 Apr 2024 15:33:45 -0600 Subject: [PATCH 10/10] Clean up TODOs and docstrings. --- floris/floris_model.py | 17 ++++++----------- floris/flow_visualization.py | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/floris/floris_model.py b/floris/floris_model.py index 2428433fa..a47fd65e2 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -951,11 +951,10 @@ def set_for_viz(self, findex: int, solver_settings: dict) -> None: findex (int): The findex to set the floris object to. solver_settings (dict): The solver settings to use for visualization. """ - # TODO: can get from properties once #843 is merged self.set( - wind_speeds=self.core.flow_field.wind_speeds[findex:findex+1], - wind_directions=self.core.flow_field.wind_directions[findex:findex+1], - turbulence_intensities=self.core.flow_field.turbulence_intensities[findex:findex+1], + wind_speeds=self.wind_speeds[findex:findex+1], + wind_directions=self.wind_directions[findex:findex+1], + turbulence_intensities=self.turbulence_intensities[findex:findex+1], yaw_angles=self.core.farm.yaw_angles[findex:findex+1,:], power_setpoints=self.core.farm.power_setpoints[findex:findex+1,:], awc_modes=self.core.farm.awc_modes[findex:findex+1,:], @@ -992,8 +991,7 @@ def calculate_cross_plane( :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w """ - # TODO update docstring - if self.core.flow_field.n_findex > 1 and findex_for_viz is None: + if self.n_findex > 1 and findex_for_viz is None: self.logger.warning( "Multiple findices detected. Using first findex for visualization." ) @@ -1059,7 +1057,7 @@ def calculate_horizontal_plane( :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w """ - if self.core.flow_field.n_findex > 1 and findex_for_viz is None: + if self.n_findex > 1 and findex_for_viz is None: self.logger.warning( "Multiple findices detected. Using first findex for visualization." ) @@ -1131,10 +1129,7 @@ def calculate_y_plane( :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w """ - # TODO update docstring - # TODO: can get from properties once #843 is merged. Do the same on - # other calculate_xx_plane methods - if self.core.flow_field.n_findex > 1 and findex_for_viz is None: + if self.n_findex > 1 and findex_for_viz is None: self.logger.warning( "Multiple findices detected. Using first findex for visualization." ) diff --git a/floris/flow_visualization.py b/floris/flow_visualization.py index 73670154b..720399d99 100644 --- a/floris/flow_visualization.py +++ b/floris/flow_visualization.py @@ -499,7 +499,7 @@ def calculate_horizontal_plane_with_turbines( y_resolution (float, optional): Output array resolution. Defaults to 200 points. x_bounds (tuple, optional): Limits of output array (in m). Defaults to None. y_bounds (tuple, optional): Limits of output array (in m). Defaults to None. - finder_for_viz (int, optional): Index of the condition to visualize. + findex_for_viz (int, optional): Index of the condition to visualize. Returns: :py:class:`~.tools.cut_plane.CutPlane`: containing values of x, y, u, v, w