From 2e8f4d58994e284458a21e83c4917354bd420494 Mon Sep 17 00:00:00 2001 From: bayc Date: Fri, 16 Feb 2024 19:55:42 -0700 Subject: [PATCH] remove layout symmetry code for optimization --- .../layout_optimization_base.py | 1 - .../yaw_optimization/yaw_optimization_base.py | 152 +----------------- .../yaw_optimization_tools.py | 63 -------- .../yaw_optimizer_geometric.py | 2 - .../yaw_optimization/yaw_optimizer_scipy.py | 2 - .../yaw_optimization/yaw_optimizer_sr.py | 2 - floris/tools/parallel_computing_interface.py | 5 - 7 files changed, 5 insertions(+), 222 deletions(-) diff --git a/floris/tools/optimization/layout_optimization/layout_optimization_base.py b/floris/tools/optimization/layout_optimization/layout_optimization_base.py index 6973ca736..456ce691c 100644 --- a/floris/tools/optimization/layout_optimization/layout_optimization_base.py +++ b/floris/tools/optimization/layout_optimization/layout_optimization_base.py @@ -58,7 +58,6 @@ def __init__(self, fi, boundaries, min_dist=None, freq=None, enable_geometric_ya fi, minimum_yaw_angle=-30.0, maximum_yaw_angle=30.0, - exploit_layout_symmetry=False ) self.initial_AEP = fi.get_farm_AEP(self.freq) diff --git a/floris/tools/optimization/yaw_optimization/yaw_optimization_base.py b/floris/tools/optimization/yaw_optimization/yaw_optimization_base.py index 19485b808..40da91cab 100644 --- a/floris/tools/optimization/yaw_optimization/yaw_optimization_base.py +++ b/floris/tools/optimization/yaw_optimization/yaw_optimization_base.py @@ -21,7 +21,7 @@ from floris.logging_manager import LoggingManager -from .yaw_optimization_tools import derive_downstream_turbines, find_layout_symmetry +from .yaw_optimization_tools import derive_downstream_turbines class YawOptimization(LoggingManager): @@ -42,7 +42,6 @@ def __init__( normalize_control_variables=False, calc_baseline_power=True, exclude_downstream_turbines=True, - exploit_layout_symmetry=False, verify_convergence=False, ): """ @@ -179,16 +178,6 @@ def __init__( self.calc_baseline_power = calc_baseline_power self.exclude_downstream_turbines = exclude_downstream_turbines - # Check if exploit_layout_symmetry is being used with heterogeneous inflow - if exploit_layout_symmetry and fi.floris.flow_field.heterogenous_inflow_config is not None: - err_msg = ( - "Layout symmetry cannot be exploited with heterogeneous inflows. " - "Setting exploit_layout_symmetry to False." - ) - self.logger.warning(err_msg, stack_info=True) - self.exploit_layout_symmetry = False - else: - self.exploit_layout_symmetry = exploit_layout_symmetry # Prepare for optimization and calculate baseline powers (if applic.) self._initialize() @@ -203,9 +192,6 @@ def __init__( # Private methods def _initialize(self): - # Derive layout symmetry, if applicable - self._derive_layout_symmetry() - # Reduce optimization problem as much as possible self._reduce_control_problem() @@ -250,9 +236,7 @@ def _reduce_control_problem(self): user-specified set of bounds (where bounds[i][0] == bounds[i][1]), or alternatively turbines that are far downstream in the wind farm and of which the wake does not impinge other turbines, if - exclude_downstream_turbines == True. This function also reduces - the optimization problem by exploiting layout symmetry, if - exploit_layout_symmetry == True. + exclude_downstream_turbines == True. """ # Initialize which turbines to optimize for self.turbs_to_opt = (self.maximum_yaw_angle - self.minimum_yaw_angle >= 0.001) @@ -277,29 +261,6 @@ def _reduce_control_problem(self): self.turbs_to_opt[iw, downstream_turbines] = False turbs_to_opt_subset = copy.deepcopy(self.turbs_to_opt) # Update - # Reduce optimization problem through layout symmetry - if (self.exploit_layout_symmetry) & (self._sym_df is not None): - # Reinitialize floris with subset of wind directions - wd_array = self.fi.floris.flow_field.wind_directions - wind_direction_subset = wd_array[self._sym_mapping_reduce] - ws_array = self.fi.floris.flow_field.wind_speeds - wind_speed_subset = ws_array[self._sym_mapping_reduce] - self.fi_subset.reinitialize( - wind_directions=wind_direction_subset, - wind_speeds=wind_speed_subset, - ) - - # Reduce control variables - red_map = self._sym_mapping_reduce - n_wind_directions_subset = len(wind_direction_subset) - minimum_yaw_angle_subset = minimum_yaw_angle_subset[red_map, :] - maximum_yaw_angle_subset = maximum_yaw_angle_subset[red_map, :] - x0_subset = x0_subset[red_map, :] - turbs_to_opt_subset = turbs_to_opt_subset[red_map, :] - turbine_weights_subset = turbine_weights_subset[red_map, :] - yaw_angles_template_subset = yaw_angles_template_subset[red_map, :] - yaw_angles_baseline_subset = yaw_angles_baseline_subset[red_map, :] - # Set up a template yaw angles array with default solutions. The default # solutions are either 0.0 or the allowable yaw angle closest to 0.0 deg. # This solution addresses both downstream turbines, minimizing their abs. @@ -417,114 +378,11 @@ def _calculate_baseline_farm_power(self): if self.calc_baseline_power: P = self._calculate_farm_power(self._yaw_angles_baseline_subset) self._farm_power_baseline_subset = P - self.farm_power_baseline = self._unreduce_variable(P) + self.farm_power_baseline = P else: self._farm_power_baseline_subset = None self.farm_power_baseline = None - def _derive_layout_symmetry(self): - """Derive symmetry lines in the wind farm layout and use that - to reduce the optimization problem by 50 %. - """ - self._sym_df = None # Default option - if self.exploit_layout_symmetry: - # Check symmetry of bounds & turbine_weights - if np.unique(self.minimum_yaw_angle, axis=0).shape[0] > 1: - print("minimum_yaw_angle is not equal over wind directions.") - print("Exploiting of symmetry has been disabled.") - return - - if np.unique(self.maximum_yaw_angle, axis=0).shape[0] > 1: - print("maximum_yaw_angle is not equal over wind directions.") - print("Exploiting of symmetry has been disabled.") - return - - if np.unique(self.maximum_yaw_angle, axis=0).shape[0] > 1: - print("maximum_yaw_angle is not equal over wind directions.") - print("Exploiting of symmetry has been disabled.") - return - - if np.unique(self.turbine_weights, axis=0).shape[0] > 1: - print("turbine_weights is not equal over wind directions.") - print("Exploiting of symmetry has been disabled.") - return - - # Check if turbine_weights are consistently 1.0 everywhere - if np.any(np.abs(self.turbine_weights - 1.0) > 0.001): - print("turbine_weights are not uniformly 1.0.") - print("Exploiting of symmetry has been disabled.") - return - - x = self.fi.layout_x - y = self.fi.layout_y - df = find_layout_symmetry(x=x, y=y) - - # If no axes of symmetry, exit function - if df.shape[0] <= 0: - print("Wind farm layout in floris is not symmetrical.") - print("Exploitation of symmetry has been disabled.") - return - - wd_array = self.fi.floris.flow_field.wind_directions - sym_step = df.iloc[0]["wd_range"][1] - if ((0.0 not in wd_array) or(sym_step not in wd_array)): - print("Floris wind direction array does not " + - "intersect {:.1f} and {:.1f}.".format(0.0, sym_step)) - print("Exploitation of symmetry has been disabled.") - return - - ids_minimal = (wd_array >= 0.0) & (wd_array < sym_step) - wd_array_min = wd_array[ids_minimal] - wd_array_remn = np.remainder(wd_array, sym_step) - - if not np.all([(x in wd_array_min) for x in wd_array_remn]): - print("Wind direction array appears irregular.") - print("Exploitation of symmetry has been disabled.") - - self._sym_mapping_extrap = np.array( - [np.where(np.abs(x - wd_array_min) < 0.0001)[0][0] - for x in wd_array_remn], dtype=int) - - self._sym_mapping_reduce = copy.deepcopy(ids_minimal) - self._sym_df = df - - return - - def _unreduce_variable(self, variable): - # Check if needed to un-reduce at all, if not, return directly - if variable is None: - return variable - - if not self.exploit_layout_symmetry: - return variable - - if self._sym_df is None: - return variable - - # Apply operation on right dimension - ndims = len(np.shape(variable)) - if ndims == 1: - full_array = variable[self._sym_mapping_extrap] - elif ndims == 2: - full_array = variable[self._sym_mapping_extrap, :] - elif ndims == 3: - # First upsample to full wind rose - full_array = variable[self._sym_mapping_extrap, :, :] - - # Now process turbine mapping - wd_array = self.fi.floris.flow_field.wind_directions - for ii, dfrow in self._sym_df.iloc[1::].iterrows(): - ids = ( - (wd_array >= dfrow["wd_range"][0]) & - (wd_array < dfrow["wd_range"][1]) - ) - tmap = np.argsort(dfrow["turbine_mapping"]) - full_array[ids, :, :] = full_array[ids, :, :][:, :, tmap] - else: - raise UserWarning("Unknown data shape.") - - return full_array - def _finalize(self, farm_power_opt_subset=None, yaw_angles_opt_subset=None): # Process final solutions if farm_power_opt_subset is None: @@ -542,8 +400,8 @@ def _finalize(self, farm_power_opt_subset=None, yaw_angles_opt_subset=None): ) # Finalization step for optimization: undo reduction step - self.farm_power_opt = self._unreduce_variable(farm_power_opt_subset) - self.yaw_angles_opt = self._unreduce_variable(yaw_angles_opt_subset) + self.farm_power_opt = farm_power_opt_subset + self.yaw_angles_opt = yaw_angles_opt_subset # Produce output table ti = np.min(self.fi.floris.flow_field.turbulence_intensities) diff --git a/floris/tools/optimization/yaw_optimization/yaw_optimization_tools.py b/floris/tools/optimization/yaw_optimization/yaw_optimization_tools.py index da7d5a443..373ea5217 100644 --- a/floris/tools/optimization/yaw_optimization/yaw_optimization_tools.py +++ b/floris/tools/optimization/yaw_optimization/yaw_optimization_tools.py @@ -142,66 +142,3 @@ def determine_if_in_wake(xt, yt): ) return turbs_downstream - - -def find_layout_symmetry(x, y, step_sizes = [15.0], eps=0.00001): - # Place center of farm at (0, 0) - x = x - np.mean(x) - y = y - np.mean(y) - nturbs = len(x) - - # Evaluate at continuously refined step size - for ss in step_sizes: - wd_array = np.arange(ss, 180.001, ss) - for wd in wd_array: - is_faulty = False - x_rot = ( - np.cos(wd * np.pi / 180.0) * x - - np.sin(wd * np.pi / 180.0) * y - ) - y_rot = ( - np.sin(wd * np.pi / 180.0) * x - + np.cos(wd * np.pi / 180.0) * y - ) - - # compare differences: force turbine 0 to (0, 0) - for ti in range(nturbs): - if np.all(np.abs(x_rot[ti] - x) > eps): - is_faulty = True - break - - if is_faulty: - continue - - for ti in range(nturbs): - if np.all(np.abs(y_rot[ti] - y) > eps): - is_faulty = True - break - - if is_faulty: - continue - - # Found a valid solution. Now find mappings - wd_eval_array = [(0.0, wd)] - mapping_array = [list(range(nturbs))] - for wd_eval in np.arange(wd, 360.0, wd): - ang = wd_eval * -1.0 # Opposite rotation - x_rot = ( - np.cos(ang * np.pi / 180.0) * x - - np.sin(ang * np.pi / 180.0) * y - ) - y_rot = ( - np.sin(ang * np.pi / 180.0) * x - + np.cos(ang * np.pi / 180.0) * y - ) - wd_eval_array.append((wd_eval, wd_eval + wd)) - id_mapping = ([ - np.where((np.abs(xr - x) < eps) &(np.abs(yr - y) < eps))[0][0] - for xr, yr in zip(x_rot, y_rot) - ]) - mapping_array.append(id_mapping) - - df = pd.DataFrame({"wd_range": wd_eval_array, "turbine_mapping": mapping_array}) - return df - - return pd.DataFrame() # Return empty dataframe if completes without finding solution diff --git a/floris/tools/optimization/yaw_optimization/yaw_optimizer_geometric.py b/floris/tools/optimization/yaw_optimization/yaw_optimizer_geometric.py index 390173940..9101af7dc 100644 --- a/floris/tools/optimization/yaw_optimization/yaw_optimizer_geometric.py +++ b/floris/tools/optimization/yaw_optimization/yaw_optimizer_geometric.py @@ -33,7 +33,6 @@ def __init__( fi, minimum_yaw_angle=0.0, maximum_yaw_angle=25.0, - exploit_layout_symmetry=False, ): """ Instantiate YawOptimizationGeometric object with a FlorisInterface @@ -44,7 +43,6 @@ def __init__( fi=fi, minimum_yaw_angle=minimum_yaw_angle, maximum_yaw_angle=maximum_yaw_angle, - exploit_layout_symmetry=exploit_layout_symmetry, calc_baseline_power=False ) diff --git a/floris/tools/optimization/yaw_optimization/yaw_optimizer_scipy.py b/floris/tools/optimization/yaw_optimization/yaw_optimizer_scipy.py index 0fbac6bcc..cf0802d48 100644 --- a/floris/tools/optimization/yaw_optimization/yaw_optimizer_scipy.py +++ b/floris/tools/optimization/yaw_optimization/yaw_optimizer_scipy.py @@ -38,7 +38,6 @@ def __init__( opt_options=None, turbine_weights=None, exclude_downstream_turbines=True, - exploit_layout_symmetry=False, verify_convergence=False, ): """ @@ -65,7 +64,6 @@ def __init__( normalize_control_variables=True, calc_baseline_power=True, exclude_downstream_turbines=exclude_downstream_turbines, - exploit_layout_symmetry=exploit_layout_symmetry, verify_convergence=verify_convergence, ) diff --git a/floris/tools/optimization/yaw_optimization/yaw_optimizer_sr.py b/floris/tools/optimization/yaw_optimization/yaw_optimizer_sr.py index 60bac753f..dc03dbafe 100644 --- a/floris/tools/optimization/yaw_optimization/yaw_optimizer_sr.py +++ b/floris/tools/optimization/yaw_optimization/yaw_optimizer_sr.py @@ -37,7 +37,6 @@ def __init__( Ny_passes=[5, 4], # Optimization options turbine_weights=None, exclude_downstream_turbines=True, - exploit_layout_symmetry=False, verify_convergence=False, ): """ @@ -55,7 +54,6 @@ def __init__( turbine_weights=turbine_weights, calc_baseline_power=True, exclude_downstream_turbines=exclude_downstream_turbines, - exploit_layout_symmetry=exploit_layout_symmetry, verify_convergence=verify_convergence, ) diff --git a/floris/tools/parallel_computing_interface.py b/floris/tools/parallel_computing_interface.py index 15b730600..aaddf4fe6 100644 --- a/floris/tools/parallel_computing_interface.py +++ b/floris/tools/parallel_computing_interface.py @@ -43,7 +43,6 @@ def _optimize_yaw_angles_serial( Ny_passes, turbine_weights, exclude_downstream_turbines, - exploit_layout_symmetry, verify_convergence, print_progress, ): @@ -57,7 +56,6 @@ def _optimize_yaw_angles_serial( Ny_passes=Ny_passes, turbine_weights=turbine_weights, exclude_downstream_turbines=exclude_downstream_turbines, - exploit_layout_symmetry=exploit_layout_symmetry, verify_convergence=verify_convergence, ) @@ -476,7 +474,6 @@ def optimize_yaw_angles( Ny_passes=[5,4], turbine_weights=None, exclude_downstream_turbines=True, - exploit_layout_symmetry=False, verify_convergence=False, print_worker_progress=False, # Recommended disabled to avoid clutter. Useful for debugging ): @@ -494,7 +491,6 @@ def optimize_yaw_angles( Ny_passes, turbine_weights, exclude_downstream_turbines, - exploit_layout_symmetry, verify_convergence, print_worker_progress, ) @@ -518,7 +514,6 @@ def optimize_yaw_angles( [j[7] for j in multiargs], [j[8] for j in multiargs], [j[9] for j in multiargs], - [j[10] for j in multiargs], ) t2 = timerpc()