Source code for floris.optimization.layout_optimization.layout_optimization_base


import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import LineString, Polygon

from floris import TimeSeries
from floris.optimization.yaw_optimization.yaw_optimizer_geometric import (
    YawOptimizationGeometric,
)
from floris.wind_data import WindDataBase

from ...logging_manager import LoggingManager


[docs] class LayoutOptimization(LoggingManager): """ Base class for layout optimization. This class should not be used directly but should be subclassed by a specific optimization method. Args: fmodel (FlorisModel): A FlorisModel object. boundaries (iterable(float, float)): Pairs of x- and y-coordinates that represent the boundary's vertices (m). min_dist (float, optional): The minimum distance to be maintained between turbines during the optimization (m). If not specified, initializes to 2 rotor diameters. Defaults to None. enable_geometric_yaw (bool, optional): If True, enables geometric yaw optimization. Defaults to False. use_value (bool, optional): If True, the layout optimization objective is to maximize annual value production using the value array in the FLORIS model's WindData object. If False, the optimization objective is to maximize AEP. Defaults to False. """ def __init__( self, fmodel, boundaries, min_dist=None, enable_geometric_yaw=False, use_value=False, ): self.fmodel = fmodel.copy() # Does not copy over the wind_data object self.fmodel.set(wind_data=fmodel.wind_data) self.boundaries = boundaries self.enable_geometric_yaw = enable_geometric_yaw self.use_value = use_value self._boundary_polygon = Polygon(self.boundaries) self._boundary_line = LineString(self.boundaries) self.xmin = np.min([tup[0] for tup in boundaries]) self.xmax = np.max([tup[0] for tup in boundaries]) self.ymin = np.min([tup[1] for tup in boundaries]) self.ymax = np.max([tup[1] for tup in boundaries]) # If no minimum distance is provided, assume a value of 2 rotor diameters if min_dist is None: self.min_dist = 2 * self.rotor_diameter else: self.min_dist = min_dist # Check that wind_data is a WindDataBase object if (not isinstance(self.fmodel.wind_data, WindDataBase)): # NOTE: it is no longer strictly necessary that fmodel use # a WindData object, but it is still recommended. self.logger.warning( "Running layout optimization without a WindData object (e.g. TimeSeries, WindRose, " "WindTIRose). We suggest that the user set the wind conditions (and if applicable, " "frequencies and values) on the FlorisModel using the wind_data keyword argument " "for layout optimizations to capture frequencies and the value of the energy " "production accurately. If a WindData object is not defined, uniform frequencies " "will be assumed. If use_value is True and a WindData object is not defined, a " "value of 1 will be used for each wind condition and layout optimization will " "simply be performed to maximize AEP." ) # Establish geometric yaw class if self.enable_geometric_yaw: self.yaw_opt = YawOptimizationGeometric( fmodel, minimum_yaw_angle=-30.0, maximum_yaw_angle=30.0, ) fmodel.run() if self.use_value: self.initial_AEP_or_AVP = fmodel.get_farm_AVP() else: self.initial_AEP_or_AVP = fmodel.get_farm_AEP() def __str__(self): return "layout" def _norm(self, val, x1, x2): return (val - x1) / (x2 - x1) def _unnorm(self, val, x1, x2): return np.array(val) * (x2 - x1) + x1 def _get_geoyaw_angles(self): # NOTE: requires that child class saves x and y locations # as self.x and self.y and updates them during optimization. if self.enable_geometric_yaw: self.yaw_opt.fmodel_subset.set(layout_x=self.x, layout_y=self.y) df_opt = self.yaw_opt.optimize() self.yaw_angles = np.vstack(df_opt['yaw_angles_opt'])[:, :] else: self.yaw_angles = None return self.yaw_angles # Public methods
[docs] def optimize(self): sol = self._optimize() return sol
[docs] def plot_layout_opt_results(self): x_initial, y_initial, x_opt, y_opt = self._get_initial_and_final_locs() plt.figure(figsize=(9, 6)) fontsize = 16 plt.plot(x_initial, y_initial, "ob") plt.plot(x_opt, y_opt, "or") # plt.title('Layout Optimization Results', fontsize=fontsize) plt.xlabel("x (m)", fontsize=fontsize) plt.ylabel("y (m)", fontsize=fontsize) plt.axis("equal") plt.grid() plt.tick_params(which="both", labelsize=fontsize) plt.legend( ["Old locations", "New locations"], loc="lower center", bbox_to_anchor=(0.5, 1.01), ncol=2, fontsize=fontsize, ) verts = self.boundaries for i in range(len(verts)): if i == len(verts) - 1: plt.plot([verts[i][0], verts[0][0]], [verts[i][1], verts[0][1]], "b") else: plt.plot( [verts[i][0], verts[i + 1][0]], [verts[i][1], verts[i + 1][1]], "b" )
########################################################################### # Properties ########################################################################### @property def nturbs(self): """ This property returns the number of turbines in the FLORIS object. Returns: nturbs (int): The number of turbines in the FLORIS object. """ self._nturbs = self.fmodel.core.farm.n_turbines return self._nturbs @property def rotor_diameter(self): return self.fmodel.core.farm.rotor_diameters_sorted[0][0]