Skip to content

Commit 8ef818f

Browse files
committed
Add Zero Emission Commitment metric
1 parent 78cd1c8 commit 8ef818f

File tree

7 files changed

+402
-3
lines changed

7 files changed

+402
-3
lines changed

changelog/204.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added Zero Emission Commitment (ZEC) metric to the ESMValTool metrics package.

packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/metrics/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
from cmip_ref_metrics_esmvaltool.metrics.ecs import EquilibriumClimateSensitivity
44
from cmip_ref_metrics_esmvaltool.metrics.example import GlobalMeanTimeseries
55
from cmip_ref_metrics_esmvaltool.metrics.tcr import TransientClimateResponse
6+
from cmip_ref_metrics_esmvaltool.metrics.zec import ZeroEmissionCommitment
67

78
__all__ = [
89
"EquilibriumClimateSensitivity",
910
"GlobalMeanTimeseries",
1011
"TransientClimateResponse",
12+
"ZeroEmissionCommitment",
1113
]
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from pathlib import Path
2+
3+
import pandas
4+
import xarray
5+
6+
from cmip_ref_core.constraints import (
7+
AddSupplementaryDataset,
8+
RequireContiguousTimerange,
9+
RequireFacets,
10+
RequireOverlappingTimerange,
11+
)
12+
from cmip_ref_core.datasets import FacetFilter, SourceDatasetType
13+
from cmip_ref_core.metrics import DataRequirement
14+
from cmip_ref_metrics_esmvaltool.metrics.base import ESMValToolMetric
15+
from cmip_ref_metrics_esmvaltool.recipe import dataframe_to_recipe
16+
from cmip_ref_metrics_esmvaltool.types import OutputBundle, Recipe
17+
18+
19+
class ZeroEmissionCommitment(ESMValToolMetric):
20+
"""
21+
Calculate the global mean Zero Emission Commitment (ZEC) temperature.
22+
"""
23+
24+
name = "Zero Emission Commitment"
25+
slug = "esmvaltool-zero-emission-commitment"
26+
base_recipe = "recipe_zec.yml"
27+
28+
experiments = (
29+
"1pctCO2",
30+
"esm-1pct-brch-1000PgC",
31+
)
32+
data_requirements = (
33+
DataRequirement(
34+
source_type=SourceDatasetType.CMIP6,
35+
filters=(
36+
FacetFilter(
37+
facets={
38+
"variable_id": ("tas",),
39+
"experiment_id": experiments,
40+
},
41+
),
42+
),
43+
group_by=("source_id", "member_id", "grid_label"),
44+
constraints=(
45+
RequireFacets("experiment_id", experiments),
46+
RequireContiguousTimerange(group_by=("instance_id",)),
47+
RequireOverlappingTimerange(group_by=("instance_id",)),
48+
AddSupplementaryDataset.from_defaults("areacella", SourceDatasetType.CMIP6),
49+
),
50+
),
51+
)
52+
53+
@staticmethod
54+
def update_recipe(recipe: Recipe, input_files: pandas.DataFrame) -> None:
55+
"""Update the recipe."""
56+
# Prepare updated datasets section in recipe. It contains two
57+
# datasets, one for the "esm-1pct-brch-1000PgC" and one for the "piControl"
58+
# experiment.
59+
datasets = dataframe_to_recipe(input_files)["tas"]["additional_datasets"]
60+
base_dataset = next(ds for ds in datasets if ds["exp"] == "1pctCO2")
61+
dataset = next(ds for ds in datasets if ds["exp"] == "esm-1pct-brch-1000PgC")
62+
start = dataset["timerange"].split("/")[0]
63+
base_start = f"{int(start[:4]) - 10:04d}{start[4:]}"
64+
base_end = f"{int(start[:4]) + 10:04d}{start[4:]}"
65+
base_dataset["timerange"] = f"{base_start}/{base_end}"
66+
variables = recipe["diagnostics"]["zec"]["variables"]
67+
variables["tas_base"] = {
68+
"short_name": "tas",
69+
"preprocessor": "anomaly_base",
70+
"additional_datasets": [base_dataset],
71+
}
72+
variables["tas"] = {
73+
"preprocessor": "spatial_mean",
74+
"additional_datasets": [dataset],
75+
}
76+
77+
@staticmethod
78+
def format_result(result_dir: Path) -> OutputBundle:
79+
"""Format the result."""
80+
zec_file = result_dir / "work/zec/zec/zec_50.nc"
81+
zec = xarray.open_dataset(zec_file)
82+
83+
source_id = zec.dataset.values[0].decode("utf-8").strip()
84+
cmec_output = {
85+
"DIMENSIONS": {
86+
"model": {source_id: {}},
87+
"region": {"global": {}},
88+
"metric": {"zec": {}},
89+
"json_structure": [
90+
"model",
91+
"region",
92+
"metric",
93+
],
94+
},
95+
"RESULTS": {
96+
source_id: {"global": {"zec": float(zec.zec.values[0])}},
97+
},
98+
}
99+
100+
return cmec_output

packages/ref-metrics-esmvaltool/src/cmip_ref_metrics_esmvaltool/recipe.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ def as_timerange(group: pd.DataFrame) -> str | None:
5454
-------
5555
A timerange.
5656
"""
57+
# TODO: apply some rounding to avoid problems?
58+
# https://github.com/ESMValGroup/ESMValCore/issues/2048
5759
start_times = group.start_time.dropna()
5860
if start_times.empty:
5961
return None
@@ -113,12 +115,15 @@ def dataframe_to_recipe(files: pd.DataFrame) -> dict[str, Any]:
113115
return variables
114116

115117

116-
_ESMVALTOOL_VERSION = "2.11.0"
118+
_ESMVALTOOL_VERSION = "2.13.0.dev10+g7883d411e"
119+
_ESMVALTOOL_COMMIT = _ESMVALTOOL_VERSION.split("+")[1][1:]
117120

118121
_RECIPES = pooch.create(
119122
path=pooch.os_cache("cmip_ref_metrics_esmvaltool"),
120-
base_url="https://raw.githubusercontent.com/ESMValGroup/ESMValTool/refs/tags/v{version}/esmvaltool/recipes/",
121-
version=_ESMVALTOOL_VERSION,
123+
# TODO: use a released version
124+
# base_url="https://raw.githubusercontent.com/ESMValGroup/ESMValTool/refs/tags/v{version}/esmvaltool/recipes/",
125+
# version=_ESMVALTOOL_VERSION,
126+
base_url=f"https://raw.githubusercontent.com/ESMValGroup/ESMValTool/{_ESMVALTOOL_COMMIT}/esmvaltool/recipes/",
122127
env="REF_METRICS_ESMVALTOOL_DATA_DIR",
123128
)
124129
_RECIPES.load_registry(importlib.resources.open_binary("cmip_ref_metrics_esmvaltool", "recipes.txt"))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
examples/recipe_python.yml ab3f06d269bb2c1368f4dc39da9bcb232fb2adb1fa556ba769e6c16294ffb4a3
22
recipe_ecs.yml 0cc57034fcb64e32015b4ff949ece5df8cdb8c6f493618b50ceded119fb37918
33
recipe_tcr.yml 35f9ef035a4e71aff5cac5dd26c49da2162fc00291bf3b0bd16b661b7b2f606b
4+
recipe_zec.yml b0af7f789b7610ab3f29a6617124aa40c40866ead958204fc199eaf82863de51
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
{
2+
"start_time":{
3+
"215":"0168-01-16T12:00:00.000",
4+
"118":"0101-01-16T12:00:00.000",
5+
"216":null,
6+
"119":null
7+
},
8+
"end_time":{
9+
"215":"0268-12-16T12:00:00.000",
10+
"118":"0250-12-16T12:00:00.000",
11+
"216":null,
12+
"119":null
13+
},
14+
"path":{
15+
"215":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/CSIRO\/ACCESS-ESM1-5\/esm-1pct-brch-1000PgC\/r1i1p1f1\/Amon\/tas\/gn\/v20191206\/tas_Amon_ACCESS-ESM1-5_esm-1pct-brch-1000PgC_r1i1p1f1_gn_016801-026812.nc",
16+
"118":"\/home\/bandela\/climate_data\/CMIP6\/CMIP\/CSIRO\/ACCESS-ESM1-5\/1pctCO2\/r1i1p1f1\/Amon\/tas\/gn\/v20191115\/tas_Amon_ACCESS-ESM1-5_1pctCO2_r1i1p1f1_gn_010101-025012.nc",
17+
"216":"\/home\/bandela\/climate_data\/CMIP6\/C4MIP\/CSIRO\/ACCESS-ESM1-5\/esm-1pct-brch-1000PgC\/r1i1p1f1\/fx\/areacella\/gn\/v20191206\/areacella_fx_ACCESS-ESM1-5_esm-1pct-brch-1000PgC_r1i1p1f1_gn.nc",
18+
"119":"\/home\/bandela\/climate_data\/CMIP6\/CMIP\/CSIRO\/ACCESS-ESM1-5\/1pctCO2\/r1i1p1f1\/fx\/areacella\/gn\/v20191115\/areacella_fx_ACCESS-ESM1-5_1pctCO2_r1i1p1f1_gn.nc"
19+
},
20+
"activity_id":{
21+
"215":"C4MIP CDRMIP",
22+
"118":"CMIP",
23+
"216":"C4MIP CDRMIP",
24+
"119":"CMIP"
25+
},
26+
"branch_method":{
27+
"215":"standard",
28+
"118":"standard",
29+
"216":"standard",
30+
"119":"standard"
31+
},
32+
"branch_time_in_child":{
33+
"215":24471.0,
34+
"118":0.0,
35+
"216":24471.0,
36+
"119":0.0
37+
},
38+
"branch_time_in_parent":{
39+
"215":24471.0,
40+
"118":0.0,
41+
"216":24471.0,
42+
"119":0.0
43+
},
44+
"experiment":{
45+
"215":"zero emissions simulation branched from 1% run after 1000 PgC cumulative emission",
46+
"118":"1 percent per year increase in CO2",
47+
"216":"zero emissions simulation branched from 1% run after 1000 PgC cumulative emission",
48+
"119":"1 percent per year increase in CO2"
49+
},
50+
"experiment_id":{
51+
"215":"esm-1pct-brch-1000PgC",
52+
"118":"1pctCO2",
53+
"216":"esm-1pct-brch-1000PgC",
54+
"119":"1pctCO2"
55+
},
56+
"frequency":{
57+
"215":"mon",
58+
"118":"mon",
59+
"216":"fx",
60+
"119":"fx"
61+
},
62+
"grid":{
63+
"215":"native atmosphere N96 grid (145x192 latxlon)",
64+
"118":"native atmosphere N96 grid (145x192 latxlon)",
65+
"216":"native atmosphere N96 grid (145x192 latxlon)",
66+
"119":"native atmosphere N96 grid (145x192 latxlon)"
67+
},
68+
"grid_label":{
69+
"215":"gn",
70+
"118":"gn",
71+
"216":"gn",
72+
"119":"gn"
73+
},
74+
"institution_id":{
75+
"215":"CSIRO",
76+
"118":"CSIRO",
77+
"216":"CSIRO",
78+
"119":"CSIRO"
79+
},
80+
"nominal_resolution":{
81+
"215":"250 km",
82+
"118":"250 km",
83+
"216":"250 km",
84+
"119":"250 km"
85+
},
86+
"parent_activity_id":{
87+
"215":"CMIP",
88+
"118":"CMIP",
89+
"216":"CMIP",
90+
"119":"CMIP"
91+
},
92+
"parent_experiment_id":{
93+
"215":"1pctCO2",
94+
"118":"piControl",
95+
"216":"1pctCO2",
96+
"119":"piControl"
97+
},
98+
"parent_source_id":{
99+
"215":"ACCESS-ESM1-5",
100+
"118":"ACCESS-ESM1-5",
101+
"216":"ACCESS-ESM1-5",
102+
"119":"ACCESS-ESM1-5"
103+
},
104+
"parent_time_units":{
105+
"215":"days since 0101-01-01",
106+
"118":"days since 0101-01-01",
107+
"216":"days since 0101-01-01",
108+
"119":"days since 0101-01-01"
109+
},
110+
"parent_variant_label":{
111+
"215":"r1i1p1f1",
112+
"118":"r1i1p1f1",
113+
"216":"r1i1p1f1",
114+
"119":"r1i1p1f1"
115+
},
116+
"product":{
117+
"215":"model-output",
118+
"118":"model-output",
119+
"216":"model-output",
120+
"119":"model-output"
121+
},
122+
"realm":{
123+
"215":"atmos",
124+
"118":"atmos",
125+
"216":"atmos",
126+
"119":"atmos"
127+
},
128+
"source_id":{
129+
"215":"ACCESS-ESM1-5",
130+
"118":"ACCESS-ESM1-5",
131+
"216":"ACCESS-ESM1-5",
132+
"119":"ACCESS-ESM1-5"
133+
},
134+
"source_type":{
135+
"215":"AOGCM BGC",
136+
"118":"AOGCM",
137+
"216":"AOGCM BGC",
138+
"119":"AOGCM"
139+
},
140+
"sub_experiment":{
141+
"215":"none",
142+
"118":"none",
143+
"216":"none",
144+
"119":"none"
145+
},
146+
"sub_experiment_id":{
147+
"215":"none",
148+
"118":"none",
149+
"216":"none",
150+
"119":"none"
151+
},
152+
"table_id":{
153+
"215":"Amon",
154+
"118":"Amon",
155+
"216":"fx",
156+
"119":"fx"
157+
},
158+
"variable_id":{
159+
"215":"tas",
160+
"118":"tas",
161+
"216":"areacella",
162+
"119":"areacella"
163+
},
164+
"variant_label":{
165+
"215":"r1i1p1f1",
166+
"118":"r1i1p1f1",
167+
"216":"r1i1p1f1",
168+
"119":"r1i1p1f1"
169+
},
170+
"member_id":{
171+
"215":"r1i1p1f1",
172+
"118":"r1i1p1f1",
173+
"216":"r1i1p1f1",
174+
"119":"r1i1p1f1"
175+
},
176+
"standard_name":{
177+
"215":"air_temperature",
178+
"118":"air_temperature",
179+
"216":"cell_area",
180+
"119":"cell_area"
181+
},
182+
"long_name":{
183+
"215":"Near-Surface Air Temperature",
184+
"118":"Near-Surface Air Temperature",
185+
"216":"Grid-Cell Area for Atmospheric Grid Variables",
186+
"119":"Grid-Cell Area for Atmospheric Grid Variables"
187+
},
188+
"units":{
189+
"215":"K",
190+
"118":"K",
191+
"216":"m2",
192+
"119":"m2"
193+
},
194+
"vertical_levels":{
195+
"215":1,
196+
"118":1,
197+
"216":1,
198+
"119":1
199+
},
200+
"init_year":{
201+
"215":null,
202+
"118":null,
203+
"216":null,
204+
"119":null
205+
},
206+
"version":{
207+
"215":"v20191206",
208+
"118":"v20191115",
209+
"216":"v20191206",
210+
"119":"v20191115"
211+
},
212+
"instance_id":{
213+
"215":"CMIP6.C4MIP CDRMIP.CSIRO.ACCESS-ESM1-5.esm-1pct-brch-1000PgC.r1i1p1f1.Amon.tas.gn.v20191206",
214+
"118":"CMIP6.CMIP.CSIRO.ACCESS-ESM1-5.1pctCO2.r1i1p1f1.Amon.tas.gn.v20191115",
215+
"216":"CMIP6.C4MIP CDRMIP.CSIRO.ACCESS-ESM1-5.esm-1pct-brch-1000PgC.r1i1p1f1.fx.areacella.gn.v20191206",
216+
"119":"CMIP6.CMIP.CSIRO.ACCESS-ESM1-5.1pctCO2.r1i1p1f1.fx.areacella.gn.v20191115"
217+
}
218+
}

0 commit comments

Comments
 (0)