Skip to content

Commit

Permalink
Merge pull request #210 from Ouranosinc/warming_lvls
Browse files Browse the repository at this point in the history
get_warming_level
  • Loading branch information
RondeauG committed Jun 5, 2023
2 parents 1b2c706 + 97c44a0 commit a4c2ffb
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 94 deletions.
7 changes: 4 additions & 3 deletions .github/PULL_REQUEST_TEMPLATE.md
Expand Up @@ -3,10 +3,11 @@
### Pull Request Checklist:
- [ ] This PR addresses an already opened issue (for bug fixes / features)
- This PR fixes #xyz
- [ ] (If applicable) Documentation has been added / updated (for bug fixes / features)
- [ ] (If applicable) Documentation has been added / updated (for bug fixes / features).
- [ ] (If applicable) Tests have been added.
- [ ] This PR does not seem to break the templates.
- [ ] HISTORY.rst has been updated (with summary of main changes)
- [ ] Link to issue (:issue:`number`) and pull request (:pull:`number`) has been added
- [ ] HISTORY.rst has been updated (with summary of main changes).
- [ ] Link to issue (:issue:`number`) and pull request (:pull:`number`) has been added.

### What kind of change does this PR introduce?

Expand Down
1 change: 1 addition & 0 deletions HISTORY.rst
Expand Up @@ -13,6 +13,7 @@ Announcements
New features and enhancements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* `xscen` now tracks code coverage using `coveralls <https://coveralls.io/>`_. (:pull:`187`).
* New function `get_warming_level` to search within the IPCC CMIP global temperatures CSV without requiring data. (:issue:`208`, :pull:`210`).

Breaking changes
^^^^^^^^^^^^^^^^
Expand Down
61 changes: 56 additions & 5 deletions docs/notebooks/5_warminglevels.ipynb
Expand Up @@ -6,15 +6,68 @@
"metadata": {},
"source": [
"# Warming levels\n",
" This notebook will demonstrate a typical workflow for showing indicators by warming levels."
"\n",
"``xs.get_warming_level`` can be used to know when a given model reaches a given warming level.\n",
"\n",
"The arguments of ``xs.get_warming_level`` are:\n",
"\n",
"- `realization`: Dataset, string, or list of strings. Strings should follow the format 'mip-era_source_experiment_member'\n",
"- `wl`: warming level.\n",
"- `window`: Number of years in the centered window during which the warming level is reached. Note that in the case of an even number, the IPCC standard is used (-n/2+1, +n/2).\n",
"- `tas_baseline_period`: The period over which the warming level is calculated, equivalent to \"+0°C\". Defaults to 1850-1900.\n",
"- `ignore_member`: The default `warming_level_csv` only contains data for 1 member. If you want a result regardless of the realization number, set this to True. This is only used when `models` is a Dataset.\n",
"- `return_horizon`: Whether to return the start/end of the horizon or to return the middle year.\n",
" \n",
"If `realization` is a list, the function returns a dictionary. Otherwise, it will return either a string or ['start_yr', 'end_yr'], depending on `return_horizon`. For entries that it fails to find in the csv, or for instances where a given warming level is not reached, the function returns None."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa38e4c5-b693-42ea-bf9e-08862742c729",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"import xscen as xs\n",
"\n",
"# Multiple entries, returns a dictionary\n",
"print(\n",
" xs.get_warming_level(\n",
" [\n",
" \"CMIP6_CanESM5_ssp126_r1i1p1f1\",\n",
" \"CMIP6_CanESM5_ssp245_r1i1p1f1\",\n",
" \"CMIP6_CanESM5_ssp370_r1i1p1f1\",\n",
" \"CMIP6_CanESM5_ssp585_r1i1p1f1\",\n",
" ],\n",
" wl=2,\n",
" window=20,\n",
" return_horizon=False,\n",
" )\n",
")\n",
"# Returns a list\n",
"print(\n",
" xs.get_warming_level(\n",
" \"CMIP6_CanESM5_ssp585_r1i1p1f1\", wl=2, window=20, return_horizon=True\n",
" )\n",
")\n",
"# Only the middle year is requested, returns a string\n",
"print(\n",
" xs.get_warming_level(\n",
" \"CMIP6_CanESM5_ssp585_r1i1p1f1\", wl=2, window=20, return_horizon=False\n",
" )\n",
")\n",
"# +10°C is never reached, returns None\n",
"print(xs.get_warming_level(\"CMIP6_CanESM5_ssp585_r1i1p1f1\", wl=10, window=20))"
]
},
{
"cell_type": "markdown",
"id": "b9b427d5",
"metadata": {},
"source": [
"First, initialize your project catalog."
"This rest of this notebook will demonstrate a typical workflow for showing indicators by warming levels. First, initialize your project catalog."
]
},
{
Expand All @@ -31,8 +84,6 @@
"import xesmf as xe\n",
"from matplotlib import pyplot as plt\n",
"\n",
"import xscen as xs\n",
"\n",
"output_folder = Path().absolute() / \"_data\"\n",
"\n",
"project = {\n",
Expand Down Expand Up @@ -110,7 +161,7 @@
"\n",
"Warming levels are computed individually in order to be able to calculate the ensemble weights properly (see [subsection below](#Ensemble-statistics)).\n",
"\n",
"The arguments of ``xs.subset_warming_level`` are:\n",
"The function calls `get_warming_level`, so the arguments are essentially the same.:\n",
"\n",
"- `ds`: input dataset.\n",
"- `wl`: warming level.\n",
Expand Down
153 changes: 153 additions & 0 deletions tests/test_extract.py
@@ -0,0 +1,153 @@
from copy import deepcopy

import numpy as np
import pytest
from xclim.testing.helpers import test_timeseries as timeseries

import xscen as xs


class TestGetWarmingLevel:
def test_list(self):
out = xs.get_warming_level(
["CMIP6_CanESM5_ssp126_r1i1p1f1", "CMIP6_CanESM5_ssp245_r1i1p1f1"],
wl=2,
window=20,
return_horizon=False,
)
assert isinstance(out, dict)
assert out["CMIP6_CanESM5_ssp126_r1i1p1f1"] == "2026"

def test_string_with_horizon(self):
out = xs.get_warming_level(
"CMIP6_CanESM5_ssp585_r1i1p1f1", wl=2, window=20, return_horizon=True
)
assert isinstance(out, list)
assert out == ["2013", "2032"]

@pytest.mark.parametrize("attrs", ["global", "regional", "regional_w_institution"])
def test_ds(self, attrs):
ds = timeseries(
np.tile(np.arange(1, 2), 30),
variable="tas",
start="2001-01-01",
freq="AS-JAN",
as_dataset=True,
)
attributes = {
"global": {
"cat:mip_era": "CMIP6",
"cat:source": "CanESM5",
"cat:experiment": "ssp126",
},
"regional": {
"cat:mip_era": "CMIP6",
"cat:driving_model": "CanESM5",
"cat:experiment": "ssp126",
},
"regional_w_institution": {
"cat:mip_era": "CMIP6",
"cat:driving_institution": "CCCma",
"cat:driving_model": "CCCma-CanESM5",
"cat:experiment": "ssp126",
},
}

ds.attrs = attributes[deepcopy(attrs)]
assert (
xs.get_warming_level(
ds, wl=2, window=20, ignore_member=True, return_horizon=False
)
== "2026"
)

def test_multiple_matches(self):
# 55 instances of CanESM2-rcp85 in the CSV, but it should still return a single value
assert (
xs.get_warming_level(
"CMIP5_CanESM2_rcp85_.*", wl=3.5, window=30, return_horizon=False
)
== "2059"
)

def test_odd_window(self):
assert xs.get_warming_level(
"CMIP6_CanESM5_ssp126_r1i1p1f1", wl=2, window=21, return_horizon=True
) == ["2016", "2036"]

def test_none(self):
assert (
xs.get_warming_level(
"CMIP6_CanESM5_ssp585_r1i1p1f1", wl=20, window=20, return_horizon=False
)
is None
)
assert (
xs.get_warming_level(
"CMIP6_notreal_ssp585_r1i1p1f1", wl=20, window=20, return_horizon=False
)
is None
)

def test_wrong_types(self):
with pytest.raises(ValueError):
xs.get_warming_level(
{"this": "is not valid."}, wl=2, window=20, return_horizon=True
)
with pytest.raises(ValueError):
xs.get_warming_level(
"CMIP6_CanESM5_ssp585_r1i1p1f1_toomany_underscores",
wl=2,
window=20,
return_horizon=True,
)
with pytest.raises(ValueError):
xs.get_warming_level(
"CMIP6_CanESM5_ssp585_r1i1p1f1", wl=2, window=3.85, return_horizon=True
)


class TestSubsetWarmingLevel:
ds = timeseries(
np.tile(np.arange(1, 2), 50),
variable="tas",
start="2000-01-01",
freq="AS-JAN",
as_dataset=True,
)
ds.attrs = {
"cat:mip_era": "CMIP6",
"cat:source": "CanESM5",
"cat:experiment": "ssp585",
"cat:member": "r1i1p1f1",
}

def test_default(self):
ds_sub = xs.subset_warming_level(TestSubsetWarmingLevel.ds, wl=2)

np.testing.assert_array_equal(ds_sub.time.dt.year[0], 2013)
np.testing.assert_array_equal(ds_sub.time.dt.year[-1], 2032)
np.testing.assert_array_equal(ds_sub.warminglevel[0], "+2Cvs1850-1900")
assert ds_sub.warminglevel.attrs["baseline"] == "1850-1900"
assert ds_sub.attrs["cat:processing_level"] == "warminglevel-2vs1850-1900"

def test_kwargs(self):
ds_sub = xs.subset_warming_level(
TestSubsetWarmingLevel.ds,
wl=1,
window=25,
tas_baseline_period=["1981", "2010"],
to_level="tests",
)

np.testing.assert_array_equal(ds_sub.time.dt.year[0], 2009)
np.testing.assert_array_equal(ds_sub.time.dt.year[-1], 2033)
np.testing.assert_array_equal(ds_sub.warminglevel[0], "+1Cvs1981-2010")
assert ds_sub.warminglevel.attrs["baseline"] == "1981-2010"
assert ds_sub.attrs["cat:processing_level"] == "tests"

def test_outofrange(self):
assert xs.subset_warming_level(TestSubsetWarmingLevel.ds, wl=5) is None

def test_none(self):
assert xs.subset_warming_level(TestSubsetWarmingLevel.ds, wl=20) is None
7 changes: 6 additions & 1 deletion xscen/__init__.py
Expand Up @@ -26,7 +26,12 @@
from .config import CONFIG, load_config # noqa
from .diagnostics import properties_and_measures
from .ensembles import *
from .extract import extract_dataset, search_data_catalogs, subset_warming_level # noqa
from .extract import ( # noqa
extract_dataset,
get_warming_level,
search_data_catalogs,
subset_warming_level,
)
from .indicators import compute_indicators # noqa
from .io import save_to_netcdf, save_to_zarr # noqa
from .reduce import build_reduction_data, reduce_ensemble
Expand Down

0 comments on commit a4c2ffb

Please sign in to comment.