From 1427fa60a76aa82ff6df7a42f28defb4e51e138e Mon Sep 17 00:00:00 2001 From: Eric Simley Date: Fri, 8 Nov 2024 09:52:10 -0700 Subject: [PATCH] adding functions and tests to set wind direction by upstream turbines --- .../dataframe_manipulations.py | 119 ++++++++++++++++++ tests/dataframe_manipulations_test.py | 12 ++ 2 files changed, 131 insertions(+) diff --git a/flasc/data_processing/dataframe_manipulations.py b/flasc/data_processing/dataframe_manipulations.py index 04229a4b..6943b6ee 100644 --- a/flasc/data_processing/dataframe_manipulations.py +++ b/flasc/data_processing/dataframe_manipulations.py @@ -370,6 +370,59 @@ def set_wd_by_all_turbines( return _set_col_by_turbines("wd", "wd", df, "all", True) +def set_wd_by_upstream_turbines( + df: Union[pd.DataFrame, FlascDataFrame], df_upstream, exclude_turbs=[] +) -> Union[pd.DataFrame, FlascDataFrame]: + """Add wind direction column using upstream turbines. + + Add a column called 'wd' in your dataframe with value equal + to the averaged wind direction measurements of all the turbines + upstream, excluding the turbines listed in exclude_turbs. As an + intermediate step, the average wind direction over all turbines + is used to determine the set of upstream turbines from which the + final wind direction signal is derived. + + Args: + df (pd.DataFrame | FlascDataFrame): Dataframe with measurements. This dataframe + typically consists of wd_%03d, ws_%03d, ti_%03d, pow_%03d, and + potentially additional measurements. + df_upstream (pd.DataFrame): Dataframe containing rows indicating + wind direction ranges and the corresponding upstream turbines for + that wind direction range. This variable can be generated with + flasc.utilities.floris_tools.get_upstream_turbs_floris(...). + exclude_turbs ([list, array]): array-like variable containing + turbine indices that should be excluded in determining the column + mean quantity. + exclude_turbs ([list, array]): array-like variable containing + turbine indices that should be excluded in determining the column + mean quantity. + + Returns: + pd.Dataframe | FlascDataFrame: Dataframe which equals the inserted dataframe + plus the additional column called 'wd'. + """ + + # First, set wind direction using all turbines + df = set_wd_by_all_turbines(df) + + # Use the farm-average wind direction to determine a new wind direction signal + # using only upstream turbines + df = _set_col_by_upstream_turbines( + col_out="wd_upstream", + col_prefix="wd", + df=df, + df_upstream=df_upstream, + circular_mean=True, + exclude_turbs=exclude_turbs, + ) + + df = df.drop(columns=["wd"]) + + df = df.rename(columns={"wd_upstream": "wd"}) + + return df + + def set_wd_by_radius_from_turbine( df: Union[pd.DataFrame, FlascDataFrame], turb_no: int, @@ -415,6 +468,72 @@ def set_wd_by_radius_from_turbine( ) +def set_wd_by_upstream_turbines_in_radius( + df: Union[pd.DataFrame, FlascDataFrame], + df_upstream: pd.DataFrame, + turb_no: int, + x_turbs: List[float], + y_turbs: List[float], + max_radius: float, + include_itself: bool = True, +) -> Union[pd.DataFrame, FlascDataFrame]: + """Add wind direction column using in-radius upstream turbines. + + Add a column called 'wd' to your dataframe, which is the + mean of the columns wd_%03d for turbines that are upstream and + also within radius [max_radius] of the turbine of interest + [turb_no]. As an intermediate step, the average wind direction + over all turbines is used to determine the set of upstream turbines + from which the final wind direction signal is derived. + + Args: + df (pd.DataFrame | FlascDataFrame): Dataframe with measurements. This dataframe + typically consists of wd_%03d, ws_%03d, ti_%03d, pow_%03d, and + potentially additional measurements. + df_upstream (pd.DataFrame): Dataframe containing rows indicating + wind direction ranges and the corresponding upstream turbines for + that wind direction range. This variable can be generated with + flasc.utilities.floris_tools.get_upstream_turbs_floris(...). + turb_no (int): Turbine number from which the radius should be calculated. + x_turbs ([list, array]): Array containing x locations of turbines. + y_turbs ([list, array]): Array containing y locations of turbines. + max_radius (float): Maximum radius for the upstream turbines + until which they are still considered as relevant/used for the + calculation of the averaged column quantity. + include_itself (bool, optional): Include the measurements of turbine + turb_no in the determination of the averaged column quantity. Defaults + to False. + + Returns: + pd.Dataframe | FlascDataFrame: Dataframe which equals the inserted dataframe + plus the additional column called 'wd'. + """ + + # First, set wind direction using all turbines + df = set_wd_by_all_turbines(df) + + # Use the farm-average wind direction to determine a new wind direction signal + # using only upstream turbines within radius + df = _set_col_by_upstream_turbines_in_radius( + col_out="wd_upstream", + col_prefix="wd", + df=df, + df_upstream=df_upstream, + turb_no=turb_no, + x_turbs=x_turbs, + y_turbs=y_turbs, + max_radius=max_radius, + circular_mean=True, + include_itself=include_itself, + ) + + df = df.drop(columns=["wd"]) + + df = df.rename(columns={"wd_upstream": "wd"}) + + return df + + def set_ws_by_turbines( df: Union[pd.DataFrame, FlascDataFrame], turbine_numbers: List[int] ) -> Union[pd.DataFrame, FlascDataFrame]: diff --git a/tests/dataframe_manipulations_test.py b/tests/dataframe_manipulations_test.py index 65ad27d9..b833441b 100644 --- a/tests/dataframe_manipulations_test.py +++ b/tests/dataframe_manipulations_test.py @@ -67,9 +67,11 @@ def test_set_by_upstream_turbines(self): df_test = dfm.set_wd_by_all_turbines(df_test) df_test = dfm.set_ws_by_upstream_turbines(df_test, df_upstream) df_test = dfm.set_ti_by_upstream_turbines(df_test, df_upstream) + df_test = dfm.set_wd_by_upstream_turbines(df_test, df_upstream) self.assertAlmostEqual(df_test.loc[0, "ws"], np.mean([5.0, 17.0])) self.assertAlmostEqual(df_test.loc[0, "ti"], np.mean([0.03, 0.09])) + self.assertAlmostEqual(df_test.loc[0, "wd"], circmean([350.0, 3.0], high=360.0)) def test_set_by_upstream_turbines_in_radius(self): # Test set_*_by_upstream_turbines_in_radius functions @@ -104,10 +106,20 @@ def test_set_by_upstream_turbines_in_radius(self): max_radius=1000, include_itself=True, # Include itself ) + df_test = dfm.set_wd_by_upstream_turbines_in_radius( + df_test, + df_upstream, + turb_no=1, + x_turbs=np.array([0.0, 500.0, 1000.0, 1500.0]), + y_turbs=np.array([0.0, 500.0, 1000.0, 1500.0]), + max_radius=1000, + include_itself=False, # Include itself + ) self.assertAlmostEqual(df_test.loc[0, "ws"], np.mean([5.0, 17.0])) self.assertAlmostEqual(df_test.loc[0, "ti"], np.mean([0.09])) self.assertAlmostEqual(df_test.loc[0, "pow_ref"], np.mean([1500.0, 1800.0])) + self.assertAlmostEqual(df_test.loc[0, "wd"], circmean([350.0], high=360.0)) def test_is_day_or_night(self): # Test is day night using noon and midnight Oct 1 2023 in London, UK