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
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
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
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
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 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