cdo cmor: basic standardizing examples (“CMORizing”)#

The CMOR (Climate Model Output Rewriter) software library allows to rewrite climate model output in a form that is compliant with the project requirements. This library has been integrated into the popular CDOs (Climate Data Operators) to facilitate the standardization process and to make it more accessible and appealing to a wider community.

To use the cdo cmor operator, cdo has to be compiled including CMOR. This is not done for the common cdo installations on the levante HPC and also not when installing cdo through package managers like APT (eg. apt-get install cdo). On levante, cdo installations that include CMOR can be found under /work/bm0021/cdo_incl_cmor/.

# Define alias for the desired cdo cmor installation
%alias cdo "/work/bm0021/cdo_incl_cmor/cdo-2022-05-11_cmor3.6.0_gcc/bin/cdo"
%store cdo
Alias stored: cdo ("/work/bm0021/cdo_incl_cmor/cdo-2022-05-11_cmor3.6.0_gcc/bin/cdo")

cmor operator#

The cmor operator reformats model output and adds metadata so the result will be compliant to the respective projects metadata requirements. This process is often refered to as “CMORizing”.

CMORizing is necessary for model intercomparison projects (MIPs) so the scientists can benefit from the resulting homogenity in metadata description and data format. Standardized workflows and tools can be applied on the data no matter the source. The alternative would be a big and error prone effort for each individual scientist to process the heterogenous model output of x climate models.

The CMORizing process and subsequent quality control is thus an important part of the workflow when submitting data for model intercomparison projects like CMIP6.

! cdo --help cmor
NAME
    cmor - Climate Model Output Rewriting to produce CMIP-compliant data

SYNOPSIS
    cmor,MIPtable[,cmor_name=VarList[,key=value[,...]]]  infile

DESCRIPTION
    
    
    The CDO operator cmor converts an infile into a CMIP-compliant format
    by using the CMOR library. Each output file contains a single output variable.
    The name of the output files are generated by CMOR according to a template based on the
    DRS (Data reference Syntax) of the project. CMOR checks and applies the information delivered
    through the project dependend MIPtable on the infile. Additional information
    which is required for the conversion can be configured via keyvalues as optional parameters.
    
    By specifying a variable selector keyvalue, e.g. cmor_name=tas, the user can
    pre-select a subset of infile variables. If name or code is specified, a
    corresponding cmor_name which can also be found in the MIPtable is also
    required to map the infile variable to the CMOR-variable. For mapping more
    variables at the operator call, one can specify a mapping table via keyword mapping_table.
    
    Global attributes must be collected in info files and can be specified via keyword
    info. All required and optional global attributes as well as information
    about table file formats are given in the 'cdo cmor manual'.
    
    If questions remain, do not hesitate to ask and send an email to wachsmannATdkrz.de.
    

PARAMETER
    MIPtable                   STRING    Name of the MIP table as used by CMOR.
                               --------------------------------------------------------------------------------------------
    cmor_name           | cn   STRING    Variable selector and specified in the MIP table.
                                         Comma-separated list of CMOR variable names.
                                         Default is to process all variables.
    name                | n    STRING    Variable selector.
                                         Name of a selected @file{infile} variable.
    code                | c    INTEGER   Variable selector. 
                                         Three digits (GRIB) Code of a selected @file{infile} variable.
                               --------------------------------------------------------------------------------------------
    info                | i    STRING    Preprozessing.
                                         Comma-separated list of filenames.
                                         Containins global attributes and control keywords.
                                         Default: CWD/.cdocmorinfo
    grid_info           | gi   STRING    Preprozessing.
                                         NetCDF or table formatted file with model grid description.
                                         Horizontal and vertical axes are substituted with the ones from grid info file.
    mapping_table       | mt   STRING    Preprozessing.
                                         Fortran Namelist containing variable information for e.g. renaming.
    keep_all_attributes | kaa  STRING    Preprozessing.
                                         'y' for passing all infile attributes. 'n' for discarding all infile attributes.
                               --------------------------------------------------------------------------------------------
    drs                 | d    CHARACTER Output control.
                                         Do(=y, default) or do not(=n) move output into the project DRS structure.
    drs_root            | dr   STRING    Output control. CMOR output root directory.
                                         Default: CWD.
    output_mode         | om   CHARACTER Output control.
                                         Either 'r' for replace (default) or 'a' for append mode.
    last_chunk          | lc   STRING    Output control. Filename of chunk to which shall be appended.  
    max_size            | ms   INTEGER   Output control. Limit of output file sie in GigaByte.
    deflate_level       | dl   INTEGER   Output control. Compression level. -1: No compression. 0: Only shuffle.
    version_date        | vd   INTEGER   Output control. Subdirectory name in CMIP6 DRS.
                               --------------------------------------------------------------------------------------------
    required_time_units | rtu  STRING    Temporal description.
                                         Time axis reference date specified by the experiment.
                                         Format: 'days since YYYY-day-month hh:mm:ss'.
    cell_methods        | cm   CHARACTER Temporal description.
                                         Cell_methods of time axis.
                                         Value is one of 'm' (default)  , 'p', 'c', 'n', 'd'
                               --------------------------------------------------------------------------------------------
    units               | u    STRING    Variable attrbiute. Units of the variable.
                                         Must be known by library UDunits.
    variable_comment    | vc   STRING    Variable attribute. Variable comment.
    positive            | p    CHARACTER Variable attrbiute.
                                         Positive flux direction, either 'u' for upward or 'd' for downward.
    z_axis              | za   STRING    Name of the coordinate variable associated with
                                         the z-axis of the target variable.
    character_axis      | ca   STRING    Name of the coordinate variable associated with
                                         a character axis of the target variable.
                                         Valid axes names are: basin, vegtype or oline. 
    t_axis              | ta   STRING    Sets time values and time bounds to the nearest value
                                         required by the project given by the value of t_axis.
                                         Valid value is: cmip

Project metadata#

The cdo cmor operator can be partly configured via the command line, however requires also input from configuration files containing project, model and operator specific metadata.

To invoke the cdo cmor operator we need to provide several metadata:

  • Project metadata / CMOR specific

    • Controlled vocabulary (“CV”, holding the project metadata including model and experiment definitions as well as part of the projects DRS (Data Reference Syntax) definition)

    • MIP tables (metadata related to the variables)

  • cdo cmor specific

    • cdocmorinfo (holding keys that link to the CV [i.e. to the model, experiment of the CV], simulation information and general cdo cmor configuration, can be split over multiple files)

    • mapping table (holding the information which model variable corresponds to which variable in the MIP tables)

Controlled vocabulary

The CV can be quite extensive. It also contains a list of required global attributes that are mandatory in every file submitted to the project. Before a file is published to the Earth System Grid Federation (ESGF), it has to undergo a quality control that performs metadata compliance checks considering both, the CV and the MIP tables. Publication via the DKRZ ESGF node requires additional metadata and data checks. These checks are usually performed by the DKRZ staff.

mip_table_dir = "/pool/data/CMIP6/cmip6-cmor-tables/Tables/"
CV = mip_table_dir + "CMIP6_CV.json"

! head -n 44 {CV}
{
    "CV":{
        "required_global_attributes":[
            "Conventions",
            "activity_id",
            "creation_date",
            "data_specs_version",
            "experiment",
            "experiment_id",
            "forcing_index",
            "frequency",
            "further_info_url",
            "grid",
            "grid_label",
            "initialization_index",
            "institution",
            "institution_id",
            "license",
            "mip_era",
            "nominal_resolution",
            "physics_index",
            "product",
            "realization_index",
            "realm",
            "source",
            "source_id",
            "source_type",
            "sub_experiment",
            "sub_experiment_id",
            "table_id",
            "tracking_id",
            "variable_id",
            "variant_label"
        ],
        "version_metadata":{
            "CV_collection_modified":"Wed Oct 28 17:40:37 2020 -0700",
            "CV_collection_version":"6.2.54.2",
            "author":"Paul J. Durack <durack1@llnl.gov>",
            "experiment_id_CV_modified":"Fri Oct 23 15:16:35 2020 -0700",
            "experiment_id_CV_note":"Register experiment_ids for CovidMIP (#973)",
            "institution_id":"PCMDI",
            "previous_commit":"e8152ae174f7f210963cd18c33acfa2c091e7296",
            "specs_doc":"v6.2.7 (10th September 2018; https://goo.gl/v1drZl)"
        },
# source-id MPI-ESM1-2-LR
!grep -A 2 '"source_id":"MPI-ESM1-2-LR"' {CV} 
                "source_id":"MPI-ESM1-2-LR",
                "source":"MPI-ESM1.2-LR (2017): \naerosol: none, prescribed MACv2-SP\natmos: ECHAM6.3 (spectral T63; 192 x 96 longitude/latitude; 47 levels; top level 0.01 hPa)\natmosChem: none\nland: JSBACH3.20\nlandIce: none/prescribed\nocean: MPIOM1.63 (bipolar GR1.5, approximately 1.5deg; 256 x 220 longitude/latitude; 40 levels; top grid cell 0-12 m)\nocnBgchem: HAMOCC6\nseaIce: unnamed (thermodynamic (Semtner zero-layer) dynamic (Hibler 79) sea ice model)"
            },
# experiment historical
!grep -B 1 '"experiment_id":"historical"' {CV}
                "experiment":"all-forcing simulation of the recent past",
                "experiment_id":"historical",

The cdo cmor operator

The cdo cmor operator can be called as follows:

cdo cmor,{MIPtable},it={cdocmorinfo_tables_comma_separated},mt={mapping_table},dr={output_path} {input_file}

eg. cdo cmor,Amon,it=cdocmorinfo,exp.cdocmorinfo,model.cdocmorinfo,mt=test_mapping.txt,dr=./ model_data.nc

Per default, the cdo cmor operator searches for a local and hidden .cdocmorinfo file. However, cdocmorinfo files specified in the command line have a higher priority when reading the defined key words.

# cdocmorinfo (also referred to as infotable)
it = "/work/bm0021/workshopcmip6pp2022/config/historical_r1i1p1f1-LR.cdocmorinfo"

! cat {it}
#
# experiment info:
#
EXPERIMENT_ID=historical
EXPERIMENT="all-forcing simulation of the recent past"
#Specify VARIANT_LABEL explicitly or realization_index, physics_index, initialization_index and forcing_index:
VARIANT_LABEL=r1i1p1f1
ACTIVITY_ID=CMIP                            
MIP_ERA=CMIP6                          
PROJECT_ID=CMIP6
REQUIRED_TIME_UNITS="days since 1850-1-1 00:00:00"
#FURTHER_INFO_URL="https://furtherinfo.es-doc.org/<mip_era>.<institution_id>.<source_id>.<experiment_id>.<sub_experiment_id>.<variant_label>
FURTHER_INFO_URL="https://furtherinfo.es-doc.org/CMIP6.MPI-M.MPI-ESM1-2-LR.historical.none.r1i1p1f1"
#Branch Info: If needed use the following 2 attributes to define the branch time
#  in Parent_Time_Units and Required_Time_Units (=child)
#  else use BRANCH_DATES to give the dates in YYYYMMDD,YYYYMMDD format
#BRANCH_TIME_IN_PARENT=
#BRANCH_TIME_IN_CHILD=
BRANCH_DATES=18500101,18500101 
BRANCH_METHOD="standard"
PARENT_MIP_ERA=CMIP6
PARENT_ACTIVITY_ID=CMIP
PARENT_EXPERIMENT=piControl 
#cmip6-spinup-HR
PARENT_EXPERIMENT_ID=piControl
PARENT_SOURCE_ID=MPI-ESM1-2-LR
PARENT_TIME_UNITS="days since 1850-1-1 00:00:00"
PARENT_VARIANT_LABEL=r1i1p1f1
SUB_EXPERIMENT_ID=none
SUB_EXPERIMENT=none
#
#model info:
#
SOURCE_ID=MPI-ESM1-2-LR
REFERENCES="MPI-ESM: Mauritsen, T. et al. (2019), Developments in the MPI-M Earth System Model version 1.2 (MPI-ESM1.2) and Its Response to Increasing CO2, J. Adv. Model. Earth Syst.,11, 998-1038, doi:10.1029/2018MS001400,\nMueller, W.A. et al. (2018): A high-resolution version of the Max Planck Institute Earth System Model MPI-ESM1.2-HR. J. Adv. Model. EarthSyst.,10,1383–1413, doi:10.1029/2017MS001217"
SOURCE="MPI-ESM1.2-LR (2017): \naerosol: none, prescribed MACv2-SP\natmos: ECHAM6.3 (spectral T63; 192 x 96 longitude/latitude; 47 levels; top level 0.01 hPa)\natmosChem: none\nland: JSBACH3.20\nlandIce: none/prescribed\nocean: MPIOM1.63 (bipolar GR15, approximately 1.5deg; 256 x 220 longitude/latitude; 40 levels; top grid cell 0-12 m)\nocnBgchem: HAMOCC6\nseaIce: unnamed (thermodynamic (Semtner zero-layer) dynamic (Hibler 79) sea ice model)"
SOURCE_TYPE="AOGCM BGC"
CALENDAR=proleptic_gregorian
GRID=gn
GRID_LABEL=gn
#
# institution and contact info:
#
# none of these can be set in the command line
#
DEFLATE_LEVEL=1
CONTACT=cmip6-mpi-esm@dkrz.de
#
# MPI-M
#
INSTITUTION_ID=MPI-M
INSTITUTION="Max Planck Institute for Meteorology, Hamburg 20146, Germany"
LICENSE="CMIP6 model data produced by MPI-M 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. 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."

#
# cdo cmor settings
#
#T_AXIS="cmip" causes the time axis to be rewritten according to the requested time stamps
T_AXIS="cmip"
#OUTPUT_MODE="a" # append mode - will append new chunks to the same output file
OUTPUT_MODE="r"  # replace mode
MAX_SIZE=0
SAVE_CHUNK="n"
#For files without time axis, if one is requested:
#firsttimeval=1 (1850-01-02 00:00:00 wenn days since 1850-01-01 00:00:00)
FIRSTTIMEVAL=1
#Directory containing the project MIP tables
MIP_TABLE_DIR="/pool/data/CMIP6/cmip6-cmor-tables/Tables/"

Examples#

In the following 4 examples the CMORization of MPI-ESM1-2-LR model output is demonstrated.

Input files location#

model_data = "/work/bm0021/workshopcmip6pp2022/outdata/"
! find {model_data} -type f 
/work/bm0021/workshopcmip6pp2022/outdata/mpiom/historical_r1i1p1f1-LR_mpiom_data_3d_mm_18500101_18500131.nc
/work/bm0021/workshopcmip6pp2022/outdata/mpiom/historical_r1i1p1f1-LR_mpiom_data_2d_mm_18500101_18501231.nc
/work/bm0021/workshopcmip6pp2022/outdata/echam6/historical_r1i1p1f1-LR_echam6_echam_1850.grb

Example 1: GRIB1 input CMORized to CMIP6 standard#

The first example showcases the CMORization of the surface temperature variable. The near surface temperature has the GRIB code 167 in the MPI-ESM echam6 output. It also has the variable name tas in the CMIP variable metadata standard, and can be found in various MIP tables. Here we CMORize the CMOR variable tas_Amon. The above information is entered in the mapping table:

# Input file
ifile = model_data + "echam6/historical_r1i1p1f1-LR_echam6_echam_1850.grb"

# Output directory
outdir = "./archive"

# cdocmorinfo - submodel specific
it_echam6 = "/work/bm0021/workshopcmip6pp2022/config/cdocmorinfo_echam6_LR"

# mapping table
mt = "/work/bm0021/workshopcmip6pp2022/config/mapping_table_example.txt"

# set a fix version date
vdate = "v20220901"
# Show the content of the mapping table
! grep tas {mt}
&parameter cmor_name="tas"         code=167                    project_mip_table="Amon"         units="K"             cell_methods="m"  /

Run cdo cmor

%cdo cmor,Amon,i={it},{it_echam6},mt={mt},dr={outdir},vd={vdate},cn=tas {ifile}
In reading info table:
          Open failed on /home/k/k204199/jupyter-guide-to-climate-data/.cdocmorinfo: No such file or directory.cdo    cmor: MIP table file = '/pool/data/CMIP6/cmip6-cmor-tables/Tables//CMIP6_Amon.json'
cdo    cmor: Attribute 'time_units' = 'days since 1850-1-1 00:00:00'
cdo    cmor: Since you set attribute 'parent_experiment_id'='piControl', further attributes are checked.
cdo    cmor: Mapping Table = '/work/bm0021/workshopcmip6pp2022/config/mapping_table_example.txt'.
cdo    cmor: Vertical axis is either default and scalar or not available.
cdo    cmor:      File stored in:  './archive/CMIP6/CMIP/MPI-M/MPI-ESM1-2-LR/historical/r1i1p1f1/Amon/tas/gn/v20220901/tas_Amon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012.nc' with cmor!
cdo    cmor: Processed 221184 values from 715 variables over 12 timesteps [0.67s 465MB].

Let us take a look …

# ncdump
outputfile = f"{outdir}/CMIP6/CMIP/MPI-M/MPI-ESM1-2-LR/historical/r1i1p1f1/Amon/"
outputfile += f"tas/gn/{vdate}/tas_Amon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012.nc"
! ncdump -h {outputfile}
netcdf tas_Amon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012 {
dimensions:
	time = UNLIMITED ; // (12 currently)
	lat = 96 ;
	lon = 192 ;
	bnds = 2 ;
variables:
	double time(time) ;
		time:bounds = "time_bnds" ;
		time:units = "days since 1850-1-1 00:00:00" ;
		time:calendar = "proleptic_gregorian" ;
		time:axis = "T" ;
		time:long_name = "time" ;
		time:standard_name = "time" ;
	double time_bnds(time, bnds) ;
	double lat(lat) ;
		lat:bounds = "lat_bnds" ;
		lat:units = "degrees_north" ;
		lat:axis = "Y" ;
		lat:long_name = "Latitude" ;
		lat:standard_name = "latitude" ;
	double lat_bnds(lat, bnds) ;
	double lon(lon) ;
		lon:bounds = "lon_bnds" ;
		lon:units = "degrees_east" ;
		lon:axis = "X" ;
		lon:long_name = "Longitude" ;
		lon:standard_name = "longitude" ;
	double lon_bnds(lon, bnds) ;
	double height ;
		height:units = "m" ;
		height:axis = "Z" ;
		height:positive = "up" ;
		height:long_name = "height" ;
		height:standard_name = "height" ;
	float tas(time, lat, lon) ;
		tas:standard_name = "air_temperature" ;
		tas:long_name = "Near-Surface Air Temperature" ;
		tas:comment = "near-surface (usually, 2 meter) air temperature" ;
		tas:units = "K" ;
		tas:cell_methods = "area: time: mean" ;
		tas:cell_measures = "area: areacella" ;
		tas:history = "2022-08-31T07:57:56Z altered by CMOR: Treated scalar dimension: \'height\'. 2022-08-31T07:57:56Z altered by CMOR: replaced missing value flag (-9e+33) and corresponding data with standard missing value (1e+20). 2022-08-31T07:57:56Z altered by CMOR: Inverted axis: lat." ;
		tas:coordinates = "height" ;
		tas:missing_value = 1.e+20f ;
		tas:_FillValue = 1.e+20f ;

// global attributes:
		:CDO = "Climate Data Operators version 2.0.6 (https://mpimet.mpg.de/cdo)" ;
		:Conventions = "CF-1.7 CMIP-6.2" ;
		:activity_id = "CMIP" ;
		:branch_method = "standard" ;
		:branch_time_in_child = 0. ;
		:branch_time_in_parent = 0. ;
		:contact = "cmip6-mpi-esm@dkrz.de" ;
		:creation_date = "2022-08-31T07:57:56Z" ;
		:data_specs_version = "01.00.30" ;
		:experiment = "all-forcing simulation of the recent past" ;
		:experiment_id = "historical" ;
		:external_variables = "areacella" ;
		:forcing_index = 1 ;
		:frequency = "mon" ;
		:further_info_url = "https://furtherinfo.es-doc.org/CMIP6.MPI-M.MPI-ESM1-2-LR.historical.none.r1i1p1f1" ;
		:grid = "gn" ;
		:grid_label = "gn" ;
		:history = "2022-08-31T07:57:56Z ; CMOR rewrote data to be consistent with CMIP6, CF-1.7 CMIP-6.2 and CF standards." ;
		:initialization_index = 1 ;
		:institution = "Max Planck Institute for Meteorology, Hamburg 20146, Germany" ;
		:institution_id = "MPI-M" ;
		:mip_era = "CMIP6" ;
		:nominal_resolution = "250 km" ;
		:parent_activity_id = "CMIP" ;
		:parent_experiment_id = "piControl" ;
		:parent_mip_era = "CMIP6" ;
		:parent_source_id = "MPI-ESM1-2-LR" ;
		:parent_time_units = "days since 1850-1-1 00:00:00" ;
		:parent_variant_label = "r1i1p1f1" ;
		:physics_index = 1 ;
		:product = "model-output" ;
		:project_id = "CMIP6" ;
		:realization_index = 1 ;
		:realm = "atmos" ;
		:references = "MPI-ESM: Mauritsen, T. et al. (2019), Developments in the MPI-M Earth System Model version 1.2 (MPI-ESM1.2) and Its Response to Increasing CO2, J. Adv. Model. Earth Syst.,11, 998-1038, doi:10.1029/2018MS001400,\n",
			"Mueller, W.A. et al. (2018): A high-resolution version of the Max Planck Institute Earth System Model MPI-ESM1.2-HR. J. Adv. Model. EarthSyst.,10,1383–1413, doi:10.1029/2017MS001217" ;
		:source = "MPI-ESM1.2-LR (2017): \n",
			"aerosol: none, prescribed MACv2-SP\n",
			"atmos: ECHAM6.3 (spectral T63; 192 x 96 longitude/latitude; 47 levels; top level 0.01 hPa)\n",
			"atmosChem: none\n",
			"land: JSBACH3.20\n",
			"landIce: none/prescribed\n",
			"ocean: MPIOM1.63 (bipolar GR1.5, approximately 1.5deg; 256 x 220 longitude/latitude; 40 levels; top grid cell 0-12 m)\n",
			"ocnBgchem: HAMOCC6\n",
			"seaIce: unnamed (thermodynamic (Semtner zero-layer) dynamic (Hibler 79) sea ice model)" ;
		:source_id = "MPI-ESM1-2-LR" ;
		:source_type = "AOGCM BGC" ;
		:sub_experiment = "none" ;
		:sub_experiment_id = "none" ;
		:table_id = "Amon" ;
		:table_info = "Creation Date:(09 May 2019) MD5:5f007c16960eee824e07a4ce809e3ed1" ;
		:title = "MPI-ESM1-2-LR output prepared for CMIP6" ;
		:variable_id = "tas" ;
		:variant_label = "r1i1p1f1" ;
		:license = "CMIP6 model data produced by MPI-M 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. 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." ;
		:cmor_version = "3.6.0" ;
		:tracking_id = "hdl:21.14100/74103715-7e1c-4fe7-aeae-6e73899a76cf" ;
}
# Plot
import hvplot.xarray, xarray as xr

ds = xr.open_dataset(outputfile)

# Plot a single timestep using xarray
ds.tas.isel(time=0).plot();

# Dynamic plot of the entire timeseries using hvplot
#ds.tas.hvplot.quadmesh(width=600)
_images/cdo-cmor_22_3.png

Example 2: GRIB1 input - diagnostic required#

The next CMOR variable to be created is pr_Amon (precipitation), and requires two model variables as in put - large scale precipitation and convective precipitation. These two variables have the GRIB codes 142 and 143.

The operator chaining also works with cdo cmor (with some restrictions that will lead too far for this short introduction), so we can chain an expression to the cdo cmor call:

Run cdo cmor

%cdo cmor,Amon,i={it},{it_echam6},mt={mt},dr={outdir},vd={vdate},cn=pr -expr,pr=var142+var143 {ifile}
cdo(1) expr: Process started
In reading info table:
          Open failed on /home/k/k204199/jupyter-guide-to-climate-data/.cdocmorinfo: No such file or directory.cdo    cmor: MIP table file = '/pool/data/CMIP6/cmip6-cmor-tables/Tables//CMIP6_Amon.json'
cdo    cmor: Attribute 'time_units' = 'days since 1850-1-1 00:00:00'
cdo    cmor: Since you set attribute 'parent_experiment_id'='piControl', further attributes are checked.
cdo    cmor: Successfully mapped variable via cmor_name to cmor_name 'pr'.
cdo    cmor: Mapping Table = '/work/bm0021/workshopcmip6pp2022/config/mapping_table_example.txt'.
cdo    cmor: Vertical axis is either default and scalar or not available.
cdo    cmor:      File stored in:  './archive/CMIP6/CMIP/MPI-M/MPI-ESM1-2-LR/historical/r1i1p1f1/Amon/pr/gn/v20220901/pr_Amon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012.nc' with cmor!
cdo(1) expr: Processed 442368 values from 143 variables over 12 timesteps.
cdo    cmor: Processed 221184 values from 5 variables over 12 timesteps [0.25s 466MB].

Let us take a look …

# ncdump
outputfile = f"{outdir}/CMIP6/CMIP/MPI-M/MPI-ESM1-2-LR/historical/r1i1p1f1/Amon/"
outputfile += f"pr/gn/{vdate}/pr_Amon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012.nc"
! ncdump -h {outputfile}
netcdf pr_Amon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012 {
dimensions:
	time = UNLIMITED ; // (12 currently)
	lat = 96 ;
	lon = 192 ;
	bnds = 2 ;
variables:
	double time(time) ;
		time:bounds = "time_bnds" ;
		time:units = "days since 1850-1-1 00:00:00" ;
		time:calendar = "proleptic_gregorian" ;
		time:axis = "T" ;
		time:long_name = "time" ;
		time:standard_name = "time" ;
	double time_bnds(time, bnds) ;
	double lat(lat) ;
		lat:bounds = "lat_bnds" ;
		lat:units = "degrees_north" ;
		lat:axis = "Y" ;
		lat:long_name = "Latitude" ;
		lat:standard_name = "latitude" ;
	double lat_bnds(lat, bnds) ;
	double lon(lon) ;
		lon:bounds = "lon_bnds" ;
		lon:units = "degrees_east" ;
		lon:axis = "X" ;
		lon:long_name = "Longitude" ;
		lon:standard_name = "longitude" ;
	double lon_bnds(lon, bnds) ;
	float pr(time, lat, lon) ;
		pr:standard_name = "precipitation_flux" ;
		pr:long_name = "Precipitation" ;
		pr:comment = "includes both liquid and solid phases" ;
		pr:units = "kg m-2 s-1" ;
		pr:original_name = "pr" ;
		pr:cell_methods = "area: time: mean" ;
		pr:cell_measures = "area: areacella" ;
		pr:history = "2022-08-31T07:58:00Z altered by CMOR: replaced missing value flag (-9e+33) and corresponding data with standard missing value (1e+20). 2022-08-31T07:58:01Z altered by CMOR: Inverted axis: lat." ;
		pr:missing_value = 1.e+20f ;
		pr:_FillValue = 1.e+20f ;

// global attributes:
		:CDO = "Climate Data Operators version 2.0.6 (https://mpimet.mpg.de/cdo)" ;
		:Conventions = "CF-1.7 CMIP-6.2" ;
		:activity_id = "CMIP" ;
		:branch_method = "standard" ;
		:branch_time_in_child = 0. ;
		:branch_time_in_parent = 0. ;
		:contact = "cmip6-mpi-esm@dkrz.de" ;
		:creation_date = "2022-08-31T07:58:00Z" ;
		:data_specs_version = "01.00.30" ;
		:experiment = "all-forcing simulation of the recent past" ;
		:experiment_id = "historical" ;
		:external_variables = "areacella" ;
		:forcing_index = 1 ;
		:frequency = "mon" ;
		:further_info_url = "https://furtherinfo.es-doc.org/CMIP6.MPI-M.MPI-ESM1-2-LR.historical.none.r1i1p1f1" ;
		:grid = "gn" ;
		:grid_label = "gn" ;
		:history = "2022-08-31T07:58:00Z ; CMOR rewrote data to be consistent with CMIP6, CF-1.7 CMIP-6.2 and CF standards." ;
		:initialization_index = 1 ;
		:institution = "Max Planck Institute for Meteorology, Hamburg 20146, Germany" ;
		:institution_id = "MPI-M" ;
		:mip_era = "CMIP6" ;
		:nominal_resolution = "250 km" ;
		:parent_activity_id = "CMIP" ;
		:parent_experiment_id = "piControl" ;
		:parent_mip_era = "CMIP6" ;
		:parent_source_id = "MPI-ESM1-2-LR" ;
		:parent_time_units = "days since 1850-1-1 00:00:00" ;
		:parent_variant_label = "r1i1p1f1" ;
		:physics_index = 1 ;
		:product = "model-output" ;
		:project_id = "CMIP6" ;
		:realization_index = 1 ;
		:realm = "atmos" ;
		:references = "MPI-ESM: Mauritsen, T. et al. (2019), Developments in the MPI-M Earth System Model version 1.2 (MPI-ESM1.2) and Its Response to Increasing CO2, J. Adv. Model. Earth Syst.,11, 998-1038, doi:10.1029/2018MS001400,\n",
			"Mueller, W.A. et al. (2018): A high-resolution version of the Max Planck Institute Earth System Model MPI-ESM1.2-HR. J. Adv. Model. EarthSyst.,10,1383–1413, doi:10.1029/2017MS001217" ;
		:source = "MPI-ESM1.2-LR (2017): \n",
			"aerosol: none, prescribed MACv2-SP\n",
			"atmos: ECHAM6.3 (spectral T63; 192 x 96 longitude/latitude; 47 levels; top level 0.01 hPa)\n",
			"atmosChem: none\n",
			"land: JSBACH3.20\n",
			"landIce: none/prescribed\n",
			"ocean: MPIOM1.63 (bipolar GR1.5, approximately 1.5deg; 256 x 220 longitude/latitude; 40 levels; top grid cell 0-12 m)\n",
			"ocnBgchem: HAMOCC6\n",
			"seaIce: unnamed (thermodynamic (Semtner zero-layer) dynamic (Hibler 79) sea ice model)" ;
		:source_id = "MPI-ESM1-2-LR" ;
		:source_type = "AOGCM BGC" ;
		:sub_experiment = "none" ;
		:sub_experiment_id = "none" ;
		:table_id = "Amon" ;
		:table_info = "Creation Date:(09 May 2019) MD5:5f007c16960eee824e07a4ce809e3ed1" ;
		:title = "MPI-ESM1-2-LR output prepared for CMIP6" ;
		:variable_id = "pr" ;
		:variant_label = "r1i1p1f1" ;
		:license = "CMIP6 model data produced by MPI-M 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. 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." ;
		:cmor_version = "3.6.0" ;
		:tracking_id = "hdl:21.14100/80fe2e37-584e-4caf-b3ac-2078318e4dc2" ;
}
ds = xr.open_dataset(outputfile)

# Plot a single timestep using xarray
ds.pr.isel(time=0).plot();

# Dynamic plot of the entire timeseries using hvplot
#ds.pr.hvplot.quadmesh(width=600)
_images/cdo-cmor_28_0.png

Example 3: netCDF input#

This example will create the variable tos out of the MPI-ESM1-2 MPIOM variable of the same name.

# Input file
ifile = model_data + "mpiom/historical_r1i1p1f1-LR_mpiom_data_2d_mm_18500101_18501231.nc"

# cdocmorinfo - submodel specific attributes
it_mpiom = "/work/bm0021/workshopcmip6pp2022/config/cdocmorinfo_mpiom_LR"

Let us take a look at the source file

! ncdump -h {ifile} | head -n 40
netcdf historical_r1i1p1f1-LR_mpiom_data_2d_mm_18500101_18501231 {
dimensions:
	x = 256 ;
	y = 220 ;
	vertices = 4 ;
	x_2 = 256 ;
	y_2 = 220 ;
	x_3 = 256 ;
	y_3 = 220 ;
	depth = 1 ;
	depth_2 = 1 ;
	depth_3 = 1 ;
	time = UNLIMITED ; // (12 currently)
variables:
	double lon(y, x) ;
		lon:standard_name = "longitude" ;
		lon:long_name = "longitude" ;
		lon:units = "degrees_east" ;
		lon:_CoordinateAxisType = "Lon" ;
		lon:bounds = "lon_vertices" ;
	double lon_vertices(y, x, vertices) ;
	double lat(y, x) ;
		lat:standard_name = "latitude" ;
		lat:long_name = "latitude" ;
		lat:units = "degrees_north" ;
		lat:_CoordinateAxisType = "Lat" ;
		lat:bounds = "lat_vertices" ;
	double lat_vertices(y, x, vertices) ;
	double lon_2(y_2, x_2) ;
		lon_2:standard_name = "longitude" ;
		lon_2:long_name = "longitude" ;
		lon_2:units = "degrees_east" ;
		lon_2:_CoordinateAxisType = "Lon" ;
		lon_2:bounds = "lon_2_vertices" ;
	double lon_2_vertices(y_2, x_2, vertices) ;
	double lat_2(y_2, x_2) ;
		lat_2:standard_name = "latitude" ;
		lat_2:long_name = "latitude" ;
		lat_2:units = "degrees_north" ;
		lat_2:_CoordinateAxisType = "Lat" ;

Run cdo cmor

%cdo cmor,Omon,i={it},{it_mpiom},mt={mt},dr={outdir},vd={vdate},cn=tos {ifile}
In reading info table:
          Open failed on /home/k/k204199/jupyter-guide-to-climate-data/.cdocmorinfo: No such file or directory.cdo    cmor: MIP table file = '/pool/data/CMIP6/cmip6-cmor-tables/Tables//CMIP6_Omon.json'
cdo    cmor: Attribute 'time_units' = 'days since 1850-1-1 00:00:00'
cdo    cmor: Since you set attribute 'parent_experiment_id'='piControl', further attributes are checked.
cdo    cmor: Successfully mapped variable via cmor_name to cmor_name 'tos'.
cdo    cmor: Mapping Table = '/work/bm0021/workshopcmip6pp2022/config/mapping_table_example.txt'.
cdo    cmor: Note that the grids of the variables found first in both files are switched.
cdo    cmor (Warning): Could not use zaxis from file '/work/bm0021/workshopcmip6pp2022/config/gridinfo_mpiom_no3.nc' configured via attribute 'ginfo'
          because total size of infile: '1' is not identical to total size of ginfo file: '40'.
cdo    cmor: Vertical axis is either default and scalar or not available.
cdo    cmor:      File stored in:  './archive/CMIP6/CMIP/MPI-M/MPI-ESM1-2-LR/historical/r1i1p1f1/Omon/tos/gn/v20220901/tos_Omon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012.nc' with cmor!
cdo    cmor: Processed 675840 values from 564 variables over 12 timesteps [0.62s 531MB].

Let us take a look …

# ncdump
outputfile = f"{outdir}/CMIP6/CMIP/MPI-M/MPI-ESM1-2-LR/historical/r1i1p1f1/Omon/"
outputfile += f"tos/gn/{vdate}/tos_Omon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012.nc"
! ncdump -h {outputfile}
netcdf tos_Omon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185012 {
dimensions:
	time = UNLIMITED ; // (12 currently)
	j = 220 ;
	i = 256 ;
	bnds = 2 ;
	vertices = 4 ;
variables:
	double time(time) ;
		time:bounds = "time_bnds" ;
		time:units = "days since 1850-1-1 00:00:00" ;
		time:calendar = "proleptic_gregorian" ;
		time:axis = "T" ;
		time:long_name = "time" ;
		time:standard_name = "time" ;
	double time_bnds(time, bnds) ;
	int j(j) ;
		j:units = "1" ;
		j:long_name = "cell index along second dimension" ;
	int i(i) ;
		i:units = "1" ;
		i:long_name = "cell index along first dimension" ;
	double latitude(j, i) ;
		latitude:standard_name = "latitude" ;
		latitude:long_name = "latitude" ;
		latitude:units = "degrees_north" ;
		latitude:missing_value = 1.e+20 ;
		latitude:_FillValue = 1.e+20 ;
		latitude:bounds = "vertices_latitude" ;
	double longitude(j, i) ;
		longitude:standard_name = "longitude" ;
		longitude:long_name = "longitude" ;
		longitude:units = "degrees_east" ;
		longitude:missing_value = 1.e+20 ;
		longitude:_FillValue = 1.e+20 ;
		longitude:bounds = "vertices_longitude" ;
	double vertices_latitude(j, i, vertices) ;
		vertices_latitude:units = "degrees_north" ;
		vertices_latitude:missing_value = 1.e+20 ;
		vertices_latitude:_FillValue = 1.e+20 ;
	double vertices_longitude(j, i, vertices) ;
		vertices_longitude:units = "degrees_east" ;
		vertices_longitude:missing_value = 1.e+20 ;
		vertices_longitude:_FillValue = 1.e+20 ;
	float tos(time, j, i) ;
		tos:standard_name = "sea_surface_temperature" ;
		tos:long_name = "Sea Surface Temperature" ;
		tos:comment = "Temperature of upper boundary of the liquid ocean, including temperatures below sea-ice and floating ice shelves." ;
		tos:units = "degC" ;
		tos:original_name = "tos" ;
		tos:cell_methods = "area: mean where sea time: mean" ;
		tos:cell_measures = "area: areacello" ;
		tos:history = "2022-08-31T07:58:02Z altered by CMOR: replaced missing value flag (-9e+33) and corresponding data with standard missing value (1e+20)." ;
		tos:missing_value = 1.e+20f ;
		tos:_FillValue = 1.e+20f ;
		tos:coordinates = "latitude longitude" ;

// global attributes:
		:CDO = "Climate Data Operators version 2.0.6 (https://mpimet.mpg.de/cdo)" ;
		:Conventions = "CF-1.7 CMIP-6.2" ;
		:activity_id = "CMIP" ;
		:branch_method = "standard" ;
		:branch_time_in_child = 0. ;
		:branch_time_in_parent = 0. ;
		:contact = "cmip6-mpi-esm@dkrz.de" ;
		:creation_date = "2022-08-31T07:58:02Z" ;
		:data_specs_version = "01.00.30" ;
		:experiment = "all-forcing simulation of the recent past" ;
		:experiment_id = "historical" ;
		:external_variables = "areacello" ;
		:forcing_index = 1 ;
		:frequency = "mon" ;
		:further_info_url = "https://furtherinfo.es-doc.org/CMIP6.MPI-M.MPI-ESM1-2-LR.historical.none.r1i1p1f1" ;
		:grid = "gn" ;
		:grid_label = "gn" ;
		:history = "2022-08-31T07:58:02Z ; CMOR rewrote data to be consistent with CMIP6, CF-1.7 CMIP-6.2 and CF standards." ;
		:initialization_index = 1 ;
		:institution = "Max Planck Institute for Meteorology, Hamburg 20146, Germany" ;
		:institution_id = "MPI-M" ;
		:mip_era = "CMIP6" ;
		:nominal_resolution = "250 km" ;
		:parent_activity_id = "CMIP" ;
		:parent_experiment_id = "piControl" ;
		:parent_mip_era = "CMIP6" ;
		:parent_source_id = "MPI-ESM1-2-LR" ;
		:parent_time_units = "days since 1850-1-1 00:00:00" ;
		:parent_variant_label = "r1i1p1f1" ;
		:physics_index = 1 ;
		:product = "model-output" ;
		:project_id = "CMIP6" ;
		:realization_index = 1 ;
		:realm = "ocean" ;
		:references = "MPI-ESM: Mauritsen, T. et al. (2019), Developments in the MPI-M Earth System Model version 1.2 (MPI-ESM1.2) and Its Response to Increasing CO2, J. Adv. Model. Earth Syst.,11, 998-1038, doi:10.1029/2018MS001400,\n",
			"Mueller, W.A. et al. (2018): A high-resolution version of the Max Planck Institute Earth System Model MPI-ESM1.2-HR. J. Adv. Model. EarthSyst.,10,1383–1413, doi:10.1029/2017MS001217" ;
		:source = "MPI-ESM1.2-LR (2017): \n",
			"aerosol: none, prescribed MACv2-SP\n",
			"atmos: ECHAM6.3 (spectral T63; 192 x 96 longitude/latitude; 47 levels; top level 0.01 hPa)\n",
			"atmosChem: none\n",
			"land: JSBACH3.20\n",
			"landIce: none/prescribed\n",
			"ocean: MPIOM1.63 (bipolar GR1.5, approximately 1.5deg; 256 x 220 longitude/latitude; 40 levels; top grid cell 0-12 m)\n",
			"ocnBgchem: HAMOCC6\n",
			"seaIce: unnamed (thermodynamic (Semtner zero-layer) dynamic (Hibler 79) sea ice model)" ;
		:source_id = "MPI-ESM1-2-LR" ;
		:source_type = "AOGCM BGC" ;
		:sub_experiment = "none" ;
		:sub_experiment_id = "none" ;
		:table_id = "Omon" ;
		:table_info = "Creation Date:(09 May 2019) MD5:5f007c16960eee824e07a4ce809e3ed1" ;
		:title = "MPI-ESM1-2-LR output prepared for CMIP6" ;
		:variable_id = "tos" ;
		:variant_label = "r1i1p1f1" ;
		:license = "CMIP6 model data produced by MPI-M 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. 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." ;
		:cmor_version = "3.6.0" ;
		:tracking_id = "hdl:21.14100/246dfb28-a460-44a5-98ad-bf227eb3f63d" ;
}
import matplotlib.pyplot as plt
import cartopy.crs as ccrs

ds = xr.open_dataset(outputfile)

# Plot a single timestep using xarray - the curvilinear ocean grid requires some additional attributes
#  for the plot to look proper

fig = plt.figure(figsize=[8, 3])
ax = plt.subplot(1, 1, 1, projection=ccrs.PlateCarree())
ds["tos"].isel(time=0).plot.pcolormesh(ax=ax, x="longitude", y="latitude", 
                                       shading="auto");

# Dynamic plot of the entire timeseries using hvplot
#ds["tos"].hvplot.quadmesh(x="longitude", y="latitude", width=600, projection=ccrs.PlateCarree())
_images/cdo-cmor_37_0.png

Example 4: netCDF input - 3D data - diagnostic and gridfile required#

The next variable (o2sat on ocean layers) requires a gridfile as input with the specified proper layer midpoints interfaces. If no gridfile is provided, cdo cmor will use linear interpolation to infer the layer interfaces from the layer midpoints, which is not correct in this case. The gridfile can be specified in the cdocmorinfo configuration files.

# Input file
ifile = model_data + "mpiom/historical_r1i1p1f1-LR_mpiom_data_3d_mm_18500101_18500131.nc"

# A look into the submodel specific configuration
! cat {it_mpiom}
#NOMINAL_RESOLUTION read from external file
#For Oce (MPIOM/HAMOCC)
NOMINAL_RESOLUTION="250 km"
#Level/Depth Bounds
grid_info="/work/bm0021/workshopcmip6pp2022/config/gridinfo_mpiom_no3.nc"
switch_z=y 
switch_xy=n

This variable requires a quite complex diagnostic, but even this fits into a cdo expression! The underscore prefix allows us to define a temporary variable in a cdo expression that is not written to the resulting output file. Besides arithmetic operations, the expression operator allows the application of a wide selection of functions, like vertical summation, vertical level selection, statistical and conditional operators, etc. (see the CDO User Guide for more information).

recipe = "'_TS=ln((298.15-thetao)/(thetao+273.15));o2sat=0.0446596*"
recipe += "exp(2.00907+3.22014*_TS+4.05010*_TS^2+4.94457*_TS^3-0.256847*_TS^4+3.88767*_TS^5"
recipe += "-so*(0.00624523+_TS*0.00737614+0.0103410*_TS^2+0.00817083*_TS^3+0.000000488682*so));'"

Run cdo cmor

%cdo cmor,Omon,i={it},{it_mpiom},mt={mt},dr={outdir},vd={vdate},cn=o2sat -expr,{recipe} {ifile}
cdo(1) expr: Process started
In reading info table:
          Open failed on /home/k/k204199/jupyter-guide-to-climate-data/.cdocmorinfo: No such file or directory.cdo    cmor: MIP table file = '/pool/data/CMIP6/cmip6-cmor-tables/Tables//CMIP6_Omon.json'
cdo    cmor: Attribute 'time_units' = 'days since 1850-1-1 00:00:00'
cdo    cmor: Since you set attribute 'parent_experiment_id'='piControl', further attributes are checked.
cdo    cmor: Successfully mapped variable via cmor_name to cmor_name 'o2sat'.
cdo    cmor: Mapping Table = '/work/bm0021/workshopcmip6pp2022/config/mapping_table_example.txt'.
cdo    cmor: Successfully substituted zaxis.
cdo    cmor: Attribute 'z_axis' = 'depth'
cdo    cmor:      File stored in:  './archive/CMIP6/CMIP/MPI-M/MPI-ESM1-2-LR/historical/r1i1p1f1/Omon/o2sat/gn/v20220901/o2sat_Omon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185001.nc' with cmor!
cdo(1) expr: Processed 4505600 values from 30 variables over 1 timestep.
cdo    cmor: Processed 2252800 values from 6 variables over 1 timestep [1.01s 721MB].

Let us take a look …

# ncdump
outputfile = f"{outdir}/CMIP6/CMIP/MPI-M/MPI-ESM1-2-LR/historical/r1i1p1f1/Omon/"
outputfile += f"o2sat/gn/{vdate}/o2sat_Omon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185001.nc"
! ncdump -h {outputfile}
netcdf o2sat_Omon_MPI-ESM1-2-LR_historical_r1i1p1f1_gn_185001-185001 {
dimensions:
	time = UNLIMITED ; // (1 currently)
	lev = 40 ;
	j = 220 ;
	i = 256 ;
	bnds = 2 ;
	vertices = 4 ;
variables:
	double time(time) ;
		time:bounds = "time_bnds" ;
		time:units = "days since 1850-1-1 00:00:00" ;
		time:calendar = "proleptic_gregorian" ;
		time:axis = "T" ;
		time:long_name = "time" ;
		time:standard_name = "time" ;
	double time_bnds(time, bnds) ;
	double lev(lev) ;
		lev:bounds = "lev_bnds" ;
		lev:units = "m" ;
		lev:axis = "Z" ;
		lev:positive = "down" ;
		lev:long_name = "ocean depth coordinate" ;
		lev:standard_name = "depth" ;
	double lev_bnds(lev, bnds) ;
	int j(j) ;
		j:units = "1" ;
		j:long_name = "cell index along second dimension" ;
	int i(i) ;
		i:units = "1" ;
		i:long_name = "cell index along first dimension" ;
	double latitude(j, i) ;
		latitude:standard_name = "latitude" ;
		latitude:long_name = "latitude" ;
		latitude:units = "degrees_north" ;
		latitude:missing_value = 1.e+20 ;
		latitude:_FillValue = 1.e+20 ;
		latitude:bounds = "vertices_latitude" ;
	double longitude(j, i) ;
		longitude:standard_name = "longitude" ;
		longitude:long_name = "longitude" ;
		longitude:units = "degrees_east" ;
		longitude:missing_value = 1.e+20 ;
		longitude:_FillValue = 1.e+20 ;
		longitude:bounds = "vertices_longitude" ;
	double vertices_latitude(j, i, vertices) ;
		vertices_latitude:units = "degrees_north" ;
		vertices_latitude:missing_value = 1.e+20 ;
		vertices_latitude:_FillValue = 1.e+20 ;
	double vertices_longitude(j, i, vertices) ;
		vertices_longitude:units = "degrees_east" ;
		vertices_longitude:missing_value = 1.e+20 ;
		vertices_longitude:_FillValue = 1.e+20 ;
	float o2sat(time, lev, j, i) ;
		o2sat:standard_name = "mole_concentration_of_dissolved_molecular_oxygen_in_sea_water_at_saturation" ;
		o2sat:long_name = "Dissolved Oxygen Concentration at Saturation" ;
		o2sat:comment = "\'Mole concentration at saturation\' means the mole concentration in a saturated solution. Mole concentration means number of moles per unit volume, also called \'molarity\', and is used in the construction \'mole_concentration_of_X_in_Y\', where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as \'nitrogen\' or a phrase such as \'nox_expressed_as_nitrogen\'." ;
		o2sat:units = "mol m-3" ;
		o2sat:original_name = "o2sat" ;
		o2sat:cell_methods = "area: mean where sea time: mean" ;
		o2sat:cell_measures = "area: areacello volume: volcello" ;
		o2sat:history = "2022-08-31T07:58:03Z altered by CMOR: replaced missing value flag (-9e+33) and corresponding data with standard missing value (1e+20)." ;
		o2sat:missing_value = 1.e+20f ;
		o2sat:_FillValue = 1.e+20f ;
		o2sat:coordinates = "latitude longitude" ;

// global attributes:
		:CDO = "Climate Data Operators version 2.0.6 (https://mpimet.mpg.de/cdo)" ;
		:Conventions = "CF-1.7 CMIP-6.2" ;
		:activity_id = "CMIP" ;
		:branch_method = "standard" ;
		:branch_time_in_child = 0. ;
		:branch_time_in_parent = 0. ;
		:contact = "cmip6-mpi-esm@dkrz.de" ;
		:creation_date = "2022-08-31T07:58:04Z" ;
		:data_specs_version = "01.00.30" ;
		:experiment = "all-forcing simulation of the recent past" ;
		:experiment_id = "historical" ;
		:external_variables = "areacello volcello" ;
		:forcing_index = 1 ;
		:frequency = "mon" ;
		:further_info_url = "https://furtherinfo.es-doc.org/CMIP6.MPI-M.MPI-ESM1-2-LR.historical.none.r1i1p1f1" ;
		:grid = "gn" ;
		:grid_label = "gn" ;
		:history = "2022-08-31T07:58:04Z ; CMOR rewrote data to be consistent with CMIP6, CF-1.7 CMIP-6.2 and CF standards." ;
		:initialization_index = 1 ;
		:institution = "Max Planck Institute for Meteorology, Hamburg 20146, Germany" ;
		:institution_id = "MPI-M" ;
		:mip_era = "CMIP6" ;
		:nominal_resolution = "250 km" ;
		:parent_activity_id = "CMIP" ;
		:parent_experiment_id = "piControl" ;
		:parent_mip_era = "CMIP6" ;
		:parent_source_id = "MPI-ESM1-2-LR" ;
		:parent_time_units = "days since 1850-1-1 00:00:00" ;
		:parent_variant_label = "r1i1p1f1" ;
		:physics_index = 1 ;
		:product = "model-output" ;
		:project_id = "CMIP6" ;
		:realization_index = 1 ;
		:realm = "ocnBgchem" ;
		:references = "MPI-ESM: Mauritsen, T. et al. (2019), Developments in the MPI-M Earth System Model version 1.2 (MPI-ESM1.2) and Its Response to Increasing CO2, J. Adv. Model. Earth Syst.,11, 998-1038, doi:10.1029/2018MS001400,\n",
			"Mueller, W.A. et al. (2018): A high-resolution version of the Max Planck Institute Earth System Model MPI-ESM1.2-HR. J. Adv. Model. EarthSyst.,10,1383–1413, doi:10.1029/2017MS001217" ;
		:source = "MPI-ESM1.2-LR (2017): \n",
			"aerosol: none, prescribed MACv2-SP\n",
			"atmos: ECHAM6.3 (spectral T63; 192 x 96 longitude/latitude; 47 levels; top level 0.01 hPa)\n",
			"atmosChem: none\n",
			"land: JSBACH3.20\n",
			"landIce: none/prescribed\n",
			"ocean: MPIOM1.63 (bipolar GR1.5, approximately 1.5deg; 256 x 220 longitude/latitude; 40 levels; top grid cell 0-12 m)\n",
			"ocnBgchem: HAMOCC6\n",
			"seaIce: unnamed (thermodynamic (Semtner zero-layer) dynamic (Hibler 79) sea ice model)" ;
		:source_id = "MPI-ESM1-2-LR" ;
		:source_type = "AOGCM BGC" ;
		:sub_experiment = "none" ;
		:sub_experiment_id = "none" ;
		:table_id = "Omon" ;
		:table_info = "Creation Date:(09 May 2019) MD5:5f007c16960eee824e07a4ce809e3ed1" ;
		:title = "MPI-ESM1-2-LR output prepared for CMIP6" ;
		:tracking_id = "hdl:21.14100/ef3e6c73-b717-452f-a256-57781db5339b" ;
		:variable_id = "o2sat" ;
		:variant_label = "r1i1p1f1" ;
		:license = "CMIP6 model data produced by MPI-M 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. 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." ;
		:cmor_version = "3.6.0" ;
}
ds = xr.open_dataset(outputfile)

# Plot a single timestep using xarray - the curvilinear ocean grid requires some additional attributes
#  for the plot to look proper

fig = plt.figure(figsize=[8, 3])
ax = plt.subplot(1, 1, 1, projection=ccrs.PlateCarree())
ds["o2sat"].isel(time=0, lev=0).plot.pcolormesh(ax=ax, x="longitude", y="latitude", 
                                       shading="auto");

# Dynamic plot of the entire timeseries using hvplot
#ds["o2sat"].isel(time=0).hvplot.quadmesh(x="longitude", y="latitude", width=600, projection=ccrs.PlateCarree())
_images/cdo-cmor_46_0.png

Further information and help#