From 972c807e315484a8cd8920bf10e427bd39fa5f68 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 23 Feb 2023 16:07:53 -0700 Subject: [PATCH 1/9] First pass code for determining impacting turbines. --- flasc/floris_tools.py | 119 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/flasc/floris_tools.py b/flasc/floris_tools.py index 79e8bfb9..76e14899 100644 --- a/flasc/floris_tools.py +++ b/flasc/floris_tools.py @@ -18,6 +18,7 @@ from pandas.errors import DataError from scipy import interpolate from time import perf_counter as timerpc +import copy from flasc import utilities as fsut @@ -670,6 +671,124 @@ def yw_lower(x): return df_upstream +def get_impacting_turbines_by_wd(fi_in, test_turbine, + wd_array=np.arange(0., 360., 2.), change_threshold=0., limit_number=None, + ws_test=9.): + """ + Wrapper for get_dependent_turbines_by_wd() that loops over all + turbines in the farm and packages their dependencies as a pandas + dataframe. + + Args: + fi ([floris object]): FLORIS object of the farm of interest. + test_turbine ([int]): Turbine for which dependencies are found. + wd_array ([np.array]): Wind directions at which to determine + dependencies. Defaults to [0, 2, ... , 358]. + change_threshold (float): Fractional change in power needed + to denote a dependency. Defaults to 0. (any change in power + is marked as a dependency) + limit_number (int | NoneType): Number of turbines that a + turbine can depend on. If None, returns all turbines that + impact test_turbine. Defaults to None. + ws_test (float): Wind speed at which FLORIS model is run to + determine dependencies. + + Returns: + dep_indices_by_wd (list): A 2-dimensional list. Each element of + the outer level list, which represents wind direction, + contains a list of the turbines that impact test_turbine for + that wind direction. The second-level list may be empty if + no turbine impacts the test_turbine for that wind direciton + (e.g., the turbine is in the front row). + """ + # Copy fi to a local to not mess with incoming + fi = copy.deepcopy(fi_in) + + # Compute the base power + fi.reinitialize( + wind_speeds=[ws_test], + wind_directions=wd_array + ) + fi.calculate_wake() + base_power = fi.get_turbine_powers()[:,0,:] # remove unneeded dimension + + # Compute the test power + fi.floris.farm.turbine_type.pop(test_turbine) # Remove test turbine from list + fi.reinitialize( + layout_x=np.delete(fi.layout_x, [test_turbine]), + layout_y=np.delete(fi.layout_y, [test_turbine]), + wind_speeds=[ws_test], + wind_directions=wd_array + ) # This will reindex the turbines; undone in following steps. + fi.calculate_wake() + test_power = fi.get_turbine_powers()[:,0,:] # remove unneeded dimension + test_power = np.insert(test_power, test_turbine, + base_power[:,test_turbine], axis=1) + + # Find the indices that have changed + dep_indices_by_wd = [None]*len(wd_array) + for i in range(len(wd_array)): + all_influences = np.abs(test_power[i,:] - base_power[i,:])/\ + base_power[i,:] + # Sort with highest influence first; trim to limit_number + influence_order = np.flip(np.argsort(all_influences))[:limit_number] + # Mask to only those that meet the threshold + influence_order = influence_order[ + all_influences[influence_order] > change_threshold + ] + + # Store in output + dep_indices_by_wd[i] = list(influence_order) + + # Remove the turbines own indice + return dep_indices_by_wd + +def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), + change_threshold=0.0, limit_number=None, ws_test=9.): + """ + Wrapper for get_impacting_turbines_by_wd() that loops over all + turbines in the farm and packages their dependencies as a pandas + dataframe. + + Args: + fi ([floris object]): FLORIS object of the farm of interest. + wd_array ([np.array]): Wind directions at which to determine + dependencies. Defaults to [0, 2, ... , 358]. + change_threshold (float): Fractional change in power needed + to denote a dependency. Defaults to 0. (any change in power + is marked as a dependency) + limit_number (int | NoneType): Number of turbines that a + turbine can depend on. If None, returns all turbines that + impact each turbine. Defaults to None. + ws_test (float): Wind speed at which FLORIS model is run to + determine dependencies. + + Returns: + df_out ([pd.DataFrame]): A Pandas Dataframe in which each row + contains a wind direction, each column is a turbine, and + each entry is the turbines that the column turbine depends + on at the row wind direction. Dependencies can be extracted + as: For wind direction wd, turbine T depends on + df_out.loc[wd, T]. + """ + + results = [] + for t_i in range(len(fi_in.layout_x)): + results.append( + get_impacting_turbines_by_wd( + fi_in, t_i, wd_array, change_threshold, limit_number, ws_test + ) + ) + + # Package as a dataframe for return. Dependencies can be extracted as: + # For wind direction wd, turbine T depends on df_out.loc[wd, T]. + df_out = (pd.DataFrame(data=results, columns=wd_array) + .transpose() + .reset_index().rename(columns={"index":"wd"}).set_index("wd") + ) + + return df_out + # Wrapper function to easily set new TI values def _fi_set_ws_wd_ti(fi, wd=None, ws=None, ti=None): From ca66a4549d7ebe717049bb986110afe49f10dfc7 Mon Sep 17 00:00:00 2001 From: misha Date: Fri, 24 Feb 2023 08:29:22 -0700 Subject: [PATCH 2/9] The current functionality produces the turbines that depend on the test_turbine, rather than the other way around as originally written. Renaming functions and updating documentation for clarity. --- flasc/floris_tools.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/flasc/floris_tools.py b/flasc/floris_tools.py index 76e14899..aa27d95c 100644 --- a/flasc/floris_tools.py +++ b/flasc/floris_tools.py @@ -671,13 +671,13 @@ def yw_lower(x): return df_upstream -def get_impacting_turbines_by_wd(fi_in, test_turbine, +def get_dependent_turbines_by_wd(fi_in, test_turbine, wd_array=np.arange(0., 360., 2.), change_threshold=0., limit_number=None, ws_test=9.): """ - Wrapper for get_dependent_turbines_by_wd() that loops over all - turbines in the farm and packages their dependencies as a pandas - dataframe. + Computes all turbines that depend on the operation of a specified + turbine (test_turbine) for each wind direction in wd_array, using + the FLORIS model specified by fi_in to detect dependencies. Args: fi ([floris object]): FLORIS object of the farm of interest. @@ -688,18 +688,18 @@ def get_impacting_turbines_by_wd(fi_in, test_turbine, to denote a dependency. Defaults to 0. (any change in power is marked as a dependency) limit_number (int | NoneType): Number of turbines that a - turbine can depend on. If None, returns all turbines that - impact test_turbine. Defaults to None. + turbine can have as dependencies. If None, returns all + turbines that depend on each turbine. Defaults to None. ws_test (float): Wind speed at which FLORIS model is run to determine dependencies. Returns: dep_indices_by_wd (list): A 2-dimensional list. Each element of the outer level list, which represents wind direction, - contains a list of the turbines that impact test_turbine for - that wind direction. The second-level list may be empty if - no turbine impacts the test_turbine for that wind direciton - (e.g., the turbine is in the front row). + contains a list of the turbines that depend on test_turbine + for that wind direction. The second-level list may be empty + if no turbine depends on the test_turbine for that wind + direciton (e.g., the turbine is in the back row). """ # Copy fi to a local to not mess with incoming fi = copy.deepcopy(fi_in) @@ -743,10 +743,10 @@ def get_impacting_turbines_by_wd(fi_in, test_turbine, # Remove the turbines own indice return dep_indices_by_wd -def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), +def get_all_dependent_turbines(fi_in, wd_array=np.arange(0., 360., 2.), change_threshold=0.0, limit_number=None, ws_test=9.): """ - Wrapper for get_impacting_turbines_by_wd() that loops over all + Wrapper for get_dependent_turbines_by_wd() that loops over all turbines in the farm and packages their dependencies as a pandas dataframe. @@ -758,30 +758,28 @@ def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), to denote a dependency. Defaults to 0. (any change in power is marked as a dependency) limit_number (int | NoneType): Number of turbines that a - turbine can depend on. If None, returns all turbines that - impact each turbine. Defaults to None. + turbine can have as dependencies. If None, returns all + turbines that depend on each turbine. Defaults to None. ws_test (float): Wind speed at which FLORIS model is run to determine dependencies. Returns: df_out ([pd.DataFrame]): A Pandas Dataframe in which each row contains a wind direction, each column is a turbine, and - each entry is the turbines that the column turbine depends - on at the row wind direction. Dependencies can be extracted - as: For wind direction wd, turbine T depends on - df_out.loc[wd, T]. + each entry is the turbines that depend on the column turbine + at the row wind direction. Dependencies can be extracted + as: For wind direction wd, the turbines that depend on + turbine T are df_out.loc[wd, T]. """ results = [] for t_i in range(len(fi_in.layout_x)): results.append( - get_impacting_turbines_by_wd( + get_dependent_turbines_by_wd( fi_in, t_i, wd_array, change_threshold, limit_number, ws_test ) ) - # Package as a dataframe for return. Dependencies can be extracted as: - # For wind direction wd, turbine T depends on df_out.loc[wd, T]. df_out = (pd.DataFrame(data=results, columns=wd_array) .transpose() .reset_index().rename(columns={"index":"wd"}).set_index("wd") From 64d1c410d86501282246538048b3873c9bbb610c Mon Sep 17 00:00:00 2001 From: misha Date: Fri, 24 Feb 2023 09:52:44 -0700 Subject: [PATCH 3/9] Function to extract all turbines that impact all other turbines. --- flasc/floris_tools.py | 72 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/flasc/floris_tools.py b/flasc/floris_tools.py index aa27d95c..363a604f 100644 --- a/flasc/floris_tools.py +++ b/flasc/floris_tools.py @@ -769,7 +769,8 @@ def get_all_dependent_turbines(fi_in, wd_array=np.arange(0., 360., 2.), each entry is the turbines that depend on the column turbine at the row wind direction. Dependencies can be extracted as: For wind direction wd, the turbines that depend on - turbine T are df_out.loc[wd, T]. + turbine T are df_out.loc[wd, T]. Dependencies are ordered, + with strongest dependencies appearing first. """ results = [] @@ -787,6 +788,75 @@ def get_all_dependent_turbines(fi_in, wd_array=np.arange(0., 360., 2.), return df_out +def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), + change_threshold=0.0, ws_test=9., df_dependencies=None): + """ + Calculate which turbines impact a specified turbine based on the + FLORIS model. Essentially a wrapper for get_all_dependent_turbines + and a reordering of the output dataframe. + + Args: + fi ([floris object]): FLORIS object of the farm of interest. + wd_array ([np.array]): Wind directions at which to determine + dependencies. Defaults to [0, 2, ... , 358]. + change_threshold (float): Fractional change in power needed + to denote a dependency. Defaults to 0. (any change in power + is marked as a dependency) + ws_test (float): Wind speed at which FLORIS model is run to + determine dependencies. + df_dependencies (pd.DataFrame): Optional, can be provided to + avoid recalculating the dependencies if they have already + been calculated separately. + + Returns: + df_out ([pd.DataFrame]): A Pandas Dataframe in which each row + contains a wind direction, each column is a turbine, and + each entry is the turbines that the column turbine depends + on at the row wind direction. Dependencies can be extracted + as: For wind direction wd, the turbines that impact turbine + T are df_out.loc[wd, T]. Impacting turbines are simply + ordered numerically, NOT by magnitude of impact. + """ + + if df_dependencies is None: + # Compute dependencies + # Note: No current method of applying a reasonable limit number, + # as impact magnitudes are not stored by get_all_dependent_turbines. + df_dependencies = get_all_dependent_turbines( + fi_in=fi_in, + wd_array=wd_array, + change_threshold=change_threshold, + limit_number=None, + ws_test=ws_test + ) + else: + print("Proceeding using provided dependencies dataframe. If "+\ + "a limit_number was specified when computing df_dependencies, "+\ + "results may not be accurate!" + ) + + # Convert to 3D list for easier looping + dependency_list = df_dependencies.to_numpy() + + # Initialize an new 2D array of empty lists + impacted_by_list = np.empty((len(wd_array), len(fi_in.layout_x)), + dtype=object) + for i in np.ndindex(impacted_by_list.shape): + impacted_by_list[i] = [] + + # Extract impacting turbines + for wd_i in range(len(wd_array)): + for t_u in range(len(fi_in.layout_x)): + for t_d in dependency_list[wd_i][t_u]: + impacted_by_list[wd_i, t_d].append(t_u) + + # Package as dataframe of similar form to get_all_dependent_turbines + df_out = pd.DataFrame( + data=impacted_by_list, + index=pd.Index(wd_array, name="wd") + ) + + return df_out # Wrapper function to easily set new TI values def _fi_set_ws_wd_ti(fi, wd=None, ws=None, ti=None): From 2bae86d6371abf7be2035c8017c9bd9f3ba541a2 Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 1 Mar 2023 09:23:11 -0700 Subject: [PATCH 4/9] Refactoring get_all_impacting_turbines() so that impacts are ordered and can be limited to a certain limit_number. --- flasc/floris_tools.py | 107 ++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 46 deletions(-) diff --git a/flasc/floris_tools.py b/flasc/floris_tools.py index 363a604f..414bcf8a 100644 --- a/flasc/floris_tools.py +++ b/flasc/floris_tools.py @@ -673,7 +673,7 @@ def yw_lower(x): def get_dependent_turbines_by_wd(fi_in, test_turbine, wd_array=np.arange(0., 360., 2.), change_threshold=0., limit_number=None, - ws_test=9.): + ws_test=9., return_influence_magnitudes=False): """ Computes all turbines that depend on the operation of a specified turbine (test_turbine) for each wind direction in wd_array, using @@ -692,6 +692,9 @@ def get_dependent_turbines_by_wd(fi_in, test_turbine, turbines that depend on each turbine. Defaults to None. ws_test (float): Wind speed at which FLORIS model is run to determine dependencies. + return_influence_magnitudes (Bool): Flag for whether to return + an array containing the magnitude of the influence of the + test_turbine on all turbines. Returns: dep_indices_by_wd (list): A 2-dimensional list. Each element of @@ -700,6 +703,10 @@ def get_dependent_turbines_by_wd(fi_in, test_turbine, for that wind direction. The second-level list may be empty if no turbine depends on the test_turbine for that wind direciton (e.g., the turbine is in the back row). + all_influence_magnitudes ([np.array]): 2-D numpy array of + influences of test_turbine on all other turbines, with size + (number of wind directions) x (number of turbines). Returned + only if return_influence_magnitudes is True. """ # Copy fi to a local to not mess with incoming fi = copy.deepcopy(fi_in) @@ -724,6 +731,9 @@ def get_dependent_turbines_by_wd(fi_in, test_turbine, test_power = fi.get_turbine_powers()[:,0,:] # remove unneeded dimension test_power = np.insert(test_power, test_turbine, base_power[:,test_turbine], axis=1) + + if return_influence_magnitudes: + all_influence_magnitudes = np.zeros_like(test_power) # Find the indices that have changed dep_indices_by_wd = [None]*len(wd_array) @@ -734,14 +744,20 @@ def get_dependent_turbines_by_wd(fi_in, test_turbine, influence_order = np.flip(np.argsort(all_influences))[:limit_number] # Mask to only those that meet the threshold influence_order = influence_order[ - all_influences[influence_order] > change_threshold + all_influences[influence_order] >= change_threshold ] # Store in output dep_indices_by_wd[i] = list(influence_order) + if return_influence_magnitudes: + all_influence_magnitudes[i,:] = all_influences + # Remove the turbines own indice - return dep_indices_by_wd + if return_influence_magnitudes: + return dep_indices_by_wd, all_influence_magnitudes + else: + return dep_indices_by_wd def get_all_dependent_turbines(fi_in, wd_array=np.arange(0., 360., 2.), change_threshold=0.0, limit_number=None, ws_test=9.): @@ -789,11 +805,12 @@ def get_all_dependent_turbines(fi_in, wd_array=np.arange(0., 360., 2.), return df_out def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), - change_threshold=0.0, ws_test=9., df_dependencies=None): + change_threshold=0.0, limit_number=None, ws_test=9.): """ Calculate which turbines impact a specified turbine based on the - FLORIS model. Essentially a wrapper for get_all_dependent_turbines - and a reordering of the output dataframe. + FLORIS model. Essentially a wrapper for + get_dependent_turbines_by_wd() that loops over all turbines and + extracts their impact magnitudes, then sorts. Args: fi ([floris object]): FLORIS object of the farm of interest. @@ -802,11 +819,11 @@ def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), change_threshold (float): Fractional change in power needed to denote a dependency. Defaults to 0. (any change in power is marked as a dependency) + limit_number (int | NoneType): Number of turbines that a + turbine can depend on. If None, returns all + turbines that each turbine depends on. Defaults to None. ws_test (float): Wind speed at which FLORIS model is run to determine dependencies. - df_dependencies (pd.DataFrame): Optional, can be provided to - avoid recalculating the dependencies if they have already - been calculated separately. Returns: df_out ([pd.DataFrame]): A Pandas Dataframe in which each row @@ -815,46 +832,44 @@ def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), on at the row wind direction. Dependencies can be extracted as: For wind direction wd, the turbines that impact turbine T are df_out.loc[wd, T]. Impacting turbines are simply - ordered numerically, NOT by magnitude of impact. + ordered by magnitude of impact. """ - if df_dependencies is None: - # Compute dependencies - # Note: No current method of applying a reasonable limit number, - # as impact magnitudes are not stored by get_all_dependent_turbines. - df_dependencies = get_all_dependent_turbines( - fi_in=fi_in, - wd_array=wd_array, - change_threshold=change_threshold, - limit_number=None, - ws_test=ws_test - ) - else: - print("Proceeding using provided dependencies dataframe. If "+\ - "a limit_number was specified when computing df_dependencies, "+\ - "results may not be accurate!" - ) - - # Convert to 3D list for easier looping - dependency_list = df_dependencies.to_numpy() - - # Initialize an new 2D array of empty lists - impacted_by_list = np.empty((len(wd_array), len(fi_in.layout_x)), - dtype=object) - for i in np.ndindex(impacted_by_list.shape): - impacted_by_list[i] = [] - - # Extract impacting turbines - for wd_i in range(len(wd_array)): - for t_u in range(len(fi_in.layout_x)): - for t_d in dependency_list[wd_i][t_u]: - impacted_by_list[wd_i, t_d].append(t_u) - - # Package as dataframe of similar form to get_all_dependent_turbines - df_out = pd.DataFrame( - data=impacted_by_list, - index=pd.Index(wd_array, name="wd") + dependency_magnitudes = np.zeros( + (len(wd_array),len(fi_in.layout_x),len(fi_in.layout_x)) ) + + for t_i in range(len(fi_in.layout_x)): + _, ti_dep_mags = get_dependent_turbines_by_wd( + fi_in, t_i, wd_array, change_threshold, limit_number, ws_test, + return_influence_magnitudes=True + ) + dependency_magnitudes[:,:,t_i] = ti_dep_mags + + # Sort + impact_order = np.flip(np.argsort(dependency_magnitudes, axis=2), axis=2) + + # Truncate to limit_number + impact_order = impact_order[:,:,:limit_number] + + # Build up multi-level results list + results = [] + + for wd in range(len(wd_array)): + wd_results = [] + for t_j in range(len(fi_in.layout_x)): + impacts_on_t_j = dependency_magnitudes[wd, t_j, :] + impact_order_t_j = impact_order[wd, t_j, :] + impact_order_t_j = impact_order_t_j[ + impacts_on_t_j[impact_order_t_j] >= change_threshold + ] + wd_results.append(list(impact_order_t_j)) + results.append(wd_results) + + # Convert to dataframe + df_out = (pd.DataFrame(data=results, index=wd_array) + .reset_index().rename(columns={"index":"wd"}).set_index("wd") + ) return df_out From 99059456974b152f4dc5eda701368bfbfc9fc40b Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 1 Mar 2023 13:37:54 -0700 Subject: [PATCH 5/9] Update default value for change_threshold; add handling for turbine_type a list of length 1. --- flasc/floris_tools.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/flasc/floris_tools.py b/flasc/floris_tools.py index 414bcf8a..dd7f83ec 100644 --- a/flasc/floris_tools.py +++ b/flasc/floris_tools.py @@ -672,7 +672,7 @@ def yw_lower(x): return df_upstream def get_dependent_turbines_by_wd(fi_in, test_turbine, - wd_array=np.arange(0., 360., 2.), change_threshold=0., limit_number=None, + wd_array=np.arange(0., 360., 2.), change_threshold=0.001, limit_number=None, ws_test=9., return_influence_magnitudes=False): """ Computes all turbines that depend on the operation of a specified @@ -720,7 +720,11 @@ def get_dependent_turbines_by_wd(fi_in, test_turbine, base_power = fi.get_turbine_powers()[:,0,:] # remove unneeded dimension # Compute the test power - fi.floris.farm.turbine_type.pop(test_turbine) # Remove test turbine from list + if len(fi.floris.farm.turbine_type) > 1: + # Remove test turbine from list + fi.floris.farm.turbine_type.pop(test_turbine) + else: # Only a single turbine type defined for the whole farm; do nothing + pass fi.reinitialize( layout_x=np.delete(fi.layout_x, [test_turbine]), layout_y=np.delete(fi.layout_y, [test_turbine]), @@ -760,7 +764,7 @@ def get_dependent_turbines_by_wd(fi_in, test_turbine, return dep_indices_by_wd def get_all_dependent_turbines(fi_in, wd_array=np.arange(0., 360., 2.), - change_threshold=0.0, limit_number=None, ws_test=9.): + change_threshold=0.001, limit_number=None, ws_test=9.): """ Wrapper for get_dependent_turbines_by_wd() that loops over all turbines in the farm and packages their dependencies as a pandas @@ -805,7 +809,7 @@ def get_all_dependent_turbines(fi_in, wd_array=np.arange(0., 360., 2.), return df_out def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), - change_threshold=0.0, limit_number=None, ws_test=9.): + change_threshold=0.001, limit_number=None, ws_test=9.): """ Calculate which turbines impact a specified turbine based on the FLORIS model. Essentially a wrapper for From d9eaecfe9e81f9a05c70d6cf8430f517312b7213 Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 1 Mar 2023 13:38:20 -0700 Subject: [PATCH 6/9] Example created. --- examples/layout/turbine_dependencies.py | 74 +++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 examples/layout/turbine_dependencies.py diff --git a/examples/layout/turbine_dependencies.py b/examples/layout/turbine_dependencies.py new file mode 100644 index 00000000..1b9b296c --- /dev/null +++ b/examples/layout/turbine_dependencies.py @@ -0,0 +1,74 @@ +import os +import matplotlib.pyplot as plt +import numpy as np + +from flasc import floris_tools as fsatools +from flasc import visualization as fsaviz + +from floris import tools as wfct + +# Set up FLORIS interface +print('Initializing the FLORIS object for our demo wind farm') +file_path = os.path.dirname(os.path.abspath(__file__)) +fi_path = os.path.join(file_path, '../demo_dataset/demo_floris_input.yaml') +fi = wfct.floris_interface.FlorisInterface(fi_path) + +# Plot the layout of the farm for reference +fsaviz.plot_layout_only(fi) + +# Get the dependencies of turbine 2 +check_directions = np.arange(0, 360., 2.) +depend_on_2 = fsatools.get_dependent_turbines_by_wd(fi, 2, check_directions) + +print("Turbines that depend on T002 at 226 degrees:", + depend_on_2[round(226/2)] + ) + +df_dependencies = fsatools.get_all_dependent_turbines(fi, check_directions) +print("\nAll turbine dependencies using default threshold "+\ + "(first 5 wind directions printed):") +print(df_dependencies.head()) + +df_dependencies = fsatools.get_all_dependent_turbines(fi, check_directions, + limit_number=2) +print("\nTwo most significant turbine dependencies using default threshold "+\ + "(first 5 wind directions printed):") +print(df_dependencies.head()) + +df_dependencies = fsatools.get_all_dependent_turbines(fi, check_directions, + change_threshold=0.01) +print("\nAll turbine dependencies using higher threshold "+\ + "(first 5 wind directions printed):") +print(df_dependencies.head()) + +print("\nAll upstream turbine impacts using default threshold "+\ + "(first 5 wind directions printed):") +df_impacting = fsatools.get_all_impacting_turbines(fi, check_directions) +print(df_impacting.head()) +# Inclusion of T005 here as an impact on T000 is surprising; try increasing +# the threshold or reducing the limit_number (see next). + +print("\nMost significant upstream turbine impact using default threshold "+\ + "(first 5 wind directions printed):") +df_impacting = fsatools.get_all_impacting_turbines(fi, check_directions, + limit_number=1) +print(df_impacting.head()) + +print("\nAll upstream turbine impacts using higher threshold "+\ + "(first 5 wind directions printed):") +df_impacting = fsatools.get_all_impacting_turbines(fi, check_directions, + change_threshold=0.01) +print(df_impacting.head()) + +# Note that there is no individual turbine version for the "impacting" +# function; instead, compute all impacting turbines and extract desired +# turbine from the output dataframe. + +# (compute using defaults again, for example) +df_impacting = fsatools.get_all_impacting_turbines(fi, check_directions) +print("\nTurbines that T006 depends on at 226 degrees:", + df_impacting.loc[226, 6] + ) + + +plt.show() From 40f2186a20a1b54c596528b990068e621f1df362 Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 1 Mar 2023 13:53:51 -0700 Subject: [PATCH 7/9] Add unit test for base get_dependent_turbines_by_wd function. --- tests/floris_tools_test.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/floris_tools_test.py b/tests/floris_tools_test.py index 3ae1cc18..ed261ca9 100644 --- a/tests/floris_tools_test.py +++ b/tests/floris_tools_test.py @@ -5,7 +5,8 @@ import unittest from flasc.floris_tools import ( calc_floris_approx_table, - interpolate_floris_from_df_approx + interpolate_floris_from_df_approx, + get_dependent_turbines_by_wd ) from floris import tools as wfct @@ -68,3 +69,21 @@ def test_floris_approx_table(self): # self.assertTrue(("ti_002" in df.columns)) self.assertTrue(("pow_003" in df.columns)) self.assertAlmostEqual(df.shape[0], 3) + + def test_get_dependent_turbines_by_wd(self): + # Load FLORIS object + fi = load_floris() + + # compute the dependency on turbine 2 at 226 degrees + dep = get_dependent_turbines_by_wd(fi, 2, np.array([226])) + self.assertEqual(dep[0], [1, 6]) + + # Test the change_threshold + dep = get_dependent_turbines_by_wd(fi, 2, np.array([226]), + change_threshold=0.01) + self.assertEqual(dep[0], [1]) + + # Test the limit_number + dep = get_dependent_turbines_by_wd(fi, 2, np.array([226]), + limit_number=1) + self.assertEqual(dep[0], [1]) \ No newline at end of file From 21bbd264f32a92366c66b371230621ccff07404b Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 1 Mar 2023 16:27:44 -0700 Subject: [PATCH 8/9] Update examples to demonstrate the magnitude of influences. --- examples/layout/turbine_dependencies.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/layout/turbine_dependencies.py b/examples/layout/turbine_dependencies.py index 1b9b296c..c7f74028 100644 --- a/examples/layout/turbine_dependencies.py +++ b/examples/layout/turbine_dependencies.py @@ -24,6 +24,14 @@ depend_on_2[round(226/2)] ) +# Can also return all influences as a matrix for other use (not ordered) +depend_on_2, influence_magnitudes = fsatools.get_dependent_turbines_by_wd( + fi, 2, check_directions, return_influence_magnitudes=True) +print("\nArray of all influences of T002 has shape (num_wds x num_turbs): ", + influence_magnitudes.shape) +print("Influence of T002 on T006 at 226 degrees: {0:.4f}".format( + influence_magnitudes[round(226/2), 6])) + df_dependencies = fsatools.get_all_dependent_turbines(fi, check_directions) print("\nAll turbine dependencies using default threshold "+\ "(first 5 wind directions printed):") From 8de18ab38e99d4c6faea2f59cfb0d52d98d71bb7 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 2 Mar 2023 09:04:31 -0700 Subject: [PATCH 9/9] Add some small comments --- examples/layout/turbine_dependencies.py | 9 +++++++++ flasc/floris_tools.py | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/layout/turbine_dependencies.py b/examples/layout/turbine_dependencies.py index c7f74028..abfed00d 100644 --- a/examples/layout/turbine_dependencies.py +++ b/examples/layout/turbine_dependencies.py @@ -6,6 +6,15 @@ from flasc import visualization as fsaviz from floris import tools as wfct + +# Demonstrate the turbine dependency functions in floris_tools +# Note a turbine is "dependent" on another if it is affected +# by the wake of the other turbine for a given wind direction. + +# A given turbine's dependent turbines are those that depend on it, +# and a turbine's impacting turbines are those turbines that +# it itself depends on. + # Set up FLORIS interface print('Initializing the FLORIS object for our demo wind farm') diff --git a/flasc/floris_tools.py b/flasc/floris_tools.py index dd7f83ec..cc009b50 100644 --- a/flasc/floris_tools.py +++ b/flasc/floris_tools.py @@ -691,7 +691,7 @@ def get_dependent_turbines_by_wd(fi_in, test_turbine, turbine can have as dependencies. If None, returns all turbines that depend on each turbine. Defaults to None. ws_test (float): Wind speed at which FLORIS model is run to - determine dependencies. + determine dependencies. Defaults to 9. m/s. return_influence_magnitudes (Bool): Flag for whether to return an array containing the magnitude of the influence of the test_turbine on all turbines. @@ -781,7 +781,7 @@ def get_all_dependent_turbines(fi_in, wd_array=np.arange(0., 360., 2.), turbine can have as dependencies. If None, returns all turbines that depend on each turbine. Defaults to None. ws_test (float): Wind speed at which FLORIS model is run to - determine dependencies. + determine dependencies. Defaults to 9. m/s. Returns: df_out ([pd.DataFrame]): A Pandas Dataframe in which each row @@ -827,7 +827,7 @@ def get_all_impacting_turbines(fi_in, wd_array=np.arange(0., 360., 2.), turbine can depend on. If None, returns all turbines that each turbine depends on. Defaults to None. ws_test (float): Wind speed at which FLORIS model is run to - determine dependencies. + determine dependencies. Defaults to 9. m/s. Returns: df_out ([pd.DataFrame]): A Pandas Dataframe in which each row