-
Notifications
You must be signed in to change notification settings - Fork 207
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
Implement climate projections #929
base: main
Are you sure you want to change the base?
Changes from 11 commits
73601f9
c68abbe
2beb879
1a24bad
174b0b1
4594541
83c6b0b
b196e5a
369bc9b
d1fd454
21a1751
33225c7
72272d5
b288ce7
7d3de0f
bc5575f
d77af95
51c8a09
cebf6a3
0d7ec1d
0e507c1
47c628a
dc735ba
4500f45
7d8e17e
42da0b8
e4780a9
dbe81ac
5fa9712
b4e1893
a292e14
3715f9f
a33b9ec
d061ef3
1c46763
f3825f2
989ae50
f68bf21
ba3d1a7
c1bfe0c
9d21fde
58ec438
a8f8636
33d90d2
f5cb70e
2177bb1
49117f2
19b65fc
7ca6216
39fd2d1
517e422
1f3520d
dd4b060
f71390f
14707c8
47e7946
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
# -*- coding: utf-8 -*- | ||
# SPDX-FileCopyrightText: PyPSA-Earth and PyPSA-Eur Authors | ||
# | ||
# SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
# -*- coding: utf-8 -*- | ||
""" | ||
Creates a dataset corresponding to the projected climate in a requested year. | ||
davide-f marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Relevant Settings | ||
----------------- | ||
|
||
.. code:: yaml | ||
|
||
projection: | ||
climate_scenario: | ||
present_year: | ||
future_year: | ||
years_window: | ||
gcm_selection: | ||
|
||
Inputs | ||
------ | ||
|
||
- ``cutouts/cutout.nc``: confer :ref:`cutout`, a cutout file produced by altile | ||
- ``data/cmip6_avr.cn``: | ||
|
||
Outputs | ||
------- | ||
|
||
- ``cutouts/{cutout}_{future_year}.nc"``: A cutout modified to account for future climate conditions | ||
|
||
Description | ||
----------- | ||
|
||
The rule :mod:`build_climate_projections` creates a cutout file which corresponds to a requested year in the future. Temperature projectons are being calculated combining data for cutout.nc which is assumed to be representative of the past climate and an ensemble of CMIP6 globale climate models to account for the future climate | ||
""" | ||
|
||
|
||
import datetime as dt | ||
import os | ||
|
||
import atlite | ||
import geopandas as gpd | ||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
import pandas as pd | ||
import pypsa | ||
import xarray as xr | ||
from _helpers import configure_logging, create_logger | ||
from shapely.geometry import LineString as Line | ||
from shapely.geometry import Point | ||
|
||
logger = create_logger(__name__) | ||
|
||
|
||
def crop_cmip6(cmip6_xr, cutout_xr): | ||
davide-f marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# spartial margin needed to avoid data losses during further interpolation | ||
d_pad = 1 | ||
|
||
cmip6_region = cmip6_xr.sel( | ||
lat=slice( | ||
min(cutout_xr.coords["y"].values) - d_pad, | ||
max(cutout_xr.coords["y"].values) + d_pad, | ||
), | ||
lon=slice( | ||
min(cutout_xr.coords["x"].values) - d_pad, | ||
max(cutout_xr.coords["x"].values) + d_pad, | ||
), | ||
) | ||
Comment on lines
+111
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very nice! I'm wondering, instead of doing min/max, may it be that the first element of the list is also the lowest? if so we could avoid the max/mins in the whole document. I'm wondering, but is this cmip6 compatible with the object cutout in atlite? is we load it as a cutout, we can use the bounds function of the cutout object and this facilitates a lot the whole style. Copying here the link to the bounds function and the atlite file, that can give some ideas for the coding style :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you! A really great idea to align with As for the particular bounds calculation, I have investigated this idea, but would say that it doesn't feel like a clear approach to use "magic" index numbers. Agree that it's quite common. But personally, I'm not capable to keep in mind which integer means what, and that is usually quite tricky to deal with the code using such conventions. Not sure it's justifiable but performance gains, as currently performance bottlenecks seem to be in spatial calculations. What would potentially be interesting to adopt from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmm maybe it would be good to at least create here an auxiliary function that, having as input an xarray, returns the boundaries like (xmin, xmax, ymin, ymax) or alike. What do you think? |
||
return cmip6_region | ||
|
||
|
||
def interpolate_cmip6_to_cutout_grid(cmip6_xr, cutout_xr): | ||
# TODO read from the cutout file instead of hardcoding | ||
dx_new = 0.3 | ||
|
||
newlon = np.arange( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May be good to use np.max and alike but unsure performences increas significantly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks a lot for the hint! Definitely worth investigation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have done some profiling, and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for checking, by looking at the atlite code it raised few ideas:
Link to the cutout.py file : https://github.com/PyPSA/atlite/blob/a71bca2f6f7221b70dbbc371752aef56d937b1ff/atlite/cutout.py#L314 |
||
round(min(cutout_xr.coords["x"].values), 1), | ||
round(max(cutout_xr.coords["x"].values) + dx_new, 1), | ||
dx_new, | ||
) | ||
newlat = np.arange( | ||
round(min(cutout_xr.coords["y"].values), 1), | ||
round(max(cutout_xr.coords["y"].values) + dx_new, 1), | ||
dx_new, | ||
) | ||
cmip6_interp = cmip6_xr.interp(time=cmip6_xr.time, lat=newlat, lon=newlon) | ||
|
||
return cmip6_interp | ||
|
||
|
||
# TODO fix years_window | ||
def subset_by_time(cmip6_xr, month, year, years_window): | ||
# TODO add warning in case there are no enough years | ||
cmip6_in_period = cmip6_xr.where( | ||
( | ||
(cmip6_xr["time.month"] == month) | ||
& (cmip6_xr["time.year"] >= year - years_window) | ||
& (cmip6_xr["time.year"] < year + years_window - 1) | ||
), | ||
drop=True, | ||
) | ||
return cmip6_in_period | ||
|
||
|
||
def calculate_proj_of_average(cmip6_xr, month, year0, year1, years_window): | ||
cmip6_interp_year0 = subset_by_time( | ||
cmip6_xr, | ||
month, | ||
year=year0, | ||
) | ||
cmip6_interp_year1 = subset_by_time(cmip6_xr, month=month, year=year1) | ||
dt_interp = cmip6_interp_year1["t"].mean("member").mean( | ||
"time" | ||
) - cmip6_interp_year0["t"].mean("member").mean("time") | ||
return dt_interp | ||
|
||
|
||
def build_projection_for_month(cutout_xr, dt_xr, month): | ||
k_time = cutout_xr.time.dt.month.isin(month) | ||
|
||
for i in range(0, len(cutout_xr.y)): | ||
for j in range(0, len(cutout_xr.x)): | ||
cutout_xr.temperature[k_time, i, j] = np.add( | ||
cutout_xr.temperature[k_time, i, j], | ||
np.array([dt_xr[i, j].item()] * k_time.sum().item()), | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At first sight, it feels like this code may take long time to compute. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to modify a temperature value at each spatial point according to a projection for an increase in the monthly temperature. You are absolutely right that it may be quite time-consuming. Do you have any ideas on possible approaches to increase performance? |
||
|
||
return cutout_xr | ||
|
||
|
||
def build_cutout_future(cutout_xr, cmip6_xr, months, year0, year1, years_window): | ||
for k_month in [months]: | ||
dt_k = calculate_proj_of_average( | ||
cmip6_xr=cmip6_xr, month=k_month, year0=year0, year1=year1, years_window=5 | ||
) | ||
|
||
build_projection_for_month(cutout_xr, dt_k, k_month) | ||
|
||
cutout_xr = cutout_xr.where(cutout_xr.time.dt.month.isin(months), drop=True) | ||
|
||
return cutout_xr | ||
|
||
|
||
if __name__ == "__main__": | ||
if "snakemake" not in globals(): | ||
from _helpers import mock_snakemake | ||
|
||
os.chdir(os.path.dirname(os.path.abspath(__file__))) | ||
snakemake = mock_snakemake( | ||
"build_climate_projections", | ||
climate_scenario="ssp2-2.6", | ||
present_year=2020, | ||
future_year=2050, | ||
years_window=5, | ||
cutout="africa-2013-era5", | ||
) | ||
configure_logging(snakemake) | ||
|
||
climate_scenario = snakemake.params.climate_scenario | ||
present_year = snakemake.params.present_year | ||
future_year = snakemake.params.future_year | ||
years_window = snakemake.params.years_window | ||
cutout = snakemake.input.cutout | ||
cmip6 = snakemake.input.cmip6_avr | ||
|
||
snapshots = pd.date_range(freq="h", **snakemake.params.snapshots) | ||
season_in_focus = snapshots.month.unique().to_list() | ||
|
||
cmip6_xr = xr.open_dataset(cmip6) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How to load this dataset? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed, the dataset is available via Copernicus and licensed as CC-BY 4.0, being IPCC product 🙏🏽 So, it can be downloaded manually or included into a dedicated databundle or loaded via Copernicus API with a request as simple as c.retrieve(
'projections-climate-atlas',
{
'format': 'zip',
'origin': 'CMIP6',
'experiment': 'ssp2_4_5',
'domain': 'global',
'period': '2015-2100',
'variable': 'monthly_mean_of_daily_mean_temperature',
},
'download.zip') |
||
cutout_xr = xr.open_dataset(cutout) | ||
cmip6_region = crop_cmip6(cmip6_xr, cutout_xr) | ||
|
||
cmip6_region_interp = interpolate_cmip6_to_cutout_grid( | ||
cmip6_xr=cmip6_region, cutout_xr=cutout_xr | ||
) | ||
|
||
# ----------------------------------------------------------------- | ||
# to be replaced after debug | ||
# ----------------------------------------------------------------- | ||
# graphical test of interpolation | ||
fig = plt.figure() | ||
cmip6_region_interp["t"].mean("time").mean("member").plot() | ||
fig.savefig("test_cmip6_interp.png", dpi=700) | ||
|
||
fig = plt.figure() | ||
(cutout_xr["temperature"] - 273.15).mean("time").plot(cmap="OrRd") | ||
fig.savefig("results/test_present.png", dpi=700) | ||
# ----------------------------------------------------------------- | ||
|
||
# TODO Add a check for time match | ||
cutout_future = build_cutout_future( | ||
cutout_xr=cutout_xr, | ||
cmip6_xr=cmip6_region_interp, | ||
months=season_in_focus, | ||
year0=present_year, | ||
year1=future_year, | ||
years_window=years_window, | ||
) | ||
|
||
cutout_future.to_netcdf(snakemake.output[0]) | ||
|
||
# ----------------------------------------------------------------- | ||
# to be replaced after debug | ||
# ----------------------------------------------------------------- | ||
# graphical test of projection | ||
fig = plt.figure() | ||
(cutout_future["temperature"] - 273.15).mean("time").plot(cmap="OrRd") | ||
fig.savefig("results/test_cmip6_project.png", dpi=700) |
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 think we could call the option climate_projection_cutout or something like that?
I was considering if adding an option different from build_cutout may be necessary, but I believe so to avoid overwriting existing "original" cutouts
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.
Yeah, agree that it would be nice to keep the main workflow as safe as possible from interactions with these additions.
The option name revised. Have I understood you idea correctly? :)