ESA CCI CMIP7 recipe example¶
Shows a bundled CMIP7 plugin recipe for a tiny ESA CCI water-vapour dataset.
In [1]:
Copied!
import numpy as np
import woodpecker_cmip7_plugin # noqa: F401 - imports plugin fixes for editable installs
import woodpecker
from woodpecker.testing import make_cmip7
import numpy as np
import woodpecker_cmip7_plugin # noqa: F401 - imports plugin fixes for editable installs
import woodpecker
from woodpecker.testing import make_cmip7
Create an ESA CCI-like input with names, metadata, and latitude order that need repair.
In [2]:
Copied!
source_name = "ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-fv3.2.zarr"
dataset = make_cmip7(variable="prw", overrides={"source_name": source_name}, seed=7)
dataset = dataset.isel(lat=slice(None, None, -1))
dataset = dataset.assign_coords(bnds=[0, 1])
dataset["lat_bnds"] = (
("lat", "bnds"),
np.column_stack([dataset["lat"].values - 0.5, dataset["lat"].values + 0.5]),
)
dataset
source_name = "ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-fv3.2.zarr"
dataset = make_cmip7(variable="prw", overrides={"source_name": source_name}, seed=7)
dataset = dataset.isel(lat=slice(None, None, -1))
dataset = dataset.assign_coords(bnds=[0, 1])
dataset["lat_bnds"] = (
("lat", "bnds"),
np.column_stack([dataset["lat"].values - 0.5, dataset["lat"].values + 0.5]),
)
dataset
Out[2]:
<xarray.Dataset> Size: 32kB
Dimensions: (time: 12, lat: 18, lon: 36, bnds: 2)
Coordinates:
* time (time) datetime64[s] 96B 2000-01-01 2000-02-01 ... 2000-12-01
* lat (lat) float64 144B 85.0 75.0 65.0 55.0 ... -55.0 -65.0 -75.0 -85.0
* lon (lon) float64 288B 0.0 10.0 20.0 30.0 ... 320.0 330.0 340.0 350.0
* bnds (bnds) int64 16B 0 1
Data variables:
prw (time, lat, lon) float32 31kB 2.101 2.13 2.169 ... 1.913 1.942
lat_bnds (lat, bnds) float64 288B 84.5 85.5 74.5 75.5 ... -74.5 -85.5 -84.5
Attributes: (12/15)
project_id: CMIP7
dataset_id: CMIP7.CMIP.MOHC.UKESM2-1.historical.r1i1p1f1.Amon.pr...
source_file: CMIP7.CMIP.MOHC.UKESM2-1.historical.r1i1p1f1.Amon.pr...
source_id: UKESM2-1
source_name: ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-f...
mip_era: CMIP7
... ...
variable_id: prw
table_id: Amon
units: 1
frequency: mon
grid_label: gn
nominal_resolution: 100 kmLoad the bundled ESA CCI recipe and inspect its match rules and fix steps.
In [3]:
Copied!
recipe = woodpecker.recipe.get("cmip7.esa_cci_water_vapour_zarr")
recipe.model_dump()
recipe = woodpecker.recipe.get("cmip7.esa_cci_water_vapour_zarr")
recipe.model_dump()
Out[3]:
{'id': 'cmip7.esa_cci_water_vapour_zarr',
'aliases': [],
'description': 'CMIP7/ESA CCI zarr-style inputs',
'match': {'attrs': {},
'dataset_id_patterns': [],
'path_patterns': ['*ESACCI-WATERVAPOUR-*.zarr']},
'steps': [{'id': 'cmip7.configurable_reformat_bridge',
'phase': 'apply',
'options': {'realm': 'atmos',
'branded_variable': 'prw_tavg-u-hxy-u',
'dim_map': {'bnds': 'nv'},
'variable_map': {'prw': 'tcwv'},
'keep_global_attrs': True},
'links': []},
{'id': 'woodpecker.ensure_latitude_is_increasing',
'phase': 'apply',
'options': {},
'links': []}],
'links': [{'rel': 'self',
'href': 'https://github.com/roocs/woodpecker/blob/main/plugins/woodpecker-cmip7-plugin/src/woodpecker_cmip7_plugin/recipes/esa_cci_water_vapour_recipe.json',
'title': 'Source recipe document'},
{'rel': 'issue',
'href': 'https://github.com/roocs/woodpecker/issues',
'title': 'Track ESA CCI fix discussions'}]}
In [4]:
Copied!
recipe.match.model_dump(), [step.id for step in recipe.steps]
recipe.match.model_dump(), [step.id for step in recipe.steps]
Out[4]:
({'attrs': {},
'dataset_id_patterns': [],
'path_patterns': ['*ESACCI-WATERVAPOUR-*.zarr']},
['cmip7.configurable_reformat_bridge',
'woodpecker.ensure_latitude_is_increasing'])
In [5]:
Copied!
findings = woodpecker.recipe.check(dataset, recipe)
findings.fix_ids
findings = woodpecker.recipe.check(dataset, recipe)
findings.fix_ids
Out[5]:
('cmip7.configurable_reformat_bridge',
'woodpecker.ensure_latitude_is_increasing')
Dry-run previews the repair without changing the dataset.
In [6]:
Copied!
result = woodpecker.recipe.apply(dataset, recipe, dry_run=True)
result.stats, result.preview, tuple(dataset.data_vars), tuple(dataset.dims)
result = woodpecker.recipe.apply(dataset, recipe, dry_run=True)
result.stats, result.preview, tuple(dataset.data_vars), tuple(dataset.dims)
Out[6]:
({'attempted': 2,
'changed': 2,
'persist_attempted': 0,
'persisted': 0,
'persist_failed': 0,
'preview': [{'path': 'ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-fv3.2.zarr',
'fix_id': 'cmip7.configurable_reformat_bridge',
'name': 'Configurable CMIP7 reformat bridge (plugin)',
'labels': ['risk.workflow_transformation'],
'label_titles': ['careful: workflow transformation'],
'label_metadata': [{'id': 'risk.workflow_transformation',
'title': 'careful: workflow transformation',
'description': 'Applies a composed workflow with structural and metadata changes.',
'category': 'risk-high'}],
'changed': True},
{'path': 'ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-fv3.2.zarr',
'fix_id': 'woodpecker.ensure_latitude_is_increasing',
'name': 'Ensure latitude is increasing',
'labels': ['risk.coordinate_reordering'],
'label_titles': ['careful: coordinate reordering'],
'label_metadata': [{'id': 'risk.coordinate_reordering',
'title': 'careful: coordinate reordering',
'description': 'Reorders coordinate-dependent data.',
'category': 'risk-medium'}],
'changed': True}]},
({'path': 'ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-fv3.2.zarr',
'fix_id': 'cmip7.configurable_reformat_bridge',
'name': 'Configurable CMIP7 reformat bridge (plugin)',
'labels': ['risk.workflow_transformation'],
'label_titles': ['careful: workflow transformation'],
'label_metadata': [{'id': 'risk.workflow_transformation',
'title': 'careful: workflow transformation',
'description': 'Applies a composed workflow with structural and metadata changes.',
'category': 'risk-high'}],
'changed': True},
{'path': 'ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-fv3.2.zarr',
'fix_id': 'woodpecker.ensure_latitude_is_increasing',
'name': 'Ensure latitude is increasing',
'labels': ['risk.coordinate_reordering'],
'label_titles': ['careful: coordinate reordering'],
'label_metadata': [{'id': 'risk.coordinate_reordering',
'title': 'careful: coordinate reordering',
'description': 'Reorders coordinate-dependent data.',
'category': 'risk-medium'}],
'changed': True}),
('prw', 'lat_bnds'),
('time', 'lat', 'lon', 'bnds'))
Apply the recipe in memory and re-check.
In [7]:
Copied!
write = woodpecker.recipe.apply(dataset, recipe, dry_run=False)
(
write.stats,
tuple(dataset.data_vars),
tuple(dataset.dims),
dataset.attrs["realm"],
dataset.attrs["branded_variable"],
float(dataset["lat"].values[0]) < float(dataset["lat"].values[-1]),
)
write = woodpecker.recipe.apply(dataset, recipe, dry_run=False)
(
write.stats,
tuple(dataset.data_vars),
tuple(dataset.dims),
dataset.attrs["realm"],
dataset.attrs["branded_variable"],
float(dataset["lat"].values[0]) < float(dataset["lat"].values[-1]),
)
Out[7]:
({'attempted': 2,
'changed': 2,
'persist_attempted': 1,
'persisted': 1,
'persist_failed': 0,
'preview': [{'path': 'ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-fv3.2.zarr',
'fix_id': 'cmip7.configurable_reformat_bridge',
'name': 'Configurable CMIP7 reformat bridge (plugin)',
'labels': ['risk.workflow_transformation'],
'label_titles': ['careful: workflow transformation'],
'label_metadata': [{'id': 'risk.workflow_transformation',
'title': 'careful: workflow transformation',
'description': 'Applies a composed workflow with structural and metadata changes.',
'category': 'risk-high'}],
'changed': True},
{'path': 'ESACCI-WATERVAPOUR-L3C-TCWV-meris-005deg-2002-2017-fv3.2.zarr',
'fix_id': 'woodpecker.ensure_latitude_is_increasing',
'name': 'Ensure latitude is increasing',
'labels': ['risk.coordinate_reordering'],
'label_titles': ['careful: coordinate reordering'],
'label_metadata': [{'id': 'risk.coordinate_reordering',
'title': 'careful: coordinate reordering',
'description': 'Reorders coordinate-dependent data.',
'category': 'risk-medium'}],
'changed': True}]},
('lat_bnds', 'tcwv'),
('time', 'lon', 'lat', 'nv'),
'atmos',
'prw_tavg-u-hxy-u',
True)
In [8]:
Copied!
recheck = woodpecker.recipe.check(dataset, recipe)
bool(recheck)
recheck = woodpecker.recipe.check(dataset, recipe)
bool(recheck)
Out[8]:
False