def load_floris(): # Load the default example floris object fi = FlorisInterface( "inputs/gch.yaml" ) # GCH model matched to the default "legacy_gauss" of V2 # fi = FlorisInterface("inputs/cc.yaml") # New CumulativeCurl model # Specify wind farm layout and update in the floris object N = 5 # number of turbines per row and per column X, Y = np.meshgrid( 5.0 * fi.floris.farm.rotor_diameters_sorted[0][0][0] * np.arange(0, N, 1), 5.0 * fi.floris.farm.rotor_diameters_sorted[0][0][0] * np.arange(0, N, 1), ) fi.reinitialize(layout=(X.flatten(), Y.flatten())) return fi
def __init__(self, config_dict, site, timestep=()): if floris_version < 3.0: raise EnvironmentError("Floris v3.1 or higher is required") self.fi = FlorisInterface(config_dict["floris_config"]) self.site = site self.wind_resource_data = self.site.wind_resource.data self.speeds, self.wind_dirs = self.parse_resource_data() self.wind_farm_xCoordinates = self.fi.layout_x self.wind_farm_yCoordinates = self.fi.layout_y self.nTurbs = len(self.wind_farm_xCoordinates) self.turb_rating = config_dict["turbine_rating_kw"] self.wind_turbine_rotor_diameter = self.fi.floris.farm.rotor_diameters[ 0] self.system_capacity = self.nTurbs * self.turb_rating # turbine power curve (array of kW power outputs) self.wind_turbine_powercurve_powerout = [] # time to simulate if len(timestep) > 0: self.start_idx = timestep[0] self.end_idx = timestep[1] else: self.start_idx = 0 self.end_idx = 8759 # results self.gen = [] self.annual_energy = None self.capacity_factor = None self.initialize_from_floris()
class Floris: def __init__(self, config_dict, site, timestep=()): if floris_version < 3.0: raise EnvironmentError("Floris v3.1 or higher is required") self.fi = FlorisInterface(config_dict["floris_config"]) self.site = site self.wind_resource_data = self.site.wind_resource.data self.speeds, self.wind_dirs = self.parse_resource_data() self.wind_farm_xCoordinates = self.fi.layout_x self.wind_farm_yCoordinates = self.fi.layout_y self.nTurbs = len(self.wind_farm_xCoordinates) self.turb_rating = config_dict["turbine_rating_kw"] self.wind_turbine_rotor_diameter = self.fi.floris.farm.rotor_diameters[ 0] self.system_capacity = self.nTurbs * self.turb_rating # turbine power curve (array of kW power outputs) self.wind_turbine_powercurve_powerout = [] # time to simulate if len(timestep) > 0: self.start_idx = timestep[0] self.end_idx = timestep[1] else: self.start_idx = 0 self.end_idx = 8759 # results self.gen = [] self.annual_energy = None self.capacity_factor = None self.initialize_from_floris() def initialize_from_floris(self): """ Please populate all the wind farm parameters """ self.nTurbs = len(self.fi.layout_x) self.wind_turbine_powercurve_powerout = [1] * 30 # dummy for now pass def value(self, name: str, set_value=None): """ if set_value = None, then retrieve value; otherwise overwrite variable's value """ if set_value: self.__setattr__(name, set_value) else: return self.__getattribute__(name) def parse_resource_data(self): # extract data for simulation speeds = np.zeros(len(self.wind_resource_data['data'])) wind_dirs = np.zeros(len(self.site.wind_resource.data['data'])) for i in range((len(self.site.wind_resource.data['data']))): speeds[i] = self.site.wind_resource.data['data'][i][2] wind_dirs[i] = self.site.wind_resource.data['data'][i][3] return speeds, wind_dirs def execute(self, project_life): print('Simulating wind farm output in FLORIS...') # find generation of wind farm power_turbines = np.zeros((self.nTurbs, 8760)) power_farm = np.zeros(8760) self.fi.reinitialize( wind_speeds=self.speeds[self.start_idx:self.end_idx], wind_directions=self.wind_dirs[self.start_idx:self.end_idx]) self.fi.calculate_wake() powers = self.fi.get_turbine_powers() power_turbines[:, self.start_idx:self.end_idx] = powers[0].reshape( (self.nTurbs, self.end_idx - self.start_idx)) power_farm = np.array(power_turbines).sum(axis=0) self.gen = power_farm / 1000 self.annual_energy = np.sum(self.gen) print('Wind annual energy: ', self.annual_energy) self.capacity_factor = np.sum(self.gen) / (8760 * self.system_capacity)
def __init__( self, configuration, het_map=None, unc_options=None, unc_pmfs=None, fix_yaw_in_relative_frame=False, ): """A wrapper around the nominal floris_interface class that adds uncertainty to the floris evaluations. One can specify a probability distribution function (pdf) for the ambient wind direction. Unless the exact pdf is specified manually using the option 'unc_pmfs', a Gaussian probability distribution function will be assumed. Args: configuration (:py:obj:`dict` or FlorisInterface object): The Floris object, configuration dictarionary, JSON file, or YAML file. The configuration should have the following inputs specified. - **flow_field**: See `floris.simulation.flow_field.FlowField` for more details. - **farm**: See `floris.simulation.farm.Farm` for more details. - **turbine**: See `floris.simulation.turbine.Turbine` for more details. - **wake**: See `floris.simulation.wake.WakeManager` for more details. - **logging**: See `floris.simulation.floris.Floris` for more details. unc_options (dictionary, optional): A dictionary containing values used to create normally-distributed, zero-mean probability mass functions describing the distribution of wind direction deviations. This argument is only used when **unc_pmfs** is None and contain the following key-value pairs: - **std_wd** (*float*): A float containing the standard deviation of the wind direction deviations from the original wind direction. - **pmf_res** (*float*): A float containing the resolution in degrees of the wind direction and yaw angle PMFs. - **pdf_cutoff** (*float*): A float containing the cumulative distribution function value at which the tails of the PMFs are truncated. Defaults to None. Initializes to {'std_wd': 4.95, 'pmf_res': 1.0, 'pdf_cutoff': 0.995}. unc_pmfs (dictionary, optional): A dictionary containing optional probability mass functions describing the distribution of wind direction deviations. Contains the following key-value pairs: - **wd_unc** (*np.array*): Wind direction deviations from the original wind direction. - **wd_unc_pmf** (*np.array*): Probability of each wind direction deviation in **wd_unc** occuring. Defaults to None, in which case default PMFs are calculated using values provided in **unc_options**. fix_yaw_in_relative_frame (bool, optional): When set to True, the relative yaw angle of all turbines is fixed and always has the nominal value (e.g., 0 deg) when evaluating uncertainty in the wind direction. Evaluating wind direction uncertainty like this will essentially come down to a Gaussian smoothing of FLORIS solutions over the wind directions. This calculation can therefore be really fast, since it does not require additional calculations compared to a non-uncertainty FLORIS evaluation. When fix_yaw_in_relative_frame=False, the yaw angles are fixed in the absolute (compass) reference frame, meaning that for each probablistic wind direction evaluation, our probablistic (relative) yaw angle evaluated goes into the opposite direction. For example, a probablistic wind direction 3 deg above the nominal value means that we evaluate it with a relative yaw angle that is 3 deg below its nominal value. This requires additional computations compared to a non- uncertainty evaluation. Typically, fix_yaw_in_relative_frame=True is used when comparing FLORIS to historical data, in which a single measurement usually represents a 10-minute average, and thus is often a mix of various true wind directions. The inherent assumption then is that the turbine perfectly tracks the wind direction changes within those 10 minutes. Then, fix_yaw_in_relative_frame=False is typically used for robust yaw angle optimization, in which we take into account that the turbine often does not perfectly know the true wind direction, and that a turbine often does not perfectly achieve its desired yaw angle offset. Defaults to fix_yaw_in_relative_frame=False. """ if (unc_options is None) & (unc_pmfs is None): # Default options: unc_options = { "std_wd": 3.0, # Standard deviation for inflow wind direction (deg) "pmf_res": 1.0, # Resolution over which to calculate angles (deg) "pdf_cutoff": 0.995, # Probability density function cut-off (-) } # Initialize floris object and uncertainty pdfs if isinstance(configuration, FlorisInterface): self.fi = configuration else: self.fi = FlorisInterface(configuration, het_map=het_map) self.reinitialize_uncertainty( unc_options=unc_options, unc_pmfs=unc_pmfs, fix_yaw_in_relative_frame=fix_yaw_in_relative_frame, )
from time import perf_counter as timerpc import numpy as np import matplotlib.pyplot as plt from floris.tools import FlorisInterface from floris.tools.optimization.yaw_optimization.yaw_optimizer_scipy import ( YawOptimizationScipy) from floris.tools.optimization.yaw_optimization.yaw_optimizer_sr import ( YawOptimizationSR) """ This example compares the SciPy-based yaw optimizer with the new Serial-Refine optimizer. First, we initialize our Floris Interface, and then generate a 3 turbine wind farm. Next, we create two yaw optimization objects, `yaw_opt_sr` and `yaw_opt_scipy` for the Serial-Refine and SciPy methods, respectively. We then perform the optimization using both methods. Finally, we compare the time it took to find the optimal angles and plot the optimal yaw angles and resulting wind farm powers. """ # Load the default example floris object fi = FlorisInterface( "inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 # fi = FlorisInterface("inputs/cc.yaml") # New CumulativeCurl model # Reinitialize as a 3-turbine farm with range of WDs and 1 WS D = 126.0 # Rotor diameter for the NREL 5 MW fi.reinitialize( layout=[[0.0, 5 * D, 10 * D], [0.0, 0.0, 0.0]], wind_directions=np.arange(0.0, 360.0, 3.0), wind_speeds=[8.0], ) print("Performing optimizations with SciPy...") start_time = timerpc() yaw_opt_scipy = YawOptimizationScipy(fi) df_opt_scipy = yaw_opt_scipy.optimize() time_scipy = timerpc() - start_time
from floris.tools import FlorisInterface from floris.tools.visualization import visualize_cut_plane """ 04_sweep_wind_directions This example demonstrates vectorization of wind direction. A vector of wind directions is passed to the intialize function and the powers of the two simulated turbines is computed for all wind directions in one call The power of both turbines for each wind direction is then plotted """ # Instantiate FLORIS using either the GCH or CC model fi = FlorisInterface( "inputs/gch.yaml") # GCH model matched to the default "legacy_gauss" of V2 # fi = FlorisInterface("inputs/cc.yaml") # New CumulativeCurl model # Define a two turbine farm D = 126. layout_x = np.array([0, D * 6]) layout_y = [0, 0] fi.reinitialize(layout=[layout_x, layout_y]) # Sweep wind speeds but keep wind direction fixed wd_array = np.arange(250, 291, 1.) fi.reinitialize(wind_directions=wd_array) # Define a matrix of yaw angles to be all 0 # Note that yaw angles is now specified as a matrix whose dimesions are # wd/ws/turbine
from floris.tools import visualize_cut_plane #, plot_turbines_with_fi """ This example makes changes to the given input file through the script. First, we plot simulation from the input file as given. Then, we make a series of changes and generate plots from those simulations. """ # Create the plotting objects using matplotlib fig, axarr = plt.subplots(2, 3, figsize=(12, 5)) axarr = axarr.flatten() MIN_WS = 1.0 MAX_WS = 8.0 # Initialize FLORIS with the given input file via FlorisInterface fi = FlorisInterface("inputs/gch.yaml") # 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) # 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,
3. Veritical slice parallel to to the turbine disc plane """ # Define the speed ups of the heterogeneous inflow, and their locations. # For the 2-dimensional case, this requires x and y locations. # The speed ups are multipliers of the ambient wind speed. speed_ups = [[2.0, 2.0, 1.0, 1.0]] x_locs = [-300.0, -300.0, 2600.0, 2600.0] y_locs = [-300.0, 300.0, -300.0, 300.0] # Generate the linear interpolation to be used for the heterogeneous inflow. het_map_2d = generate_heterogeneous_wind_map(speed_ups, x_locs, y_locs) # Initialize FLORIS with the given input file via FlorisInterface. # Also, pass the heterogeneous map into the FlorisInterface. fi_2d = FlorisInterface("inputs/gch.yaml", het_map=het_map_2d) # Set shear to 0.0 to highlight the heterogeneous inflow fi_2d.reinitialize(wind_shear=0.0) # Using the FlorisInterface functions for generating plots, run FLORIS # and extract 2D planes of data. horizontal_plane_2d = fi_2d.calculate_horizontal_plane(x_resolution=200, y_resolution=100, height=90.0) y_plane_2d = fi_2d.calculate_y_plane(x_resolution=200, z_resolution=100, crossstream_dist=0.0) cross_plane_2d = fi_2d.calculate_cross_plane(y_resolution=100, z_resolution=100, downstream_dist=500.0)
import matplotlib.pyplot as plt import numpy as np 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 reasonably smooth """ ws_array = np.arange(0.1, 30, 0.2) yaw_angles = np.linspace(-30, 30, 60) wind_speed_to_test_yaw = 11 # Grab the gch model fi = FlorisInterface("inputs/gch.yaml") # Make one turbine sim fi.reinitialize(layout=[[0], [0]]) # Apply wind speeds fi.reinitialize(wind_speeds=ws_array) # Get a list of available turbine models turbines = os.listdir('../floris/turbine_library') turbines = [t.strip('.yaml') for t in turbines] # Declare a set of figures for comparing cp and ct across models fig_cp_ct, axarr_cp_ct = plt.subplots(2, 1, sharex=True, figsize=(10, 10)) # For each turbine model available plot the basic info
ws_array = np.array(df_wr["ws"].unique(), dtype=float) # Format the frequency array into the conventional FLORIS v3 format, which is # an np.array with shape (n_wind_directions, n_wind_speeds). To avoid having # to manually derive how the variables are sorted and how to reshape the # one-dimensional frequency array, we use a nearest neighbor interpolant. This # ensures the frequency values are mapped appropriately to the new 2D array. wd_grid, ws_grid = np.meshgrid(wd_array, ws_array, indexing="ij") freq_interp = NearestNDInterpolator(df_wr[["wd", "ws"]], df_wr["freq_val"]) freq = freq_interp(wd_grid, ws_grid) # Normalize the frequency array to sum to exactly 1.0 freq = freq / np.sum(freq) # Load the FLORIS object fi = FlorisInterface("inputs/gch.yaml") # GCH model # fi = FlorisInterface("inputs/cc.yaml") # CumulativeCurl model # Assume a three-turbine wind farm with 5D spacing. We reinitialize the # floris object and assign the layout, wind speed and wind direction arrays. D = fi.floris.farm.rotor_diameters[0] # Rotor diameter for the NREL 5 MW fi.reinitialize( layout=[[0.0, 5 * D, 10 * D], [0.0, 0.0, 0.0]], wind_directions=wd_array, wind_speeds=ws_array, ) # Compute the AEP using the default settings aep = fi.get_farm_AEP(freq=freq) print("Farm AEP (default options): {:.3f} GWh".format(aep / 1.0e9))
class UncertaintyInterface(LoggerBase): def __init__( self, configuration, het_map=None, unc_options=None, unc_pmfs=None, fix_yaw_in_relative_frame=False, ): """A wrapper around the nominal floris_interface class that adds uncertainty to the floris evaluations. One can specify a probability distribution function (pdf) for the ambient wind direction. Unless the exact pdf is specified manually using the option 'unc_pmfs', a Gaussian probability distribution function will be assumed. Args: configuration (:py:obj:`dict` or FlorisInterface object): The Floris object, configuration dictarionary, JSON file, or YAML file. The configuration should have the following inputs specified. - **flow_field**: See `floris.simulation.flow_field.FlowField` for more details. - **farm**: See `floris.simulation.farm.Farm` for more details. - **turbine**: See `floris.simulation.turbine.Turbine` for more details. - **wake**: See `floris.simulation.wake.WakeManager` for more details. - **logging**: See `floris.simulation.floris.Floris` for more details. unc_options (dictionary, optional): A dictionary containing values used to create normally-distributed, zero-mean probability mass functions describing the distribution of wind direction deviations. This argument is only used when **unc_pmfs** is None and contain the following key-value pairs: - **std_wd** (*float*): A float containing the standard deviation of the wind direction deviations from the original wind direction. - **pmf_res** (*float*): A float containing the resolution in degrees of the wind direction and yaw angle PMFs. - **pdf_cutoff** (*float*): A float containing the cumulative distribution function value at which the tails of the PMFs are truncated. Defaults to None. Initializes to {'std_wd': 4.95, 'pmf_res': 1.0, 'pdf_cutoff': 0.995}. unc_pmfs (dictionary, optional): A dictionary containing optional probability mass functions describing the distribution of wind direction deviations. Contains the following key-value pairs: - **wd_unc** (*np.array*): Wind direction deviations from the original wind direction. - **wd_unc_pmf** (*np.array*): Probability of each wind direction deviation in **wd_unc** occuring. Defaults to None, in which case default PMFs are calculated using values provided in **unc_options**. fix_yaw_in_relative_frame (bool, optional): When set to True, the relative yaw angle of all turbines is fixed and always has the nominal value (e.g., 0 deg) when evaluating uncertainty in the wind direction. Evaluating wind direction uncertainty like this will essentially come down to a Gaussian smoothing of FLORIS solutions over the wind directions. This calculation can therefore be really fast, since it does not require additional calculations compared to a non-uncertainty FLORIS evaluation. When fix_yaw_in_relative_frame=False, the yaw angles are fixed in the absolute (compass) reference frame, meaning that for each probablistic wind direction evaluation, our probablistic (relative) yaw angle evaluated goes into the opposite direction. For example, a probablistic wind direction 3 deg above the nominal value means that we evaluate it with a relative yaw angle that is 3 deg below its nominal value. This requires additional computations compared to a non- uncertainty evaluation. Typically, fix_yaw_in_relative_frame=True is used when comparing FLORIS to historical data, in which a single measurement usually represents a 10-minute average, and thus is often a mix of various true wind directions. The inherent assumption then is that the turbine perfectly tracks the wind direction changes within those 10 minutes. Then, fix_yaw_in_relative_frame=False is typically used for robust yaw angle optimization, in which we take into account that the turbine often does not perfectly know the true wind direction, and that a turbine often does not perfectly achieve its desired yaw angle offset. Defaults to fix_yaw_in_relative_frame=False. """ if (unc_options is None) & (unc_pmfs is None): # Default options: unc_options = { "std_wd": 3.0, # Standard deviation for inflow wind direction (deg) "pmf_res": 1.0, # Resolution over which to calculate angles (deg) "pdf_cutoff": 0.995, # Probability density function cut-off (-) } # Initialize floris object and uncertainty pdfs if isinstance(configuration, FlorisInterface): self.fi = configuration else: self.fi = FlorisInterface(configuration, het_map=het_map) self.reinitialize_uncertainty( unc_options=unc_options, unc_pmfs=unc_pmfs, fix_yaw_in_relative_frame=fix_yaw_in_relative_frame, ) # Private methods def _generate_pdfs_from_dict(self): """Generates the uncertainty probability distributions from a dictionary only describing the wd_std and yaw_std, and discretization resolution. """ wd_unc = np.zeros(1) wd_unc_pmf = np.ones(1) # create normally distributed wd and yaw uncertaitny pmfs if appropriate unc_options = self.unc_options if unc_options["std_wd"] > 0: wd_bnd = int(np.ceil(norm.ppf(unc_options["pdf_cutoff"], scale=unc_options["std_wd"]) / unc_options["pmf_res"])) bound = wd_bnd * unc_options["pmf_res"] wd_unc = np.linspace(-1 * bound, bound, 2 * wd_bnd + 1) wd_unc_pmf = norm.pdf(wd_unc, scale=unc_options["std_wd"]) wd_unc_pmf /= np.sum(wd_unc_pmf) # normalize so sum = 1.0 unc_pmfs = { "wd_unc": wd_unc, "wd_unc_pmf": wd_unc_pmf, } # Save to self self.unc_pmfs = unc_pmfs def _expand_wind_directions_and_yaw_angles(self): """Expands the nominal wind directions and yaw angles to the full set of conditions that need to be evaluated for the probablistic calculation of the floris solutions. This produces the np.NDArrays "wd_array_probablistic" and "yaw_angles_probablistic", with shapes: ( num_wind_direction_pdf_points_to_evaluate, num_nominal_wind_directions, ) and ( num_wind_direction_pdf_points_to_evaluate, num_nominal_wind_directions, num_nominal_wind_speeds, num_turbines ), respectively. """ # First initialize unc_pmfs from self unc_pmfs = self.unc_pmfs # We first save the nominal settings, since we will be overwriting # the floris wind conditions and yaw angles to include all # probablistic conditions. wd_array_nominal = self.fi.floris.flow_field.wind_directions yaw_angles_nominal = self.fi.floris.farm.yaw_angles # Expand wind direction and yaw angle array into the direction # of uncertainty over the ambient wind direction. wd_array_probablistic = np.vstack( [np.expand_dims(wd_array_nominal, axis=0) + dy for dy in unc_pmfs["wd_unc"]] ) if self.fix_yaw_in_relative_frame: # The relative yaw angle is fixed and always has the nominal # value (e.g., 0 deg) when evaluating uncertainty. Evaluating # wind direction uncertainty like this would essentially come # down to a Gaussian smoothing of FLORIS solutions over the # wind directions. This can also be really fast, since it would # not require any additional calculations compared to the # non-uncertainty FLORIS evaluation. yaw_angles_probablistic = np.vstack( [np.expand_dims(yaw_angles_nominal, axis=0) for _ in unc_pmfs["wd_unc"]] ) else: # Fix yaw angles in the absolute (compass) reference frame, # meaning that for each probablistic wind direction evaluation, # our probablistic (relative) yaw angle evaluated goes into # the opposite direction. For example, a probablistic wind # direction 3 deg above the nominal value means that we evaluate # it with a relative yaw angle that is 3 deg below its nominal # value. yaw_angles_probablistic = np.vstack( [np.expand_dims(yaw_angles_nominal, axis=0) - dy for dy in unc_pmfs["wd_unc"]] ) self.wd_array_probablistic = wd_array_probablistic self.yaw_angles_probablistic = yaw_angles_probablistic def _reassign_yaw_angles(self, yaw_angles=None): # Overwrite the yaw angles in the FlorisInterface object if yaw_angles is not None: self.fi.floris.farm.yaw_angles = yaw_angles # Public methods def copy(self): """Create an independent copy of the current UncertaintyInterface object""" fi_unc_copy = copy.deepcopy(self) fi_unc_copy.fi = self.fi.copy() return fi_unc_copy def reinitialize_uncertainty( self, unc_options=None, unc_pmfs=None, fix_yaw_in_relative_frame=None ): """Reinitialize the wind direction and yaw angle probability distributions used in evaluating FLORIS. Must either specify 'unc_options', in which case distributions are calculated assuming a Gaussian distribution, or `unc_pmfs` must be specified directly assigning the probability distribution functions. Args: unc_options (dictionary, optional): A dictionary containing values used to create normally-distributed, zero-mean probability mass functions describing the distribution of wind direction and yaw position deviations when wind direction and/or yaw position uncertainty is included. This argument is only used when **unc_pmfs** is None and contains the following key-value pairs: - **std_wd** (*float*): A float containing the standard deviation of the wind direction deviations from the original wind direction. - **std_yaw** (*float*): A float containing the standard deviation of the yaw angle deviations from the original yaw angles. - **pmf_res** (*float*): A float containing the resolution in degrees of the wind direction and yaw angle PMFs. - **pdf_cutoff** (*float*): A float containing the cumulative distribution function value at which the tails of the PMFs are truncated. Defaults to None. unc_pmfs (dictionary, optional): A dictionary containing optional probability mass functions describing the distribution of wind direction and yaw position deviations when wind direction and/or yaw position uncertainty is included in the power calculations. Contains the following key-value pairs: - **wd_unc** (*np.array*): Wind direction deviations from the original wind direction. - **wd_unc_pmf** (*np.array*): Probability of each wind direction deviation in **wd_unc** occuring. - **yaw_unc** (*np.array*): Yaw angle deviations from the original yaw angles. - **yaw_unc_pmf** (*np.array*): Probability of each yaw angle deviation in **yaw_unc** occuring. Defaults to None. fix_yaw_in_relative_frame (bool, optional): When set to True, the relative yaw angle of all turbines is fixed and always has the nominal value (e.g., 0 deg) when evaluating uncertainty in the wind direction. Evaluating wind direction uncertainty like this will essentially come down to a Gaussian smoothing of FLORIS solutions over the wind directions. This calculation can therefore be really fast, since it does not require additional calculations compared to a non-uncertainty FLORIS evaluation. When fix_yaw_in_relative_frame=False, the yaw angles are fixed in the absolute (compass) reference frame, meaning that for each probablistic wind direction evaluation, our probablistic (relative) yaw angle evaluated goes into the opposite direction. For example, a probablistic wind direction 3 deg above the nominal value means that we evaluate it with a relative yaw angle that is 3 deg below its nominal value. This requires additional computations compared to a non- uncertainty evaluation. Typically, fix_yaw_in_relative_frame=True is used when comparing FLORIS to historical data, in which a single measurement usually represents a 10-minute average, and thus is often a mix of various true wind directions. The inherent assumption then is that the turbine perfectly tracks the wind direction changes within those 10 minutes. Then, fix_yaw_in_relative_frame=False is typically used for robust yaw angle optimization, in which we take into account that the turbine often does not perfectly know the true wind direction, and that a turbine often does not perfectly achieve its desired yaw angle offset. Defaults to fix_yaw_in_relative_frame=False. """ # Check inputs if ((unc_options is not None) and (unc_pmfs is not None)): self.logger.error( "Must specify either 'unc_options' or 'unc_pmfs', not both." ) # Assign uncertainty probability distributions if unc_options is not None: self.unc_options = unc_options self._generate_pdfs_from_dict() if unc_pmfs is not None: self.unc_pmfs = unc_pmfs if fix_yaw_in_relative_frame is not None: self.fix_yaw_in_relative_frame = bool(fix_yaw_in_relative_frame) def reinitialize( self, wind_speeds=None, wind_directions=None, wind_shear=None, wind_veer=None, reference_wind_height=None, turbulence_intensity=None, air_density=None, layout=None, turbine_type=None, solver_settings=None, ): """Pass to the FlorisInterface reinitialize function. To allow users to directly replace a FlorisInterface object with this UncertaintyInterface object, this function is required.""" # Just passes arguments to the floris object self.fi.reinitialize( wind_speeds=wind_speeds, wind_directions=wind_directions, wind_shear=wind_shear, wind_veer=wind_veer, reference_wind_height=reference_wind_height, turbulence_intensity=turbulence_intensity, air_density=air_density, layout=layout, turbine_type=turbine_type, solver_settings=solver_settings, ) def calculate_wake(self, yaw_angles=None): """Replaces the 'calculate_wake' function in the FlorisInterface object. Fundamentally, this function only overwrites the nominal yaw angles in the FlorisInterface object. The actual wake calculations are performed once 'get_turbine_powers' or 'get_farm_powers' is called. However, to allow users to directly replace a FlorisInterface object with this UncertaintyInterface object, this function is required. Args: yaw_angles: NDArrayFloat | list[float] | None = None, """ self._reassign_yaw_angles(yaw_angles) def get_turbine_powers(self, no_wake=False): """Calculates the probability-weighted power production of each turbine in the wind farm. Args: no_wake (bool, optional): disable the wakes in the flow model. This can be useful to determine the (probablistic) power production of the farm in the artificial scenario where there would never be any wake losses. Defaults to False. Returns: NDArrayFloat: Power production of all turbines in the wind farm. This array has the shape (num_wind_directions, num_wind_speeds, num_turbines). """ # To include uncertainty, we expand the dimensionality # of the problem along the wind direction pdf and/or yaw angle # pdf. We make use of the vectorization of FLORIS to # evaluate all conditions in a single call, rather than in # loops. Therefore, the effective number of wind conditions and # yaw angle combinations we evaluate expands. unc_pmfs = self.unc_pmfs self._expand_wind_directions_and_yaw_angles() # Get dimensions of nominal conditions wd_array_nominal = self.fi.floris.flow_field.wind_directions num_wd = self.fi.floris.flow_field.n_wind_directions num_ws = self.fi.floris.flow_field.n_wind_speeds num_wd_unc = len(unc_pmfs["wd_unc"]) num_turbines = self.fi.floris.farm.n_turbines # Format into conventional floris format by reshaping wd_array_probablistic = np.reshape(self.wd_array_probablistic, -1) yaw_angles_probablistic = np.reshape( self.yaw_angles_probablistic, (-1, num_ws, num_turbines) ) # Wrap wind direction array around 360 deg wd_array_probablistic = wrap_360(wd_array_probablistic) # Find minimal set of solutions to evaluate wd_exp = np.tile(wd_array_probablistic, (1, num_ws, 1)).T _, id_unq, id_unq_rev = np.unique( np.append(yaw_angles_probablistic, wd_exp, axis=2), axis=0, return_index=True, return_inverse=True ) wd_array_probablistic_min = wd_array_probablistic[id_unq] yaw_angles_probablistic_min = yaw_angles_probablistic[id_unq, :, :] # Evaluate floris for minimal probablistic set self.fi.reinitialize(wind_directions=wd_array_probablistic_min) self.fi.calculate_wake( yaw_angles=yaw_angles_probablistic_min, no_wake=no_wake ) # Retrieve all power productions using the nominal call turbine_powers = self.fi.get_turbine_powers() self.fi.reinitialize(wind_directions=wd_array_nominal) # Reshape solutions back to full set power_probablistic = turbine_powers[id_unq_rev, :] power_probablistic = np.reshape( power_probablistic, (num_wd_unc, num_wd, num_ws, num_turbines) ) # Calculate probability weighing terms wd_weighing = ( np.expand_dims(unc_pmfs["wd_unc_pmf"], axis=(1, 2, 3)) ).repeat(num_wd, 1).repeat(num_ws, 2).repeat(num_turbines, 3) # Now apply probability distribution weighing to get turbine powers return np.sum(wd_weighing * power_probablistic, axis=0) def get_farm_power(self, no_wake=False): """Calculates the probability-weighted power production of the collective of all turbines in the farm, for each wind direction and wind speed specified. Args: no_wake (bool, optional): disable the wakes in the flow model. This can be useful to determine the (probablistic) power production of the farm in the artificial scenario where there would never be any wake losses. Defaults to False. Returns: NDArrayFloat: Expectation of power production of the wind farm. This array has the shape (num_wind_directions, num_wind_speeds). """ turbine_powers = self.get_turbine_powers(no_wake=no_wake) return np.sum(turbine_powers, axis=2) # Define getter functions that just pass information from FlorisInterface @property def floris(self): return self.fi.floris @property def layout_x(self): return self.fi.layout_x @property def layout_y(self): return self.fi.layout_y
from floris.tools import FlorisInterface from floris.tools.visualization import visualize_cut_plane from floris.tools.visualization import plot_rotor_values """ This example initializes the FLORIS software, and then uses internal functions to run a simulation and plot the results. In this case, we are plotting three slices of the resulting flow field: 1. Horizontal slice parallel to the ground and located at the hub height 2. Vertical slice of parallel with the direction of the wind 3. Veritical slice parallel to to the turbine disc plane """ # Initialize FLORIS with the given input file via FlorisInterface. # For basic usage, FlorisInterface provides a simplified and expressive # entry point to the simulation routines. fi = FlorisInterface("inputs/gch.yaml") # The rotor plots show what is happening at each turbine, but we do not # see what is happening between each turbine. For this, we use a # grid that has points regularly distributed throughout the fluid domain. # The FlorisInterface contains functions for configuring the new grid, # running the simulation, and generating plots of 2D slices of the # flow field. # Note this visualization grid created within the calculate_horizontal_plane function will be reset # to what existed previously at the end of the function # Using the FlorisInterface functions, get 2D slices. horizontal_plane = fi.calculate_horizontal_plane(x_resolution=200, y_resolution=100, height=90.0)
from floris.tools import FlorisInterface import floris.tools.optimization.pyoptsparse as opt """ This example shows a simple layout optimization using the python module pyOptSparse. A 4 turbine array is optimized such that the layout of the turbine produces the highest annual energy production (AEP) based on the given wind resource. The turbines are constrained to a square boundary and a randomw wind resource is supplied. The results of the optimization show that the turbines are pushed to the outer corners of the boundary, which makes sense in order to maximize the energy production by minimizing wake interactions. """ # Initialize the FLORIS interface fi file_dir = os.path.dirname(os.path.abspath(__file__)) fi = FlorisInterface('inputs/gch.yaml') # Setup 72 wind directions with a random wind speed and frequency distribution wind_directions = np.arange(0, 360.0, 5.0) np.random.seed(1) wind_speeds = 8.0 + np.random.randn(1) * 0.5 freq = np.abs(np.sort(np.random.randn(len(wind_directions)))) freq = freq / freq.sum() fi.reinitialize(wind_directions=wind_directions, wind_speeds=wind_speeds) # The boundaries for the turbines, specified as vertices boundaries = [(0.0, 0.0), (0.0, 1000.0), (1000.0, 1000.0), (1000.0, 0.0), (0.0, 0.0)] # Set turbine locations to 4 turbines in a rectangle D = 126.0 # rotor diameter for the NREL 5MW
# Set up the visualization plot fig_viz, axarr_viz = plt.subplots(num_models_to_viz, 2) # Set up the turbine power plot fig_turb_pow, ax_turb_pow = plt.subplots() # Set up a list to save the farm power results farm_power_results = [] # Now complete all these plots in a loop for fm in floris_models: # Analyze the base case================================================== print('Loading: ', fm) fi = FlorisInterface("inputs/%s.yaml" % fm) # Set the layout, wind direction and wind speed fi.reinitialize(layout=(X, Y), wind_speeds=[wind_speed], wind_directions=[wind_direction], turbulence_intensity=turbulence_intensity) fi.calculate_wake(yaw_angles=yaw_angles_base) turbine_powers = fi.get_turbine_powers() / 1000. ax_turb_pow.plot(turbine_labels, turbine_powers.flatten(), color=color_dict[fm], ls='-', marker='s', label='%s - baseline' % fm)
import numpy as np from floris.tools import FlorisInterface """ This example creates a FLORIS instance 1) Makes a two-turbine layout 2) Demonstrates single ws/wd simulations 3) Demonstrates mulitple ws/wd simulations Main concept is introduce FLORIS and illustrate essential structure of most-used FLORIS calls """ # Initialize FLORIS with the given input file via FlorisInterface. # For basic usage, FlorisInterface provides a simplified and expressive # entry point to the simulation routines. fi = FlorisInterface("inputs/gch.yaml") # Convert to a simple two turbine layout fi.reinitialize(layout=([0, 500.], [0., 0.])) # Single wind speed and wind direction print( '\n============================= Single Wind Direction and Wind Speed =============================' ) # Get the turbine powers assuming 1 wind speed and 1 wind direction fi.reinitialize(wind_directions=[270.], wind_speeds=[8.0]) # Set the yaw angles to 0 yaw_angles = np.zeros([1, 1, 2]) # 1 wind direction, 1 wind speed, 2 turbines fi.calculate_wake(yaw_angles=yaw_angles)
from floris.tools import FlorisInterface, UncertaintyInterface """ This example demonstrates how one can create an "UncertaintyInterface" object, which adds uncertainty on the inflow wind direction on the FlorisInterface class. The UncertaintyInterface class is interacted with in the exact same manner as the FlorisInterface class is. This example demonstrates how the wind farm power production is calculated with and without uncertainty. Other use cases of UncertaintyInterface are, e.g., comparing FLORIS to historical SCADA data and robust optimization. """ # Instantiate FLORIS using either the GCH or CC model fi = FlorisInterface("inputs/gch.yaml") # GCH model fi_unc = UncertaintyInterface("inputs/gch.yaml") # Add uncertainty with default settings # Define a two turbine farm D = 126.0 layout_x = np.array([0, D*6, D*12]) layout_y = [0, 0, 0] wd_array = np.arange(0.0, 360.0, 1.0) fi.reinitialize(layout=[layout_x, layout_y], wind_directions=wd_array) fi_unc.reinitialize(layout=[layout_x, layout_y], wind_directions=wd_array) # Define a matrix of yaw angles to be all 0 # Note that yaw angles is now specified as a matrix whose dimesions are # wd/ws/turbine num_wd = len(wd_array) # Number of wind directions num_ws = 1 # Number of wind speeds
# See https://floris.readthedocs.io for documentation import matplotlib.pyplot as plt import numpy as np from floris.tools import FlorisInterface from floris.tools.visualization import visualize_cut_plane """ This example uses an input file where multiple turbine types are defined. The first two turbines are the NREL 5MW, and the third turbine is the IEA 10MW. """ # Initialize FLORIS with the given input file via FlorisInterface. # For basic usage, FlorisInterface provides a simplified and expressive # entry point to the simulation routines. fi = FlorisInterface("inputs/gch_multiple_turbine_types.yaml") # Using the FlorisInterface functions for generating plots, run FLORIS # and extract 2D planes of data. horizontal_plane = fi.calculate_horizontal_plane(x_resolution=200, y_resolution=100, height=90) y_plane = fi.calculate_y_plane(x_resolution=200, z_resolution=100, crossstream_dist=0.0) cross_plane = fi.calculate_cross_plane(y_resolution=100, z_resolution=100, downstream_dist=500.0) # Create the plots fig, ax_list = plt.subplots(3, 1, figsize=(10, 8))