CMIP6-decadal recipe example¶
Shows the standalone Woodpecker calls used for C3S CMIP6-decadal adaptation.
Flow: create synthetic decadal data -> load c3s.cmip6_decadal -> run prepare -> run apply -> re-check.
In [1]:
Copied!
import numpy as np
import woodpecker_cmip6_decadal_plugin # noqa: F401 - imports plugin fixes for editable installs
import xarray as xr
import woodpecker
from woodpecker.recipe import APPLY_PHASE, PREPARE_PHASE
from woodpecker.testing import make_cmip6_decadal
import numpy as np
import woodpecker_cmip6_decadal_plugin # noqa: F401 - imports plugin fixes for editable installs
import xarray as xr
import woodpecker
from woodpecker.recipe import APPLY_PHASE, PREPARE_PHASE
from woodpecker.testing import make_cmip6_decadal
Create a tiny CMIP6-decadal-like dataset with changes that exercise both phases.
The calendar encoding is a prepare-phase change. The remaining metadata and coordinate changes are normal apply-phase adaptations.
In [2]:
Copied!
source_name = (
"c3s-cmip6-decadal.DCPP.EC-Earth-Consortium.EC-Earth3."
"dcppA-hindcast.s1960-r2i1p1f1.Amon.tas.gr.v20201215.nc"
)
dataset = make_cmip6_decadal(
overrides={
"source_name": source_name,
"startdate": "s1960",
"sub_experiment_id": "s1960",
"realization_index": "2",
"forcing_description": "wrong",
}
)
dataset = dataset.isel(time=slice(0, 2))
dataset = dataset.assign_coords(time=np.array(["1960-11-16", "1960-12-16"], dtype="datetime64[D]"))
dataset["time"].attrs["long_name"] = "time"
dataset["time"].encoding["calendar"] = "proleptic_gregorian"
dataset["realization"] = xr.DataArray(
2,
attrs={"comment": "short", "long_name": "member"},
)
dataset["realization"].encoding["_FillValue"] = -9999
dataset
source_name = (
"c3s-cmip6-decadal.DCPP.EC-Earth-Consortium.EC-Earth3."
"dcppA-hindcast.s1960-r2i1p1f1.Amon.tas.gr.v20201215.nc"
)
dataset = make_cmip6_decadal(
overrides={
"source_name": source_name,
"startdate": "s1960",
"sub_experiment_id": "s1960",
"realization_index": "2",
"forcing_description": "wrong",
}
)
dataset = dataset.isel(time=slice(0, 2))
dataset = dataset.assign_coords(time=np.array(["1960-11-16", "1960-12-16"], dtype="datetime64[D]"))
dataset["time"].attrs["long_name"] = "time"
dataset["time"].encoding["calendar"] = "proleptic_gregorian"
dataset["realization"] = xr.DataArray(
2,
attrs={"comment": "short", "long_name": "member"},
)
dataset["realization"].encoding["_FillValue"] = -9999
dataset
Out[2]:
<xarray.Dataset> Size: 6kB
Dimensions: (lat: 18, lon: 36, time: 2)
Coordinates:
* lat (lat) float64 144B -85.0 -75.0 -65.0 -55.0 ... 65.0 75.0 85.0
* lon (lon) float64 288B 0.0 10.0 20.0 30.0 ... 330.0 340.0 350.0
* time (time) datetime64[s] 16B 1960-11-16 1960-12-16
Data variables:
tos (time, lat, lon) float32 5kB 254.2 254.1 254.1 ... 254.7 254.6
realization int64 8B 2
Attributes: (12/19)
project_id: CMIP6
dataset_id: CMIP6.DCPP.MPI-M.MPI-ESM1-2-HR.dcppA-hindcast.s1960...
source_file: CMIP6.DCPP.MPI-M.MPI-ESM1-2-HR.dcppA-hindcast.s1960...
source_id: MPI-ESM1-2-HR
source_name: c3s-cmip6-decadal.DCPP.EC-Earth-Consortium.EC-Earth...
mip_era: CMIP6
... ...
frequency: mon
grid_label: gn
nominal_resolution: 100 km
startdate: s1960
realization_index: 2
forcing_description: wrongLoad the bundled recipe and inspect the phase layout.
In [3]:
Copied!
recipe = woodpecker.recipe.get("c3s.cmip6_decadal")
[(step.phase, step.id) for step in recipe.steps]
recipe = woodpecker.recipe.get("c3s.cmip6_decadal")
[(step.phase, step.id) for step in recipe.steps]
Out[3]:
[('prepare', 'cmip6_decadal.calendar_normalization'),
('apply', 'cmip6_decadal.time_metadata'),
('apply', 'cmip6_decadal.realization_variable'),
('apply', 'cmip6_decadal.coordinates_encoding_cleanup'),
('apply', 'cmip6_decadal.realization_comment_normalization'),
('apply', 'cmip6_decadal.realization_dtype_normalization'),
('apply', 'cmip6_decadal.fillvalue_encoding_cleanup'),
('apply', 'cmip6_decadal.further_info_url_normalization'),
('apply', 'cmip6_decadal.start_token_normalization'),
('apply', 'cmip6_decadal.realization_long_name_normalization'),
('apply', 'cmip6_decadal.realization_index_normalization'),
('apply', 'cmip6_decadal.leadtime_metadata_normalization'),
('apply', 'cmip6_decadal.model_global_attributes'),
('apply', 'cmip6_decadal.reftime_coordinate'),
('apply', 'cmip6_decadal.leadtime_coordinate')]
Run the pre-concatenation preparation step explicitly with phase=PREPARE_PHASE.
In [4]:
Copied!
prepare_findings = woodpecker.recipe.check(dataset, recipe, phase=PREPARE_PHASE)
prepare_preview = woodpecker.recipe.apply(dataset, recipe, phase=PREPARE_PHASE, dry_run=True)
prepare_write = woodpecker.recipe.apply(dataset, recipe, phase=PREPARE_PHASE, dry_run=False)
(
prepare_findings.fix_ids,
prepare_preview.changed,
prepare_write.changed,
dataset["time"].encoding["calendar"],
)
prepare_findings = woodpecker.recipe.check(dataset, recipe, phase=PREPARE_PHASE)
prepare_preview = woodpecker.recipe.apply(dataset, recipe, phase=PREPARE_PHASE, dry_run=True)
prepare_write = woodpecker.recipe.apply(dataset, recipe, phase=PREPARE_PHASE, dry_run=False)
(
prepare_findings.fix_ids,
prepare_preview.changed,
prepare_write.changed,
dataset["time"].encoding["calendar"],
)
Out[4]:
(('cmip6_decadal.calendar_normalization',), 1, 1, 'standard')
The normal C3S/CDS adaptation uses the same public API with phase=APPLY_PHASE.
In [5]:
Copied!
apply_findings = woodpecker.recipe.check(dataset, recipe, phase=APPLY_PHASE)
apply_preview = woodpecker.recipe.apply(dataset, recipe, phase=APPLY_PHASE, dry_run=True)
apply_write = woodpecker.recipe.apply(dataset, recipe, phase=APPLY_PHASE, dry_run=False)
(
apply_findings.fix_ids,
apply_preview.changed,
apply_write.changed,
)
apply_findings = woodpecker.recipe.check(dataset, recipe, phase=APPLY_PHASE)
apply_preview = woodpecker.recipe.apply(dataset, recipe, phase=APPLY_PHASE, dry_run=True)
apply_write = woodpecker.recipe.apply(dataset, recipe, phase=APPLY_PHASE, dry_run=False)
(
apply_findings.fix_ids,
apply_preview.changed,
apply_write.changed,
)
Out[5]:
(('cmip6_decadal.time_metadata',
'cmip6_decadal.realization_comment_normalization',
'cmip6_decadal.realization_dtype_normalization',
'cmip6_decadal.fillvalue_encoding_cleanup',
'cmip6_decadal.start_token_normalization',
'cmip6_decadal.realization_long_name_normalization',
'cmip6_decadal.realization_index_normalization',
'cmip6_decadal.model_global_attributes',
'cmip6_decadal.reftime_coordinate'),
9,
10)
After both phases, the synthetic dataset has the expected C3S-ready fields.
In [6]:
Copied!
(
dataset.attrs["startdate"],
dataset.attrs["sub_experiment_id"],
dataset.attrs["forcing_description"],
dataset.attrs["realization_index"],
dataset["time"].attrs["long_name"],
dataset["time"].encoding["calendar"],
dataset["realization"].dtype,
dataset["realization"].attrs["long_name"],
"_FillValue" in dataset["realization"].encoding,
"reftime" in dataset.coords,
"leadtime" in dataset.coords,
)
(
dataset.attrs["startdate"],
dataset.attrs["sub_experiment_id"],
dataset.attrs["forcing_description"],
dataset.attrs["realization_index"],
dataset["time"].attrs["long_name"],
dataset["time"].encoding["calendar"],
dataset["realization"].dtype,
dataset["realization"].attrs["long_name"],
"_FillValue" in dataset["realization"].encoding,
"reftime" in dataset.coords,
"leadtime" in dataset.coords,
)
Out[6]:
('s196011',
's196011',
'f1, CMIP6 historical forcings',
2,
'valid_time',
'standard',
dtype('int32'),
'realization',
False,
True,
True)
In [7]:
Copied!
assert dataset.attrs["startdate"] == "s196011"
assert dataset.attrs["sub_experiment_id"] == "s196011"
assert dataset.attrs["forcing_description"] == "f1, CMIP6 historical forcings"
assert dataset.attrs["realization_index"] == 2
assert dataset["time"].attrs["long_name"] == "valid_time"
assert dataset["time"].encoding["calendar"] == "standard"
assert dataset["realization"].dtype == np.int32
assert dataset["realization"].attrs["long_name"] == "realization"
assert "_FillValue" not in dataset["realization"].encoding
assert "reftime" in dataset.coords
assert "leadtime" in dataset.coords
bool(woodpecker.recipe.check(dataset, recipe))
assert dataset.attrs["startdate"] == "s196011"
assert dataset.attrs["sub_experiment_id"] == "s196011"
assert dataset.attrs["forcing_description"] == "f1, CMIP6 historical forcings"
assert dataset.attrs["realization_index"] == 2
assert dataset["time"].attrs["long_name"] == "valid_time"
assert dataset["time"].encoding["calendar"] == "standard"
assert dataset["realization"].dtype == np.int32
assert dataset["realization"].attrs["long_name"] == "realization"
assert "_FillValue" not in dataset["realization"].encoding
assert "reftime" in dataset.coords
assert "leadtime" in dataset.coords
bool(woodpecker.recipe.check(dataset, recipe))
Out[7]:
False