-
Notifications
You must be signed in to change notification settings - Fork 86
Freestream wind speed heterogeneity corrections for wake loss analysis #310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Freestream wind speed heterogeneity corrections for wake loss analysis #310
Conversation
…am wind speed estimates
…for La Haute Borne
|
Note that some tests for parts of the code not modified in this PR are failing. This is caused by an updated version of NumPy for at least one test. The other failing tests might be caused by updated package dependencies, but will need to better understand what's going on. |
…g to get expected behavior at end of time series with NumPy v2
Codecov Report❌ Patch coverage is
Additional details and impacted files☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Tests are passing now |
RHammond2
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for putting all these changes together, @ejsimley, there isn't really much to comment on from a software perspective! I only left one comment on a type check that I think needs to be updated, otherwise the other suggestions are exactly that.
| if self.correct_for_ws_heterogeneity & ( | ||
| type(self.ws_speedup_factor_map) not in [pd.DataFrame, str] | ||
| ): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if self.correct_for_ws_heterogeneity & ( | |
| type(self.ws_speedup_factor_map) not in [pd.DataFrame, str] | |
| ): | |
| if self.correct_for_ws_heterogeneity & isinstance(self.ws_speedup_factor_map, (pd.DataFrame, str)) |
In Python 3.10 or 3.11 (can't remember which) the preferred format for the multi-type check will use the union symbol, but the tuple will be find since we haven't updated our minimum python or version targeting yet.
| self.power_curve_func = power_curve.IEC( | ||
| self.aggregate_df_sample.loc[:, "windspeed_normal"].stack(future_stack=True), | ||
| self.aggregate_df_sample.loc[:, "power_normal"].stack(future_stack=True), | ||
| windspeed_end=50.0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an option? It doesn't have to be (especially if my thought process is underinformed on the mechanics at play), but it caught my attention as something that a user could want control over.
| self.aggregate_df_sample[("windspeed_freestream_estimate", t)] = np.nan | ||
| self.aggregate_df_sample[("power_freestream_estimate", t)] = np.nan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| self.aggregate_df_sample[("windspeed_freestream_estimate", t)] = np.nan | |
| self.aggregate_df_sample[("power_freestream_estimate", t)] = np.nan | |
| new_cols = ["windspeed_freestream_estimate", "power_freestream_estimate"] | |
| self.aggregate_df_sample[list(itertools.product(new_cols, self.turbine_ids))] = np.nan |
It's generally preferable to do data frame manipulations like the above once rather than repeatedly. For larger data frames or more than a handful of turbines this will have a much more noticeable impact. import itertools or from itertools import product will also have to be added up top, but it'll be worth it with the reduced number of column additions.
| valid_inds_freestream_power = ( | ||
| (self.aggregate_df_sample["power_mean_freestream"] > 0) | ||
| & ( | ||
| ( | ||
| ~self.aggregate_df_sample["derate_flag"] | ||
| * self.aggregate_df_sample["power_freestream_estimate"] | ||
| ).sum(axis=1) | ||
| > 0 | ||
| ) | ||
| & (self.aggregate_df_sample["power_mean_freestream_estimate"] > 1.0) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| valid_inds_freestream_power = ( | |
| (self.aggregate_df_sample["power_mean_freestream"] > 0) | |
| & ( | |
| ( | |
| ~self.aggregate_df_sample["derate_flag"] | |
| * self.aggregate_df_sample["power_freestream_estimate"] | |
| ).sum(axis=1) | |
| > 0 | |
| ) | |
| & (self.aggregate_df_sample["power_mean_freestream_estimate"] > 1.0) | |
| ) | |
| valid_ix = self.aggregate_df_sample["power_mean_freestream"] > 0 | |
| valid_ix &= ( | |
| ( | |
| ~self.aggregate_df_sample["derate_flag"] | |
| * self.aggregate_df_sample["power_freestream_estimate"] | |
| ).sum(axis=1) | |
| > 0 | |
| ) | |
| valid_ix &= self.aggregate_df_sample["power_mean_freestream_estimate"] > 1.0 |
I would recommend shortening up the valid_inds_freestream_power to valid_ix and breaking this multicriteria check into 3 distinct steps just to make it a little bit easier to follow. I think the name simplification is justified by the thorough inline documentation about this process.
| total_potential_freestream_power.loc[ | ||
| ~valid_inds_freestream_power | ||
| ] = self.aggregate_df_sample.loc[ | ||
| ~valid_inds_freestream_power, "power_mean_freestream" | ||
| ] * ( | ||
| ~self.aggregate_df_sample.loc[~valid_inds_freestream_power, "derate_flag"] | ||
| ).sum( | ||
| axis=1 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| total_potential_freestream_power.loc[ | |
| ~valid_inds_freestream_power | |
| ] = self.aggregate_df_sample.loc[ | |
| ~valid_inds_freestream_power, "power_mean_freestream" | |
| ] * ( | |
| ~self.aggregate_df_sample.loc[~valid_inds_freestream_power, "derate_flag"] | |
| ).sum( | |
| axis=1 | |
| ) | |
| total_potential_freestream_power.loc[~valid_ix] = ( | |
| self.aggregate_df_sample.loc[~valid_ix, "power_mean_freestream"] | |
| * ~self.aggregate_df_sample.loc[~valid_ix, "derate_flag"] | |
| ).sum(axis=1) |
If the above suggestion is accepted, then this piece will also need to be updated, which helps reduce the amount of line breakage from the automated formatting.
| # turbines are greater than zero, and mean estimated freestream power is | ||
| # sufficiently large (treated as greater than 1 kW times the number of normally | ||
| # operating freestream turbines), allowing valid potential power corrections. | ||
| valid_inds_freestream_power = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In comparison to the above shortening, there isn't much to be done to fit any of the nested checks onto fewer lines without butchering the naming or assigning temporary variables, so this whole block works well as-is.
|
|
||
| df_wd_bin = self.aggregate_df_sample.groupby("wind_direction_bin").sum() | ||
|
|
||
| index = np.arange(0.0, 360.0, self.wd_bin_width_LT_corr).tolist() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think index needs to be converted to a list since Pandas can handle arrays fairly easily.
| # Save long-term corrected plant and turbine-level wake losses binned by wind direction | ||
| df_1hr_wd_bin = df_1hr_bin.groupby(level=[0]).sum() | ||
|
|
||
| index = np.arange(0.0, 360.0, self.wd_bin_width_LT_corr).tolist() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment from before about not needing to convert this to a list.
This PR enhances the operational wake loss estimation method
open.analysis.wake_losses.WakeLossesby adding an option to correct the estimated potential power in the absence of wake interactions for freestream wind speed variations across a wind plant (rather than assuming the wind resource is the same at all turbines). This method was used for the wake loss estimates in the WP3 Benchmark wake loss analysis paper. An overview of this method is as follows:Additionally, two other enhancements are made in this PR.
correct_for_derating=Truein theWakeLossesmethod, the power curve outlier detection now classifies abnormal wind speeds in addition to derating. Derating still detects outliers where the power production is below normal for a given wind speed, but abnormal wind speeds are classified when wind speeds are below normal for a given power production. All calculations based on wind speed in the code are now performed with both derated points and abnormal wind speed points removed, instead of only derated points being filtered out.openoa.utils.power_curve.functions.IECpower curve model to linearly interpolate power between wind speed bins instead of returning the mean power of the wind speed bin closest to the input wind speed. This enhancement was made to assist with the empirical power curve used by theWakeLossesfreestream wind speed heterogeneity corrections.Affected locations in the code are described below.
openoa.analysis.wake_losses.WakeLossesincludes the enhancements described above. Two new arguments are used when correcting for wind speed heterogeneity:correct_for_ws_heterogeneity(TrueorFalse) andws_speedup_factor_map, which is either a DataFrame or path to a csv file containing the speedup factors.openoa.utils.power_curve.functions.IECnow contains the argumentinterpolate(TrueorFalse) to return linearly interpolated points from the power curve, as described above.WakeLossesanalysis method.examples/06_wake_loss_analysis.ipynbdemonstrates how to use the freestream wind speed heterogeneity option, relying on an example wind speedup factor csv file in the examples folder calledexample_la_haute_borne_ws_speedup_factors.csv.test/regression/wake_losses.pyincludes a regression test for wake loss estimation using the heterogeneity corrections. Results for two other tests were updated because of the slight change in the results now that abnormal wind speed points are flagged and removed from wind speed-based calculations in the wake loss analysis method.The PR addresses issue #257, though it currently doesn't use WRG files. This could be added as an option later if there is interest.