Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
    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()
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
    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,
        )
Ejemplo n.º 5
0
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
Ejemplo n.º 7
0
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)
Ejemplo n.º 9
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
Ejemplo n.º 10
0
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))
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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)
Ejemplo n.º 13
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
Ejemplo n.º 14
0
# 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)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
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))