Beispiel #1
0
    def resample_wind_direction(self, df, wd=np.arange(0, 360, 5.)):
        """
        Modify the default bins for sorting wind direction.

        Args:
            df (pd.DataFrame): Wind direction data
                wd (np.array, optional): Vector of wind direction bins
                for WindRose. Defaults to np.arange(0, 360, 5.).

        Returns:
            df (pd.DataFrame): Resampled wind direction for WindRose
        """
        # Make a copy of incoming dataframe
        df = df.copy(deep=True)

        # Get the wind step
        wd_step = wd[1] - wd[0]

        # Get bin edges
        wd_edges = (wd - wd_step / 2.0)
        wd_edges = np.append(wd_edges, np.array(wd[-1] + wd_step / 2.0))

        # Get the overhangs
        negative_overhang = wd_edges[0]
        positive_overhang = wd_edges[-1] - 360.

        # Need potentially to wrap high angle direction to negative for correct binning
        df['wd'] = geo.wrap_360(df.wd)
        if negative_overhang < 0:
            print('Correcting negative Overhang:%.1f' % negative_overhang)
            df['wd'] = np.where(df.wd.values >= 360. + negative_overhang,
                                df.wd.values - 360., df.wd.values)

        # Check on other side
        if positive_overhang > 0:
            print('Correcting positive Overhang:%.1f' % positive_overhang)
            df['wd'] = np.where(df.wd.values <= positive_overhang,
                                df.wd.values + 360., df.wd.values)

        # Cut into bins
        df['wd'] = pd.cut(df.wd, wd_edges, labels=wd)

        # Regroup
        df = df.groupby(['ws', 'wd']).sum()

        # Fill nans
        df = df.fillna(0)

        # Reset the index
        df = df.reset_index()

        # Set to float Re-wrap
        df['wd'] = df.wd.astype(float)
        df['ws'] = df.ws.astype(float)
        df['wd'] = geo.wrap_360(df.wd)

        return df
Beispiel #2
0
    def parse_wind_toolkit_folder(self,
                                  folder_name,
                                  wd=np.arange(0, 360, 5.),
                                  ws=np.arange(0, 26, 1.),
                                  limit_month=None):
        """
        Load and

        Args:
            folder_name (str): path to wind toolkit input data.
            wd (np.array, optional): Wind direction bin limits.
                Defaults to np.arange(0, 360, 5.).
            ws (np.array, optional): Wind speed bin limits.
                Defaults to np.arange(0, 26, 1.).
            limit_month (str, optional): name of month(s) to consider.
                Defaults to None.

        Returns:
            df (pd.DataFrame): DataFrame updated with wind toolkit info.
        """
        # Load the wind toolkit data into a dataframe
        df = self.load_wind_toolkit_folder(folder_name,
                                           limit_month=limit_month)

        # Start by simply round and wrapping the wind direction and wind speed columns
        df['wd'] = geo.wrap_360(df.wd.round())
        df['ws'] = geo.wrap_360(df.ws.round())

        # Now group up
        df['freq_val'] = 1.
        df = df.groupby(['ws', 'wd']).sum()
        df['freq_val'] = df.freq_val.astype(float) / df.freq_val.sum()
        df = df.reset_index()

        # Save the df at this point
        self.df = df

        # Resample onto the provided wind speed and wind direction binnings
        self.internal_resample_wind_speed(ws=ws)
        self.internal_resample_wind_direction(wd=wd)

        return self.df
Beispiel #3
0
def test_wrap_360():
    assert wrap_360(0.0) == 0.0
    assert wrap_360(360.0) == 0.0
    assert wrap_360(-1.0) == 359.0
    assert wrap_360(1.0) == 1.0
    assert wrap_360(359.0) == 359.0
    assert wrap_360(361.0) == 1.0
Beispiel #4
0
    def import_from_wind_toolkit_hsds(self,
                                      lat,
                                      lon,
                                      ht=100,
                                      wd=np.arange(0, 360, 5.),
                                      ws=np.arange(0, 26, 1.),
                                      limit_month=None,
                                      st_date=None,
                                      en_date=None):
        """
        Given a lat/long coordinate in the continental US return a
        dataframe containing the normalized frequency of each pair of
        wind speed and wind direction values specified. The wind data
        is obtained from the WIND Toolkit dataset (https://www.nrel.gov/
        grid/wind-toolkit.html) using the HSDS service (see https://
        github.com/NREL/hsds-examples). The wind data returned is
        obtained from the nearest 2km x 2km grid point to the input
        coordinate and is limited to the years 2007-2013.

        Requires h5pyd package, which can be installed using:
            pip install --user git+http://github.com/HDFGroup/h5pyd.git

        Then, make a configuration file at ~/.hscfg containing:

            hs_endpoint = https://developer.nrel.gov/api/hsds/
            hs_username = None
            hs_password = None
            hs_api_key = 3K3JQbjZmWctY0xmIfSYvYgtIcM3CN0cb1Y2w9bf

        The example API key above is for demonstation and is
        rate-limited per IP. To get your own API key, visit https://
        developer.nrel.gov/signup/

        More information can be found at: https://github.com/NREL/
        hsds-examples

        Args:
            lat (float): coordinates in degrees
            lon (float): coordinates in degrees
            ht (int, optional): height above ground where wind
                information is obtained (m). Defaults to 100.
            wd (np.array, optional): Wind direction bin limits.
                Defaults to np.arange(0, 360, 5.).
            ws (np.array, optional): Wind speed bin limits.
                Defaults to np.arange(0, 26, 1.).
            limit_month (str, optional): limit loaded data to specified
                month. Defaults to None.
            st_date (str, optional): 'MM-DD-YYYY'.
                Defaults to None.
            en_date (str, optional): 'MM-DD-YYYY'.
                Defaults to None.

        Returns:
            df (pd.DataFrame): DataFrame with wind speed and direction
                data.
        """

        # check inputs
        if st_date is not None:
            if dateutil.parser.parse(st_date) > dateutil.parser.parse(
                    '12-13-2013 23:00'):
                print(
                    'Error, invalid date range. Valid range: 01-01-2007 - 12/31/2013'
                )
                return None
        if en_date is not None:
            if dateutil.parser.parse(en_date) < dateutil.parser.parse(
                    '01-01-2007 00:00'):
                print(
                    'Error, invalid date range. Valid range: 01-01-2007 - 12/31/2013'
                )
                return None

        if ht not in [10, 40, 60, 80, 100, 120, 140, 160, 200]:
            print(
                'Error, invalid height. Valid heights: 10, 40, 60, 80, 100, 120, 140, 160, 200'
            )
            return None

        # load wind speeds and directions from WIND Toolkit
        df = self.load_wind_toolkit_hsds(lat,
                                         lon,
                                         ht=ht,
                                         limit_month=limit_month,
                                         st_date=st_date,
                                         en_date=en_date)

        # check for errors in loading wind toolkit data
        if df is None:
            return None

        # Start by simply round and wrapping the wind direction and wind speed columns
        df['wd'] = geo.wrap_360(df.wd.round())
        df['ws'] = geo.wrap_360(df.ws.round())

        # Now group up
        df['freq_val'] = 1.
        df = df.groupby(['ws', 'wd']).sum()
        df['freq_val'] = df.freq_val.astype(float) / df.freq_val.sum()
        df = df.reset_index()

        # Save the df at this point
        self.df = df

        # Resample onto the provided wind speed and wind direction binnings
        self.internal_resample_wind_speed(ws=ws)
        self.internal_resample_wind_direction(wd=wd)

        return df
Beispiel #5
0
    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)
Beispiel #6
0
def process_NoiseLabSlice_file(
        filename,
        root_dir_noiselab='/Volumes/Aeroacoustics/_ProcessedData/SliceData',
        root_dir_tdms='/Volumes/Tests/_raw data/Slow',
        dataset_file='aeroacoustics_dataset.p',
        third_octave=True,
        unweighted=False):
    """Read a noiseLAB slice file, combine it with turbine and met data and 
    append to the master dataset

    inputs:
    filename: noiseLAB slice file to process
    root_dir_noiselab: directory where noiseLAB slice files are saved
    root_dir_tdms: directory where SCADA tdms files are stored
    dataset_file: name of file containing master dataset
    """

    if third_octave:
        if unweighted:
            df_nl = rd.read_NoiseLabSlice_file(os.path.join(
                root_dir_noiselab, filename),
                                               unweighted=unweighted)
        else:
            df_nl = rd.read_NoiseLabSlice_file(
                os.path.join(root_dir_noiselab, filename))
    else:
        df_nl = rd.read_NoiseLabFFTSlice_file(
            os.path.join(root_dir_noiselab, filename))

    # DEBUGGING, REMOVE!!!
    # df_nl.time = df_nl.time+pd.DateOffset(days=2)

    # drop clip name column, not needed. TODO: just remove this column in the reader function?
    df_nl.drop(columns=['Clip Name'], inplace=True)

    # group variables by mic name, set time as index
    df_new = df_nl.set_index(['time', 'mic'])
    df_new = df_new.unstack()
    df_new.columns = ['_'.join(col[::-1]) for col in df_new.columns.values]
    df_new.sort_index(inplace=True)

    # load tdms SCADA files covering the noiseLAB data time period
    # TODO: It seems the previous day's folder is more likely to contain the next day's data.
    # See if this needs to be updated later
    tdms_dir = os.path.join(root_dir_tdms,
                            (df_new.index[0] -
                             pd.DateOffset(days=1)).strftime('%Y-%m-%d'))
    files = os.listdir(tdms_dir)
    files = [f for f in files if 'tdms_index' not in f]

    df_sc = pd.DataFrame()
    for f in files:
        df_sc = df_sc.append(rd.read_tdmsSCADA_file(os.path.join(tdms_dir, f)))

    # do we need to load previous day?
    if df_sc.time.min() > df_new.index.min():
        tdms_dir = os.path.join(root_dir_tdms,
                                (df_new.index[0] -
                                 pd.DateOffset(days=2)).strftime('%Y-%m-%d'))

        files = os.listdir(tdms_dir)
        files = [f for f in files if 'tdms_index' not in f]

        for f in files:
            df_sc = df_sc.append(
                rd.read_tdmsSCADA_file(os.path.join(tdms_dir, f)))

    # do we need to load next couple days?
    for i in [0, 1]:
        if df_sc.time.max() + pd.DateOffset(seconds=10) <= df_new.index.max():
            tdms_dir = os.path.join(
                root_dir_tdms,
                (df_new.index[0] + pd.DateOffset(days=i)).strftime('%Y-%m-%d'))

            if os.path.isdir(tdms_dir):
                files = os.listdir(tdms_dir)
                files = [f for f in files if 'tdms_index' not in f]

                for f in files:
                    df_sc = df_sc.append(
                        rd.read_tdmsSCADA_file(os.path.join(tdms_dir, f)))

    df_sc.drop_duplicates('time', inplace=True)
    df_sc.set_index('time', inplace=True)
    df_sc.sort_index(inplace=True)

    df_sc = df_sc[df_sc.index >= df_new.index[0]]

    # resample to 10 seconds w/ circular averaging for wind directions
    # columns to circularly average
    cols_circ = [
        'WD1_87m', 'Wind_Direction_38m', 'Yaw_Encoder',
        'EGD_Yaw_PositionToNorth', 'Yaw_Offset_Cmd'
    ]

    for col in cols_circ:
        df_sc[col + '_cos'] = np.cos(np.radians(df_sc[col]))
        df_sc[col + '_sin'] = np.sin(np.radians(df_sc[col]))

    df_sc = df_sc.resample('10s', base=df_new.index[0].second).mean()

    for col in [c for c in cols_circ if c != 'Yaw_Offset_Cmd']:
        df_sc[col] = geo.wrap_360(
            np.degrees(np.arctan2(df_sc[col + '_sin'], df_sc[col + '_cos'])))

    col = 'Yaw_Offset_Cmd'
    df_sc[col] = geo.wrap_180(
        np.degrees(np.arctan2(df_sc[col + '_sin'], df_sc[col + '_cos'])))

    cols_vane = ['WD_Nacelle', 'WD_Nacelle_Mod']
    for col in cols_vane:
        df_sc[col] = df_sc[col] - 180.

    df_sc.drop(columns=[
        col for col in df_sc.columns if (('_cos' in col) | ('_sin' in col))
    ],
               inplace=True)

    # trim to only same times as in df
    df_sc = df_sc[df_sc.index <= df_new.index[-1]]

    # combine df and df_sc
    df_new = pd.concat([df_new, df_sc], axis=1)

    # append to existing dataset
    df = pd.read_pickle(dataset_file)
    df = df.append(df_new)
    df = df.loc[~df.index.duplicated(keep='first')]
    df.sort_index(inplace=True)
    df.to_pickle(dataset_file)

    return 1