-
Notifications
You must be signed in to change notification settings - Fork 108
feat(wind): Bias correct wind speeds based on scaling to a known average #405
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: master
Are you sure you want to change the base?
Conversation
4412bcd
to
0346ceb
Compare
0346ceb
to
545ac65
Compare
@lkstrp Do you understand the missing schema errors in the macos test runs for the data download? |
Ahh, we had that here in #433. Which was a big mystery to me as the PR doesn't even touch anything related |
Damn, the way i retrieve the long-run average of the era5 data does not work, since the monthly means are of the vector components. rather than the wind speed. which is necessarily lower. there is probably no way around retrieving the full hourly 100m windspeeds for several years. i think we have to provide this as an external input. |
Adds a new function :py:func:`atlite.datasets.era5.retrieve_average_windspeed` to retrieve average windspeeds. This dataset function is called by :py:func:`atlite.wind.calculate_windspeed_bias_correction` to derive a bias correction which can be passed to the default wind generation. Example usage: ```python windspeed_bias_correction = atlite.wind.calculate_windspeed_bias_correction( cutout, real_average="gwa3_250_windspeed_100m.tif", height=100, ) cutout.wind( atlite.windturbines.Enercon_E101_3000kW, windspeed_bias_correction=windspeed_bias_correction, windspeed_height=100, ) ```
24e94dc
to
f1e154e
Compare
Ok, updated to use the hourly windspeeds and new usage modi which will allow us to distribute a global correction factor, but also embed it into the cutout. |
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 only had a quick look and I have a couple of style things. But some are probably a general issue of the architecture of atlite
, to which this PR fits well, and nothing that is introduced here. But I would like to keep it in mind for the history books/resolve at some point.
Can we add unit tests to all the new methods? I know the code cov diff is a pain, but every new method should have unit tests.
And how did you solve the MacOS runner problem?
atlite/convert.py
Outdated
) -> xr.DataArray: | ||
""" | ||
Convert wind speeds for turbine to wind energy generation. | ||
""" | ||
V, POW, hub_height, P = itemgetter("V", "POW", "hub_height", "P")(turbine) | ||
|
||
ds, from_height = windm.apply_windspeed_bias_correction( |
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.
The check if apply_windspeed_bias_correction
actually does something should be done here and not within the method. If it's called it should do/ raise something or should not even be called.
@@ -31,14 +31,15 @@ def get_features( | |||
tmpdir=None, | |||
monthly_requests=False, | |||
concurrent_requests=False, | |||
**parameter_updates, |
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.
We can use this for internal methods, but it's much more readable and gives a better docs/api reference if we avoid this general kwargs handling
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.
Well, we don't know at cutout creation time or in the prepare doc-string, which of these parameters exist, since they are datasets module specific. So, I don't think there is much i can do about this here.
atlite/datasets/era5.py
Outdated
""" | ||
real_average_path = creation_parameters.get("windspeed_real_average_path") | ||
if real_average_path is None: | ||
logger.warning( |
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.
This warning is going to appear for every single cutout creation where the path is not specified, isn't it? Shouldn't we make it less verbose and more optional? The information should go in the docstring for the argument in Cutout()
instead
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.
Cutout creation is not happening often and it is easy to miss this new feature. Further, it is new that the preparation of an available feature is skipped, when a data argument is missing, previously the implicit dataset generation rules meant that it should raise, but raising would be to invasive here.
Therefore, I decided for it to be verbose.
Same as above, it cannot go into the Cutout, since this is an era5 specific argument.
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.
It is already in the cutout init, so the info should be added there as well.
I still disagree that this should be a default warning, but I see your points. If you want to go with it, mention in the warning that this is a new feature from v0.5.0
and all docs would need to be updated
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.
Ok, I am not married to the warning. Down-graded it to a debug message and copied the GWA reference also into the Cutout.init docstring.
@@ -552,8 +673,10 @@ def get_data( | |||
If True, the monthly data requests are posted concurrently. | |||
Only has an effect if `monthly_requests` is True. | |||
**creation_parameters : | |||
Additional keyword arguments. The only effective argument is 'sanitize' | |||
(default True) which sets sanitization of the data on or off. | |||
Additional keyword arguments. |
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.
As mentioned above
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.
Inherent to the data preparation routines.
atlite/wind.py
Outdated
Dataset containing wind speed time-series (at one or multiple heights) | ||
with keys like 'wnd{height:d}m' and potentially a pre-calculated scaling | ||
factor at key 'wnd_bias_correction' | ||
windspeed_bias_correction : bool or DataArray, optional |
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 am also not a fan of these "could be anything" arguments.
False
is not needed, you just don't execute the function.
None
uses a default from ds if it exists, otherwise does nothing is not clean either. This logic can be done before. You either use it or you don't by not calling this method. None
can be used, but if you rely on a default it should raise an Error if the data for that does not exist. Same goes for True
.
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.
There are four different possibilities, i would like to support:
- Explicit externally built scaling_factor as a dataarray
- Must have bias correction factor in cutout (or raise) -> True
- Do not apply bias correction factor -> False
- Apply bias correction factor if it is there -> None (that would be the default setting)
That is why these arguments exists, i had the impression that treating them in the convert_wind function made that too noisy, but i can move some of the code around.
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.
The default has been chosen to not introduce any problems for existing uses.
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.
But 2-4 can be handled before calling all those apply_
... methods that may do nothing.
It just makes it much harder to follow the code. Mypy will also be a pain to implement and less helpful with these many typed variables. I am not saying that this needs to be changed here, but in an ideal world we would have most of the variables in a single type. For sure internally
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.
Ok, moved the argument treatment up into the cutout.wind method. Does this make your mypy life less painful?
Hi @lkstrp , thanks for the comments. I think most of your issues point at us having to overhaul the cutout generation and disentangle it from Cutout's I did not solve the MacOS runner failure. When i force-pushed my new changes (which were rebased on the latest master), they did not reappear. No idea. |
@lkstrp I think I addressed your code style comments where adequate. Additional changes would require us to come up with a better cutout preparation routine. I still need to add tests and update docs. |
@coroa I haven't looked at the new version yet - let me know when I should review. Thanks for the great effort! :) |
Superseeds #403 .
This PR adds several functions to support bias correcting wind speeds at a given height by scaling them to a known average.
A new keyword argument
windspeed_bias_correction
is introduced, ie.cutout.wind(..., windspeed_bias_correction=...)
, which accepts an explicit scaling factor for wind speeds, if it is a DataArray with a height meta data attribute. This scaling factor can also be part of the cutout (and calledwnd_bias_correction
there).This opens up the possibility for three usage modi with the average wind speed dataset by GWA 3.1 (Global Wind Atlas), which can be retrieved using their API https://globalwindatlas.info/en/download/gis-files (only loosely called
gwa_path
here)The bias correction factor can be calculated manually for an ERA5 cutout by calling
atlite.wind.calculate_windspeed_bias_correction(cutout, gwa_path)
. This retrieves the hourly 100m wind speeds for the years 2008-2017 (which are the GWA 3.1 support years) for the cutout from CDS to calculate a data_average, then re-projects the raster dataset thatgwa_path
is pointing to the cutout coordinates as real_average and then returnsreal_average / data_average
.We can calculate a constant global bias correction factor that we should distribute through zenodo with:
This can be used with any cutout (with default resolution 0.25/0.25) simply by:
Result is small:
era5-gwa34-windspeed_bias_correction.zip (2.7MB). Should probably be added to zenodo, once the PR lands.
During cutout generation (or feature preparation) one can specify the parameter
windspeed_real_average_path
to add the newwindspeed_bias_correction
feature to the cutout:If
windspeed_real_average_path
is not provided the new feature is skipped with a warning. So this change is cleanly backwards-compatible. The default value ofwindspeed_bias_correction
ofcutout.wind
isNone
, which is interpreted as apply the bias correction factor if it is exists in the cutout. The correction can still be disabled or required with an explicit value ofcutout.wind(..., windspeed_bias_correction=False/True)
.I think that is the most flexible and consistent set of functionality possible. Still open to suggestions, of course.
Closes #373 .
Bias correction factor examples
For our default western europe (ie. UK+Ireland) cutout, this means moderate onshore windspeed scalings between 10% and 70%, offshore the changes are small.
You can see here also that GWA only provides offshore data within 300km of the shoreline. Others are nan and are returned as nan by atlite as well. Is this problematic for floating offshore in pypsa-eur?
Open questions
Checklist
doc
.environment.yaml
,environment_docs.yaml
andsetup.py
(if applicable).doc/release_notes.rst
of the upcoming release is included.