Introduction to xcmor#
This notebook is a brief introduction to xcmor’s current capabilities.
import xarray as xr
import xcmor
# For this notebooks, it's nicer if we don't show the array values by default
xr.set_options(display_expand_data=False)
<xarray.core.options.set_options at 0x7babb2c7d390>
xcmor works best when xarray keeps attributes by default.
xr.set_options(keep_attrs=True)
<xarray.core.options.set_options at 0x7bab8b996d40>
We use an example dataset with a 2D temperature field. Let’s load a regular gridded dataset:
from xcmor.datasets import reg_ds
reg_ds
<xarray.Dataset> Size: 248B
Dimensions: (x: 2, y: 2, time: 3)
Coordinates:
lon (x) float64 16B 80.17 80.68
lat (y) float64 16B 42.25 42.21
* time (time) datetime64[ns] 24B 2014-09-06 2014-09-07 2014-09-08
Dimensions without coordinates: x, y
Data variables:
temperature (x, y, time) float64 96B 29.11 18.2 22.83 ... 16.15 26.63
precipitation (x, y, time) float64 96B 5.68 9.256 0.7104 ... 4.615 7.805Also, let’s load some example cmor tables, e.g.
from xcmor.tests.tables import coords, dataset, mip_amon
These tables are just some subsets of the original CMIP6 CMOR tables. Now, we can use those tables to rewrite variable attributes acoording to CF conventions and the CMIP6 data request using
ds_cmor = xcmor.cmorize(
reg_ds.rename({"temperature": "tas"}).tas,
mip_table=mip_amon,
coords_table=coords,
dataset_table=dataset,
)
ds_cmor
Downloading data from 'https://raw.githubusercontent.com/cf-convention/cf-convention.github.io/master/Data/cf-standard-names/current/src/cf-standard-name-table.xml' to file '/home/docs/.cache/pooch/47dae451573e45041e8665efd76db5a9-cf-standard-name-table.xml'.
I think 'lon' is of type 'longitude'. It matched regex.Regex('x?(nav_lon|lon|glam)[a-z0-9]*', flags=regex.V0)
I think 'lat' is of type 'latitude'. It matched regex.Regex('y?(nav_lat|lat|gphi)[a-z0-9]*', flags=regex.V0)
I think 'time' is of type 'time'. It has a datetime-like type.
SHA256 hash of downloaded file: 3653c1e1a55cd0d3dd7b63c1c0cdf86b51681d672d8407cecccece2047ab6c94
Use this value as the 'known_hash' argument of 'pooch.retrieve' to ensure that the file hasn't changed if it is downloaded again in the future.
2026-06-03 09:54:55,478 - xcmor.rules - INFO - converting tas from float64 to float32 (rules.py:23)
2026-06-03 09:54:55,498 - xcmor.xcmor - DEBUG - curvilinear: False (xcmor.py:384)
2026-06-03 09:54:55,499 - xcmor.xcmor - DEBUG - has_grid_mapping: False (xcmor.py:385)
2026-06-03 09:54:55,516 - xcmor.xcmor - WARNING - "Dataset.cf does not understand the key 'X'. Use 'repr(Dataset.cf)' (or 'Dataset.cf' in a Jupyter environment) to see a list of key names that can be interpreted." (xcmor.py:110)
2026-06-03 09:54:55,537 - xcmor.xcmor - DEBUG - has lonlat: True (xcmor.py:398)
2026-06-03 09:54:55,538 - xcmor.xcmor - DEBUG - has xy: True (xcmor.py:399)
2026-06-03 09:54:55,538 - xcmor.xcmor - DEBUG - x-axis, y-axis: lon, lat (xcmor.py:400)
2026-06-03 09:54:55,539 - xcmor.xcmor - DEBUG - longitude, latitude: lon, lat (xcmor.py:401)
2026-06-03 09:54:55,540 - xcmor.xcmor - DEBUG - auxiliary coordinates: False (xcmor.py:407)
2026-06-03 09:54:55,540 - xcmor.xcmor - DEBUG - tas: ['longitude', 'latitude', 'time', 'height2m'] (xcmor.py:449)
2026-06-03 09:54:55,541 - xcmor.xcmor - DEBUG - longitude, {'standard_name': 'longitude', 'units': 'degrees_east', 'axis': 'X', 'long_name': 'Longitude', 'climatology': '', 'formula': '', 'must_have_bounds': 'yes', 'out_name': 'lon', 'positive': '', 'requested': '', 'requested_bounds': '', 'stored_direction': 'increasing', 'tolerance': '', 'type': 'double', 'valid_max': '360.0', 'valid_min': '0.0', 'value': '', 'z_bounds_factors': '', 'z_factors': '', 'bounds_values': '', 'generic_level_name': ''} (xcmor.py:313)
2026-06-03 09:54:55,541 - xcmor.xcmor - DEBUG - adding coordinate attribtes: lon (xcmor.py:272)
2026-06-03 09:54:55,542 - xcmor.xcmor - DEBUG - latitude, {'standard_name': 'latitude', 'units': 'degrees_north', 'axis': 'Y', 'long_name': 'Latitude', 'climatology': '', 'formula': '', 'must_have_bounds': 'yes', 'out_name': 'lat', 'positive': '', 'requested': '', 'requested_bounds': '', 'stored_direction': 'increasing', 'tolerance': '', 'type': 'double', 'valid_max': '90.0', 'valid_min': '-90.0', 'value': '', 'z_bounds_factors': '', 'z_factors': '', 'bounds_values': '', 'generic_level_name': ''} (xcmor.py:313)
2026-06-03 09:54:55,542 - xcmor.xcmor - DEBUG - adding coordinate attribtes: lat (xcmor.py:272)
2026-06-03 09:54:55,543 - xcmor.xcmor - DEBUG - time, {'standard_name': 'time', 'units': 'days since ?', 'axis': 'T', 'long_name': 'time', 'climatology': '', 'formula': '', 'must_have_bounds': 'yes', 'out_name': 'time', 'positive': '', 'requested': '', 'requested_bounds': '', 'stored_direction': 'increasing', 'tolerance': '', 'type': 'double', 'valid_max': '', 'valid_min': '', 'value': '', 'z_bounds_factors': '', 'z_factors': '', 'bounds_values': '', 'generic_level_name': ''} (xcmor.py:313)
2026-06-03 09:54:55,543 - xcmor.xcmor - DEBUG - adding coordinate attribtes: time (xcmor.py:272)
2026-06-03 09:54:55,545 - xcmor.xcmor - DEBUG - height2m, {'standard_name': 'height', 'units': 'm', 'axis': 'Z', 'long_name': 'height', 'climatology': '', 'formula': '', 'must_have_bounds': 'no', 'out_name': 'height', 'positive': 'up', 'requested': '', 'requested_bounds': '', 'stored_direction': 'increasing', 'tolerance': '', 'type': 'double', 'valid_max': '10.0', 'valid_min': '1.0', 'value': '2.', 'z_bounds_factors': '', 'z_factors': '', 'bounds_values': '', 'generic_level_name': ''} (xcmor.py:313)
2026-06-03 09:54:55,545 - xcmor.xcmor - DEBUG - adding coordinate attribtes: height (xcmor.py:272)
2026-06-03 09:54:55,580 - xcmor.xcmor - INFO - adding coordinate: height (xcmor.py:278)
2026-06-03 09:54:55,581 - xcmor.xcmor - DEBUG - added coordinates: ['lon', 'lat', 'time', 'height'] (xcmor.py:478)
2026-06-03 09:54:55,677 - xcmor.rules - DEBUG - checking bounds for lon: True (rules.py:89)
2026-06-03 09:54:55,680 - xcmor.rules - WARNING - lon must have bounds (rules.py:91)
2026-06-03 09:54:55,681 - xcmor.rules - INFO - adding bounds for lon (rules.py:93)
2026-06-03 09:54:55,737 - xcmor.rules - DEBUG - checking bounds for lat: True (rules.py:89)
2026-06-03 09:54:55,740 - xcmor.rules - WARNING - lat must have bounds (rules.py:91)
2026-06-03 09:54:55,741 - xcmor.rules - INFO - adding bounds for lat (rules.py:93)
2026-06-03 09:54:55,843 - xcmor.rules - DEBUG - checking bounds for time: True (rules.py:89)
2026-06-03 09:54:55,848 - xcmor.rules - WARNING - time must have bounds (rules.py:91)
2026-06-03 09:54:55,848 - xcmor.rules - INFO - adding bounds for time (rules.py:93)
2026-06-03 09:54:55,908 - xcmor.xcmor - DEBUG - setting time units: days since 2014-09-06T00:00:00 (xcmor.py:53)
2026-06-03 09:54:55,910 - xcmor.xcmor - DEBUG - coord: lon (xcmor.py:654)
2026-06-03 09:54:55,911 - xcmor.xcmor - DEBUG - coord: lat (xcmor.py:654)
2026-06-03 09:54:55,912 - xcmor.xcmor - INFO - swap dims: {'x': 'lon', 'y': 'lat'} (xcmor.py:656)
/home/docs/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/cf_xarray/accessor.py:718: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
unused_keys = set(attribute.keys()) - set(inverted)
/home/docs/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/cf_xarray/accessor.py:719: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
for key, value in attribute.items():
/home/docs/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/cf_xarray/accessor.py:727: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
newmap.update({key: attribute[key] for key in unused_keys})
2026-06-03 09:54:55,916 - xcmor.xcmor - DEBUG - transposing order: ['T', 'Y', 'X'] (xcmor.py:31)
<xarray.Dataset> Size: 224B
Dimensions: (lon: 2, lat: 2, time: 3, bounds: 2)
Coordinates:
* lon (lon) float64 16B 80.17 80.68
* lat (lat) float64 16B 42.25 42.21
height float64 8B ...
lon_bounds (lon, bounds) float64 32B ...
lat_bounds (lat, bounds) float64 32B ...
time_bounds (time, bounds) datetime64[ns] 48B 2014-09-05T12:00:00 ... 20...
* time (time) datetime64[ns] 24B 2014-09-06 2014-09-07 2014-09-08
Dimensions without coordinates: bounds
Data variables:
tas (time, lat, lon) float32 48B ...
Attributes: (12/42)
activity_id: ISMIP6
branch_method: standard
branch_time_in_child: 59400.0
branch_time_in_parent: 59400.0
calendar: 360_day
comment:
... ...
source_type: AOGCM ISM AER
sub_experiment: none
sub_experiment_id: none
tracking_prefix: hdl:21.14100
variable_id: tas
version: 20260603The Cmorizer class#
xcmor comes with some pre-configures table options through the Cmorizer class. A simple example for CMIP6 would be:
from xcmor import Cmorizer
cmor = Cmorizer(project="CMIP6")
ds_out = cmor.cmorize(
reg_ds.rename(temperature="tas").tas, "Amon", cmor.tables["input_example"]
)
ds_out
Downloading data from 'https://raw.githubusercontent.com/PCMDI/cmip6-cmor-tables/master/Tables/CMIP6_input_example.json' to file '/home/docs/.cache/pooch/b1e6a504651492874b7eb0c52a902532-CMIP6_input_example.json'.
SHA256 hash of downloaded file: fdaa955f10fe95bbe62d1c0cf08810338a97d92a553abcb73bb33456b098bc5b
Use this value as the 'known_hash' argument of 'pooch.retrieve' to ensure that the file hasn't changed if it is downloaded again in the future.
Downloading data from 'https://raw.githubusercontent.com/PCMDI/cmip6-cmor-tables/master/Tables/CMIP6_Amon.json' to file '/home/docs/.cache/pooch/69da11734d03d2070269b48e6322d094-CMIP6_Amon.json'.
SHA256 hash of downloaded file: d3ac72751f401e551ab60ae94a1c2a24441563ad8e5c0be84687775f5b75ca1d
Use this value as the 'known_hash' argument of 'pooch.retrieve' to ensure that the file hasn't changed if it is downloaded again in the future.
Downloading data from 'https://raw.githubusercontent.com/PCMDI/cmip6-cmor-tables/master/Tables/CMIP6_coordinate.json' to file '/home/docs/.cache/pooch/914887963ad537da427851a882fab071-CMIP6_coordinate.json'.
SHA256 hash of downloaded file: 0536448a0e85a4a57122839f2d0f58701beb89cf2b5245f2ff34456c348bf8f0
Use this value as the 'known_hash' argument of 'pooch.retrieve' to ensure that the file hasn't changed if it is downloaded again in the future.
Downloading data from 'https://raw.githubusercontent.com/PCMDI/cmip6-cmor-tables/master/Tables/CMIP6_CV.json' to file '/home/docs/.cache/pooch/1d0ce1bf51bbab9d775c345522ab3938-CMIP6_CV.json'.
SHA256 hash of downloaded file: 4554d289977a3c0c88f712ff5d03c6dfcb44a86a4cc434b4feb2e3ec0874e9ab
Use this value as the 'known_hash' argument of 'pooch.retrieve' to ensure that the file hasn't changed if it is downloaded again in the future.
Downloading data from 'https://raw.githubusercontent.com/PCMDI/cmip6-cmor-tables/master/Tables/CMIP6_grids.json' to file '/home/docs/.cache/pooch/21d259528b2aae0c41a9c08d8dc74a61-CMIP6_grids.json'.
SHA256 hash of downloaded file: 50477cedb65c6a28c2c484c4c6dd20479defb12937a7466a2f6121c22cfcd573
Use this value as the 'known_hash' argument of 'pooch.retrieve' to ensure that the file hasn't changed if it is downloaded again in the future.
2026-06-03 09:54:56,936 - xcmor.rules - INFO - converting tas from float64 to float32 (rules.py:23)
2026-06-03 09:54:56,956 - xcmor.xcmor - DEBUG - curvilinear: False (xcmor.py:384)
2026-06-03 09:54:56,957 - xcmor.xcmor - DEBUG - has_grid_mapping: False (xcmor.py:385)
2026-06-03 09:54:56,974 - xcmor.xcmor - WARNING - "Dataset.cf does not understand the key 'X'. Use 'repr(Dataset.cf)' (or 'Dataset.cf' in a Jupyter environment) to see a list of key names that can be interpreted." (xcmor.py:110)
2026-06-03 09:54:56,995 - xcmor.xcmor - DEBUG - has lonlat: True (xcmor.py:398)
2026-06-03 09:54:56,995 - xcmor.xcmor - DEBUG - has xy: True (xcmor.py:399)
2026-06-03 09:54:56,996 - xcmor.xcmor - DEBUG - x-axis, y-axis: lon, lat (xcmor.py:400)
2026-06-03 09:54:56,996 - xcmor.xcmor - DEBUG - longitude, latitude: lon, lat (xcmor.py:401)
2026-06-03 09:54:56,997 - xcmor.xcmor - DEBUG - auxiliary coordinates: False (xcmor.py:407)
2026-06-03 09:54:56,997 - xcmor.xcmor - DEBUG - tas: ['longitude', 'latitude', 'time', 'height2m'] (xcmor.py:449)
2026-06-03 09:54:56,998 - xcmor.xcmor - DEBUG - longitude, {'standard_name': 'longitude', 'units': 'degrees_east', 'axis': 'X', 'long_name': 'Longitude', 'climatology': '', 'formula': '', 'must_have_bounds': 'yes', 'out_name': 'lon', 'positive': '', 'requested': '', 'requested_bounds': '', 'stored_direction': 'increasing', 'tolerance': '', 'type': 'double', 'valid_max': '360.0', 'valid_min': '0.0', 'value': '', 'z_bounds_factors': '', 'z_factors': '', 'bounds_values': '', 'generic_level_name': ''} (xcmor.py:313)
2026-06-03 09:54:56,998 - xcmor.xcmor - DEBUG - adding coordinate attribtes: lon (xcmor.py:272)
2026-06-03 09:54:56,999 - xcmor.xcmor - DEBUG - latitude, {'standard_name': 'latitude', 'units': 'degrees_north', 'axis': 'Y', 'long_name': 'Latitude', 'climatology': '', 'formula': '', 'must_have_bounds': 'yes', 'out_name': 'lat', 'positive': '', 'requested': '', 'requested_bounds': '', 'stored_direction': 'increasing', 'tolerance': '', 'type': 'double', 'valid_max': '90.0', 'valid_min': '-90.0', 'value': '', 'z_bounds_factors': '', 'z_factors': '', 'bounds_values': '', 'generic_level_name': ''} (xcmor.py:313)
2026-06-03 09:54:56,999 - xcmor.xcmor - DEBUG - adding coordinate attribtes: lat (xcmor.py:272)
2026-06-03 09:54:57,000 - xcmor.xcmor - DEBUG - time, {'standard_name': 'time', 'units': 'days since ?', 'axis': 'T', 'long_name': 'time', 'climatology': '', 'formula': '', 'must_have_bounds': 'yes', 'out_name': 'time', 'positive': '', 'requested': '', 'requested_bounds': '', 'stored_direction': 'increasing', 'tolerance': '', 'type': 'double', 'valid_max': '', 'valid_min': '', 'value': '', 'z_bounds_factors': '', 'z_factors': '', 'bounds_values': '', 'generic_level_name': ''} (xcmor.py:313)
2026-06-03 09:54:57,000 - xcmor.xcmor - DEBUG - adding coordinate attribtes: time (xcmor.py:272)
2026-06-03 09:54:57,000 - xcmor.xcmor - DEBUG - height2m, {'standard_name': 'height', 'units': 'm', 'axis': 'Z', 'long_name': 'height', 'climatology': '', 'formula': '', 'must_have_bounds': 'no', 'out_name': 'height', 'positive': 'up', 'requested': '', 'requested_bounds': '', 'stored_direction': 'increasing', 'tolerance': '', 'type': 'double', 'valid_max': '10.0', 'valid_min': '1.0', 'value': '2.', 'z_bounds_factors': '', 'z_factors': '', 'bounds_values': '', 'generic_level_name': ''} (xcmor.py:313)
2026-06-03 09:54:57,001 - xcmor.xcmor - DEBUG - adding coordinate attribtes: height (xcmor.py:272)
2026-06-03 09:54:57,038 - xcmor.xcmor - INFO - adding coordinate: height (xcmor.py:278)
2026-06-03 09:54:57,039 - xcmor.xcmor - DEBUG - added coordinates: ['lon', 'lat', 'time', 'height'] (xcmor.py:478)
I think 'lon' is of type 'longitude'. It matched regex.Regex('x?(nav_lon|lon|glam)[a-z0-9]*', flags=regex.V0)
I think 'lat' is of type 'latitude'. It matched regex.Regex('y?(nav_lat|lat|gphi)[a-z0-9]*', flags=regex.V0)
I think 'time' is of type 'time'. It has a datetime-like type.
2026-06-03 09:54:57,086 - xcmor.rules - DEBUG - checking bounds for lon: True (rules.py:89)
2026-06-03 09:54:57,090 - xcmor.rules - WARNING - lon must have bounds (rules.py:91)
2026-06-03 09:54:57,091 - xcmor.rules - INFO - adding bounds for lon (rules.py:93)
2026-06-03 09:54:57,193 - xcmor.rules - DEBUG - checking bounds for lat: True (rules.py:89)
2026-06-03 09:54:57,197 - xcmor.rules - WARNING - lat must have bounds (rules.py:91)
2026-06-03 09:54:57,198 - xcmor.rules - INFO - adding bounds for lat (rules.py:93)
2026-06-03 09:54:57,251 - xcmor.rules - DEBUG - checking bounds for time: True (rules.py:89)
2026-06-03 09:54:57,256 - xcmor.rules - WARNING - time must have bounds (rules.py:91)
2026-06-03 09:54:57,256 - xcmor.rules - INFO - adding bounds for time (rules.py:93)
2026-06-03 09:54:57,373 - xcmor.xcmor - DEBUG - setting time units: days since 2014-09-06T00:00:00 (xcmor.py:53)
2026-06-03 09:54:57,374 - xcmor.xcmor - DEBUG - coord: lon (xcmor.py:654)
2026-06-03 09:54:57,376 - xcmor.xcmor - DEBUG - coord: lat (xcmor.py:654)
2026-06-03 09:54:57,376 - xcmor.xcmor - INFO - swap dims: {'x': 'lon', 'y': 'lat'} (xcmor.py:656)
2026-06-03 09:54:57,378 - xcmor.xcmor - DEBUG - for attribute 'activity' --> add value 'Ice Sheet Model Intercomparison Project for CMIP6' (xcmor.py:592)
2026-06-03 09:54:57,378 - xcmor.xcmor - INFO - attribute 'experiment_id' has value 'piControl-withism' and requires attribute 'experiment' to be set to 'preindustrial control with interactive ice sheet' (xcmor.py:616)
2026-06-03 09:54:57,379 - xcmor.xcmor - WARNING - attribute 'experiment_id' has value 'piControl-withism' but attribute 'parent_activity_id' has value 'CMIP' which is not in the list of expected values: ['no parent'] (xcmor.py:611)
2026-06-03 09:54:57,380 - xcmor.xcmor - WARNING - attribute 'experiment_id' has value 'piControl-withism' but attribute 'parent_experiment_id' has value 'historical' which is not in the list of expected values: ['no parent'] (xcmor.py:611)
2026-06-03 09:54:57,381 - xcmor.xcmor - DEBUG - for attribute 'frequency_info' --> add value 'monthly mean samples' (xcmor.py:602)
2026-06-03 09:54:57,382 - xcmor.xcmor - DEBUG - for attribute 'grid_label_info' --> add value 'data reported on a model's native grid' (xcmor.py:602)
2026-06-03 09:54:57,382 - xcmor.xcmor - DEBUG - for attribute 'institution' --> add value 'Program for Climate Model Diagnosis and Intercomparison, Lawrence Livermore National Laboratory, Livermore, CA 94550, USA' (xcmor.py:592)
2026-06-03 09:54:57,383 - xcmor.xcmor - WARNING - attribute 'source_id' has value 'PCMDI-test-1-0' but attribute 'source' is set to 'PCMDI-test 1.0 (1989)' but CV requires 'PCMDI-test 1.0 (1989):
aerosol: none
atmos: Earth1.0-gettingHotter (360 x 180 longitude/latitude; 50 levels; top level 0.1 mb)
atmosChem: none
land: Earth1.0
landIce: none
ocean: BlueMarble1.0-warming (360 x 180 longitude/latitude; 50 levels; top grid cell 0-10 m)
ocnBgchem: none
seaIce: Declining1.0-warming (360 x 180 longitude/latitude)'! (xcmor.py:620)
2026-06-03 09:54:57,384 - xcmor.xcmor - DEBUG - for attribute 'sub_experiment' --> add value 'none' (xcmor.py:592)
2026-06-03 09:54:57,384 - xcmor.xcmor - DEBUG - Checking global attribute 'Conventions' with value 'CF-1.7 CMIP-6.2' (xcmor.py:540)
2026-06-03 09:54:57,386 - xcmor.xcmor - DEBUG - Checking global attribute 'activity_id' with value 'ISMIP6' (xcmor.py:540)
2026-06-03 09:54:57,387 - xcmor.xcmor - DEBUG - Found valid value 'ISMIP6...' for 'activity_id' (xcmor.py:548)
2026-06-03 09:54:57,387 - xcmor.xcmor - DEBUG - Checking global attribute 'creation_date' with value '2026-06-03T09:54:57UTC' (xcmor.py:540)
2026-06-03 09:54:57,387 - xcmor.xcmor - DEBUG - Found global attribute 'creation_date' with value '2026-06-03T09:54:57UTC...' which has no specific requirements (xcmor.py:544)
2026-06-03 09:54:57,388 - xcmor.xcmor - DEBUG - Checking global attribute 'data_specs_version' with value '01.00.33' (xcmor.py:540)
2026-06-03 09:54:57,388 - xcmor.xcmor - DEBUG - Checking global attribute 'experiment' with value 'preindustrial control with interactive ice sheet' (xcmor.py:540)
2026-06-03 09:54:57,389 - xcmor.xcmor - DEBUG - Found global attribute 'experiment' with value 'preindustrial control with interactive ice sheet...' which has no specific requirements (xcmor.py:544)
2026-06-03 09:54:57,389 - xcmor.xcmor - DEBUG - Checking global attribute 'experiment_id' with value 'piControl-withism' (xcmor.py:540)
2026-06-03 09:54:57,390 - xcmor.xcmor - DEBUG - Found valid value 'piControl-withism...' for 'experiment_id' (xcmor.py:548)
2026-06-03 09:54:57,390 - xcmor.xcmor - DEBUG - Checking global attribute 'forcing_index' with value '1' (xcmor.py:540)
2026-06-03 09:54:57,391 - xcmor.xcmor - DEBUG - Checking global attribute 'frequency' with value 'mon' (xcmor.py:540)
2026-06-03 09:54:57,391 - xcmor.xcmor - DEBUG - Found valid value 'mon...' for 'frequency' (xcmor.py:548)
2026-06-03 09:54:57,392 - xcmor.xcmor - DEBUG - Checking global attribute 'further_info_url' with value 'None' (xcmor.py:540)
2026-06-03 09:54:57,392 - xcmor.xcmor - ERROR - global further_info_url not found but required (xcmor.py:542)
2026-06-03 09:54:57,392 - xcmor.xcmor - DEBUG - Checking global attribute 'grid' with value 'native atmosphere regular grid (3x4 latxlon)' (xcmor.py:540)
2026-06-03 09:54:57,393 - xcmor.xcmor - DEBUG - Found global attribute 'grid' with value 'native atmosphere regular grid (3x4 latxlon)...' which has no specific requirements (xcmor.py:544)
2026-06-03 09:54:57,393 - xcmor.xcmor - DEBUG - Checking global attribute 'grid_label' with value 'gn' (xcmor.py:540)
2026-06-03 09:54:57,394 - xcmor.xcmor - DEBUG - Found valid value 'gn...' for 'grid_label' (xcmor.py:548)
2026-06-03 09:54:57,394 - xcmor.xcmor - DEBUG - Checking global attribute 'initialization_index' with value '1' (xcmor.py:540)
2026-06-03 09:54:57,395 - xcmor.xcmor - DEBUG - Checking global attribute 'institution' with value 'Program for Climate Model Diagnosis and Intercomparison, Lawrence Livermore National Laboratory, Livermore, CA 94550, USA' (xcmor.py:540)
2026-06-03 09:54:57,395 - xcmor.xcmor - DEBUG - Found global attribute 'institution' with value 'Program for Climate Model Diagnosis and Intercompa...' which has no specific requirements (xcmor.py:544)
2026-06-03 09:54:57,395 - xcmor.xcmor - DEBUG - Checking global attribute 'institution_id' with value 'PCMDI' (xcmor.py:540)
2026-06-03 09:54:57,396 - xcmor.xcmor - DEBUG - Found valid value 'PCMDI...' for 'institution_id' (xcmor.py:548)
2026-06-03 09:54:57,396 - xcmor.xcmor - DEBUG - Checking global attribute 'license' with value 'CMIP6 model data produced by Lawrence Livermore PCMDI is licensed under a Creative Commons Attribution ShareAlike 4.0 International License (https://creativecommons.org/licenses). Consult https://pcmdi.llnl.gov/CMIP6/TermsOfUse for terms of use governing CMIP6 output, including citation requirements and proper acknowledgment. Further information about this data, including some limitations, can be found via the further_info_url (recorded as a global attribute in this file) and at https:///pcmdi.llnl.gov/. The data producers and data providers make no warranty, either express or implied, including, but not limited to, warranties of merchantability and fitness for a particular purpose. All liabilities arising from the supply of the information (including any liability arising in negligence) are excluded to the fullest extent permitted by law.' (xcmor.py:540)
2026-06-03 09:54:57,398 - xcmor.xcmor - ERROR - global attribute 'license' has value 'CMIP6 model data produced by Lawrence Livermore PC...' which does not match expected regex '^CMIP6 model data produced by .* is licensed under a Creative Commons .* License (https://creativecommons\.org/.*)\. *Consult https://pcmdi\.llnl\.gov/CMIP6/TermsOfUse for terms of use governing CMIP6 output, including citation requirements and proper acknowledgment\. *Further information about this data, including some limitations, can be found via the further_info_url (recorded as a global attribute in this file).*\. *The data producers and data providers make no warranty, either express or implied, including, but not limited to, warranties of merchantability and fitness for a particular purpose\. *All liabilities arising from the supply of the information (including any liability arising in negligence) are excluded to the fullest extent permitted by law\.$' (xcmor.py:555)
2026-06-03 09:54:57,401 - xcmor.xcmor - DEBUG - Checking global attribute 'mip_era' with value 'CMIP6' (xcmor.py:540)
2026-06-03 09:54:57,401 - xcmor.xcmor - DEBUG - Found valid value 'CMIP6...' for 'mip_era' (xcmor.py:548)
2026-06-03 09:54:57,402 - xcmor.xcmor - DEBUG - Checking global attribute 'nominal_resolution' with value '10000 km' (xcmor.py:540)
2026-06-03 09:54:57,402 - xcmor.xcmor - DEBUG - Found valid value '10000 km...' for 'nominal_resolution' (xcmor.py:548)
2026-06-03 09:54:57,402 - xcmor.xcmor - DEBUG - Checking global attribute 'physics_index' with value '1' (xcmor.py:540)
2026-06-03 09:54:57,403 - xcmor.xcmor - DEBUG - Checking global attribute 'product' with value 'model-output' (xcmor.py:540)
2026-06-03 09:54:57,403 - xcmor.xcmor - DEBUG - Found valid value 'model-output...' for 'product' (xcmor.py:548)
2026-06-03 09:54:57,404 - xcmor.xcmor - DEBUG - Checking global attribute 'realization_index' with value '3' (xcmor.py:540)
2026-06-03 09:54:57,404 - xcmor.xcmor - DEBUG - Checking global attribute 'realm' with value 'atmos atmosChem' (xcmor.py:540)
2026-06-03 09:54:57,405 - xcmor.xcmor - ERROR - global attribute 'realm' has value 'atmos atmosChem...' which is not one of the valid values: ['aerosol', 'atmos', 'atmosChem', 'land', 'landIce... (xcmor.py:559)
2026-06-03 09:54:57,405 - xcmor.xcmor - DEBUG - Checking global attribute 'source' with value 'PCMDI-test 1.0 (1989):
aerosol: none
atmos: Earth1.0-gettingHotter (360 x 180 longitude/latitude; 50 levels; top level 0.1 mb)
atmosChem: none
land: Earth1.0
landIce: none
ocean: BlueMarble1.0-warming (360 x 180 longitude/latitude; 50 levels; top grid cell 0-10 m)
ocnBgchem: none
seaIce: Declining1.0-warming (360 x 180 longitude/latitude)' (xcmor.py:540)
2026-06-03 09:54:57,405 - xcmor.xcmor - DEBUG - Found global attribute 'source' with value 'PCMDI-test 1.0 (1989):
aerosol: none
atmos: Earth...' which has no specific requirements (xcmor.py:544)
2026-06-03 09:54:57,407 - xcmor.xcmor - DEBUG - Checking global attribute 'source_id' with value 'PCMDI-test-1-0' (xcmor.py:540)
2026-06-03 09:54:57,407 - xcmor.xcmor - DEBUG - Found valid value 'PCMDI-test-1-0...' for 'source_id' (xcmor.py:548)
2026-06-03 09:54:57,407 - xcmor.xcmor - DEBUG - Checking global attribute 'source_type' with value 'AOGCM ISM AER' (xcmor.py:540)
2026-06-03 09:54:57,408 - xcmor.xcmor - ERROR - global attribute 'source_type' has value 'AOGCM ISM AER...' which is not one of the valid values: ['AER', 'AGCM', 'AOGCM', 'BGC', 'CHEM', 'ISM', 'LA... (xcmor.py:559)
2026-06-03 09:54:57,408 - xcmor.xcmor - DEBUG - Checking global attribute 'sub_experiment' with value 'none' (xcmor.py:540)
2026-06-03 09:54:57,409 - xcmor.xcmor - DEBUG - Found global attribute 'sub_experiment' with value 'none...' which has no specific requirements (xcmor.py:544)
2026-06-03 09:54:57,409 - xcmor.xcmor - DEBUG - Checking global attribute 'sub_experiment_id' with value 'none' (xcmor.py:540)
2026-06-03 09:54:57,410 - xcmor.xcmor - DEBUG - Found valid value 'none...' for 'sub_experiment_id' (xcmor.py:548)
2026-06-03 09:54:57,410 - xcmor.xcmor - DEBUG - Checking global attribute 'table_id' with value 'Amon' (xcmor.py:540)
2026-06-03 09:54:57,410 - xcmor.xcmor - DEBUG - Found valid value 'Amon...' for 'table_id' (xcmor.py:548)
2026-06-03 09:54:57,411 - xcmor.xcmor - DEBUG - Checking global attribute 'tracking_id' with value 'None' (xcmor.py:540)
2026-06-03 09:54:57,411 - xcmor.xcmor - ERROR - global tracking_id not found but required (xcmor.py:542)
2026-06-03 09:54:57,412 - xcmor.xcmor - DEBUG - Checking global attribute 'variable_id' with value 'tas' (xcmor.py:540)
2026-06-03 09:54:57,412 - xcmor.xcmor - DEBUG - Found global attribute 'variable_id' with value 'tas...' which has no specific requirements (xcmor.py:544)
2026-06-03 09:54:57,412 - xcmor.xcmor - DEBUG - Checking global attribute 'variant_label' with value 'None' (xcmor.py:540)
2026-06-03 09:54:57,413 - xcmor.xcmor - ERROR - global variant_label not found but required (xcmor.py:542)
/home/docs/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/cf_xarray/accessor.py:718: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
unused_keys = set(attribute.keys()) - set(inverted)
/home/docs/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/cf_xarray/accessor.py:719: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
for key, value in attribute.items():
/home/docs/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/cf_xarray/accessor.py:727: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
newmap.update({key: attribute[key] for key in unused_keys})
2026-06-03 09:54:57,424 - xcmor.xcmor - DEBUG - transposing order: ['T', 'Y', 'X'] (xcmor.py:31)
<xarray.Dataset> Size: 224B
Dimensions: (lon: 2, lat: 2, time: 3, bounds: 2)
Coordinates:
* lon (lon) float64 16B 80.17 80.68
* lat (lat) float64 16B 42.25 42.21
height float64 8B ...
lon_bounds (lon, bounds) float64 32B ...
lat_bounds (lat, bounds) float64 32B ...
time_bounds (time, bounds) datetime64[ns] 48B 2014-09-05T12:00:00 ... 20...
* time (time) datetime64[ns] 24B 2014-09-06 2014-09-07 2014-09-08
Dimensions without coordinates: bounds
Data variables:
tas (time, lat, lon) float32 48B ...
Attributes: (12/50)
Conventions: CF-1.7 CMIP-6.2
activity: Ice Sheet Model Intercomparison Project for CMIP6
activity_id: ISMIP6
branch_method: standard
branch_time_in_child: 59400.0
branch_time_in_parent: 59400.0
... ...
sub_experiment: none
sub_experiment_id: none
table_id: Amon
tracking_prefix: hdl:21.14100
variable_id: tas
version: 20260603Let’s write this to NetCDF and use the compliance checker to find issues:
ds_out.to_netcdf("tas.nc")
/tmp/ipykernel_2788/313901145.py:1: UserWarning: Times can't be serialized faithfully to int64 with requested units 'days since 2014-09-06'. Resolution of 'hours' needed. Serializing times to floating point instead. Set encoding['dtype'] to integer dtype to serialize to int64. Set encoding['dtype'] to floating point dtype to silence this warning.
ds_out.to_netcdf("tas.nc")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[7], line 1
----> 1 ds_out.to_netcdf("tas.nc")
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/core/dataset.py:2030, in Dataset.to_netcdf(self, path, mode, format, group, engine, encoding, unlimited_dims, compute, invalid_netcdf, auto_complex)
2027 encoding = {}
2028 from xarray.backends.api import to_netcdf
-> 2030 return to_netcdf( # type: ignore[return-value] # mypy cannot resolve the overloads:(
2031 self,
2032 path,
2033 mode=mode,
2034 format=format,
2035 group=group,
2036 engine=engine,
2037 encoding=encoding,
2038 unlimited_dims=unlimited_dims,
2039 compute=compute,
2040 multifile=False,
2041 invalid_netcdf=invalid_netcdf,
2042 auto_complex=auto_complex,
2043 )
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/backends/api.py:1928, in to_netcdf(dataset, path_or_file, mode, format, group, engine, encoding, unlimited_dims, compute, multifile, invalid_netcdf, auto_complex)
1923 # TODO: figure out how to refactor this logic (here and in save_mfdataset)
1924 # to avoid this mess of conditionals
1925 try:
1926 # TODO: allow this work (setting up the file for writing array data)
1927 # to be parallelized with dask
-> 1928 dump_to_store(
1929 dataset, store, writer, encoding=encoding, unlimited_dims=unlimited_dims
1930 )
1931 if autoclose:
1932 store.close()
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/backends/api.py:1975, in dump_to_store(dataset, store, writer, encoder, encoding, unlimited_dims)
1972 if encoder:
1973 variables, attrs = encoder(variables, attrs)
-> 1975 store.store(variables, attrs, check_encoding, writer, unlimited_dims=unlimited_dims)
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/backends/common.py:456, in AbstractWritableDataStore.store(self, variables, attributes, check_encoding_set, writer, unlimited_dims)
453 if writer is None:
454 writer = ArrayWriter()
--> 456 variables, attributes = self.encode(variables, attributes)
458 self.set_attributes(attributes)
459 self.set_dimensions(variables, unlimited_dims=unlimited_dims)
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/backends/common.py:640, in WritableCFDataStore.encode(self, variables, attributes)
637 def encode(self, variables, attributes):
638 # All NetCDF files get CF encoded by default, without this attempting
639 # to write times, for example, would fail.
--> 640 variables, attributes = cf_encoder(variables, attributes)
641 variables = {
642 k: ensure_dtype_not_object(v, name=k) for k, v in variables.items()
643 }
644 variables = {k: self.encode_variable(v) for k, v in variables.items()}
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/conventions.py:787, in cf_encoder(variables, attributes)
784 # add encoding for time bounds variables if present.
785 _update_bounds_encoding(variables)
--> 787 new_vars = {k: encode_cf_variable(v, name=k) for k, v in variables.items()}
789 # Remove attrs from bounds variables (issue #2921)
790 for var in new_vars.values():
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/conventions.py:787, in <dictcomp>(.0)
784 # add encoding for time bounds variables if present.
785 _update_bounds_encoding(variables)
--> 787 new_vars = {k: encode_cf_variable(v, name=k) for k, v in variables.items()}
789 # Remove attrs from bounds variables (issue #2921)
790 for var in new_vars.values():
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/conventions.py:102, in encode_cf_variable(var, needs_copy, name)
90 ensure_not_multiindex(var, name=name)
92 for coder in [
93 CFDatetimeCoder(),
94 CFTimedeltaCoder(),
(...)
100 variables.BooleanCoder(),
101 ]:
--> 102 var = coder.encode(var, name=name)
104 for attr_name in CF_RELATED_DATA:
105 pop_to(var.encoding, var.attrs, attr_name)
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/coding/times.py:1377, in CFDatetimeCoder.encode(self, variable, name)
1374 dtype = data.dtype if data.dtype.kind == "f" else "float64"
1375 (data, units, calendar) = encode_cf_datetime(data, units, calendar, dtype)
-> 1377 safe_setitem(attrs, "units", units, name=name)
1378 safe_setitem(attrs, "calendar", calendar, name=name)
1380 return Variable(dims, data, attrs, encoding, fastpath=True)
File ~/checkouts/readthedocs.org/user_builds/xcmor/conda/latest/lib/python3.10/site-packages/xarray/coding/common.py:108, in safe_setitem(dest, key, value, name)
106 if key in dest:
107 var_str = f" on variable {name!r}" if name else ""
--> 108 raise ValueError(
109 f"failed to prevent overwriting existing key {key} in attrs{var_str}. "
110 "This is probably an encoding field used by xarray to describe "
111 "how a variable is serialized. To proceed, remove this key from "
112 "the variable's attributes manually."
113 )
114 dest[key] = value
ValueError: failed to prevent overwriting existing key units in attrs on variable 'time_bounds'. This is probably an encoding field used by xarray to describe how a variable is serialized. To proceed, remove this key from the variable's attributes manually.
!compliance-checker -t cf:1.7 tas.nc