From eceafc156006e4f3e44b21e0e80c3e0b613c5b43 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 26 Nov 2024 08:08:56 +0000 Subject: [PATCH] site deploy Auto-generated via `{sandpaper}` Source : 040d2624f1efdff954b2a702807c3ce03ab86b34 Branch : md-outputs Author : GitHub Actions Time : 2024-11-26 08:08:36 +0000 Message : markdown source builds Auto-generated via `{sandpaper}` Source : d2b3b94d7cfe949a9ccb98802d196b4da8b354df Branch : main Author : Toby Hodges Time : 2024-11-26 08:04:33 +0000 Message : complete lesson configuration --- .nojekyll | 0 00-introduction.html | 674 ++ 01-quickstart.html | 616 ++ 02-installation.html | 748 ++ 03-configuration.html | 934 ++ 04-recipe.html | 1068 ++ 05-conclusions.html | 595 ++ 06-preprocessor.html | 1234 +++ 07-development-setup.html | 1070 ++ 08-diagnostics.html | 1122 +++ 09-cmorization.html | 1493 +++ 10-debugging.html | 1033 ++ 404.html | 453 + CODE_OF_CONDUCT.html | 465 + LICENSE.html | 513 + aio.html | 6322 ++++++++++++ android-chrome-192x192.png | Bin 0 -> 2103 bytes android-chrome-512x512.png | Bin 0 -> 6231 bytes apple-touch-icon.png | Bin 0 -> 2096 bytes assets/fonts/Mulish-Black.eot | Bin 0 -> 120480 bytes assets/fonts/Mulish-Black.svg | 8557 ++++++++++++++++ assets/fonts/Mulish-Black.ttf | Bin 0 -> 106576 bytes assets/fonts/Mulish-Black.woff | Bin 0 -> 55084 bytes assets/fonts/Mulish-Black.woff2 | Bin 0 -> 39388 bytes assets/fonts/Mulish-BlackItalic.eot | Bin 0 -> 123372 bytes assets/fonts/Mulish-BlackItalic.svg | 8605 +++++++++++++++++ assets/fonts/Mulish-BlackItalic.ttf | Bin 0 -> 110200 bytes assets/fonts/Mulish-BlackItalic.woff | Bin 0 -> 58392 bytes assets/fonts/Mulish-BlackItalic.woff2 | Bin 0 -> 42108 bytes assets/fonts/Mulish-Bold.eot | Bin 0 -> 120312 bytes assets/fonts/Mulish-Bold.svg | 8522 ++++++++++++++++ assets/fonts/Mulish-Bold.ttf | Bin 0 -> 106576 bytes assets/fonts/Mulish-Bold.woff | Bin 0 -> 54668 bytes assets/fonts/Mulish-Bold.woff2 | Bin 0 -> 39184 bytes assets/fonts/Mulish-BoldItalic.eot | Bin 0 -> 123200 bytes assets/fonts/Mulish-BoldItalic.svg | 8570 ++++++++++++++++ assets/fonts/Mulish-BoldItalic.ttf | Bin 0 -> 110260 bytes assets/fonts/Mulish-BoldItalic.woff | Bin 0 -> 57988 bytes assets/fonts/Mulish-BoldItalic.woff2 | Bin 0 -> 41456 bytes assets/fonts/Mulish-ExtraBold.eot | Bin 0 -> 120524 bytes assets/fonts/Mulish-ExtraBold.svg | 8335 ++++++++++++++++ assets/fonts/Mulish-ExtraBold.ttf | Bin 0 -> 106536 bytes assets/fonts/Mulish-ExtraBold.woff | Bin 0 -> 55068 bytes assets/fonts/Mulish-ExtraBold.woff2 | Bin 0 -> 39624 bytes assets/fonts/Mulish-ExtraBoldItalic.eot | Bin 0 -> 123384 bytes assets/fonts/Mulish-ExtraBoldItalic.svg | 8384 ++++++++++++++++ assets/fonts/Mulish-ExtraBoldItalic.ttf | Bin 0 -> 110256 bytes assets/fonts/Mulish-ExtraBoldItalic.woff | Bin 0 -> 58500 bytes assets/fonts/Mulish-ExtraBoldItalic.woff2 | Bin 0 -> 42252 bytes assets/fonts/Mulish-ExtraLight.eot | Bin 0 -> 121852 bytes assets/fonts/Mulish-ExtraLight.svg | 7998 +++++++++++++++ assets/fonts/Mulish-ExtraLight.ttf | Bin 0 -> 106380 bytes assets/fonts/Mulish-ExtraLight.woff | Bin 0 -> 53980 bytes assets/fonts/Mulish-ExtraLight.woff2 | Bin 0 -> 38176 bytes assets/fonts/Mulish-ExtraLightItalic.eot | Bin 0 -> 124640 bytes assets/fonts/Mulish-ExtraLightItalic.svg | 8053 +++++++++++++++ assets/fonts/Mulish-ExtraLightItalic.ttf | Bin 0 -> 110000 bytes assets/fonts/Mulish-ExtraLightItalic.woff | Bin 0 -> 56860 bytes assets/fonts/Mulish-ExtraLightItalic.woff2 | Bin 0 -> 40368 bytes .../fonts/Mulish-Italic-VariableFont_wght.ttf | Bin 0 -> 219768 bytes assets/fonts/Mulish-Italic.eot | Bin 0 -> 123192 bytes assets/fonts/Mulish-Italic.svg | 8504 ++++++++++++++++ assets/fonts/Mulish-Italic.ttf | Bin 0 -> 110060 bytes assets/fonts/Mulish-Italic.woff | Bin 0 -> 58184 bytes assets/fonts/Mulish-Italic.woff2 | Bin 0 -> 41600 bytes assets/fonts/Mulish-Light.eot | Bin 0 -> 120600 bytes assets/fonts/Mulish-Light.svg | 8099 ++++++++++++++++ assets/fonts/Mulish-Light.ttf | Bin 0 -> 106572 bytes assets/fonts/Mulish-Light.woff | Bin 0 -> 54436 bytes assets/fonts/Mulish-Light.woff2 | Bin 0 -> 38984 bytes assets/fonts/Mulish-LightItalic.eot | Bin 0 -> 123408 bytes assets/fonts/Mulish-LightItalic.svg | 8132 ++++++++++++++++ assets/fonts/Mulish-LightItalic.ttf | Bin 0 -> 110192 bytes assets/fonts/Mulish-LightItalic.woff | Bin 0 -> 57736 bytes assets/fonts/Mulish-LightItalic.woff2 | Bin 0 -> 41496 bytes assets/fonts/Mulish-Medium.eot | Bin 0 -> 120540 bytes assets/fonts/Mulish-Medium.svg | 8546 ++++++++++++++++ assets/fonts/Mulish-Medium.ttf | Bin 0 -> 106568 bytes assets/fonts/Mulish-Medium.woff | Bin 0 -> 55068 bytes assets/fonts/Mulish-Medium.woff2 | Bin 0 -> 39504 bytes assets/fonts/Mulish-MediumItalic.eot | Bin 0 -> 123416 bytes assets/fonts/Mulish-MediumItalic.svg | 8585 ++++++++++++++++ assets/fonts/Mulish-MediumItalic.ttf | Bin 0 -> 110160 bytes assets/fonts/Mulish-MediumItalic.woff | Bin 0 -> 58488 bytes assets/fonts/Mulish-MediumItalic.woff2 | Bin 0 -> 41984 bytes assets/fonts/Mulish-Regular.eot | Bin 0 -> 120472 bytes assets/fonts/Mulish-Regular.svg | 8479 ++++++++++++++++ assets/fonts/Mulish-Regular.ttf | Bin 0 -> 106528 bytes assets/fonts/Mulish-Regular.woff | Bin 0 -> 54864 bytes assets/fonts/Mulish-Regular.woff2 | Bin 0 -> 39332 bytes assets/fonts/Mulish-SemiBold.eot | Bin 0 -> 120564 bytes assets/fonts/Mulish-SemiBold.svg | 8550 ++++++++++++++++ assets/fonts/Mulish-SemiBold.ttf | Bin 0 -> 106508 bytes assets/fonts/Mulish-SemiBold.woff | Bin 0 -> 55100 bytes assets/fonts/Mulish-SemiBold.woff2 | Bin 0 -> 39344 bytes assets/fonts/Mulish-SemiBoldItalic.eot | Bin 0 -> 123412 bytes assets/fonts/Mulish-SemiBoldItalic.svg | 8599 ++++++++++++++++ assets/fonts/Mulish-SemiBoldItalic.ttf | Bin 0 -> 110120 bytes assets/fonts/Mulish-SemiBoldItalic.woff | Bin 0 -> 58600 bytes assets/fonts/Mulish-SemiBoldItalic.woff2 | Bin 0 -> 42132 bytes assets/fonts/Mulish-VariableFont_wght.ttf | Bin 0 -> 212500 bytes assets/fonts/MulishExtraLight-Regular.eot | Bin 0 -> 27692 bytes assets/fonts/MulishExtraLight-Regular.svg | 3643 +++++++ assets/fonts/MulishExtraLight-Regular.woff | Bin 0 -> 15908 bytes assets/fonts/MulishExtraLight-Regular.woff2 | Bin 0 -> 12248 bytes assets/fonts/mulish-v5-latin-regular.eot | Bin 0 -> 12924 bytes assets/fonts/mulish-v5-latin-regular.svg | 305 + assets/fonts/mulish-v5-latin-regular.ttf | Bin 0 -> 25836 bytes assets/fonts/mulish-v5-latin-regular.woff | Bin 0 -> 14344 bytes assets/fonts/mulish-v5-latin-regular.woff2 | Bin 0 -> 11292 bytes assets/fonts/mulish-variablefont_wght.woff | Bin 0 -> 81560 bytes assets/fonts/mulish-variablefont_wght.woff2 | Bin 0 -> 50460 bytes assets/images/carpentries-logo-sm.svg | 7 + assets/images/carpentries-logo.svg | 19 + assets/images/data-logo-sm.svg | 1 + assets/images/data-logo.svg | 1 + assets/images/dropdown-arrow.svg | 12 + assets/images/incubator-logo-sm.svg | 10 + assets/images/incubator-logo.svg | 10 + assets/images/lab-logo-sm.svg | 125 + assets/images/lab-logo.svg | 262 + assets/images/library-logo-sm.svg | 23 + assets/images/library-logo.svg | 23 + assets/images/minus.svg | 1 + assets/images/plus.svg | 1 + assets/images/software-logo-sm.svg | 3 + assets/images/software-logo.svg | 202 + assets/scripts.js | 1 + assets/styles.css | 5 + assets/styles.css.map | 1 + assets/themetoggle.js | 1 + bootstrap-toc.css | 60 + bootstrap-toc.js | 159 + config.yaml | 98 + data/dataset.urls | 6 + docsearch.css | 148 + docsearch.js | 85 + favicon-16x16.png | Bin 0 -> 424 bytes favicon-32x32.png | Bin 0 -> 615 bytes favicons/cp/apple-touch-icon-114x114.png | Bin 0 -> 9527 bytes favicons/cp/apple-touch-icon-120x120.png | Bin 0 -> 7363 bytes favicons/cp/apple-touch-icon-144x144.png | Bin 0 -> 14680 bytes favicons/cp/apple-touch-icon-152x152.png | Bin 0 -> 16094 bytes favicons/cp/apple-touch-icon-57x57.png | Bin 0 -> 3180 bytes favicons/cp/apple-touch-icon-60x60.png | Bin 0 -> 2787 bytes favicons/cp/apple-touch-icon-72x72.png | Bin 0 -> 4338 bytes favicons/cp/apple-touch-icon-76x76.png | Bin 0 -> 4718 bytes favicons/cp/favicon-128.png | Bin 0 -> 6528 bytes favicons/cp/favicon-16x16.png | Bin 0 -> 569 bytes favicons/cp/favicon-196x196.png | Bin 0 -> 26095 bytes favicons/cp/favicon-32x32.png | Bin 0 -> 1272 bytes favicons/cp/favicon-96x96.png | Bin 0 -> 6197 bytes favicons/cp/favicon.ico | Bin 0 -> 34494 bytes favicons/cp/mstile-144x144.png | Bin 0 -> 14680 bytes favicons/cp/mstile-150x150.png | Bin 0 -> 34546 bytes favicons/cp/mstile-310x150.png | Bin 0 -> 73438 bytes favicons/cp/mstile-310x310.png | Bin 0 -> 152844 bytes favicons/cp/mstile-70x70.png | Bin 0 -> 6528 bytes favicons/dc/apple-touch-icon-114x114.png | Bin 0 -> 3888 bytes favicons/dc/apple-touch-icon-120x120.png | Bin 0 -> 3088 bytes favicons/dc/apple-touch-icon-144x144.png | Bin 0 -> 4058 bytes favicons/dc/apple-touch-icon-152x152.png | Bin 0 -> 5839 bytes favicons/dc/apple-touch-icon-57x57.png | Bin 0 -> 1438 bytes favicons/dc/apple-touch-icon-60x60.png | Bin 0 -> 1404 bytes favicons/dc/apple-touch-icon-72x72.png | Bin 0 -> 1546 bytes favicons/dc/apple-touch-icon-76x76.png | Bin 0 -> 1934 bytes favicons/dc/favicon-128.png | Bin 0 -> 2525 bytes favicons/dc/favicon-16x16.png | Bin 0 -> 398 bytes favicons/dc/favicon-196x196.png | Bin 0 -> 9560 bytes favicons/dc/favicon-32x32.png | Bin 0 -> 699 bytes favicons/dc/favicon-96x96.png | Bin 0 -> 1839 bytes favicons/dc/favicon.ico | Bin 0 -> 34494 bytes favicons/dc/mstile-144x144.png | Bin 0 -> 4058 bytes favicons/dc/mstile-150x150.png | Bin 0 -> 16349 bytes favicons/dc/mstile-310x150.png | Bin 0 -> 25258 bytes favicons/dc/mstile-310x310.png | Bin 0 -> 60282 bytes favicons/dc/mstile-70x70.png | Bin 0 -> 2525 bytes favicons/lc/apple-touch-icon-114x114.png | Bin 0 -> 6851 bytes favicons/lc/apple-touch-icon-120x120.png | Bin 0 -> 5646 bytes favicons/lc/apple-touch-icon-144x144.png | Bin 0 -> 10064 bytes favicons/lc/apple-touch-icon-152x152.png | Bin 0 -> 11313 bytes favicons/lc/apple-touch-icon-57x57.png | Bin 0 -> 2426 bytes favicons/lc/apple-touch-icon-60x60.png | Bin 0 -> 2216 bytes favicons/lc/apple-touch-icon-72x72.png | Bin 0 -> 3170 bytes favicons/lc/apple-touch-icon-76x76.png | Bin 0 -> 3650 bytes favicons/lc/favicon-128.png | Bin 0 -> 4763 bytes favicons/lc/favicon-16x16.png | Bin 0 -> 458 bytes favicons/lc/favicon-196x196.png | Bin 0 -> 17613 bytes favicons/lc/favicon-32x32.png | Bin 0 -> 998 bytes favicons/lc/favicon-96x96.png | Bin 0 -> 4583 bytes favicons/lc/favicon.ico | Bin 0 -> 34494 bytes favicons/lc/mstile-144x144.png | Bin 0 -> 10064 bytes favicons/lc/mstile-150x150.png | Bin 0 -> 25278 bytes favicons/lc/mstile-310x150.png | Bin 0 -> 56617 bytes favicons/lc/mstile-310x310.png | Bin 0 -> 117944 bytes favicons/lc/mstile-70x70.png | Bin 0 -> 4763 bytes favicons/swc/apple-touch-icon-114x114.png | Bin 0 -> 9156 bytes favicons/swc/apple-touch-icon-120x120.png | Bin 0 -> 6887 bytes favicons/swc/apple-touch-icon-144x144.png | Bin 0 -> 14431 bytes favicons/swc/apple-touch-icon-152x152.png | Bin 0 -> 15451 bytes favicons/swc/apple-touch-icon-57x57.png | Bin 0 -> 2944 bytes favicons/swc/apple-touch-icon-60x60.png | Bin 0 -> 2593 bytes favicons/swc/apple-touch-icon-72x72.png | Bin 0 -> 4119 bytes favicons/swc/apple-touch-icon-76x76.png | Bin 0 -> 4379 bytes favicons/swc/favicon-128.png | Bin 0 -> 6298 bytes favicons/swc/favicon-16x16.png | Bin 0 -> 557 bytes favicons/swc/favicon-196x196.png | Bin 0 -> 25107 bytes favicons/swc/favicon-32x32.png | Bin 0 -> 1181 bytes favicons/swc/favicon-96x96.png | Bin 0 -> 6269 bytes favicons/swc/favicon.ico | Bin 0 -> 34494 bytes favicons/swc/mstile-144x144.png | Bin 0 -> 14431 bytes favicons/swc/mstile-150x150.png | Bin 0 -> 33163 bytes favicons/swc/mstile-310x150.png | Bin 0 -> 77018 bytes favicons/swc/mstile-310x310.png | Bin 0 -> 162023 bytes favicons/swc/mstile-70x70.png | Bin 0 -> 6298 bytes ...ries_temperature_1900_2000_timeseries_.png | Bin 0 -> 38000 bytes fig/data_flow.png | Bin 0 -> 124787 bytes fig/data_flow.pptx | Bin 0 -> 79053 bytes ...ies_temperature_1900_2000_timeseries_0.png | Bin 0 -> 37037 bytes fig/esmvaltool_architecture.png | Bin 0 -> 115471 bytes fig/warming_stripes.png | Bin 0 -> 25980 bytes files/esgf-pyclient.yml | 15 + files/recipe_check_fluxcom.yml | 21 + files/recipe_warming_stripes.yml | 40 + ...pe_warming_stripes_additional_datasets.yml | 54 + files/recipe_warming_stripes_local.yml | 39 + ...ming_stripes_multiple_ensemble_members.yml | 37 + ...ipe_warming_stripes_multiple_locations.yml | 52 + files/recipe_warming_stripes_periods.yml | 46 + files/warming_stripes.py | 58 + glossary.html | 537 + images.html | 521 + index.html | 774 ++ instructor-notes.html | 576 ++ instructor/00-introduction.html | 676 ++ instructor/01-quickstart.html | 618 ++ instructor/02-installation.html | 750 ++ instructor/03-configuration.html | 936 ++ instructor/04-recipe.html | 1070 ++ instructor/05-conclusions.html | 597 ++ instructor/06-preprocessor.html | 1236 +++ instructor/07-development-setup.html | 1072 ++ instructor/08-diagnostics.html | 1124 +++ instructor/09-cmorization.html | 1495 +++ instructor/10-debugging.html | 1035 ++ instructor/404.html | 453 + instructor/CODE_OF_CONDUCT.html | 467 + instructor/LICENSE.html | 515 + instructor/aio.html | 6337 ++++++++++++ instructor/glossary.html | 539 ++ instructor/images.html | 525 + instructor/index.html | 895 ++ instructor/instructor-notes.html | 591 ++ instructor/key-points.html | 605 ++ instructor/profiles.html | 417 + instructor/reference.html | 466 + key-points.html | 601 ++ link.svg | 12 + md5sum.txt | 21 + mstile-150x150.png | Bin 0 -> 1465 bytes pkgdown.css | 384 + pkgdown.js | 108 + pkgdown.yml | 5 + profiles.html | 417 + reference.html | 464 + safari-pinned-tab.svg | 68 + site.webmanifest | 19 + sitemap.xml | 111 + 268 files changed, 185469 insertions(+) create mode 100644 .nojekyll create mode 100644 00-introduction.html create mode 100644 01-quickstart.html create mode 100644 02-installation.html create mode 100644 03-configuration.html create mode 100644 04-recipe.html create mode 100644 05-conclusions.html create mode 100644 06-preprocessor.html create mode 100644 07-development-setup.html create mode 100644 08-diagnostics.html create mode 100644 09-cmorization.html create mode 100644 10-debugging.html create mode 100644 404.html create mode 100644 CODE_OF_CONDUCT.html create mode 100644 LICENSE.html create mode 100644 aio.html create mode 100644 android-chrome-192x192.png create mode 100644 android-chrome-512x512.png create mode 100644 apple-touch-icon.png create mode 100644 assets/fonts/Mulish-Black.eot create mode 100644 assets/fonts/Mulish-Black.svg create mode 100644 assets/fonts/Mulish-Black.ttf create mode 100644 assets/fonts/Mulish-Black.woff create mode 100644 assets/fonts/Mulish-Black.woff2 create mode 100644 assets/fonts/Mulish-BlackItalic.eot create mode 100644 assets/fonts/Mulish-BlackItalic.svg create mode 100644 assets/fonts/Mulish-BlackItalic.ttf create mode 100644 assets/fonts/Mulish-BlackItalic.woff create mode 100644 assets/fonts/Mulish-BlackItalic.woff2 create mode 100644 assets/fonts/Mulish-Bold.eot create mode 100644 assets/fonts/Mulish-Bold.svg create mode 100644 assets/fonts/Mulish-Bold.ttf create mode 100644 assets/fonts/Mulish-Bold.woff create mode 100644 assets/fonts/Mulish-Bold.woff2 create mode 100644 assets/fonts/Mulish-BoldItalic.eot create mode 100644 assets/fonts/Mulish-BoldItalic.svg create mode 100644 assets/fonts/Mulish-BoldItalic.ttf create mode 100644 assets/fonts/Mulish-BoldItalic.woff create mode 100644 assets/fonts/Mulish-BoldItalic.woff2 create mode 100644 assets/fonts/Mulish-ExtraBold.eot create mode 100644 assets/fonts/Mulish-ExtraBold.svg create mode 100644 assets/fonts/Mulish-ExtraBold.ttf create mode 100644 assets/fonts/Mulish-ExtraBold.woff create mode 100644 assets/fonts/Mulish-ExtraBold.woff2 create mode 100644 assets/fonts/Mulish-ExtraBoldItalic.eot create mode 100644 assets/fonts/Mulish-ExtraBoldItalic.svg create mode 100644 assets/fonts/Mulish-ExtraBoldItalic.ttf create mode 100644 assets/fonts/Mulish-ExtraBoldItalic.woff create mode 100644 assets/fonts/Mulish-ExtraBoldItalic.woff2 create mode 100644 assets/fonts/Mulish-ExtraLight.eot create mode 100644 assets/fonts/Mulish-ExtraLight.svg create mode 100644 assets/fonts/Mulish-ExtraLight.ttf create mode 100644 assets/fonts/Mulish-ExtraLight.woff create mode 100644 assets/fonts/Mulish-ExtraLight.woff2 create mode 100644 assets/fonts/Mulish-ExtraLightItalic.eot create mode 100644 assets/fonts/Mulish-ExtraLightItalic.svg create mode 100644 assets/fonts/Mulish-ExtraLightItalic.ttf create mode 100644 assets/fonts/Mulish-ExtraLightItalic.woff create mode 100644 assets/fonts/Mulish-ExtraLightItalic.woff2 create mode 100644 assets/fonts/Mulish-Italic-VariableFont_wght.ttf create mode 100644 assets/fonts/Mulish-Italic.eot create mode 100644 assets/fonts/Mulish-Italic.svg create mode 100644 assets/fonts/Mulish-Italic.ttf create mode 100644 assets/fonts/Mulish-Italic.woff create mode 100644 assets/fonts/Mulish-Italic.woff2 create mode 100644 assets/fonts/Mulish-Light.eot create mode 100644 assets/fonts/Mulish-Light.svg create mode 100644 assets/fonts/Mulish-Light.ttf create mode 100644 assets/fonts/Mulish-Light.woff create mode 100644 assets/fonts/Mulish-Light.woff2 create mode 100644 assets/fonts/Mulish-LightItalic.eot create mode 100644 assets/fonts/Mulish-LightItalic.svg create mode 100644 assets/fonts/Mulish-LightItalic.ttf create mode 100644 assets/fonts/Mulish-LightItalic.woff create mode 100644 assets/fonts/Mulish-LightItalic.woff2 create mode 100644 assets/fonts/Mulish-Medium.eot create mode 100644 assets/fonts/Mulish-Medium.svg create mode 100644 assets/fonts/Mulish-Medium.ttf create mode 100644 assets/fonts/Mulish-Medium.woff create mode 100644 assets/fonts/Mulish-Medium.woff2 create mode 100644 assets/fonts/Mulish-MediumItalic.eot create mode 100644 assets/fonts/Mulish-MediumItalic.svg create mode 100644 assets/fonts/Mulish-MediumItalic.ttf create mode 100644 assets/fonts/Mulish-MediumItalic.woff create mode 100644 assets/fonts/Mulish-MediumItalic.woff2 create mode 100644 assets/fonts/Mulish-Regular.eot create mode 100644 assets/fonts/Mulish-Regular.svg create mode 100644 assets/fonts/Mulish-Regular.ttf create mode 100644 assets/fonts/Mulish-Regular.woff create mode 100644 assets/fonts/Mulish-Regular.woff2 create mode 100644 assets/fonts/Mulish-SemiBold.eot create mode 100644 assets/fonts/Mulish-SemiBold.svg create mode 100644 assets/fonts/Mulish-SemiBold.ttf create mode 100644 assets/fonts/Mulish-SemiBold.woff create mode 100644 assets/fonts/Mulish-SemiBold.woff2 create mode 100644 assets/fonts/Mulish-SemiBoldItalic.eot create mode 100644 assets/fonts/Mulish-SemiBoldItalic.svg create mode 100644 assets/fonts/Mulish-SemiBoldItalic.ttf create mode 100644 assets/fonts/Mulish-SemiBoldItalic.woff create mode 100644 assets/fonts/Mulish-SemiBoldItalic.woff2 create mode 100644 assets/fonts/Mulish-VariableFont_wght.ttf create mode 100644 assets/fonts/MulishExtraLight-Regular.eot create mode 100644 assets/fonts/MulishExtraLight-Regular.svg create mode 100644 assets/fonts/MulishExtraLight-Regular.woff create mode 100644 assets/fonts/MulishExtraLight-Regular.woff2 create mode 100644 assets/fonts/mulish-v5-latin-regular.eot create mode 100644 assets/fonts/mulish-v5-latin-regular.svg create mode 100644 assets/fonts/mulish-v5-latin-regular.ttf create mode 100644 assets/fonts/mulish-v5-latin-regular.woff create mode 100644 assets/fonts/mulish-v5-latin-regular.woff2 create mode 100644 assets/fonts/mulish-variablefont_wght.woff create mode 100644 assets/fonts/mulish-variablefont_wght.woff2 create mode 100644 assets/images/carpentries-logo-sm.svg create mode 100644 assets/images/carpentries-logo.svg create mode 100644 assets/images/data-logo-sm.svg create mode 100644 assets/images/data-logo.svg create mode 100644 assets/images/dropdown-arrow.svg create mode 100644 assets/images/incubator-logo-sm.svg create mode 100644 assets/images/incubator-logo.svg create mode 100644 assets/images/lab-logo-sm.svg create mode 100644 assets/images/lab-logo.svg create mode 100644 assets/images/library-logo-sm.svg create mode 100644 assets/images/library-logo.svg create mode 100644 assets/images/minus.svg create mode 100644 assets/images/plus.svg create mode 100644 assets/images/software-logo-sm.svg create mode 100644 assets/images/software-logo.svg create mode 100644 assets/scripts.js create mode 100644 assets/styles.css create mode 100644 assets/styles.css.map create mode 100644 assets/themetoggle.js create mode 100644 bootstrap-toc.css create mode 100644 bootstrap-toc.js create mode 100644 config.yaml create mode 100644 data/dataset.urls create mode 100644 docsearch.css create mode 100644 docsearch.js create mode 100644 favicon-16x16.png create mode 100644 favicon-32x32.png create mode 100644 favicons/cp/apple-touch-icon-114x114.png create mode 100644 favicons/cp/apple-touch-icon-120x120.png create mode 100644 favicons/cp/apple-touch-icon-144x144.png create mode 100644 favicons/cp/apple-touch-icon-152x152.png create mode 100644 favicons/cp/apple-touch-icon-57x57.png create mode 100644 favicons/cp/apple-touch-icon-60x60.png create mode 100644 favicons/cp/apple-touch-icon-72x72.png create mode 100644 favicons/cp/apple-touch-icon-76x76.png create mode 100644 favicons/cp/favicon-128.png create mode 100644 favicons/cp/favicon-16x16.png create mode 100644 favicons/cp/favicon-196x196.png create mode 100644 favicons/cp/favicon-32x32.png create mode 100644 favicons/cp/favicon-96x96.png create mode 100644 favicons/cp/favicon.ico create mode 100644 favicons/cp/mstile-144x144.png create mode 100644 favicons/cp/mstile-150x150.png create mode 100644 favicons/cp/mstile-310x150.png create mode 100644 favicons/cp/mstile-310x310.png create mode 100644 favicons/cp/mstile-70x70.png create mode 100644 favicons/dc/apple-touch-icon-114x114.png create mode 100644 favicons/dc/apple-touch-icon-120x120.png create mode 100644 favicons/dc/apple-touch-icon-144x144.png create mode 100644 favicons/dc/apple-touch-icon-152x152.png create mode 100644 favicons/dc/apple-touch-icon-57x57.png create mode 100644 favicons/dc/apple-touch-icon-60x60.png create mode 100644 favicons/dc/apple-touch-icon-72x72.png create mode 100644 favicons/dc/apple-touch-icon-76x76.png create mode 100644 favicons/dc/favicon-128.png create mode 100644 favicons/dc/favicon-16x16.png create mode 100644 favicons/dc/favicon-196x196.png create mode 100644 favicons/dc/favicon-32x32.png create mode 100644 favicons/dc/favicon-96x96.png create mode 100644 favicons/dc/favicon.ico create mode 100644 favicons/dc/mstile-144x144.png create mode 100644 favicons/dc/mstile-150x150.png create mode 100644 favicons/dc/mstile-310x150.png create mode 100644 favicons/dc/mstile-310x310.png create mode 100644 favicons/dc/mstile-70x70.png create mode 100644 favicons/lc/apple-touch-icon-114x114.png create mode 100644 favicons/lc/apple-touch-icon-120x120.png create mode 100644 favicons/lc/apple-touch-icon-144x144.png create mode 100644 favicons/lc/apple-touch-icon-152x152.png create mode 100644 favicons/lc/apple-touch-icon-57x57.png create mode 100644 favicons/lc/apple-touch-icon-60x60.png create mode 100644 favicons/lc/apple-touch-icon-72x72.png create mode 100644 favicons/lc/apple-touch-icon-76x76.png create mode 100644 favicons/lc/favicon-128.png create mode 100644 favicons/lc/favicon-16x16.png create mode 100644 favicons/lc/favicon-196x196.png create mode 100644 favicons/lc/favicon-32x32.png create mode 100644 favicons/lc/favicon-96x96.png create mode 100644 favicons/lc/favicon.ico create mode 100644 favicons/lc/mstile-144x144.png create mode 100644 favicons/lc/mstile-150x150.png create mode 100644 favicons/lc/mstile-310x150.png create mode 100644 favicons/lc/mstile-310x310.png create mode 100644 favicons/lc/mstile-70x70.png create mode 100644 favicons/swc/apple-touch-icon-114x114.png create mode 100644 favicons/swc/apple-touch-icon-120x120.png create mode 100644 favicons/swc/apple-touch-icon-144x144.png create mode 100644 favicons/swc/apple-touch-icon-152x152.png create mode 100644 favicons/swc/apple-touch-icon-57x57.png create mode 100644 favicons/swc/apple-touch-icon-60x60.png create mode 100644 favicons/swc/apple-touch-icon-72x72.png create mode 100644 favicons/swc/apple-touch-icon-76x76.png create mode 100644 favicons/swc/favicon-128.png create mode 100644 favicons/swc/favicon-16x16.png create mode 100644 favicons/swc/favicon-196x196.png create mode 100644 favicons/swc/favicon-32x32.png create mode 100644 favicons/swc/favicon-96x96.png create mode 100644 favicons/swc/favicon.ico create mode 100644 favicons/swc/mstile-144x144.png create mode 100644 favicons/swc/mstile-150x150.png create mode 100644 favicons/swc/mstile-310x150.png create mode 100644 favicons/swc/mstile-310x310.png create mode 100644 favicons/swc/mstile-70x70.png create mode 100644 fig/MultipleModels__thetaoga_prep_timeseries_diag_timeseries_temperature_1900_2000_timeseries_.png create mode 100644 fig/data_flow.png create mode 100644 fig/data_flow.pptx create mode 100644 fig/diag_CMIP5_HadGEM2-ES_Omon_historical_r1i1p1_thetaoga_prep_timeseries_diag_timeseries_temperature_1900_2000_timeseries_0.png create mode 100644 fig/esmvaltool_architecture.png create mode 100644 fig/warming_stripes.png create mode 100644 files/esgf-pyclient.yml create mode 100644 files/recipe_check_fluxcom.yml create mode 100644 files/recipe_warming_stripes.yml create mode 100644 files/recipe_warming_stripes_additional_datasets.yml create mode 100644 files/recipe_warming_stripes_local.yml create mode 100644 files/recipe_warming_stripes_multiple_ensemble_members.yml create mode 100644 files/recipe_warming_stripes_multiple_locations.yml create mode 100644 files/recipe_warming_stripes_periods.yml create mode 100644 files/warming_stripes.py create mode 100644 glossary.html create mode 100644 images.html create mode 100644 index.html create mode 100644 instructor-notes.html create mode 100644 instructor/00-introduction.html create mode 100644 instructor/01-quickstart.html create mode 100644 instructor/02-installation.html create mode 100644 instructor/03-configuration.html create mode 100644 instructor/04-recipe.html create mode 100644 instructor/05-conclusions.html create mode 100644 instructor/06-preprocessor.html create mode 100644 instructor/07-development-setup.html create mode 100644 instructor/08-diagnostics.html create mode 100644 instructor/09-cmorization.html create mode 100644 instructor/10-debugging.html create mode 100644 instructor/404.html create mode 100644 instructor/CODE_OF_CONDUCT.html create mode 100644 instructor/LICENSE.html create mode 100644 instructor/aio.html create mode 100644 instructor/glossary.html create mode 100644 instructor/images.html create mode 100644 instructor/index.html create mode 100644 instructor/instructor-notes.html create mode 100644 instructor/key-points.html create mode 100644 instructor/profiles.html create mode 100644 instructor/reference.html create mode 100644 key-points.html create mode 100644 link.svg create mode 100644 md5sum.txt create mode 100644 mstile-150x150.png create mode 100644 pkgdown.css create mode 100644 pkgdown.js create mode 100644 pkgdown.yml create mode 100644 profiles.html create mode 100644 reference.html create mode 100644 safari-pinned-tab.svg create mode 100644 site.webmanifest create mode 100644 sitemap.xml diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/00-introduction.html b/00-introduction.html new file mode 100644 index 00000000..3f16a761 --- /dev/null +++ b/00-introduction.html @@ -0,0 +1,674 @@ + +ESMValTool Tutorial: Introduction +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Introduction

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • What is ESMValTool?
  • +
  • Who are the people behind ESMValTool?
  • +
+
+
+
+
+
+

Objectives

+
  • Familiarize with ESMValTool
  • +
  • Synchronize expectations
  • +
+
+
+
+
+

What is ESMValTool?

+

This tutorial is a first introduction to ESMValTool. Before diving +into the technical steps, let’s talk about what ESMValTool is all +about.

+
+
+ +
+
+

What is ESMValTool?

+
+

What do you already know about or expect from ESMValTool?

+
+
+
+
+
+ +
+
+

EMSValTool is many things, but in this tutorial we will focus on the +following traits:

+

A tool to analyse climate data

+

A collection of diagnostics for reproducible climate +science

+

A community effort

+
+
+
+
+

A tool to analyse climate data

+

ESMValTool takes care of finding, opening, checking, fixing, +concatenating, and preprocessing CMIP data and several other supported +datasets.

+

The central component of ESMValTool that we will see in this tutorial +is the recipe. Any ESMValTool recipe is basically a set +of instructions to reproduce a certain result. The basic structure of a +recipe is as follows:

+
  • +Documentation with relevant (citation) +information
  • +
  • +Datasets that should be analysed
  • +
  • +Preprocessor steps that must be applied
  • +
  • +Diagnostic scripts performing more specific +evaluation steps
  • +

An example recipe could look like this:

+
+

YAML +

+
documentation:
+  title: This is an example recipe.
+  description: Example recipe
+  authors:
+    - lastname_firstname
+
+datasets:
+  - {dataset: HadGEM2-ES, project: CMIP5, exp: historical, mip: Amon, 
+     ensemble: r1i1p1, start_year: 1960, end_year: 2005}
+
+preprocessors:
+  global_mean:
+    area_statistics:
+      operator: mean
+
+diagnostics:
+  hockeystick_plot:
+    description: plot of global mean temperature change
+    variables:
+      temperature:
+        short_name: tas
+        preprocessor: global_mean
+    scripts: hockeystick.py
+
+
+
+ +
+
+

Understanding the different section of the recipe

+
+

Try to figure out the meaning of the different dataset keys. Hint: +they can be found in the documentation of ESMValTool.

+
+
+
+
+
+ +
+
+

The keys are explained in the ESMValTool documentation, in the +Recipe section, under [datasets](https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/ +overview.html#recipe-section-datasets)

+
+
+
+
+

A collection of diagnostics for reproducible climate science

+

More than a tool, ESMValTool is a collection of publicly available +recipes and diagnostic scripts. This makes it possible to easily +reproduce important results.

+
+
+ +
+
+

Explore the available recipes

+
+

Go to the ESMValTool +Documentation webpage and explore the Available recipes +section. Which recipe(s) would you like to try?

+
+
+
+

A community effort

+

ESMValTool is built and maintained by an active community of +scientists and software engineers. It is an open source project to which +anyone can contribute. Many of the interactions take place on GitHub. +Here, we briefly introduce you to some of the most important pages.

+
+
+ +
+
+

Meet the ESMValGroup

+
+

Go to github.com/ESMValGroup. This +is the GitHub page of our ‘organization’. Have a look around. How many +collaborators are there? Do you know any of them?

+

Near the top of the page there are 2 pinned repositories: ESMValTool +and ESMValCore. Visit each of the repositories. How many people have +contributed to each of them? Can you also find out how many people have +contributed to this tutorial?

+
+
+
+
+
+ +
+
+

Issues and pull requests

+
+

Go back to the repository pages of ESMValTool or ESMValCore. There +are tabs for ‘issues’ and ‘pull requests’. You can use the labels to +navigate them a bit more. How many open issues are about enhancements of +ESMValTool? And how many bugs have been fixed in ESMValCore? There is +also an ‘insights’ tab, where you can see a summary of recent activity. +How many issues have been opened and closed in the past month?

+
+
+
+

Conclusion

+

This concludes the introduction of the tutorial. You now have a basic +knowledge of ESMValTool and its community. The following episodes will +walk you through the installation, configuration and running your first +recipes.

+
+
+ +
+
+

Key Points

+
+
  • ESMValTool provides a reliable interface to analyse and evaluate +climate data
  • +
  • A large collection of recipes and diagnostic scripts is already +available
  • +
  • ESMValTool is built and maintained by an active community of +scientists and developers
  • +
+
+
+
+
+ + +
+
+ + + diff --git a/01-quickstart.html b/01-quickstart.html new file mode 100644 index 00000000..1bc9a7e2 --- /dev/null +++ b/01-quickstart.html @@ -0,0 +1,616 @@ + +ESMValTool Tutorial: Quickstart guide +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Quickstart guide

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • What is the purpose of the quickstart guide?
  • +
  • How do I load and check the ESMValTool environment?
  • +
  • How do I configure ESMValTool?
  • +
  • How do I run a recipe?
  • +
+
+
+
+
+
+

Objectives

+
  • Understand the purpose of the quickstart guide
  • +
  • Load and check the ESMValTool environment
  • +
  • Configure ESMValTool
  • +
  • Run a recipe
  • +
+
+
+
+
+
+
+ +
+
+

What is the purpose of the quickstart guide?

+
+
  • The purpose of the quickstart guide is to enable a user of +ESMValTool to run ESMValTool as quickly as possible by making the bare +minimum number of changes.
  • +
+
+
+
+
+ +
+
+

How do I load and check the ESMValTool environment?

+
+
  • For this quickstart guide, an assumption is made that ESMValTool +has already been installed at the site where ESMValTool will be run. If +this is not the case, see the [Installation][lesson-installation] +episode in this tutorial.

  • +
  • Load the ESMValTool environment by following the instructions at +[ESMValTool: Pre-installed versions on HPC clusters / other +servers][activate-environment].

  • +
  • +

    Check the ESMValTool environment by accessing the help for +ESMValTool:

    +
    +

    BASH +

    +
    esmvaltool --help
    +
    +
  • +
+
+
+
+
+ +
+
+

How do I configure ESMValTool?

+
+
  • +

    Create the ESMValTool user configuration file (the file is +written by default to ~/.esmvaltool/config-user.yml):

    +
    +

    BASH +

    +
    esmvaltool config get_config_user
    +
    +
  • +
  • Edit the ESMValTool user configuration file using your favourite +text editor to uncomment the lines relating to the site where ESMValTool +will be run.

  • +
  • For more details about the ESMValTool user configuration file see +the [Configuration][lesson-configuration] episode in this +tutorial.

  • +
+
+
+
+
+ +
+
+

How do I run a recipe?

+
+
  • +

    Run the example Python recipe:

    +
    +

    BASH +

    +
    esmvaltool run examples/recipe_python.yml 
    +
    +
  • +
  • +

    Wait for the recipe to complete. If the recipe completes +successfully, the last line printed to screen at the end of the log will +look something like:

    +
    +

    BASH +

    +
    YYYY-MM-DD HH:mm:SS, NNN UTC [NNNNN] INFO    Run was successful
    +
    +
  • +
  • +

    View the output of the recipe by opening the HTML file produced +by ESMValTool (the location of this file is printed to screen near the +end of the log):

    +
    +

    BASH +

    +
    YYYY-MM-DD HH:mm:SS, NNN UTC [NNNNN] INFO    Wrote recipe output to:
    +file:///$HOME/esmvaltool_output/recipe_python_<date>_<time>/index.html
    +
    +
  • +
  • For more details about running recipes see the [Running your +first recipe][lesson-recipe] episode in this tutorial.

  • +
+
+
+
+
+ +
+
+

Key Points

+
+
  • The purpose of the quickstart guide is to enable a user of +ESMValTool to run ESMValTool as quickly as possible without having to go +through the whole tutorial
  • +
  • Use the module load command to load the ESMValTool +environment, see the \[Installation\]\[lesson-installation\] episode for more +details and use esmvaltool --help to check the ESMValTool +environment
  • +
  • Use esmvaltool config get_config_user to create the +ESMValTool user configuration file
  • +
  • Use esmvaltool run <recipe>.yml to run a +recipe
  • +
+
+
+ + + +
+
+ + +
+
+ + + diff --git a/02-installation.html b/02-installation.html new file mode 100644 index 00000000..a6a625cd --- /dev/null +++ b/02-installation.html @@ -0,0 +1,748 @@ + +ESMValTool Tutorial: Installation +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Installation

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • What are the prerequisites for installing ESMValTool?
  • +
  • How do I confirm that the installation was successful?
  • +
+
+
+
+
+
+

Objectives

+
  • Install ESMValTool
  • +
  • Demonstrate that the installation was successful
  • +
+
+
+
+
+

Overview

+

The instructions help with the installation of ESMValTool on +operating systems like Linux/MacOSX/Windows. We use the Mamba +package manager to install the ESMValTool. Other installation methods +are also available; they can be found in the documentation. +We will first install Mamba, and then ESMValTool. We end this chapter by +testing that the installation was successful.

+

Before we begin, here are all the possible ways in which you can use +ESMValTool depending on your level of expertise or involvement with +ESMValTool and associated software such as GitHub and Mamba.

+
  1. If you have access to a server where ESMValTool is already installed +as a module, for e.g., the [CEDA JASMIN](https://help.jasmin.ac.uk/article +/4955-community-software-esmvaltool) server, you can simply load the +module with the following command:
  2. +
+

BASH +

+
module load esmvaltool
+
+

After loading esmvaltool, we can start using ESMValTool +right away. Please see the next +lesson. 2. If you would like to install ESMValTool as a mamba +package, then this lesson will tell you how! 3. If you would like to +start experimenting with existing diagnostics or contributing to +ESMvalTool, please see the instructions for source installation in the +lesson Development and +contribution and in the documentation.

+
+
+ +
+
+

Install ESMValTool on Windows

+
+

ESMValTool does not directly support Windows, but successful usage +has been reported through the Windows Subsystem +for Linux(WSL), available in Windows 10. To install the WSL please +follow the instructions on the +Windows Documentation page. After installing the WSL, installation +can be done using the same instructions for Linux/MacOSX.

+
+
+
+

Install ESMValTool on Linux/MacOSX

+
+

Install Mamba

+

ESMValTool is distributed using Mamba. To +install mamba on Linux or MacOSX, follow the +instructions below:

+
  1. Please download the installation file for the latest Mamba +version here.

  2. +
  3. Next, run the installer from the place where you downloaded +it:

  4. +

On Linux:

+
+

BASH +

+
bash Mambaforge-Linux-x86_64.sh
+
+

On MacOSX:

+
+

BASH +

+
bash Mambaforge-MacOSX-x86_64.sh
+
+
  1. Follow the instructions in the installer. The defaults should +normally suffice.

  2. +
  3. You will need to restart your terminal for the changes to have +effect.

  4. +
  5. We recommend updating mamba before the esmvaltool installation. +To do so, run:

  6. +
+

BASH +

+
mamba update --name base mamba
+
+
  1. Verify you have a working mamba installation by:
  2. +
+

BASH +

+
which mamba
+
+

This should show the path to your mamba executable, +e.g. ~/mambaforge/bin/mamba.

+

For more information about installing mamba, see [the mamba +installation documentation](https://docs.esmvaltool.org/en +/latest/quickstart/installation.html#mamba-installation).

+
+
+

Install the ESMValTool package

+

The ESMValTool package contains diagnostics scripts in four +languages: R, Python, Julia and NCL. This introduces a lot of +dependencies, and therefore the installation can take quite long. It is, +however, possible to install ‘subpackages’ for each of the languages. +The following (sub)packages are available:

+
  • esmvaltool-python
  • +
  • esmvaltool-ncl
  • +
  • esmvaltool-r
  • +
  • +esmvaltool –> the complete package, i.e. the +combination of the above.
  • +

For the tutorial, we will install the complete package. Thus, to +install the ESMValTool package, run

+
+

BASH +

+
mamba create --name esmvaltool esmvaltool 
+
+

On MacOSX ESMValTool functionalities in Julia, NCL, and R are not +supported. To install a Mamba environment on MacOSX, please refer to +specific information.

+

This will create a new [Mamba environment](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks +/manage-environments.html) called esmvaltool, with the +ESMValTool package and all of its dependencies installed in it.

+
+
+ +
+
+

Common issues

+
+

You find a list of common installation problems and their solutions +in the [documentation](https://docs.esmvaltool.org/en/latest/quickstart/installation +.html#common-installation-problems-and-their-solutions).

+
+
+
+
+
+

Install Julia

+

Some ESMValTool diagnostics are written in the Julia programming +language. If you want a full installation of ESMValTool including Julia +diagnostics, you need to make sure Julia is installed before installing +ESMValTool.

+

In this tutorial, we will not use Julia, but for reference, we have +listed the steps to install Julia below. Complete instructions for +installing Julia can be found on the Julia +installation page.

+
+
+ +
+
+

First, open a bash terminal and activate the newly created +esmvaltool environment.

+
+

BASH +

+
conda activate esmvaltool
+
+

Next, to install Julia via mamba, you can use the +following command:

+
+

BASH +

+
mamba install julia
+
+

To check that the Julia executable can be found, run

+
+

BASH +

+
which julia
+
+

to display the path to the Julia executable, it should be

+
+

OUTPUT +

+
~/mambaforge/envs/esmvaltool/bin/julia
+
+

To test that Julia is installed correctly, run

+
+

BASH +

+
julia
+
+

to start the interactive Julia interpreter. Press Ctrl+D +to exit.

+
+
+
+
+
+
+

Test that the installation was successful

+

To test that the installation was successful, run

+
+

BASH +

+
conda activate esmvaltool
+
+

to activate the conda environment called esmvaltool. In +the shell prompt the active conda environment should have been changed +from (base) to (esmvaltool).

+

Next, run

+
+

BASH +

+
esmvaltool --help
+
+

to display the command line help.

+
+
+ +
+
+

Version of ESMValTool

+
+

Can you figure out which version of ESMValTool has been +installed?

+
+
+
+
+
+ +
+
+

The esmvaltool --help command lists version +as a command to get the version

+

When you run

+
+

BASH +

+
esmvaltool version
+
+

The version of ESMValTool installed should be displayed on the screen +as:

+
+

OUTPUT +

+
ESMValCore: 2.11.0
+ESMValTool: 2.11.0
+
+

Note that on HPC servers such as JASMIN, sometimes a more recent +development version may be displayed for ESMValTool, for e.g. +ESMValTool: 2.11.0.dev71+g2c60b4d97

+
+
+
+
+
+
+ +
+
+

Key Points

+
+
  • All the required packages can be installed using mamba.
  • +
  • You can find more information about installation in the documentation.
  • +
+
+
+
+
+
+ + +
+
+ + + diff --git a/03-configuration.html b/03-configuration.html new file mode 100644 index 00000000..1a51f093 --- /dev/null +++ b/03-configuration.html @@ -0,0 +1,934 @@ + +ESMValTool Tutorial: Configuration +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Configuration

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • What is the user configuration file and how should I use it?
  • +
+
+
+
+
+
+

Objectives

+
  • Understand the contents of the user-config.yml file
  • +
  • Prepare a personalized user-config.yml file
  • +
  • Configure ESMValTool to use some settings
  • +
+
+
+
+
+

The configuration file

+

For the purposes of this tutorial, we will create a directory in our +home directory called esmvaltool_tutorial and use that as +our working directory. The following steps should do that:

+
+

BASH +

+
 mkdir esmvaltool_tutorial
+ cd esmvaltool_tutorial
+
+

The config-user.yml configuration file contains all the +global level information needed by ESMValTool to run. This is a YAML file.

+

You can get the default configuration file by running:

+
+

BASH +

+
  esmvaltool config get_config_user --path=<target_dir>
+
+

The default configuration file will be downloaded to the directory +specified with the --path variable. For instance, you can +provide the path to your working directory as the +target_dir. If this option is not used, the file will be +saved to the default location: +~/.esmvaltool/config-user.yml, where ~ is the +path to your home directory. Note that files and directories starting +with a period are “hidden”, to see the .esmvaltool +directory in the terminal use ls -la ~. Note that if a +configuration file by that name already exists in the default location, +the get_config_user command will not update the file as +ESMValTool will not overwrite the file. You will have to move the file +first if you want an updated copy of the user configuration file.

+

We run a text editor called nano to have a look inside +the configuration file and then modify it if needed:

+
+

BASH +

+
  nano ~/.esmvaltool/config-user.yml
+
+

Any other editor can be used, e.g.vim.

+

This file contains the information for:

+
  • Output settings
  • +
  • Destination directory
  • +
  • Auxiliary data directory
  • +
  • Number of tasks that can be run in parallel
  • +
  • Rootpath to input data
  • +
  • Directory structure for the data from different projects
  • +
+
+ +
+
+

Text editor side note

+
+

No matter what editor you use, you will need to know where it +searches for and saves files. If you start it from the shell, it will +(probably) use your current working directory as its default location. +We use nano in examples here because it is one of the least +complex text editors. Press ctrl + O to save the +file, and then ctrl + X to exit +nano.

+
+
+
+

Output settings

+

The configuration file starts with output settings that inform +ESMValTool about your preference for output. You can turn on or off the +setting by true or false values. Most of these +settings are fairly self-explanatory.

+
+
+ +
+
+

Saving preprocessed data

+
+

Later in this tutorial, we will want to look at the contents of the +preproc folder. This folder contains preprocessed data and +is removed by default when ESMValTool is run. In the configuration file, +which settings can be modified to prevent this from happening?

+
+
+
+
+
+ +
+
+

If the option remove_preproc_dir is set to +false, then the preproc/ directory contains +all the pre-processed data and the metadata interface files. If the +option save_intermediary_cubes is set to true +then data will also be saved after each preprocessor step in the folder +preproc. Note that saving all intermediate results to file +will result in a considerable slowdown, and can quickly fill your +disk.

+
+
+
+
+

Destination directory

+

The destination directory is the rootpath where ESMValTool will store +its output folders containing e.g. figures, data, logs, etc. With every +run, ESMValTool automatically generates a new output folder determined +by recipe name, and date and time using the format: YYYYMMDD_HHMMSS.

+
+
+ +
+
+

Set the destination directory

+
+

Let’s name our destination directory esmvaltool_output +in the working directory. ESMValTool should write the output to this +path, so make sure you have the disk space to write output to this +directory. How do we set this in the config-user.yml?

+
+
+
+
+
+ +
+
+

We use output_dir entry in the +config-user.yml file as:

+
+

YAML +

+
output_dir: ./esmvaltool_output
+
+

If the esmvaltool_output does not exist, ESMValTool will +generate it for you.

+
+
+
+
+

Rootpath to input data

+

ESMValTool uses several categories (in ESMValTool, this is referred +to as projects) for input data based on their source. The current +categories in the configuration file are mentioned below. For example, +CMIP is used for a dataset from the Climate Model Intercomparison +Project whereas OBS may be used for an observational dataset. More +information about the projects used in ESMValTool is available in the +[documentation](https://docs.esmvaltool.org/projects/esmvalcore/en/latest/ +quickstart/find_data.html). When using ESMValTool on your own machine, +you can create a directory to download climate model data or observation +data sets and let the tool use data from there. It is also possible to +ask ESMValTool to download climate model data as needed. This can be +done by specifying a download directory and by setting the option to +download data as shown below.

+
+

YAML +

+
# Directory for storing downloaded climate data
+download_dir: ~/climate_data
+search_esgf: always
+
+

If you are working offline or do not want to download the data then +set the option above to never. If you want to download data +only when the necessary files are missing at the usual location, you can +set the option to when_missing.

+

The rootpath specifies the directories where ESMValTool +will look for input data. For each category, you can define either one +path or several paths as a list. For example:

+
+

YAML +

+
rootpath:
+  CMIP5: [~/cmip5_inputpath1, ~/cmip5_inputpath2]
+  OBS: ~/obs_inputpath
+  RAWOBS: ~/rawobs_inputpath
+  default: ~/climate_data
+
+

These are typically available in the default configuration file you +downloaded, so simply removing the machine specific lines should be +sufficient to access input data.

+
+
+ +
+
+

Set the correct rootpath

+
+

In this tutorial, we will work with data from CMIP5 and CMIP6. How can we +modify the rootpath to make sure the data path is set +correctly for both CMIP5 and CMIP6? Note: to get the data, check the +instructions in Setup.

+
+
+
+
+
+ +
+
+
  • Are you working on your own local machine? You need to add the root +path of the folder where the data is available to the +config-user.yml file as:
  • +
+

YAML +

+
  rootpath:
+  ...
+    CMIP5: ~/esmvaltool_tutorial/data
+    CMIP6: ~/esmvaltool_tutorial/data
+
+
  • Are you working on your local machine and have downloaded data using +ESMValTool? You need to add the root path of the folder where the data +has been downloaded to as specified in the +download_dir.
  • +
+

YAML +

+
rootpath:
+...
+  CMIP5: ~/climate_data
+  CMIP6: ~/climate_data
+
+
  • Are you working on a computer cluster like Jasmin or DKRZ? +Site-specific path to the data for JASMIN/DKRZ/ETH/IPSL are already +listed at the end of the config-user.yml file. You need to +uncomment the related lines. For example, on JASMIN:
  • +
+

YAML +

+
auxiliary_data_dir: /gws/nopw/j04/esmeval/aux_data/AUX
+rootpath: 
+ CMIP6: /badc/cmip6/data/CMIP6
+ CMIP5: /badc/cmip5/data/cmip5/output1
+ OBS: /gws/nopw/j04/esmeval/obsdata-v2
+ OBS6: /gws/nopw/j04/esmeval/obsdata-v2
+ obs4MIPs: /gws/nopw/j04/esmeval/obsdata-v2
+ ana4mips: /gws/nopw/j04/esmeval/obsdata-v2
+ default: /gws/nopw/j04/esmeval/obsdata-v2
+
+
+
+
+
+

Directory structure for the data from different projects

+

Input data can be from various models, observations and reanalysis +data that adhere to the CF/CMOR +standard. The drs setting describes the file +structure.

+

The drs setting describes the file structure for several +projects (e.g. CMIP6, CMIP5, obs4mips, OBS6, OBS) on several key +machines (e.g. BADC, CP4CDS, DKRZ, ETHZ, SMHI, BSC). For more +information about drs, you can visit the ESMValTool +documentation on [Data Reference Syntax (DRS)](https://docs.esmvaltool.org/projects/esmvalcore/ +en/latest/quickstart/find_data.html#cmor-drs).

+
+
+ +
+
+

Set the correct drs

+
+

In this lesson, we will work with data from CMIP5 and CMIP6. How can we +set the correct drs?

+
+
+
+
+
+ +
+
+
  • Are you working on your own local machine? You need to set the +drs of the data in the config-user.yml file +as:
  • +
+

YAML +

+
  drs:
+    CMIP5: default
+    CMIP6: default
+
+
  • Are you asking ESMValTool to download the data for use with your +diagnostics? You need to set the drs of the data in the +config-user.yml file as:
  • +
+

YAML +

+
   drs:
+     CMIP5: ESGF
+     CMIP6: ESGF
+     CORDEX: ESGF
+     obs4MIPs: ESGF
+
+
  • Are you working on a computer cluster like Jasmin or DKRZ? +Site-specific drs of the data are already listed at the end +of the config-user.yml file. You need to uncomment the +related lines. For example, on Jasmin:
  • +
+

YAML +

+
  # Site-specific entries: Jasmin
+  # Uncomment the lines below to locate data on JASMIN
+  drs:
+    CMIP6: BADC
+    CMIP5: BADC
+    OBS: default
+    OBS6: default
+    obs4mips: default
+    ana4mips: default
+
+
+
+
+
+
+
+ +
+
+

Explain the default drs (if working on local machine)

+
+
  1. In the previous exercise, we set the drs of CMIP5 data +to default. Can you explain why?
  2. +
  3. Have a look at the directory structure of the OBS data. +There is a folder called Tier1. What does it mean?
  4. +
+
+
+
+
+ +
+
+
  1. drs: default is one way to retrieve data from a ROOT +directory that has no DRS-like structure. default indicates +that all the files are in a folder without any structure.

  2. +
  3. Observational data are organized in Tiers depending on their +level of public availability. Therefore the default directory must be +structured accordingly with sub-directories TierX +e.g. Tier1, Tier2 or Tier3, even when drs: default. More +details can be found in the [documentation](https://docs.esmvaltool.org/projects/esmvalcore/en/latest/ +quickstart/find_data.html#observational-data).

  4. +
+
+
+
+

Other settings

+
+
+ +
+
+

Auxiliary data directory

+
+

The auxiliary_data_dir setting is the path where any +required additional auxiliary data files are stored. This location +allows us to tell the diagnostic script where to find the files if they +can not be downloaded at runtime. This option should not be used for +model or observational datasets, but for data files (e.g. shape files) +used in plotting such as coastline descriptions and if you want to feed +some additional data (e.g. shape files) to your recipe.

+
+

YAML +

+
auxiliary_data_dir: ~/auxiliary_data
+
+

See more information in ESMValTool [document](https://docs.esmvaltool.org/projects/ESMValCore/en/latest/quickstart +/configure.html?highlight=auxiliary_data#user-configuration-file).

+
+
+
+
+
+ +
+
+

Number of parallel tasks

+
+

This option enables you to perform parallel processing. You can +choose the number of tasks in parallel as 1/2/3/4/… or you can set it to +null. That tells ESMValTool to use the maximum number of +available CPUs. For the purpose of the tutorial, please set ESMValTool +use only 1 cpu:

+
+

YAML +

+
max_parallel_tasks: 1
+
+

In general, if you run out of memory, try setting +max_parallel_tasks to 1. Then, check the amount of memory +you need for that by inspecting the file +run/resource_usage.txt in the output directory. Using the +number there you can increase the number of parallel tasks again to a +reasonable number for the amount of memory available in your system.

+
+
+
+
+
+ +
+
+

Make your own configuration file

+
+

It is possible to have several configuration files with different +purposes, for example: config-user_formalised_runs.yml, +config-user_debugging.yml. In this case, you have to pass the path of +your own configuration file as a command-line option when running the +ESMValTool. We will learn how to do this in the next lesson.

+
+
+
+
+
+ +
+
+

Key Points

+
+
  • The config-user.yml tells ESMValTool where to find +input data.
  • +
  • +output_dir defines the destination directory.
  • +
  • +rootpath defines the root path of the data.
  • +
  • +drs defines the directory structure of the data.
  • +
+
+
+
+
+ + +
+
+ + + diff --git a/04-recipe.html b/04-recipe.html new file mode 100644 index 00000000..4fe5480d --- /dev/null +++ b/04-recipe.html @@ -0,0 +1,1068 @@ + +ESMValTool Tutorial: Running your first recipe +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Running your first recipe

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • How to run a recipe?
  • +
  • What happens when I run a recipe?
  • +
+
+
+
+
+
+

Objectives

+
  • Run an existing ESMValTool recipe
  • +
  • Examine the log information
  • +
  • Navigate the output created by ESMValTool
  • +
  • Make small adjustments to an existing recipe
  • +
+
+
+
+
+

This episode describes how ESMValTool recipes work, how to run a +recipe and how to explore the recipe output. By the end of this episode, +you should be able to run your first recipe, look at the recipe output, +and make small modifications.

+

Running an existing recipe

+

The recipe format has briefly been introduced in the +[Introduction][lesson-introduction] episode. To see all the recipes that +are shipped with ESMValTool, type

+
+

BASH +

+
esmvaltool recipes list
+
+

We will start by running [examples/recipe_python.yml](https://docs.esmvaltool. +org/en/latest/recipes/recipe_examples.html)

+
esmvaltool run examples/recipe_python.yml
+

or if you have the user configuration file in your current directory +then

+
esmvaltool run --config_file ./config-user.yml examples/recipe_python.yml
+

If everything is okay, you should see that ESMValTool is printing a +lot of output to the command line. The final message should be “Run was +successful”. The exact output varies depending on your machine, but it +should look something like the example log output on terminal below.

+

{% include example_output.txt %}

+
+
+ +
+
+

Pro tip: ESMValTool search paths

+
+

You might wonder how ESMValTool was able find the recipe file, even +though it’s not in your working directory. All the recipe paths printed +from esmvaltool recipes list are relative to ESMValTool’s +installation location. This is where ESMValTool will look if it cannot +find the file by following the path from your working directory.

+
+
+
+

Investigating the log messages

+

Let’s dissect what’s happening here.

+
+
+ +
+
+

Output files and directories

+
+

After the banner and general information, the output starts with some +important locations.

+
  1. Did ESMValTool use the right config file?
  2. +
  3. What is the path to the example recipe?
  4. +
  5. What is the main output folder generated by ESMValTool?
  6. +
  7. Can you guess what the different output directories are for?
  8. +
  9. ESMValTool creates two log files. What is the difference?
  10. +
+
+
+
+
+ +
+
+
  1. The config file should be the one we edited in the previous episode, +something like +/home/<username>/.esmvaltool/config-user.yml or +~/esmvaltool_tutorial/config-user.yml.
  2. +
  3. ESMValTool found the recipe in its installation directory, something +like +/home/users/username/mambaforge/envs/esmvaltool/bin/esmvaltool/recipes/examples/ +or if you are using a pre-installed module on a server, something like +/apps/jasmin/community/esmvaltool/ESMValTool_<version> /esmvaltool/recipes/examples/recipe_python.yml, +where <version> is the latest release.
  4. +
  5. ESMValTool creates a time-stamped output directory for every run. In +this case, it should be something like +recipe_python_YYYYMMDD_HHMMSS. This folder is made inside +the output directory specified in the previous episode: +~/esmvaltool_tutorial/esmvaltool_output.
  6. +
  7. There should be four output folders:
  8. +
  • +plots/: this is where output figures are stored.
  • +
  • +preproc/: this is where pre-processed data are +stored.
  • +
  • +run/: this is where esmvaltool stores general +information about the run, such as log messages and a copy of the recipe +file.
  • +
  • +work/: this is where output files (not figures) are +stored.
  • +
  1. The log files are:
  2. +
  • +main_log.txt is a copy of the command-line output
  • +
  • +main_log_debug.txt contains more detailed information +that may be useful for debugging.
  • +
+
+
+
+
+
+ +
+
+

Debugging: No ‘preproc’ directory?

+
+

If you’re missing the preproc directory, then your +config-user.yml file has the value +remove_preproc_dir set to true (this is used +to save disk space). Please set this value to false and run +the recipe again.

+
+
+
+

After the output locations, there are two main sections that can be +distinguished in the log messages:

+
  • Creating tasks
  • +
  • Executing tasks
  • +
+
+ +
+
+

Analyse the tasks

+
+

List all the tasks that ESMValTool is executing for this recipe. Can +you guess what this recipe does?

+
+
+
+
+
+ +
+
+

Just after all the ‘creating tasks’ and before ‘executing tasks’, we +find the following line in the output:

+
[134535] INFO    These tasks will be executed: map/tas, timeseries/tas_global,
+timeseries/script1, map/script1, timeseries/tas_amsterdam
+

So there are three tasks related to timeseries: global temperature, +Amsterdam temperature, and a script (tas: near-surface air +temperature). And then there are two tasks related to a map: something +with temperature, and again a script.

+
+
+
+
+

Examining the recipe file

+

To get more insight into what is happening, we will have a look at +the recipe file itself. Use the following command to copy the recipe to +your working directory

+
+

BASH +

+
esmvaltool recipes get examples/recipe_python.yml
+
+

Now you should see the recipe file in your working directory (type +ls to verify). Use the nano editor to open +this file:

+
+

BASH +

+
nano recipe_python.yml
+
+

For reference, you can also view the recipe by unfolding the box +below.

+
+
+ +
+
+
+

YAML +

+
# ESMValTool
+# recipe_python.yml
+#
+# See https://docs.esmvaltool.org/en/latest/recipes/recipe_examples.html
+# for a description of this recipe.
+#
+# See https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/overview.html
+# for a description of the recipe format.
+---
+documentation:
+ description: |
+   Example recipe that plots a map and timeseries of temperature.
+
+ title: Recipe that runs an example diagnostic written in Python.
+
+ authors:
+   - andela_bouwe
+   - righi_mattia
+
+ maintainer:
+   - schlund_manuel
+
+ references:
+   - acknow_project
+
+ projects:
+   - esmval
+   - c3s-magic
+
+datasets:
+ - {dataset: BCC-ESM1, project: CMIP6, exp: historical, ensemble: r1i1p1f1, grid: gn}
+ - {dataset: bcc-csm1-1, project: CMIP5, exp: historical, ensemble: r1i1p1}
+
+preprocessors:
+ # See https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/preprocessor.html
+ # for a description of the preprocessor functions.
+
+ to_degrees_c:
+   convert_units:
+     units: degrees_C
+
+ annual_mean_amsterdam:
+   extract_location:
+     location: Amsterdam
+     scheme: linear
+   annual_statistics:
+     operator: mean
+   multi_model_statistics:
+     statistics:
+       - mean
+     span: overlap
+   convert_units:
+     units: degrees_C
+
+ annual_mean_global:
+   area_statistics:
+     operator: mean
+   annual_statistics:
+     operator: mean
+   convert_units:
+     units: degrees_C
+
+diagnostics:
+
+ map:
+   description: Global map of temperature in January 2000.
+   themes:
+     - phys
+   realms:
+     - atmos
+   variables:
+     tas:
+       mip: Amon
+       preprocessor: to_degrees_c
+       timerange: 2000/P1M
+       caption: |
+         Global map of {long_name} in January 2000 according to {dataset}.
+   scripts:
+     script1:
+       script: examples/diagnostic.py
+       quickplot:
+         plot_type: pcolormesh
+         cmap: Reds
+
+ timeseries:
+   description: Annual mean temperature in Amsterdam and global mean since 1850.
+   themes:
+     - phys
+   realms:
+     - atmos
+   variables:
+     tas_amsterdam:
+       short_name: tas
+       mip: Amon
+       preprocessor: annual_mean_amsterdam
+       timerange: 1850/2000
+       caption: Annual mean {long_name} in Amsterdam according to {dataset}.
+     tas_global:
+       short_name: tas
+       mip: Amon
+       preprocessor: annual_mean_global
+       timerange: 1850/2000
+       caption: Annual global mean {long_name} according to {dataset}.
+   scripts:
+     script1:
+       script: examples/diagnostic.py
+       quickplot:
+         plot_type: plot
+
+
+
+
+
+

Do you recognize the basic recipe structure that was introduced in +episode 1?

+
  • +Documentation with relevant (citation) +information
  • +
  • +Datasets that should be analysed
  • +
  • +Preprocessors groups of common preprocessing +steps
  • +
  • +Diagnostics scripts performing more specific +evaluation steps
  • +
+
+ +
+
+

Analyse the recipe

+
+

Try to answer the following questions:

+
  1. Who wrote this recipe?
  2. +
  3. Who should be approached if there is a problem with this +recipe?
  4. +
  5. How many datasets are analyzed?
  6. +
  7. What does the preprocessor called annual_mean_global +do?
  8. +
  9. Which script is applied for the diagnostic called +map?
  10. +
  11. Can you link specific lines in the recipe to the tasks that we saw +before?
  12. +
  13. How is the location of the city specified?
  14. +
  15. How is the temporal range of the data specified?
  16. +
+
+
+
+
+ +
+
+
  1. The example recipe is written by Bouwe Andela and Mattia +Righi.

  2. +
  3. Manuel Schlund is listed as the maintainer of this +recipe.

  4. +
  5. Two datasets are analysed:

  6. +
  • CMIP6 data from the model BCC-ESM1
  • +
  • CMIP5 data from the model bcc-csm1-1
  • +
  1. The preprocessor annual_mean_global computes an area +mean as well as annual means

  2. +
  3. The diagnostic called map executes a script referred +to as script1. This is a python script named +examples/diagnostic.py

  4. +
  5. There are two diagnostics: map and +timeseries. Under the diagnostic map we find +two tasks:

  6. +
  • a preprocessor task called tas, applying the +preprocessor called to_degrees_c to the variable +tas.
  • +
  • a diagnostic task called script1, applying the script +examples/diagnostic.py to the preprocessed data +(map/tas).
  • +

Under the diagnostic timeseries we find three tasks:

+
  • a preprocessor task called tas_amsterdam, applying the +preprocessor called annual_mean_amsterdam to the variable +tas.
  • +
  • a preprocessor task called tas_global, applying the +preprocessor called annual_mean_global to the variable +tas.
  • +
  • a diagnostic task called script1, applying the script +examples/diagnostic.py to the preprocessed data +(timeseries/tas_global and +timeseries/tas_amsterdam).
  • +
  1. The extract_location preprocessor is used to get +data for a specific location here. ESMValTool interpolates to the +location based on the chosen scheme. Can you tell the scheme used here? +For more ways to extract areas, see the [Area +operations][preproc-area-manipulation] page.

  2. +
  3. The timerange tag is used to extract data from a +specific time period here. The start time is 01/01/2000 and +the span of time to calculate means is 1 Month given by +P1M. For more options on how to specify time ranges, see +the [timerange documentation][timeranges].

  4. +
+
+
+
+
+
+ +
+
+

Pro tip: short names and variable groups

+
+

The preprocessor tasks in ESMValTool are called ‘variable groups’. +For the diagnostic timeseries, we have two variable groups: +tas_amsterdam and tas_global. Both of them +operate on the variable tas (as indicated by the +short_name), but they apply different preprocessors. For +the diagnostic map the variable group itself is named +tas, and you’ll notice that we do not explicitly provide +the short_name. This is a shorthand built into +ESMValTool.

+
+
+
+
+
+ +
+
+

Output files

+
+

Have another look at the output directory created by the ESMValTool +run.

+

Which files/folders are created by each task?

+
+
+
+
+
+ +
+
+
  • +map/tas: creates /preproc/map/tas, +which contains preprocessed data for each of the input datasets, a file +called metadata.yml describing the contents of these +datasets and provenance information in the form of .xml +files.
  • +
  • +timeseries/tas_global: creates +/preproc/timeseries/tas_global, which contains preprocessed +data for each of the input datasets, a metadata.yml file +and provenance information in the form of .xml files.
  • +
  • +timeseries/tas_amsterdam: creates +/preproc/timeseries/tas_amsterdam, which contains +preprocessed data for each of the input datasets, plus a combined +MultiModelMean, a metadata.yml file and +provenance files.
  • +
  • +map/script1: creates /run/map/script1 +with general information and a log of the diagnostic script run. It also +creates /plots/map/script1/ and +/work/map/script1, which contain output figures and output +datasets, respectively. For each output file, there is also +corresponding provenance information in the form of .xml, +.bibtex and .txt files.
  • +
  • +timeseries/script1: creates +/run/timeseries/script1 with general information and a log +of the diagnostic script run. It also creates +/plots/timeseries/script1 and +/work/timeseries/script1, which contain output figures and +output datasets, respectively. For each output file, there is also +corresponding provenance information in the form of .xml, +.bibtex and .txt files.
  • +
+
+
+
+
+
+ +
+
+

Pro tip: diagnostic logs

+
+

When you run ESMValTool, any log messages from the diagnostic script +are not printed on the terminal. But they are written to the +log.txt files in the folder +/run/<diag_name>/log.txt.

+

ESMValTool does print a command that can be used to re-run a +diagnostic script. When you use this the output will be printed +to the command line.

+
+
+
+

Modifying the example recipe

+

Let’s make a small modification to the example recipe. Notice that +now that you have copied and edited the recipe, you can use

+
esmvaltool run recipe_python.yml
+

to refer to your local file rather than the default version shipped +with ESMValTool.

+
+
+ +
+
+

Change your location

+
+

Modify and run the recipe to analyse the temperature for your own +location.

+
+
+
+
+
+ +
+
+

In principle, you only have to modify the location in the +preprocessor called annual_mean_amsterdam. However, it is +good practice to also replace all instances of amsterdam +with the correct name of your location. Otherwise the log messages and +output will be confusing. You are free to modify the names of +preprocessors or diagnostics.

+

In the diff file below you will see the changes we have +made to the file. The top 2 lines are the filenames and the lines like +@@ -39,9 +39,9 @@ represent the line numbers in the +original and modified file, respectively. For more info on this format, +see here.

+
+

DIFF +

+
--- recipe_python.yml	
++++ recipe_python_london.yml	
+@@ -39,9 +39,9 @@
+    convert_units:
+      units: degrees_C
+
+-  annual_mean_amsterdam:
++  annual_mean_london:
+    extract_location:
+-      location: Amsterdam
++      location: London
+      scheme: linear
+    annual_statistics:
+      operator: mean
+@@ -83,7 +83,7 @@
+          cmap: Reds
+
+  timeseries:
+-    description: Annual mean temperature in Amsterdam and global mean since 1850.
++    description: Annual mean temperature in London and global mean since 1850.
+    themes:
+      - phys
+    realms:
+@@ -92,9 +92,9 @@
+      tas_amsterdam:
+        short_name: tas
+        mip: Amon
+-        preprocessor: annual_mean_amsterdam
++        preprocessor: annual_mean_london
+        timerange: 1850/2000
+-        caption: Annual mean {long_name} in Amsterdam according to {dataset}.
++        caption: Annual mean {long_name} in London according to {dataset}.
+      tas_global:
+        short_name: tas
+        mip: Amon
+
+
+
+
+
+
+
+ +
+
+

Key Points

+
+
  • ESMValTool recipes work ‘out of the box’ (if input data is +available)
  • +
  • There are strong links between the recipe, log file, and output +folders
  • +
  • Recipes can easily be modified to re-use existing code for your own +use case
  • +
+
+
+
+
+ + +
+
+ + + diff --git a/05-conclusions.html b/05-conclusions.html new file mode 100644 index 00000000..4c157fc6 --- /dev/null +++ b/05-conclusions.html @@ -0,0 +1,595 @@ + +ESMValTool Tutorial: Conclusion of the basic tutorial +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Conclusion of the basic tutorial

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • What do I do now?
  • +
  • Where can I get help?
  • +
  • What if I find a bug?
  • +
  • Where can I find more information about ESMValtool?
  • +
  • How can I cite ESMValtool?
  • +
+
+
+
+
+
+

Objectives

+
  • Breathe - you’re finished now!
  • +
  • Congratulations & Thanks!
  • +
  • Find out about the mini-tutorials, and what to do next.
  • +
+
+
+
+
+

Congratulations!

+

Congratulations on completing the ESMValTool tutorial! You should be +now ready to go and start using ESMValTool independently.

+

The rest of this tutorial contains individual mini-tutorials to help +work through a specific issue (not developed yet).

+
+

What next?

+

From here, there are lots of ways that you can continue to use +ESMValTool.

+
+
+ +
+
+

Exercise: What do you want to do next?

+
+
  • Think about what you want to do with ESMValTool. +
    • Decide what datasets and variables you want to use.
    • +
    • Is any observational data available?
    • +
    • How will you preprocess the data?
    • +
    • What will your diagnostic script need to do?
    • +
    • What will your final figure show?
    • +
  • +
+
+
+
+
+

Where can I get more information on ESMValTool?

+ +
+
+

Where can I get more help?

+

There are lots of resources available to assist you in using +ESMValTool.

+

The ESMValTool Discussions +page is a good place to find information on general issues, or check +if your question has already been addressed. If you have a GitHub +account, you can also post your questions there.

+

If you encounter difficulties, a great starting point is to visit +issues page issue page +to check whether your issues have already been reported or not. If they +have been reported before, suggestions provided by developers can help +you to solve the issues you encountered. Note that you will need a +GitHub account for this.

+

Additionally, there is an ESMValTool email list. Please see information +on how to subscribe to the user mailing list.

+
+
+

What if I find a bug?

+

If you find a bug, please report it to the ESMValTool team. This will +help us fix issues, ensuring not only your uninterrupted workflow but +also contributing to the overall stability of ESMValTool for all +users.

+

To report a bug, please create a new issue using the issue +page.

+

In your bug report, please describe the problem as clearly and as +completely as possible. You may need to include a recipe or the output +log as well.

+
+
+

How do I cite the Tutorial?

+

Please use citation information available at https://doi.org/10.5281/zenodo.3974591.

+
+
+ +
+
+

Key Points

+
+
  • Individual mini-tutorials help work through a specific issue (not +developed yet).
  • +
  • We are constantly improving this tutorial.
  • +
+
+
+
+
+
+ + +
+
+ + + diff --git a/06-preprocessor.html b/06-preprocessor.html new file mode 100644 index 00000000..9740b645 --- /dev/null +++ b/06-preprocessor.html @@ -0,0 +1,1234 @@ + +ESMValTool Tutorial: Writing your own recipe +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Writing your own recipe

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • How do I create a new recipe?
  • +
  • Can I use different preprocessors for different variables?
  • +
  • Can I use different datasets for different variables?
  • +
  • How can I combine different preprocessor functions?
  • +
  • Can I run the same recipe for multiple ensemble members?
  • +
+
+
+
+
+
+

Objectives

+
  • Create a recipe with multiple preprocessors
  • +
  • Use different preprocessors for different variables
  • +
  • Run a recipe with variables from different datasets
  • +
+
+
+
+
+

Introduction

+

One of the key strengths of ESMValTool is in making complex analyses +reusable and reproducible. But that doesn’t mean everything in +ESMValTool needs to be complex. Sometimes, the biggest challenge is in +keeping things simple. You probably know the ‘warming stripes’ +visualization by Professor Ed Hawkins. On the site https://showyourstripes.info{:target=“_blank”} you can +find the same visualization for many regions in the world.

+

Warming stripesShared by Ed Hawkins under a Creative Commons 4.0 Attribution +International licence. Source: https://showyourstripes.info

+

In this episode, we will reproduce and extend this functionality with +ESMValTool. We have prepared a small Python script that takes a NetCDF +file with timeseries data, and visualizes it in the form of our desired +warming stripes figure.

+

The diagnostic script that we will use is called +warming_stripes.py and can be downloaded here.

+

Download the file and store it in your working directory. If you +want, you may also have a look at the contents, but it is not necessary +to do so for this lesson.

+

We will write an ESMValTool recipe that takes some data, performs the +necessary preprocessing, and then runs this Python script.

+
+
+ +
+
+

Drawing up a plan

+
+

Previously, we saw that running ESMValTool executes a number of +tasks. What tasks do you think we will need to execute and what should +each of these tasks do to generate the warming stripes?

+
+
+
+
+
+ +
+
+

In this episode, we will need to do the following two tasks:

+
  • A preprocessing task that converts the gridded temperature data to a +timeseries of global temperature anomalies
  • +
  • A diagnostic tasks that calls our Python script, taking our +preprocessed timeseries data as input.
  • +
+
+
+
+

Building a recipe from scratch

+

The easiest way to make a new recipe is to start from an existing +one, and modify it until it does exactly what you need. However, in this +episode we will start from scratch. This forces us to think about all +the steps involved in processing the data. We will also deal with +commonly occurring errors through the development of the recipe.

+

Remember the basic structure of a recipe, and notice that each +component is extensively described in the documentation under the +section, [“Overview”][recipe-overview]{:target=“_blank”}:

+
  • [documentation][recipe-section-documentation]{:target=“_blank”}
  • +
  • [datasets][recipe-section-datasets]{:target=“_blank”}
  • +
  • [preprocessors][recipe-section-preprocessors]{:target=“_blank”}
  • +
  • [diagnostics][recipe-section-diagnostics]{:target=“_blank”}
  • +

This is the first place to look for help if you get stuck.

+

Open a new file called recipe_warming_stripes.yml:

+
+

BASH +

+
nano recipe_warming_stripes.yml
+
+

Let’s add the standard header comments (these do not do anything), +and a first description.

+
+

YAML +

+
# ESMValTool
+# recipe_warming_stripes.yml
+---
+documentation:
+  description: Reproducing Ed Hawkins' warming stripes visualization
+  title: Reproducing Ed Hawkins' warming stripes visualization.
+
+

Notice that yaml always requires two spaces +indentation between the different levels. Pressing ctrl+o +will save the file. Verify the filename at the bottom and press enter. +Then use ctrl+x to exit the editor.

+

We will try to run the recipe after every modification we make, to +see if it (still) works!

+
+

BASH +

+
esmvaltool run recipe_warming_stripes.yml
+
+

In this case, it gives an error. Below you see the last few lines of +the error message.

+
+

ERROR +

+
...
+yamale.yamale_error.YamaleError:
+Error validating data '/home/users/username/esmvaltool_tutorial/recipe_warming_stripes.yml'
+with schema
+'/apps/jasmin/community/esmvaltool/miniconda3_py311_23.11.0-2/envs/esmvaltool/lib/python3.11/
+site-packages/esmvalcore/_recipe/recipe_schema.yml'
+	documentation.authors: Required field missing
+2024-05-27 13:21:23,805 UTC [41924] INFO
+If you have a question or need help, please start a new discussion on
+https://github.com/ESMValGroup/ESMValTool/discussions
+If you suspect this is a bug, please open an issue on
+https://github.com/ESMValGroup/ESMValTool/issues
+To make it easier to find out what the problem is, please consider attaching the
+files run/recipe_*.yml and run/main_log_debug.txt from the output directory.
+
+
+

We can use the the log message above, to understand why ESMValTool +failed. Here, this is because we missed a required field with author +names. The text +documentation.authors: Required field missing tells us +that. We see that ESMValTool always tries to validate the recipe at an +early stage. Note also the suggestion to open a GitHub issue if you need +help debugging the error message. This is something most users do when +they cannot understand the error or are not able to fix it on their +own.

+

Let’s add some additional information to the recipe. Open the recipe +file again, and add an authors section below the description. ESMValTool +expects the authors as a list, like so:

+
+

YAML +

+
authors:
+  - lastname_firstname
+
+

To bypass a number of similar error messages, add a minimal +diagnostics section below the documentation. The file should now look +like:

+
+

YAML +

+
# ESMValTool
+# recipe_warming_stripes.yml
+---
+documentation:
+  description: Reproducing Ed Hawkins' warming stripes visualization
+  title: Reproducing Ed Hawkins' warming stripes visualization.
+
+  authors:
+    - doe_john
+diagnostics:
+  dummy_diagnostic_1:
+    scripts: null
+
+

This is the minimal recipe layout that is required by ESMValTool. If +we now run the recipe again, you will probably see the following +error:

+
+

ERROR +

+
ValueError: Tag 'doe_john' does not exist in section
+'authors' of /apps/jasmin/community/esmvaltool/ESMValTool_2.10.0/esmvaltool/config-references.yml
+
+
+
+
+ +
+
+

Pro tip: config-references.yml

+
+

The error message above points to a file named +[config-references.yml][config-references] This is where ESMValTool +stores all its citation information. To add yourself as an author, add +your name in the form lastname_firstname in alphabetical +order following the existing entries, under the +# Development team section. See the [List of +authors][list-of-authors]{:target=“_blank”} section in the ESMValTool +documentation for more information.

+
+
+
+

For now, let’s just use one of the existing references. Change the +author field to righi_mattia, who cannot receive enough +credit for all the effort he put into ESMValTool. If you now run the +recipe again, you should see the final message

+
+

OUTPUT +

+
ERROR   No tasks to run!
+
+

Although there is no actual error in the recipe, ESMValTool assumes +you mistakenly left out a variable name to process and alerts you with +this error message.

+

Adding a dataset entry

+

Let’s add a datasets section.

+
+
+ +
+
+

Filling in the dataset keys

+
+

Use the paths specified in the configuration file to explore the data +directory, and look at the explanation of the dataset entry in the +[ESMValTool documentation][recipe-section-datasets]{:target=“_blank”}. +For both the datasets, write down the following properties:

+
  • project
  • +
  • variable (short name)
  • +
  • CMIP table
  • +
  • dataset (model name or obs/reanalysis dataset)
  • +
  • experiment
  • +
  • ensemble member
  • +
  • grid
  • +
  • start year
  • +
  • end year
  • +
+
+
+
+
+ +
+
+
+key | file 1 | +file 2 |
+project | CMIP6 | CMIP5 |
+short name | tas | tas |
+CMIP table | Amon | Amon |
+dataset | BCC-ESM1 | bcc-csm1-1|
+experiment | historical | historical |
+ensemble | r1i1p1f1 | r1i1p1 |
+grid | gn (native grid) | N/A |
+start year | 1850 | 1850 |
+end year | 2014 | 2005 |
+

Note that the grid key is only required for CMIP6 data, and that the +extent of the historical period has changed between CMIP5 and CMIP6.

+
+
+
+
+

Let us start with the BCC-ESM1 dataset and add a datasets section to +the recipe, listing this single dataset, as shown below. Note that key +fields such as mip or start_year are included +in the datasets section here but are part of the +diagnostic section in the recipe example seen in Running your first recipe.

+
+

YAML +

+
# ESMValTool
+# recipe_warming_stripes.yml
+---
+documentation:
+  description: Reproducing Ed Hawkins' warming stripes visualization
+  title: Reproducing Ed Hawkins' warming stripes visualization.
+
+  authors:
+    - doe_john
+datasets:
+  - {dataset: BCC-ESM1, project: CMIP6, mip: Amon, exp: historical,
+     ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2014}
+
+diagnostics:
+  dummy_diagnostic_1:
+    scripts: null
+
+

The recipe should run but produce the same message as in the previous +case since we still have not included a variable to actually process. We +have not included the short name of the variable in this dataset section +because this allows us to reuse this dataset entry with different +variable names later on. This is not really necessary for our simple use +case, but it is common practice in ESMValTool.

+
+
+ +
+
+

Pro-tip: Automatically populating a recipe with all available datasets

+
+

You can select all available models for processing using +glob patterns or wildcards. An example +datasets section that uses all available CMIP6 models and +ensemble members for the historical experiment is available +[here] [include-all-datasets]{:target=“_blank”}. Note that you will have +to set the search_esgf option in the +config_file to always so that you can download +data from ESGF nodes as needed.

+
+
+
+

Adding the preprocessor section

+

Above, we already described the preprocessing task that needs to +convert the standard, gridded temperature data to a timeseries of +temperature anomalies.

+
+
+ +
+
+

Defining the preprocessor

+
+

Have a look at the available preprocessors in the +[documentation][preprocessor]{:target=“_blank”}. Write down

+
  • Which preprocessor functions do you think we should use?
  • +
  • What are the parameters that we can pass to these functions?
  • +
  • What do you think should be the order of the preprocessors?
  • +
  • A suitable name for the overall preprocessor
  • +
+
+
+
+
+ +
+
+

We need to calculate anomalies and global means. There is an +anomalies preprocessor which takes in as arguments, a time +period, a reference period, and whether or not to standardize the data. +The global means can be calculated with the area_statistics +preprocessor, which takes an operator as argument (in our case we want +to compute the mean).

+

The default order in which these preprocessors are applied can be +seen [here][preprocessor-functions]{:target=“_blank”}: +area_statistics comes before anomalies. If you +want to change this, you can use the custom_order +preprocessor as described +[here][recipe-section-preprocessors]{:target=“_blank”}. For this +example, we will keep the default order..

+

Let’s name our preprocessor global_anomalies.

+
+
+
+
+

Add the following block to your recipe file between the +datasets and diagnostics block:

+
+

YAML +

+
preprocessors:
+  global_anomalies:
+    area_statistics:
+      operator: mean
+    anomalies:
+        period: month
+        reference:
+          start_year: 1981
+          start_month: 1
+          start_day: 1
+          end_year: 2010
+          end_month: 12
+          end_day: 31
+        standardize: false
+
+

Completing the diagnostics section

+

We are now ready to finish our diagnostics section. Remember that we +want to create two tasks: a preprocessor task, and a diagnostic task. To +illustrate that we can also pass settings to the diagnostic script, we +add the option to specify a custom colormap.

+
+
+ +
+
+

Fill in the blanks

+
+

Extend the diagnostics section in your recipe by filling in the +blanks in the following template:

+
+

YAML +

+
diagnostics:
+  <... (suitable name for our diagnostic)>:
+    description: <...>
+    variables:
+      <... (suitable name for the preprocessed variable)>:
+        short_name: <...>
+        preprocessor: <...>
+    scripts:
+      <... (suitable name for our python script)>:
+        script: <full path to python script>
+        colormap: <... choose from matplotlib colormaps>
+
+
+
+
+
+
+ +
+
+
+

YAML +

+
diagnostics:
+  diagnostic_warming_stripes:
+    description: visualize global temperature anomalies as warming stripes
+    variables:
+      global_temperature_anomalies_global:
+        short_name: tas
+        preprocessor: global_anomalies
+    scripts:
+      warming_stripes_script:
+        script: ~/esmvaltool_tutorial/warming_stripes.py
+        colormap: 'bwr'
+
+
+
+
+
+

You should now be able to run the recipe to get your own warming +stripes.

+

Note: for the purpose of simplicity in this episode, we have not +added logging or provenance tracking in the diagnostic script. Once you +start to develop your own diagnostic scripts and want to add them to the +ESMValTool repositories, this will be required. Writing your own +diagnostic script is discussed in a later +episode.

+

Bonus exercises

+

Below are a few exercises to practice modifying an ESMValTool recipe. +For your reference, here’s a copy of the recipe at this point. This +will be the point of departure for each of the modifications we’ll make +below.

+
+
+ +
+
+

Specific location selection

+
+

On showyourstripes.org, you can download stripes for specific +locations. Here we show how this can be done with ESMValTool. Instead of +the global mean, we can pick a location to plot the stripes for. Can you +find a suitable preprocessor to do this?

+
+
+
+
+
+ +
+
+

You can use extract_point or extract_region +to select a location. We used extract_point. Here’s a copy +of the recipe at this +point and this is the difference from the previous recipe:

+
+

DIFF +

+
--- recipe_warming_stripes.yml
++++ recipe_warming_stripes_local.yml
+@@ -10,9 +10,11 @@
+   - {dataset: BCC-ESM1, project: CMIP6, mip: Amon, exp: historical,
+      ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2014}
+
+ preprocessors:
+-  global_anomalies:
+-    area_statistics:
+-      operator: mean
++  anomalies_amsterdam:
++    extract_point:
++      latitude: 52.379189
++      longitude: 4.899431
++      scheme: linear
+     anomalies:
+       period: month
+       reference:
+@@ -27,9 +29,9 @@
+ diagnostics:
+   diagnostic_warming_stripes:
+     variables:
+-      global_temperature_anomalies:
++      temperature_anomalies_amsterdam:
+         short_name: tas
+-        preprocessor: global_anomalies
++        preprocessor: anomalies_amsterdam
+     scripts:
+       warming_stripes_script:
+         script: ~/esmvaltool_tutorial/warming_stripes.py
+
+
+
+
+
+
+
+ +
+
+

Different time periods

+
+

Split the diagnostic in two with two different time periods for the +same variable. You can choose the time periods yourself. In the example +below, we have chosen the recent past and the 20th century and have used +variable grouping.

+
+
+
+
+
+ +
+
+

Here’s a copy of the recipe at this point +and this is the difference with the previous recipe:

+
+

DIFF +

+
--- recipe_warming_stripes_local.yml
++++ recipe_warming_stripes_periods.yml
+@@ -7,7 +7,7 @@
+
+ datasets:
+-  - {dataset: BCC-ESM1, project: CMIP6, mip: Amon, exp: historical,
+-  	  ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2014}
++  - {dataset: BCC-ESM1, project: CMIP6, mip: Amon, exp: historical,
++     ensemble: r1i1p1f1, grid: gn}
+
+ preprocessors:
+   anomalies_amsterdam:
+@@ -29,9 +29,16 @@
+ diagnostics:
+   diagnostic_warming_stripes:
+     variables:
+-      temperature_anomalies_amsterdam:
++      temperature_anomalies_recent:
+         short_name: tas
+         preprocessor: anomalies_amsterdam
++        start_year: 1950
++        end_year: 2014
++      temperature_anomalies_20th_century:
++        short_name: tas
++        preprocessor: anomalies_amsterdam
++        start_year: 1900
++        end_year: 1999
+     scripts:
+       warming_stripes_script:
+         script: ~/esmvaltool_tutorial/warming_stripes.py
+
+
+
+
+
+
+
+ +
+
+

Different preprocessors

+
+

Now that you have different variable groups, we can also use +different preprocessors. Add a second preprocessor to add another +location of your choosing.

+
+

Solution

+

Here’s a copy of the recipe at +this point and this is the difference with the previous recipe:

+
+

DIFF +

+
--- recipe_warming_stripes_periods.yml
++++ recipe_warming_stripes_multiple_locations.yml
+@@ -15,7 +15,7 @@
+       latitude: 52.379189
+       longitude: 4.899431
+       scheme: linear
+-    anomalies:
++    anomalies: &anomalies
+       period: month
+       reference:
+         start_year: 1981
+@@ -25,18 +25,24 @@
+         end_month: 12
+         end_day: 31
+       standardize: false
++  anomalies_london:
++    extract_point:
++      latitude: 51.5074
++      longitude: 0.1278
++      scheme: linear
++    anomalies: *anomalies
+
+ diagnostics:
+   diagnostic_warming_stripes:
+     variables:
+-      temperature_anomalies_recent:
++      temperature_anomalies_recent_amsterdam:
+         short_name: tas
+         preprocessor: anomalies_amsterdam
+         start_year: 1950
+         end_year: 2014
+-      temperature_anomalies_20th_century:
++      temperature_anomalies_20th_century_london:
+         short_name: tas
+-        preprocessor: anomalies_amsterdam
++        preprocessor: anomalies_london
+         start_year: 1900
+         end_year: 1999
+     scripts:
+
+
+
+
+
+
+
+ +
+
+

Pro-tip: YAML anchors

+
+

If you want to avoid retyping the arguments used in your +preprocessor, you can use YAML anchors as seen in the +anomalies preprocessor specifications in the recipe +above.

+
+
+
+
+
+ +
+
+

Additional datasets

+
+

So far we have defined the datasets in the datasets section of the +recipe. However, it’s also possible to add specific datasets only for +specific variables or variable groups. Take a look at the documentation +to learn about the additional_datasets keyword +[here][additional-datasets]{:target=“_blank”}, and add a second dataset +only for one of the variable groups.

+
+
+
+
+
+ +
+
+

Here’s a copy of the recipe at +this point and this is the difference with the previous recipe:

+
+

DIFF +

+
--- recipe_warming_stripes_multiple_locations.yml
++++ recipe_warming_stripes_additional_datasets.yml
+@@ -45,6 +45,8 @@
+         preprocessor: anomalies_london
+         start_year: 1900
+         end_year: 1999
++        additional_datasets:
++          - {dataset: CanESM2, project: CMIP5, mip: Amon, exp: historical, ensemble: r1i1p1}
+     scripts:
+       warming_stripes_script:
+         script: ~/esmvaltool_tutorial/warming_stripes.py
+
+
+
+
+
+
+
+ +
+
+

Multiple ensemble members

+
+

You can choose data from multiple ensemble members for a model in a +single line.

+
+
+
+
+
+ +
+
+

The dataset section allows you to choose more than one +ensemble member Here’s a copy of the changed recipe +to do that. Changes made are shown in the diff output below:

+
+

DIFF +

+
--- recipe_warming_stripes.yml	2024-05-27 15:37:52.340358967 +0100
++++ recipe_warming_stripes_multiens.yml	2024-05-27 22:18:42.035558837 +0100
+@@ -10,7 +10,7 @@
+-     ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2014}
++     ensemble: "r(1:2)i1p1f1", grid: gn, start_year: 1850, end_year: 2014}
+
+
+
+
+
+
+
+ +
+
+

Pro-tip: Concatenating datasets

+
+

Check out the section on a different way to use multiple ensemble +members or even multiple experiments at [Concatenating data +corresponding to multiple facets] +[concatenating-datasets]{:target=“_blank”}.

+
+
+
+
+
+ +
+
+

Key Points

+
+
  • A recipe can work with different preprocessors at the same +time.
  • +
  • The setting additional_datasets can be used to add a +different dataset.
  • +
  • Variable groups are useful for defining different settings for +different variables.
  • +
  • Multiple ensemble members and experiments can be analysed in a +single recipe through concatenation.
  • +
+
+
+
+
+ + +
+
+ + + diff --git a/07-development-setup.html b/07-development-setup.html new file mode 100644 index 00000000..b43b3732 --- /dev/null +++ b/07-development-setup.html @@ -0,0 +1,1070 @@ + +ESMValTool Tutorial: Development and contribution +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Development and contribution

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • What is a development installation?
  • +
  • How can I test new or improved code?
  • +
  • How can I incorporate my contributions into ESMValTool?
  • +
+
+
+
+
+
+

Objectives

+
  • Execute a successful ESMValTool installation from the source +code.
  • +
  • Contribute to ESMValTool development.
  • +
+
+
+
+
+

We now know how ESMValTool works, but how do we develop it? +ESMValTool is an open-source project in ESMValGroup. We can contribute +to its development by:

+

In this lesson, we first show how to set up a development +installation of ESMValTool so you can make changes or additions. We then +explain how you can contribute these changes to the community.

+
+
+ +
+
+

Git knowledge

+
+

For this episode, you need some knowledge of Git. You can refresh your knowledge in +the corresponding Git carpentries +course.

+
+
+
+

Development installation

+

We’ll explore how ESMValTool can be installed it in a +develop mode. Even if you aren’t collaborating with the +community, this installation is needed to run your new codes with +ESMValTool. Let’s get started.

+
+

1 Source code

+

The ESMValTool source code is available on a public GitHub +repository: https://github.com/ESMValGroup/ESMValTool. To obtain the +code, there are two options:

+
  1. Download the code from the repository. A ZIP file called +ESMValTool-main.zip is downloaded. To continue the +installation, unzip the file, move to the ESMValTool-main +directory and then follow the sequence of steps starting from the +section on ESMValTool dependencies below.
  2. +
  3. Clone the repository if you want to contribute to the ESMValTool +development:
  4. +
+

BASH +

+
git clone https://github.com/ESMValGroup/ESMValTool.git
+
+

This command will ask your GitHub username and a personal +token as password. Please follow instructions on +[GitHub token authentication +requirements][token-authentication-requirements] to create a personal +access token. Alternatively, you could [generate a new SSH +key][generate-ssh-key] and [add it to your GitHub account][add-ssh-key]. +After the authentication, the output might look like:

+
+

OUTPUT +

+
Cloning into 'ESMValTool'...
+remote: Enumerating objects: 163, done.
+remote: Counting objects: 100% (163/163), done.
+remote: Compressing objects: 100% (125/125), done.
+remote: Total 95049 (delta 84), reused 76 (delta 30), pack-reused 94886
+Receiving objects: 100% (95049/95049), 175.16 MiB | 5.48 MiB/s, done.
+Resolving deltas: 100% (68808/68808), done.
+
+

Now, a folder called ESMValTool has been created in your +working directory. This folder contains the source code of the tool. To +continue the installation, we move into the ESMValTool +directory:

+
+

BASH +

+
cd ESMValTool
+
+

Note that the main branch is checked out by default. We +can see this if we run:

+
+

BASH +

+
git status
+
+
+

OUTPUT +

+
On branch main
+Your branch is up to date with 'origin/main'.
+
+nothing to commit, working tree clean
+
+
+
+

2 ESMValTool dependencies

+

Please don’t forget if an esmvaltool environment is already created +following the lesson Installation, we +should choose another name for the new environment in this lesson.

+

ESMValTool now uses mamba instead of conda +for the recommended installation. For a minimal mamba installation, see +section Install Mamba in lesson Installation.

+

It is good practice to update the version of mamba and conda on your +machine before setting up ESMValTool. This can be done as follows:

+
+

BASH +

+
mamba update --name base mamba conda
+
+

To simplify the installation process, an environment file +environment.yml is provided in the ESMValTool directory. We +create an environment by running:

+
+

BASH +

+
mamba env create --name esmvaltool --file environment.yml
+
+

The environment is called esmvaltool by default. If an +esmvaltool environment is already created following the +lesson Installation, we should choose +another name for the new environment in this lesson by:

+
+

BASH +

+
mamba env create --name a_new_name --file environment.yml
+
+

This will create a new conda environment and install ESMValTool (with +all dependencies that are needed for development purposes) into it with +a single command.

+

For more information see [conda managing +environments][manage-environments].

+

Now, we should activate the environment:

+
+

BASH +

+
conda activate esmvaltool
+
+

where esmvaltool is the name of the environment (replace +by a_new_name in case another environment name was +used).

+
+
+

3 ESMValTool installation

+

ESMValTool can be installed in a develop mode by +running:

+
+

BASH +

+
pip install --editable '.[develop]'
+
+

This will add the esmvaltool directory to the Python +path in editable mode and install the development dependencies. We +should check if the installation works properly. To do this, run the +tool with:

+
+

BASH +

+
esmvaltool --help
+
+

If the installation is successful, ESMValTool prints a help message +to the console.

+
+
+ +
+
+

Checking the development installation

+
+

We can use the command mamba list to list installed +packages in the esmvaltool environment. Use this command to +check that ESMValTool is installed in a develop mode.

+

Tip: see the documentation +on conda list.

+
+
+
+
+
+ +
+
+

Run:

+
+

BASH +

+
mamba list esmvaltool
+
+
+

BASH +

+
# Name                    Version                   Build  Channel
+esmvaltool                2.10.0.dev3+g2dbc2cfcc    pypi_0   pypi
+
+
+
+
+
+
+
+

4 Updating ESMValTool

+

The main branch has the latest features of ESMValTool. +Please make sure that the source code on your machine is up-to-date. If +you obtain the source code using git clone as explained in step +1 Source code, you can run git pull to +update the source code. Then ESMValTool installation will be updated +with changes from the main branch.

+
+

Contribution

+

We have seen how to install ESMValTool in a develop +mode. Now, we try to contribute to its development. Let’s see how this +can be achieved. We first discuss our ideas in an issue +in ESMValTool repository. This can avoid disappointment at a later +stage, for example, if more people are doing the same thing. It also +gives other people an early opportunity to provide input and +suggestions, which results in more valuable contributions.

+

Then, we create a new branch locally and start +developing new codes. To create a new branch:

+
+

BASH +

+
git checkout -b your_branch_name
+
+

If needed, a link to a git tutorial can be found in Setup.

+

Once our development is finished, we can initiate a +pull request. To this end, we encourage you to join the +ESMValTool development team.

+

For more extensive documentation on contributing code, including a +section on the GitHubWorkflow, +please see the [Contributing code and documentation][code-documentation] +section in the ESMValtool documentation.

+
+

Review process

+

The pull request will be tested, discussed and merged as part of the +“review process”. The process will take some effort and +time to learn. However, a few (command line) tools can get you a long +way, and we’ll cover those essentials in the next sections.

+

Tip: we encourage you to keep the pull requests +small. Reviewing small incremental changes is more efficient.

+
+
+

Working example

+

We saw the ‘warming stripes’ diagnostic in lesson Writing your own recipe. Imagine the +following task: you want to contribute warming stripes recipe and +diagnostics to ESMValTool. You have to add the diagnostics warming_stripes.py and the recipe recipe_warming_stripes.yml +to their locations in ESMValTool directory. After these changes, you +should also check if everything works fine. This is where we take +advantage of the tools that are introduced later.

+

Let’s get started. Note that since this is an exercise to get +familiar with the development and contribution process, we will not +create a GitHub issue at this time but proceed as though it has been +done.

+
+
+

Check code quality

+

We aim to adhere to best practices and coding standards. There are +several tools that check our code against those standards like:

+
  • +flake8 for +checking against the PEP8 style guide
  • +
  • +yapf to ensure +consistent formatting for the whole project
  • +
  • +isort to consistently +sort the import statements
  • +
  • +yamllint +to ensure there are no syntax errors in our recipes and config +files
  • +
  • +lintr for +diagnostic scripts written in R
  • +
  • +codespell to check +grammar
  • +

The good news is that pre-commit has been already +installed when we chose development installation. +pre-commit is a command line and runs all of those tools. +It also fixes some of those errors. To explore other tools, have a look +at ESMValTool documentation on Code +style.

+
+
+ +
+
+

Using pre-commit

+
+

Let’s checkout our local branch and add the script warming_stripes.py to the +esmvaltool/diag_scripts directory.

+
+

BASH +

+
cd ESMValTool
+git checkout your_branch_name
+cp path_of_warming_stripes.py esmvaltool/diag_scripts/
+
+

By default, pre-commit only runs on the files that have +been staged in git:

+
+

BASH +

+
git status
+git add esmvaltool/diag_scripts/warming_stripes.py
+pre-commit run --files esmvaltool/diag_scripts/warming_stripes.py
+
+

Inspect the output of pre-commit and fix the remaining +errors.

+
+
+
+
+
+ +
+
+

The tail of the output of pre-commit:

+
+

BASH +

+
Check for added large files..............................................Passed
+Check python ast.........................................................Passed
+Check for case conflicts.................................................Passed
+Check for merge conflicts................................................Passed
+Debug Statements (Python)................................................Passed
+Fix End of Files.........................................................Passed
+Trim Trailing Whitespace.................................................Passed
+yamllint.............................................(no files to check)Skipped
+nclcodestyle.........................................(no files to check)Skipped
+style-files..........................................(no files to check)Skipped
+lintr................................................(no files to check)Skipped
+codespell................................................................Passed
+isort....................................................................Passed
+yapf.....................................................................Passed
+docformatter.............................................................Failed
+- hook id: docformatter
+- files were modified by this hook
+flake8...................................................................Failed
+- hook id: flake8
+- exit code: 1
+
+esmvaltool/diag_scripts/warming_stripes.py:20:5:
+F841 local variable 'nx' is assigned to but never used
+
+

As can be seen above, there are two Failed check:

+
  1. +docformatter: it is mentioned that “files were modified +by this hook”. We run git diff to see the modifications. +The output includes the following:
  2. +
+

BASH +

+
+in the form of the popular warming stripes figure by Ed Hawkins."""
+
+

The syntax """ at the end of docstring is moved by one +line. Shifting it to the next line should fix this error.

+
  1. +flake8: the error message is about an unused local +variable nx. We should check our codes regarding the usage +of nx. For now, let’s assume that it is added by mistake +and remove it. Note that you have to run git add again to +re-stage the file. Then rerun pre-commit and check that it passes.
  2. +
+
+
+
+
+
+

Run unit tests

+

Previous section introduced some tools to check code style and +quality. There is lack of mechanism to determine whether or not our code +is getting the right answer. To achieve that, we need to write and run +tests for widely-used functions. ESMValTool comes with a lot of tests +that are in the folder tests.

+

To run tests, first we make sure that the working directory is +ESMValTool and our local branch is checked out. Then, we +can run tests using pytest locally:

+
+

BASH +

+
pytest
+
+

Tests will also be run automatically by CircleCI, when you submit a pull +request.

+
+
+ +
+
+

Running tests

+
+

Make sure our local branch is checked out and add the recipe recipe_warming_stripes.yml +to the esmvaltool/recipes directory:

+
+

BASH +

+
cp path_of_recipe_warming_stripes.yml esmvaltool/recipes/
+
+

Run pytest and inspect the results, this might take a +few minutes. If a test is failed, try to fix it.

+
+
+
+
+
+ +
+
+

Run:

+
+

BASH +

+
pytest
+
+

When pytest run is complete, you can inspect the test +reports that are printed in the console. Have a look at the second +section of the report FAILURES:

+
+

BASH +

+
================================ FAILURES ==========================================
+______________ test_recipe_valid[recipe_warming_stripes.yml] ______________
+
+

The test message shows that the recipe +recipe_warming_stripes.yml is not a valid recipe. Look for +a line that starts with an E in the rest of the +message:

+
+

BASH +

+

+E           esmvalcore._task.DiagnosticError: Cannot execute script
+'~/esmvaltool_tutorial/warming_stripes.py' (~/esmvaltool_tutorial/warming_stripes.py):
+file does not exist.
+
+

To fix the recipe, we need to edit the path of the diagnostic script +as warming_stripes.py:

+
+

YAML +

+
   scripts:
+     warming_stripes_script:
+       script: warming_stripes.py
+
+

For details, see lesson Writing your +own diagnostic script.

+
+
+
+
+
+
+

Build documentation

+

When we add or update a code, we also update its corresponding +documentation. The ESMValTool documentation is available on docs.esmvaltool.org. +The source files are located in +ESMValTool/doc/sphinx/source/.

+

To build documentation locally, first we make sure that the working +directory is ESMValTool and our local branch is checked +out. Then, we run:

+
+

BASH +

+
sphinx-build -Ea doc/sphinx/source/ doc/sphinx/build/
+
+

Similar to code, documentation should be well written and adhere to +standards. If the documentation is built properly, the previous command +prints a message to the console:

+
+

OUTPUT +

+
build succeeded.
+
+The HTML pages are in doc/sphinx/build.
+
+

The main page of the documentation has been built into +index.html in doc/sphinx/build/ directory. To +preview this page locally, we open the file in a web browser:

+
+

BASH +

+
xdg-open doc/sphinx/build/index.html
+
+
+
+ +
+
+

Creating a documentation

+
+

In previous exercises, we added the recipe recipe_warming_stripes.yml +to ESMValTool. Now, we create a documentation file +recipe_warming_stripes.rst for this recipe:

+
+

BASH +

+
nano doc/sphinx/source/recipes/recipe_warming_stripes.rst
+
+

Add a reference i.e. .. _recipe_warming_stripes:, a +section title and some text about the recipe like:

+
.. _recipe_warming_stripes:
+
+Reproducing Ed Hawkins' warming stripes visualization
+======================================================
+
+This recipe produces warming stripes plots.
+

Save and close the file. We can think of this file as one page of a +book. Examples of documentation pages can be found in the folder +ESMValTool/doc/sphinx/source/recipes. Then, we need to +decide where this page should be located inside the book. The table of +content is defined by index.rst. Let’s have a look at the +content:

+
+

BASH +

+
nano doc/sphinx/source/recipes/index.rst
+
+

Add the recipe name i.e. recipe_warming_stripes to the +section Other in this file and preview the recipe +documentation page locally.

+
+
+
+
+
+ +
+
+

First, we add the recipe name recipe_warming_stripes to +the section Other:

+
Other
+^^^^^
+.. toctree::
+  :maxdepth: 1
+  ...
+  ...
+  recipe_warming_stripes
+

Then, we build and preview the documentation page:

+
+

BASH +

+
sphinx-build -Ea doc/sphinx/source/ doc/sphinx/build/
+xdg-open doc/sphinx/build/recipes/recipe_warming_stripes.html
+
+
+
+
+
+

Congratulations! You are now ready to make a pull request.

+
+
+ +
+
+

Key Points

+
+
  • A development installation is needed if you want to incorporate your +code into ESMValTool.
  • +
  • Contributions include adding a new or improved script or helping +with a review process.
  • +
  • There are several tools to help improve the quality of your +code.
  • +
  • It is possible to run tests on your machine.
  • +
  • You can preview documentation pages locally.
  • +
+
+
+
+
+
+ + +
+
+ + + diff --git a/08-diagnostics.html b/08-diagnostics.html new file mode 100644 index 00000000..ca132bd9 --- /dev/null +++ b/08-diagnostics.html @@ -0,0 +1,1122 @@ + +ESMValTool Tutorial: Writing your own diagnostic script +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Writing your own diagnostic script

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • How do I write a new diagnostic in ESMValTool?
  • +
  • How do I use the preprocessor output in a Python diagnostic?
  • +
+
+
+
+
+
+

Objectives

+
  • Write a new Python diagnostic script.
  • +
  • Explain how a diagnostic script reads the preprocessor output.
  • +
+
+
+
+
+

Introduction

+

The diagnostic script is an important component of ESMValTool and it +is where the scientific analysis or performance metric is implemented. +With ESMValTool, you can adapt an existing diagnostic or write a new +script from scratch. Diagnostics can be written in a number of open +source languages such as Python, R, Julia and NCL but we will focus on +understanding and writing Python diagnostics in this lesson.

+

In this lesson, we will explain how to find an existing diagnostic +and run it using ESMValTool installed in editable/development mode. For +a development installation, see the instructions in the lesson Development and contribution. Also, +we will work with the recipe [recipe_python.yml][recipe] and the +diagnostic script [diagnostic.py][diagnostic] called by this recipe that +we have seen in the lesson Running your first +recipe .

+

Let’s get started!

+

Understanding an existing Python diagnostic

+

If you clone the ESMValTool repository, a folder called +ESMValTool is created in your home/working directory, see +the instructions in the lesson Development and contribution .

+

The folder ESMValTool contains the source code of the +tool. We can find the recipe recipe_python.yml and the +python script diagnostic.py in these directories:

+
  • ~/ESMValTool/esmvaltool/recipes/examples/recipe_python.yml
  • +
  • ~/ESMValTool/esmvaltool/diag_scripts/examples/diagnostic.py
  • +

Let’s have look at the code in diagnostic.py. For +reference, we show the diagnostic code in the dropdown box below. There +are four main sections in the script:

+
  • A description i.e. the docstring (line 1).
  • +
  • Import statements (line 2-16).
  • +
  • Functions that implement our analysis (line 21-102).
  • +
  • A typical Python top-level script +i.e. if __name__ == '__main__' (line 105-108).
  • +
+
+ +
+
+
+

PYTHON +

+
 1:  """Python example diagnostic."""
+ 2:  import logging
+ 3:  from pathlib import Path
+ 4:  from pprint import pformat
+ 5:
+ 6:  import iris
+ 7:
+ 8:  from esmvaltool.diag_scripts.shared import (
+ 9:      group_metadata,
+10:      run_diagnostic,
+11:      save_data,
+12:      save_figure,
+13:      select_metadata,
+14:      sorted_metadata,
+15:  )
+16:  from esmvaltool.diag_scripts.shared.plot import quickplot
+17:
+18:  logger = logging.getLogger(Path(__file__).stem)
+19:
+20:
+21:  def get_provenance_record(attributes, ancestor_files):
+22:      """Create a provenance record describing the diagnostic data and plot."""
+23:      caption = caption = attributes['caption'].format(**attributes)
+24:
+25:      record = {
+26:          'caption': caption,
+27:          'statistics': ['mean'],
+28:          'domains': ['global'],
+29:          'plot_types': ['zonal'],
+30:          'authors': [
+31:              'andela_bouwe',
+32:              'righi_mattia',
+33:          ],
+34:          'references': [
+35:              'acknow_project',
+36:          ],
+37:          'ancestors': ancestor_files,
+38:      }
+39:      return record
+40:
+41:
+42:  def compute_diagnostic(filename):
+43:      """Compute an example diagnostic."""
+44:      logger.debug("Loading %s", filename)
+45:      cube = iris.load_cube(filename)
+46:
+47:      logger.debug("Running example computation")
+48:      cube = iris.util.squeeze(cube)
+49:      return cube
+50:
+51:
+52:  def plot_diagnostic(cube, basename, provenance_record, cfg):
+53:      """Create diagnostic data and plot it."""
+54:
+55:      # Save the data used for the plot
+56:      save_data(basename, provenance_record, cfg, cube)
+57:
+58:      if cfg.get('quickplot'):
+59:          # Create the plot
+60:          quickplot(cube, **cfg['quickplot'])
+61:          # And save the plot
+62:          save_figure(basename, provenance_record, cfg)
+63:
+64:
+65:  def main(cfg):
+66:      """Compute the time average for each input dataset."""
+67:      # Get a description of the preprocessed data that we will use as input.
+68:      input_data = cfg['input_data'].values()
+69:
+70:      # Demonstrate use of metadata access convenience functions.
+71:      selection = select_metadata(input_data, short_name='tas', project='CMIP5')
+72:      logger.info("Example of how to select only CMIP5 temperature data:\n%s",
+73:                  pformat(selection))
+74:
+75:      selection = sorted_metadata(selection, sort='dataset')
+76:      logger.info("Example of how to sort this selection by dataset:\n%s",
+77:                  pformat(selection))
+78:
+79:      grouped_input_data = group_metadata(input_data,
+80:                                          'variable_group',
+81:                                          sort='dataset')
+82:      logger.info(
+83:          "Example of how to group and sort input data by variable groups from "
+84:          "the recipe:\n%s", pformat(grouped_input_data))
+85:
+86:      # Example of how to loop over variables/datasets in alphabetical order
+87:      groups = group_metadata(input_data, 'variable_group', sort='dataset')
+88:      for group_name in groups:
+89:          logger.info("Processing variable %s", group_name)
+90:          for attributes in groups[group_name]:
+91:              logger.info("Processing dataset %s", attributes['dataset'])
+92:              input_file = attributes['filename']
+93:              cube = compute_diagnostic(input_file)
+94:
+95:              output_basename = Path(input_file).stem
+96:              if group_name != attributes['short_name']:
+97:                  output_basename = group_name + '_' + output_basename
+98:              if "caption" not in attributes:
+99:                  attributes['caption'] = input_file
+100:              provenance_record = get_provenance_record(
+101:                  attributes, ancestor_files=[input_file])
+102:              plot_diagnostic(cube, output_basename, provenance_record, cfg)
+103:
+104:
+105:  if __name__ == '__main__':
+106:
+107:      with run_diagnostic() as config:
+108:          main(config)
+
+
+
+
+
+
+
+ +
+
+

What is the starting point of a diagnostic?

+
+
  1. Can you spot a function called main in the code +above?
  2. +
  3. What are its input arguments?
  4. +
  5. How many times is this function mentioned?
  6. +
+
+
+
+
+ +
+
+
  1. The main function is defined in line 65 as +main(cfg).
  2. +
  3. The input argument to this function is the variable +cfg, a Python dictionary that holds all the necessary +information needed to run the diagnostic script such as the location of +input data and various settings. We will next parse this +cfg variable in the main function and extract +information as needed to do our analyses (e.g. in line 68).
  4. +
  5. The main function is called near the very end on line +108. So, it is mentioned twice in our code - once where it is called by +the top-level Python script and second where it is defined.
  6. +
+
+
+
+
+
+ +
+
+

The function run_diagnostic

+
+

The function run_diagnostic (line 107) is called a +context manager provided with ESMValTool and is the main entry point for +most Python diagnostics.

+
+
+
+

Preprocessor-diagnostic interface

+

In the previous exercise, we have seen that the variable +cfg is the input argument of the main +function. The first argument passed to the diagnostic via the +cfg dictionary is a path to a file called +settings.yml. The ESMValTool documentation page provides an +overview of what is in this file, see [Diagnostic script +interfaces][interface].

+
+
+ +
+
+

What information do I need when writing a diagnostic script?

+
+

From the lesson Configuration, we +saw how to change the configuration settings before running a recipe. +First we set the option remove_preproc_dir to +false in the configuration file, then run the recipe +recipe_python.yml:

+
+

BASH +

+
esmvaltool run examples/recipe_python.yml
+
+
  1. Find one example of the file settings.yml in the +run directory?
  2. +
  3. Open the file settings.yml and look at the +input_files list. It contains paths to some files +metadata.yml. What information do you think is saved in +those files?
  4. +
+
+
+
+
+ +
+
+
  1. One example of settings.yml can be found in the +directory: +path_to_recipe_output/run/map/script1/settings.yml +
  2. +
  3. The metadata.yml files hold information about the +preprocessed data. There is one file for each variable having detailed +information on your data including project (e.g., CMIP6, CMIP5), dataset +names (e.g., BCC-ESM1, CanESM2), variable attributes (e.g., +standard_name, units), preprocessor applied and time range of the data. +You can use all of this information in your own diagnostic.
  4. +
+
+
+
+

Diagnostic shared functions

+

Looking at the code in diagnostic.py, we see that +input_data is read from the cfg dictionary +(line 68). Now we can group the input_data according to +some criteria such as the model or experiment. To do so, ESMValTool +provides many functions such as select_metadata (line 71), +sorted_metadata (line 75), and group_metadata +(line 79). As you can see in line 8, these functions are imported from +esmvaltool.diag_scripts.shared that means these are shared +across several diagnostics scripts. A list of available functions and +their description can be found in [The ESMValTool Diagnostic API +reference][shared].

+
+

Extracting +information needed for analyses

+

We have seen the functions used for selecting, sorting and grouping +data in the script. What do these functions do?

+
+

Answer

+

There is a statement after use of select_metadata, +sorted_metadata and group_metadata that starts +with logger.info (lines 72, 76 and 82). These lines print +output to the log files. In the previous exercise, we ran the recipe +recipe_python.yml. If you look at the log file +recipe_python_#_#/run/map/script1/log.txt in +esmvaltool_output directory, you can see the output from +each of these functions, for example:

+
2023-06-28 12:47:14,038 [2548510] INFO     diagnostic,106	Example of how to
+group and sort input data by variable groups from the recipe:
+{'tas': [{'alias': 'CMIP5',
+         'caption': 'Global map of {long_name} in January 2000 according to '
+                    '{dataset}.\n',
+         'dataset': 'bcc-csm1-1',
+         'diagnostic': 'map',
+         'end_year': 2000,
+         'ensemble': 'r1i1p1',
+         'exp': 'historical',
+         'filename': '~/recipe_python_20230628_124639/preproc/map/tas/
+
+
+
            CMIP5_bcc-csm1-1_Amon_historical_r1i1p1_tas_2000-P1M.nc',
+
+
+
     'frequency': 'mon',
+     'institute': ['BCC'],
+     'long_name': 'Near-Surface Air Temperature',
+     'mip': 'Amon',
+     'modeling_realm': ['atmos'],
+     'preprocessor': 'to_degrees_c',
+     'product': ['output1', 'output2'],
+     'project': 'CMIP5',
+     'recipe_dataset_index': 1,
+     'short_name': 'tas',
+     'standard_name': 'air_temperature',
+     'start_year': 2000,
+     'timerange': '2000/P1M',
+     'units': 'degrees_C',
+     'variable_group': 'tas',
+     'version': 'v1'},
+    {'activity': 'CMIP',
+     'alias': 'CMIP6',
+     'caption': 'Global map of {long_name} in January 2000 according to '
+                '{dataset}.\n',
+     'dataset': 'BCC-ESM1',
+     'diagnostic': 'map',
+     'end_year': 2000,
+     'ensemble': 'r1i1p1f1',
+     'exp': 'historical',
+     'filename': '~/recipe_python_20230628_124639/preproc/map/tas/
+
+
+
            CMIP6_BCC-ESM1_Amon_historical_r1i1p1f1_tas_gn_2000-P1M.nc',
+
+
+ +
+
+

Challenge

+
+ +
+
+
+
+
+ +
+
+
     'frequency': 'mon',
+     'grid': 'gn',
+     'institute': ['BCC'],
+     'long_name': 'Near-Surface Air Temperature',
+     'mip': 'Amon',
+     'modeling_realm': ['atmos'],
+     'preprocessor': 'to_degrees_c',
+     'project': 'CMIP6',
+     'recipe_dataset_index': 0,
+     'short_name': 'tas',
+     'standard_name': 'air_temperature',
+     'start_year': 2000,
+     'timerange': '2000/P1M',
+     'units': 'degrees_C',
+     'variable_group': 'tas',
+     'version': 'v20181214'}]}
+

+This is how we can access preprocessed data within our diagnostic.
+
+
+
+
+

Diagnostic computation

+

After grouping and selecting data, we can read individual attributes +(such as filename) of each item. Here, we have grouped the input data by +variables, so we loop over the variables (line 88). +Following this is a call to the function compute_diagnostic +(line 93). Let’s look at the definition of this function in line 42, +where the actual analysis of the data is done.

+

Note that output from the ESMValCore preprocessor is in the form of +NetCDF files. Here, compute_diagnostic uses Iris +to read data from a netCDF file and performs an operation +squeeze to remove any dimensions of length one. We can +adapt this function to add our own analysis. As an example, here we +calculate the bias using the average of the data using Iris cubes.

+
+

PYTHON +

+
def compute_diagnostic(filename):
+    """Compute an example diagnostic."""
+    logger.debug("Loading %s", filename)
+    cube = iris.load_cube(filename)
+
+    logger.debug("Running example computation")
+    cube = iris.util.squeeze(cube)
+
+    # Calculate a bias using the average of data
+    cube.data = cube.core_data() - cube.core_data.mean()
+    return cube
+
+
+
+ +
+
+

iris cubes

+
+

Iris reads data from NetCDF files into data structures called cubes. +The data in these cubes can be modified, combined with other cubes’ data +or plotted.

+
+
+
+
+
+ +
+
+

Reading data using xarray

+
+

Alternately, you can use xarrays to read the data +instead of Iris.

+
+
+
+
+
+ +
+
+

First, import xarray package at the top of the script +as:

+
+

PYTHON +

+
import xarray as xr
+
+

Then, change the compute_diagnostic as:

+
+

PYTHON +

+
def compute_diagnostic(filename):
+   """Compute an example diagnostic."""
+   logger.debug("Loading %s", filename)
+   dataset = xr.open_dataset(filename)
+
+   #do your analyses on the data here
+
+   return dataset
+
+

Caution: If you read data using xarray keep in mind to change +accordingly the other functions in the diagnostic which are dealing at +the moment with Iris cubes.

+
+
+
+
+
+
+ +
+
+

Reading data using the netCDF4 package

+
+

Yet another option to read the NetCDF file data is to use the +[netCDF-4 Python interface][netCDF] to the netCDF C library.

+
+
+
+
+
+ +
+
+

First, import the netCDF4 package at the top of the +script as:

+
+

PYTHON +

+
import netCDF4
+
+

Then, change compute_diagnostic as:

+
+

PYTHON +

+
def compute_diagnostic(filename):
+   """Compute an example diagnostic."""
+   logger.debug("Loading %s", filename)
+   nc_data = netCDF4.Dataset(filename,'r')
+
+   #do your analyses on the data here
+
+   return nc_data
+
+

Caution: If you read data using netCDF4 keep in mind to change +accordingly the other functions in the diagnostic which are dealing at +the moment with Iris cubes.

+
+
+
+
+

Diagnostic output

+
+

Plotting the output

+

Often, the end product of a diagnostic script is a plot or figure. +The Iris cube returned from the compute_diagnostic function +(line 93) is passed to the plot_diagnostic function (line +102). Let’s have a look at the definition of this function in line 52. +This is where we would plug in our plotting routine in the diagnostic +script.

+

More specifically, the quickplot function (line 60) can +be replaced with the function of our choice. As can be seen, this +function uses **cfg['quickplot'] as an input argument. If +you look at the diagnostic section in the recipe +recipe_python.yml, you see quickplot is a key +there:

+
+

YAML +

+
     script1:
+       script: examples/diagnostic.py
+        quickplot:
+          plot_type: pcolormesh
+          cmap: Reds
+
+

This way, we can pass arguments such as the type of plot +pcolormesh and the colormap cmap:Reds from the +recipe to the quickplot function in the diagnostic.

+
+
+ +
+
+

Passing arguments from the recipe to the diagnostic

+
+

Change the type of the plot and its colormap and inspect the output +figure.

+
+
+
+
+
+ +
+
+

In the recipe recipe_python.yml, you could change +plot_type and cmap. As an example, we choose +plot_type: pcolor and cmap: BuGn:

+
+

YAML +

+
    script1:
+      script: examples/diagnostic.py
+       quickplot:
+         plot_type: pcolor
+         cmap: BuGn
+
+

The plot can be found at +path_to_recipe_output/plots/map/script1/png.

+
+
+
+
+ +
+
+

Saving the output

+

In our example, the function save_data in line 56 is +used to save the Iris cube. The saved files can be found under the +work directory in a .nc format. There is also +the function save_figure in line 62 to save the plots under +the plot directory in a .png format (or +preferred format specified in your configuration settings). Again, you +may choose your own method of saving the output.

+
+
+

Recording the provenance

+

When developing a diagnostic script, it is good practice to record +provenance. To do so, we use the function +get_provenance_record (line 100). Let us have a look at the +definition of this function in line 21 where we describe the diagnostic +data and plot. Using the dictionary record, it is possible +to add custom provenance to our diagnostics output. Provenance is stored +in the W3C PROV +XML format and also in an SVG file under the +work and plot directory. For more information, +see [recording provenance][provenance].

+
+

Congratulations!

+

You now know the basic diagnostic script structure and some available +tools for putting together your own diagnostics. Have a look at existing +recipes and diagnostics in the repository for more examples of functions +you can use in your diagnostics!

+
+
+ +
+
+

Key Points

+
+
  • ESMValTool provides helper functions to interface a Python +diagnostic script with preprocessor output.
  • +
  • Existing diagnostics can be used as templates and modified to write +new diagnostics.
  • +
  • Helper functions can be imported from +esmvaltool.diag_scripts.shared and used in your own +diagnostic script.
  • +
+
+
+
+
+ + +
+
+ + + diff --git a/09-cmorization.html b/09-cmorization.html new file mode 100644 index 00000000..2c69dca2 --- /dev/null +++ b/09-cmorization.html @@ -0,0 +1,1493 @@ + +ESMValTool Tutorial: CMORization: adding new datasets to ESMValTool +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

CMORization: adding new datasets to ESMValTool

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • CMORization: what is it and why do we need it?
  • +
  • How to use the existing CMORizer scripts shipped with +ESMValTool?
  • +
  • How to add support for new (observational) datasets?
  • +
+
+
+
+
+
+

Objectives

+
  • Understand what CMORization is and why it is necessary.
  • +
  • Use existing scripts to CMORize your data.
  • +
  • Write a new CMORizer script to support additional data.
  • +
+
+
+
+
+
Data flow with ESMValTool

Introduction

+

This episode deals with “CMORization”. ESMValTool is designed to work +with data that follow the CMOR standards. Unfortunately, not all +datasets follow these standards. In order to use such datasets in +ESMValTool we first need to reformat the data. This process is called +“CMORization”.

+
+
+ +
+
+

What are the CMOR standards?

+
+

The name “CMOR” originates from a tool: the Climate Model Output Rewriter. +This tool is used to create “CF-Compliant netCDF files for use in the +CMIP projects”. So CMOR extends the CF-standard with additional +requirements for the Coupled Model Intercomparison Projects (see e.g. here).

+

Concretely, the CMOR standards dictate e.g. the variable names and +units, coordinate information, how the data should be structured (e.g. 1 +variable per file), additional metadata requirements, and file naming +conventions a.k.a. the data reference syntax (DRS). +All this information is stored in so-called CMOR tables. For example, +the CMOR tables for the CMIP6 project can be found here.

+
+
+
+

ESMValTool offers two ways to CMORize data:

+
  1. A reformatting script can be used to create a CMOR-compliant copy. +CMORizer scripts for several popular datasets are included in +ESMValTool, and ESMValTool also provides a convenient way to execute +them.
  2. +
  3. ESMValCore can execute CMOR fixes ‘[on the fly](https://docs.esmvaltool.org/projects/esmvalcore/en/latest/develop/ +fixing_data.html#fixing-data)’. The advantage is that you don’t need to +store an additional, reformatted copy of the data. The disadvantage is +that these fixes should be implemented inside ESMValCore, which is +beyond the scope of this tutorial.
  4. +

In this lesson, we will re-implement a CMORizer script for the +FLUXCOM dataset that contains observations of the Gross Primary +Production (GPP), a variable that is important for calculating +components of the global carbon cycle. See the next section on how to +obtain data.

+

As in the previous episode (Development and Contribution +episode ), we will be using the development installation of +ESMValTool.

+

Obtaining the data

+

The data for this episode is available via the FluxCom Data +Portal. First you’ll need to register. After registration, in the +dropdown boxes, select FLUXCOM as the data choice and click download. +Three files will be displayed. Click the download button on the “FLUXCOM +(RS+METEO) Global Land Carbon Fluxes using CRUNCEP climate data”. You’ll +receive an email with the FTP address to access the server. Connect to +the server, follow the path in your email, and look for the file +raw/monthly/GPP.ANN.CRUNCEPv6.monthly.2000.nc. Download +that file and save it in a folder called +~/data/RAWOBS/Tier3/FLUXCOM.

+

Note: you’ll need a user-friendly ftp client. On Linux, +ncftp works okay.

+
+
+ +
+
+

What is the deal with those “tiers”?

+
+

Many datasets come with access restrictions. In this way the data +providers can keep track of how their data is used. In many cases +“restricted access” just means that one has to register with an email +address and accept the terms of use, which typically ask that you +acknowledge the data providers.

+

There are also datasets available that do not need a registration. +The “obs4MIPs” or “ana4MIPs” datasets, for example, are specifically +produced to facilitate comparisons with model simulations.

+

To reflect these different levels of access restriction, the +ESMValTool team has created a tier-system. The definition of the +different tiers are as follows:

+
  • +Tier1: obs4MIPs and ana4MIPS datasets (can be used +directly with the ESMValTool)
  • +
  • +Tier2: other freely available datasets (most of +them will need some kind of cmorization)
  • +
  • +Tier3: datasets with access restrictions (most of +these datasets will also need some kind of cmorization)
  • +

These access restrictions are also why the ESMValTool developers +cannot distribute copies or automate downloading of all observations and +reanalysis data used in the recipes. As a compromise, we provide the +CMORization scripts so that each user can CMORize their own copy of the +access restricted datasets if needed.

+
+
+
+

Run the existing CMORizer script

+

Before we develop our own CMORizer script, let’s first see what +happens when we run the existing one. There is a specific command +available in the ESMValTool to run the CMORizer scripts:

+
+

BASH +

+
esmvaltool data format --config_file <path to config-user.yml>  <dataset-name>
+
+

The config-user.yml is the file in which we define the +different data paths, see the episode on Configuration. In the +rootpath of your config-user.yml, make sure to +add the right directory for “RAWOBS” data in which you downloaded the +FLUXCOM dataset:

+
+

YAML +

+
rootpath:
+  RAWOBS: ~/data/RAWOBS
+
+

This enables ESMValTool to find the raw observational datasets stored +in the “RAWOBS” folder. The dataset-name needs to be +identical to the folder name that was created to store the raw +observation data files, i.e. RAWOBS/TierX/dataset-name. In +our case this would be “FLUXCOM”.

+

If everything is okay, the output should look something like +this:

+
+

OUTPUT +

+
...
+... Starting the CMORization Tool at time: 2022-07-26 14:02:16 UTC
+... ----------------------------------------------------------------------
+... input_dir  = /home/peter/data/RAWOBS
+... output_dir = /home/peter/esmvaltool_output/data_formatting_20220726_140216
+... ----------------------------------------------------------------------
+... Running the CMORization scripts.
+... Processing datasets ['FLUXCOM']
+... Input data from: /home/peter/data/RAWOBS/Tier3/FLUXCOM
+... Output will be written to: /home/peter/esmvaltool_output/
+      data_formatting_20220726_140216/Tier3/FLUXCOM
+... Reformat script: /home/peter/mambaforge/envs/esmvaltool/lib/python3.9/
+      site-packages/esmvaltool/cmorizers/data/formatters/datasets/fluxcom
+... CMORizing dataset FLUXCOM using Python script /home/peter/mambaforge/envs/
+      esmvaltool/lib/python3.9/site-packages/esmvaltool/cmorizers/data/formatters/
+      datasets/fluxcom.py
+... Found input file '/home/peter/data/RAWOBS/Tier3/FLUXCOM/GPP.ANN.CRUNCEPv6.monthly.*.nc'
+... CMORizing variable 'gpp'
+... Lmon
+... Var is gpp
+... ... UserWarning: Ignoring netCDF variable 'GPP' invalid units 'gC m-2 day-1'
+
+... Fixing time...
+... Fixing latitude...
+... Fixing longitude...
+... Flipping dimensional coordinate latitude...
+... Saving file
+... Saving: /home/peter/esmvaltool_output/data_formatting_20220726_140216/Tier3/
+      FLUXCOM/OBS_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_200001-200012.nc
+... Cube has lazy data [lazy is preferred]
+... CMORization of dataset FLUXCOM finished!
+... Formatting successful for dataset FLUXCOM
+
+

So you can see that several fixes are applied, and the CMORized file +is written to the ESMValTool output directory, i.e. +~/esmvaltool_output/data_formatting_YYYYMMDD_HHMMSS/TierX/dataset-name/filename.nc +In order to use it, we’ll have to copy it from the output directory to a +folder called ~/data/OBS/Tier3/FLUXCOM and make sure the +path to OBS is set correctly in our config-user file:

+
+

YAML +

+
rootpath:
+  OBS: ~/data/OBS
+
+

You can also see the path where ESMValTool stores the reformatting +script: +~/ESMValTool/esmvaltool/data/formatters/datasets/fluxcom.py. +You may have a look at this file if you want. The script also uses a +configuration file: +~/ESMValTool/esmvaltool/cmorizers/data/cmor_config/FLUXCOM.yml.

+

Make a test recipe

+

To verify that the data is correctly CMORized, we will make a simple +test recipe. As illustrated in the figure at the top of this episode, +one of the steps that ESMValTool executes is a CMOR-check. If the data +is not correctly CMORized, ESMValTool will give a warning or error.

+
+
+ +
+
+

Create a test recipe

+
+

Create a simple recipe called recipe_check_fluxcom.yml that +loads the FLUXCOM data. It should include a datasets section with a +single entry for the “FLUXCOM” dataset with the correct dataset keys, +and a diagnostics section with two variables: gpp. We don’t need any +preprocessors or scripts (set scripts: null), but we have +to add a documentation section with a description, authors and +maintainer, otherwise the recipe will fail.

+

Use the following dataset keys:

+
  • project: OBS
  • +
  • dataset: FLUXCOM
  • +
  • type: reanaly
  • +
  • version: ANN-v1
  • +
  • mip: Lmon
  • +
  • start_year: 2000
  • +
  • end_year: 2000
  • +
  • tier: 3
  • +

Some of these dataset keys are further explained in the callout boxes +in this episode.

+
+
+
+
+
+ +
+
+

Here’s an example recipe

+
+

YAML +

+
documentation:
+
+  description: Test recipe for FLUXCOM data
+  title: This is a test recipe for the FLUXCOM data.
+
+  authors:
+    - kalverla_peter
+
+  maintainer:
+    - kalverla_peter
+
+datasets:
+  - {project: OBS, dataset: FLUXCOM, mip: Lmon, tier: 3, start_year: 2000, 
+     end_year: 2000, type: reanaly, version: ANN-v1}
+
+diagnostics:
+  check_fluxcom:
+    description: Check that ESMValTool can load the cmorized fluxnet data without errors.
+    variables:
+      gpp:
+    scripts: null
+
+

To learn more about writing a recipe, please refer to Writing your own recipe.

+
+
+
+
+

Try to run the example recipe with

+
+

BASH +

+
esmvaltool run recipe_check_fluxcom.yml --config_file <path to config-user.yml> --log_level debug
+
+

If everything is okay, the recipe should run without problems.

+

Starting from scratch

+

Now that you’ve seen how to use an existing CMORizer script, let’s +think about adding a new one. We will remove the existing CMORizer +script, and re-implement it from scratch. This exercise allows us to +point out all the details of what’s going on. We’ll also remove the +CMORized data that we’ve just created, so our test recipe will not be +able to use it anymore.

+
+

BASH +

+
rm ~/data/OBS/Tier3/FLUXCOM/OBS_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_200001-200012.nc
+rm ~/ESMValTool/esmvaltool/cmorizers/data/formatters/datasets/fluxcom.py
+rm ~/ESMValTool/esmvaltool/cmorizers/data/cmor_config/FLUXCOM.yml
+
+

If you now run the test recipe again it should fail, and somewhere in +the output you should find something like:

+
+

ERROR +

+
No input files found for ...
+Looked for files matching: /home/peter/data/OBS/Tier3/
+      FLUXCOM/OBS_FLUXCOM_reanaly_ANN-v1_Lmon_gpp[_.]*nc
+
+

From this we can see that the first thing our CMORizer should do is +to rename the file so that it follows the CMOR filename conventions.

+

Create a new CMORizer script and a corresponding config file

+

The first step now is to create a new file in the right folder that +will contain our new CMORizer instructions. Create a file called +fluxcom.py

+
+

BASH +

+
nano ~/ESMValTool/esmvaltool/cmorizers/data/formatters/datasets/fluxcom.py
+
+

and fill it with the following boilerplate code:

+
+

PYTHON +

+
"""ESMValTool CMORizer for FLUXCOM GPP data.
+
+<We will add some useful info here later>
+"""
+import logging
+from esmvaltool.cmorizers.data import utilities as utils
+
+logger = logging.getLogger(__name__)
+
+def cmorization(in_dir, out_dir, cfg, cfg_user, start_date, end_date):
+    """Cmorize the dataset."""
+
+    # This is where you'll add the cmorization code
+    # 1. find the input data
+    # 2. apply the necessary fixes
+    # 3. store the data with the correct filename
+
+

Here, in_dir corresponds to the input directory of the +raw files, out_dir to the output directory of final +reformatted data set and cfg to a configuration dictionary +given by a configuration file that we will get to shortly. The last +three arguments will not be considered in this script but can be used in +other cases. cfg_user corresponds to the user configuration +file, start_date to the start of the period to format, and +end_date to the end of the period to format. When you type +the command esmvaltool data format in the terminal, +ESMValTool will call this function with the settings found in your +configuration files.

+

The ESMValTool CMORizer also needs a dataset configuration file. +Create a file called +~/ESMValTool/esmvaltool/cmorizers/data/cmor_config/FLUXCOM.yml +and fill it with the following boilerplate:

+
+

YAML +

+
---
+# filename: ???
+
+attributes:
+  project_id: OBS6
+#   dataset_id: ???
+#   version: ???
+#   tier: ???
+#   modeling_realm: ???
+#   source: ???
+#   reference: ???
+#   comment: ???
+
+# variables:
+#   ???:
+#     mip: ???
+
+

Note: the name of this file must be +identical to dataset-name.

+

As you can see, the configuration file contains information about the +original filename of the dataset, and some additional metadata that you +might recognize from the CMOR filename structure. It also contains a +list of variables that’s available for this dataset. We’ll add this +information step by step in the following sections.

+
+
+ +
+
+

RAWOBS, OBS, OBS6!?

+
+

In the configuration above we’ve already filled in the +project_id. ESMValTool uses these project IDs to find the +data on your hard drive, and also to find more information about the +data. The RAWOBS and OBS projects refer to +external data before and after CMORization, respectively. +Historically, most external data were observations, hence the +naming.

+

In going from CMIP5 to CMIP6, the CMOR standards changed a bit. For +example, some variables were renamed, which posed a dilemma: should +CMORization reformat to the CMIP5 or CMIP6 definition? To solve this, +the OBS6 project was created. So OBS6 data +follow the CMIP6 standards, and that’s what we’ll use for the new +CMORizer.

+
+
+
+

You can try running the CMORizer at this point, and it should work +without errors. However, it doesn’t produce any output yet:

+
+

BASH +

+
esmvaltool data format --config_file <path to config-user.yml> FLUXCOM
+
+
+

1. Find the input data

+

First we’ll get the CMORizer script to locate our FLUXCOM data. We +can use the information from the in_dir and +cfg variables. Add the following snippet to your CMORizer +script:

+
+

PYTHON +

+
# 1. find the input data
+logger.info("in_dir: '%s'", in_dir)
+logger.info("cfg: '%s'", cfg)
+
+

If you run the CMORizer again, it will print out the content of these +variables and the output should contain something like this:

+
+

OUTPUT +

+
... in_dir: '/home/peter/data/RAWOBS/Tier3/FLUXCOM'
+... cfg: '{'attributes': {'project_id': 'OBS6', 'comment': ''},
+    'cmor_table': <esmvalcore.cmor.table.CMIP6Info object at 0x7fbd0a0f6bf0>}'
+
+
+
+ +
+
+

Load the data

+
+

Try to locate the input data inside the CMORizer script and load it +(we’ll use iris because ESMValTool includes helper +utilities for iris cubes). Confirm that you’ve loaded the data by +logging the correct path and (part of the) file content.

+
+
+
+
+
+ +
+
+

There are many ways to do it. In any case, you should have added the +original filename to the configuration file (and un-commented this +line): filename: 'GPP.ANN.CRUNCEPv6.monthly.*.nc'. Note the +*: this is a useful shorthand to find multiple files for +different years. In a similar way we can also look for multiple +variables, etc.

+

Here’s an example solution (inserted directly under the original +comment):

+
+

PYTHON +

+
# 1. find the input data
+filename_pattern = cfg['filename']
+matches = Path(in_dir).glob(filename_pattern)
+
+for match in matches:
+    input_file = str(match)
+    logger.info("found: %s", input_file)
+    cube = iris.load_cube(input_file)
+    logger.info("content: %s", cube)
+
+

To make this work we’ve added import iris and +from pathlib import Path at the top of the file. Note that +we’ve started a loop, since we may find multiple files if there’s more +than one year of data available.

+
+
+
+
+
+
+

2. Save the data with the correct filename

+

Before we start adding fixes, we’ll first make sure that our CMORizer +can also write output files with the correct name. This will enable us +to use the test recipe for the CMOR compatibility check.

+

We can use the save function from the utils +that we imported at the top. The call signature looks like this: +utils.save_variables(cube, var, outdir, attrs, **kwargs).

+

We already have the cube and the outdir. +The variable short name (var) and attributes +(attrs) are set through the configuration file. So we need +to find out what the correct short name and attributes are.

+

The standard attributes for CMIP variables are defined in the CMIP +tables. These tables are differentiated according to the “MIP” they +belong to. The tables are a copy of the PCMDI guidelines.

+
+
+ +
+
+

Find the variable “gpp” in a CMOR table

+
+

Check the available CMOR tables to find the variable “gpp” with the +following characteristics:

+
  • standard_name: +gross_primary_productivity_of_biomass_expressed_as_carbon +
  • +
  • frequency: mon +
  • +
  • modeling_realm: land +
  • +
+
+
+
+
+ +
+
+

The variable “gpp” belongs to the land variables. The temporal +resolution that we are looking for is “monthly”. This information points +to the “Lmon” CMIP table. And indeed, the variable “gpp” can be found in +the file [here](https://github.com/ESMValGroup/ESMValCore/blob/main/esmvalcore/ +cmor/tables/cmip6/Tables/CMIP6_Lmon.json).

+
+
+
+
+

If the variable you are interested in is not available in the +standard CMOR tables, you could write a custom CMOR table entry for the +variable. This, however, is beyond the scope of this tutorial.

+
+
+ +
+
+

Fill the configuration file

+
+

Uncomment the following entries in your configuration file and fill +them with appropriate values:

+
  • dataset_id
  • +
  • version
  • +
  • tier
  • +
  • modeling_realm
  • +
  • short_name (the ??? immediately under variables)
  • +
  • mip
  • +
+
+
+
+
+ +
+
+

The configuration file now look something like this:

+
+

YAML +

+
---
+filename: 'GPP.ANN.CRUNCEPv6.monthly.*.nc'
+
+attributes:
+  project_id: OBS6
+  dataset_id: FLUXCOM
+  version: 'ANN-v1'
+  tier: 3
+  modeling_realm: reanaly
+  source: ''
+  reference: ''
+  comment: ''
+
+variables:
+  gpp:
+    mip: Lmon
+
+
+
+
+
+

Now that we have set this information correctly in the config file, +we can call the save function. Add the following python code to your +CMORizer script:

+
+

PYTHON +

+
# 3. store the data with the correct filename
+attributes = cfg['attributes']
+variables = cfg['variables']
+
+for short_name, variable_info in variables.items():
+    all_attributes = {**attributes, **variable_info}  # add the mip to the other attributes
+    utils.save_variable(cube=cube, var=short_name, outdir=out_dir, attrs=all_attributes)
+
+

Since we only have one variable (gpp), the loop is not strictly +necessary. However, this makes it possible to add more variables later +on.

+
+
+ +
+
+

Was the CMORization successful so far?

+
+

If you run the CMORizer again, you should see that it creates an +output file named +OBS6_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_xxxx01-xxxx12.nc +stored in your ESMValTool output directory +~/esmvaltool_output/data_formatting_YYYYMMDD_HHMMSS/Tier3/FLUXCOM/. +The “xxxx” and “yyyy” represent the start and end year of the data.

+
+
+
+

Great! So we have produced a NetCDF file with the CMORizer that +follows the naming convention for ESMValTool datasets. Let’s have a look +at the NetCDF file as it was written with the very basic CMORizer from +above.

+
+

BASH +

+
ncdump -h OBS6_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_200001-200012.nc
+
+
+

OUTPUT +

+
netcdf OBS6_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_200001-200012 {
+dimensions:
+        time = 12 ;
+        lat = 360 ;
+        lon = 720 ;
+variables:
+        float GPP(time, lat, lon) ;
+                GPP:_FillValue = 1.e+20f ;
+                GPP:long_name = "GPP" ;
+        double time(time) ;
+                time:axis = "T" ;
+                time:units = "days since 1582-10-15 00:00:00" ;
+                time:standard_name = "time" ;
+                time:calendar = "gregorian" ;
+        double lat(lat) ;
+        double lon(lon) ;
+
+// global attributes:
+                :_NCProperties = "version=2,netcdf=4.7.4,hdf5=1.10.6" ;
+                :created_by = "Fabian Gans [fgans@bgc-jena.mpg.de], Ulrich Weber
+		  [uweber@bgc-jena.mpg.de]" ;
+                :flux = "GPP" ;
+                :forcing = "CRUNCEPv6" ;
+                :institution = "MPI-BGC-BGI" ;
+                :invalid_units = "gC m-2 day-1" ;
+                :method = "Artificial Neural Networks" ;
+                :provided_by = "Martin Jung [mjung@bgc-jena.mpg.de] on behalf of FLUXCOM team" ;
+                :reference = "Jung et al. 2016, Nature; Tramontana et al. 2016, Biogeosciences" ;
+                :temporal_resolution = "monthly" ;
+                :title = "GPP based on FLUXCOM RS+METEO with CRUNCEPv6 climate " ;
+                :version = "v1" ;
+                :Conventions = "CF-1.7" ;
+}
+
+

The file contains a variable named “GPP” that contains three +dimensions: “time”, “lat”, “lon”. Notice the strange time units, and the +invalid_units in the global attributes section. Also it +seems that there is not information available about the lat and lon +coordinates. These are just some of the things we’ll address in the next +section.

+
+
+

3. Implementing additional fixes

+

Copy the output of the CMORizer to your folder +~/data/OBS6/Tier3/FLUXCOM/ and change the test recipe to +look for OBS6 data instead of OBS (note: we’re upgrading the CMORizer to +newer standards here!). Make sure the path to OBS6 is set +correctly in our config-user file:

+
+

YAML +

+
rootpath:
+  OBS6: ~/data/OBS6
+
+

If we now run the test recipe on our newly ‘CMORized’ data,

+
+

BASH +

+
esmvaltool run recipe_check_fluxcom.yml --config_file <path to config-user.yml> --log_level debug
+
+

it should be able to find the correct file, but it does not succeed +yet. The first thing that the ESMValTool CMOR checker brings up is:

+
+

ERROR +

+
iris.exceptions.UnitConversionError: Cannot convert from unknown units. The
+"units" attribute may be set directly.
+
+

If you look closely at the error messages, you can see that this +error concerns the units of the coordinates. ESMValTool tries to fix +them automatically, but since no units are defined on the coordinates, +this fails.

+

The cmorizer utilities also include a function called +fix_coords, but before we can use it, we’ll also need to +make sure the coordinates have the correct standard name. Add the +following code to your cmorizer:

+
+

PYTHON +

+
# 2. Apply the necessary fixes
+# 2a. Fix/add coordinate information and metadata
+cube.coord('lat').standard_name = 'latitude'
+cube.coord('lon').standard_name = 'longitude'
+utils.fix_coords(cube)
+
+

With some additional refactoring, our cmorization function might then +look something like this:

+
+

PYTHON +

+
def cmorization(in_dir, out_dir, cfg, cfg_user, start_date, end_date):
+    """Cmorize the dataset."""
+
+    # Get general information from the config file
+    attributes = cfg['attributes']
+    variables = cfg['variables']
+
+    for short_name, variable_info in variables.items():
+        logger.info("CMORizing variable: %s", short_name)
+
+        # 1a. Find the input data (one file for each year)
+        filename_pattern = cfg['filename']
+        matches = Path(in_dir).glob(filename_pattern)
+
+        for match in matches:
+            # 1b. Load the input data
+            input_file = str(match)
+            logger.info("found: %s", input_file)
+            cube = iris.load_cube(input_file)
+
+            # 2. Apply the necessary fixes
+            # 2a. Fix/add coordinate information and metadata
+            cube.coord('lat').standard_name = 'latitude'
+            cube.coord('lon').standard_name = 'longitude'
+            utils.fix_coords(cube)
+
+            # 3. Save the CMORized data
+            all_attributes = {**attributes, **variable_info}
+            utils.save_variable(cube=cube, var=short_name, outdir=out_dir, attrs=all_attributes)
+
+

Run the CMORizer script once more. Have a look at the netCDF file, +and confirm that the coordinates now have much more metadata added to +them. Then, run the test recipe again with the latest CMORizer output. +The next error is:

+
+

ERROR +

+
esmvalcore.cmor.check.CMORCheckError: There were errors in variable GPP:
+Variable GPP units unknown can not be converted to kg m-2 s-1 in cube:
+
+

Okay, so let’s fix the units of the “GPP” variable in the CMORizer. +Remember that you can find the correct units in the CMOR table. Add the +following three lines to our CMORizer:

+
+

PYTHON +

+
# 2b. Fix gpp units
+logger.info("Changing units for gpp from gc/m2/day to kg/m2/s")
+cube.data = cube.core_data() / (1000 * 86400)
+cube.units = 'kg m-2 s-1'
+
+

If everything is okay, the test recipe should now pass. We’re getting +there. Looking through the output though, there’s still a warning.

+
+

OUTPUT +

+
WARNING There were warnings in variable GPP:
+Standard name for GPP changed from None to gross_primary_productivity_of_biomass_expressed_as_carbon
+Long name for GPP changed from GPP to Carbon Mass Flux out of Atmosphere Due to
+      Gross Primary Production on Land [kgC m-2 s-1]
+
+

ESMValTool is able to apply automatic fixes here, but if we are +running a CMORizer script anyway, we might as well fix it +immediately.

+

Add the following snippet:

+
+

PYTHON +

+
# 2c. Fix metadata
+cmor_table = cfg['cmor_table']
+cmor_info = cmor_table.get_variable(variable_info['mip'], short_name)
+utils.fix_var_metadata(cube, cmor_info)
+
+

You can see that we’re using the CMOR table here. This was passed on +by ESMValTool as part of the CFG input variable. So here +we’re making sure that we’re updating the cubes metadata to conform to +the CMOR table.

+

Finally, the test recipe should run without errors or warnings.

+
+
+

4. Finalizing the CMORizer

+

Once everything works as expected, there’s a couple of things that we +can still do.

+
  • +Add download instructions. The header of the +CMORizer contains information about where to obtain the data, when it +was accessed the last time, which ESMValTool “tier” it is associated +with, and more detailed information about the necessary downloading and +processing steps.
  • +
+
+ +
+
+

Fill out the header for the “FLUXCOM” dataset

+
+

Fill out the header of the new CMORizer. The different parts that +need to be present in the header are the following:

+
  • Caption: the first line of the docstring should summarize what the +script does.
  • +
  • Tier
  • +
  • Source
  • +
  • Last access
  • +
  • Download and processing instructions
  • +
+
+
+
+
+ +
+
+

The header for the “FLUXCOM” dataset could look something like +this:

+
+

PYTHON +

+
"""ESMValTool CMORizer for FLUXCOM GPP data.
+
+Tier
+    Tier 3: restricted dataset.
+
+Source
+    http://www.bgc-jena.mpg.de/geodb/BGI/Home
+
+Last access
+    20190727
+
+Download and processing instructions
+    From the website, select FLUXCOM as the data choice and click download.
+    Two files will be displayed. One for Land Carbon Fluxes and one for
+    Land Energy fluxes. The Land Carbon Flux file (RS + METEO) using
+    CRUNCEP data file has several data files for different variables.
+    The data for GPP generated using the
+    Artificial Neural Network Method will be in files with name:
+    GPP.ANN.CRUNCEPv6.monthly.\*.nc
+    A registration is required for downloading the data.
+    Users in the UK with a CEDA-JASMIN account may request access to the jules
+    workspace and access the data.
+    Note : This data may require rechunking of the netcdf files.
+    This constraint will not exist once iris is updated to
+    version 2.3.0 Aug 2019
+"""
+
+
+
+
+
+
  • +Fill the dataset information list. The file +[datasets.yml](https://github.com/ESMValGroup/ESMValTool/blob/main/esmvaltool/ +cmorizers/data/datasets.yml) contains the ESMValTool “tier”, the data +source, the last access time and download instructions for all supported +datasets in ESMValTool. You can simply reuse the information written in +the header of the CMORizer.
  • +
+
+ +
+
+

Fill out the FLUXCOM entry in +datasets.yml +

+
+

Fill out the FLUXCOM entry in datasets.yml. The +different parts that need to be present in the entry are the +following:

+
  • Dataset-name
  • +
  • Tier
  • +
  • Source
  • +
  • Last access
  • +
  • Download and processing instructions
  • +
+
+
+
+
+ +
+
+

The entry for the “FLUXCOM” dataset should look like:

+
+

YAML +

+
FLUXCOM:
+  tier: 3
+  source: http://www.bgc-jena.mpg.de/geodb/BGI/Home
+  last_access: 2019-07-27
+  info: |
+    From the website, select FLUXCOM as the data choice and click download.
+    Two files will be displayed. One for Land Carbon Fluxes and one for
+    Land Energy fluxes. The Land Carbon Flux file (RS + METEO) using
+    CRUNCEP data file has several data files for different variables.
+    The data for GPP generated using the
+    Artificial Neural Network Method will be in files with name:
+    GPP.ANN.CRUNCEPv6.monthly.*.nc
+    A registration is required for downloading the data.
+    Users in the UK with a CEDA-JASMIN account may request access to the jules
+    workspace and access the data.
+    Note : This data may require rechunking of the netcdf files.
+    This constraint will not exist once iris is updated to
+    version 2.3.0 Aug 2019
+
+
+
+
+
+

Once the datasets.yml file is filled, you can check that +ESMValTool can display information about the added dataset with:

+
+

BASH +

+
esmvaltool data info FLUXCOM
+
+

If everything is okay, the output should look something like +this:

+
+

OUTPUT +

+
 $ esmvaltool data info FLUXCOM
+FLUXCOM
+
+Tier: 3
+Source: http://www.bgc-jena.mpg.de/geodb/BGI/Home
+Automatic download: No
+
+From the website, select FLUXCOM as the data choice and click download.
+Two files will be displayed. One for Land Carbon Fluxes and one for
+Land Energy fluxes. The Land Carbon Flux file (RS + METEO) using
+CRUNCEP data file has several data files for different variables.
+The data for GPP generated using the
+Artificial Neural Network Method will be in files with name:
+GPP.ANN.CRUNCEPv6.monthly.*.nc
+A registration is required for downloading the data.
+Users in the UK with a CEDA-JASMIN account may request access to the jules
+workspace and access the data.
+Note : This data may require rechunking of the netcdf files.
+This constraint will not exist once iris is updated to
+version 2.3.0 Aug 2019
+
+

Note that Automatic download: No means that no automatic +downloading script is available in ESMValTool for this dataset. The +implementation of such a script is beyond the scope of this tutorial. To +find out which datasets come with an automatic download script, you can +run: esmvaltool data list to list all datasets supported in +ESMValTool. More information about the usage of automatic downloading +scripts can be found in the User +Guide.

+
  • +Complete the metadata in the config file. We have +left a few fields empty in the configuration file, such as ‘source’. By +filling out these fields we can make sure the relevant metadata is +passed on as attributes in the CMORized data. To make this work, add the +following line to the CMORizer script:
  • +
+

PYTHON +

+
# 2d. Update the cubes metadata with all info from the config file
+utils.set_global_atts(cube, attributes)
+
+
  • Add a reference. Make sure that there is a +reference file available for the dataset, see the instruction [here](https://docs.esmvaltool.org/en +/latest/community/diagnostic.html#adding-references).

  • +
  • Make a pull request. Since you have gone through +all the trouble to reformat the dataset so that the ESMValTool can work +with it, it would be great if you could provide the CMORizer, and +ultimately with that the dataset, to the rest of the community. For more +information, see the episode on Development and +contribution.

  • +
  • Add documentation. Make sure that you have added +the info of your dataset to the User Guide so that people know it is +available for the ESMValTool Obtaining +input data.

  • +
+

Some final comments

+

Congratulations! You have just added support for a new dataset to +ESMValTool! Adding a new CMORizer is definitely already an advanced task +when working with the ESMValTool. You need to have a basic understanding +of how the ESMValTool works and how it’s internal structure looks like. +In addition, you need to have a basic understanding of NetCDF files and +a programming language. In our example we used python for the CMORizing +script since we advocate for focusing the code development on only a few +different programming languages. This helps to maintain the code and to +ensure the compatibility of the code with possible fundamental changes +to the structure of the ESMValTool and ESMValCore.

+

More information about adding observations to the ESMValTool can be +found in the documentation.

+
+
+ +
+
+

Key Points

+
+
  • CMORizers are dataset-specific scripts that can be run once to +generate CMOR-compliant data.
  • +
  • ESMValTool comes with a set of CMORizers readily available, but you +can also add your own.
  • +
+
+
+
+
+ + +
+
+ + + diff --git a/10-debugging.html b/10-debugging.html new file mode 100644 index 00000000..d9cd46d8 --- /dev/null +++ b/10-debugging.html @@ -0,0 +1,1033 @@ + +ESMValTool Tutorial: Debugging +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Debugging

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • How can I handle errors/warnings?
  • +
+
+
+
+
+
+

Objectives

+
  • Fix a broken recipe
  • +
+
+
+
+
+

Every user encounters errors. Once you know why you get certain types +of errors, they become much easier to fix. The good news is that +ESMValTool creates a record of the output messages and stores them in +log files. They can be used for debugging or monitoring the process. +This lesson helps you understand the different types of errors and when +you are likely to encounter them.

+

Log files

+

Each time we run ESMValTool, it will produce a new output directory. +This directory should contain the run folder that is +automatically generated by ESMValTool. To examine this, we run a +recipe_python.yml that can be found in lesson Running your first recipe . Check lesson Configuration on how to set paths.

+

In a new terminal, run the recipe:

+
+

BASH +

+
  cd esmvaltool_tutorial
+  esmvaltool run examples/recipe_python.yml
+
+
+

ERROR +

+
esmvaltool: command not found
+
+

ESMValTool encounters this error because the conda environment +esmvaltool has not been activated. To fix the error, before +running the recipe, activate the environment:

+
+

BASH +

+
conda activate esmvaltool
+
+
+
+ +
+
+

conda environment

+
+

More information about the conda environment can be found at Installation.

+
+
+
+

Let’s list the files in the run directory:

+
+

BASH +

+
  ls esmvaltool_output/recipe_python_#_#/run
+
+
+

OUTPUT +

+
main_log_debug.txt  main_log.txt  map  recipe_python.yml  resource_usage.txt
+timeseries
+
+

In the main_log_debug.txt and main_log.txt, +ESMValTool writes the output messages, warnings and possible errors that +might happen during pre-processings. To inspect them, we can look inside +the files. For example:

+
+

BASH +

+
  cat esmvaltool_output/recipe_python_#_#/run/main_log.txt
+
+

Now, let’s have a look inside the folder +timeseries/script1:

+
+

BASH +

+
  ls esmvaltool_output/recipe_python_#_#/run/timeseries/script1/
+
+
+

OUTPUT +

+
diagnostic_provenance.yml log.txt  resource_usage.txt  settings.yml
+
+

In the log.txt, ESMValTool writes the output messages, +warnings and possible errors that are related to the diagnostic +script.

+

If you encounter an error and don’t know what it means, it is +important to read the log information. Sometimes knowing where the error +occurred is enough to fix it, even if you don’t entirely understand the +message. However, note that you may not always be able to find the error +or fix it. In that case, ESMValTool community helps you figure out what +went wrong.

+
+
+ +
+
+

Different log files

+
+

In the run directory, there are two log files +main_log_debug.txt and main_log.txt. What are +their differences?

+
+
+
+
+
+ +
+
+

The main_log_debug.txt contains the output messages from +the pre-processor whereas the main_log.txt shows general +errors and warnings that might happen in running the recipe and +diagnostics script.

+
+
+
+
+

Let’s change some settings in the recipe to run a regional +pre-processor. We use a text editor called nano to open the +recipe file:

+
+

BASH +

+
  nano recipe_python.yml
+
+
+
+ +
+
+

Text editor side note

+
+

No matter what editor you use, you will need to know where it +searches for and saves files. If you start it from the shell, it will +(probably) use your current working directory as its default location. +We use nano in examples here because it is one of the least +complex text editors. Press ctrl + O to save the +file, and then ctrl + X to exit +nano.

+
+
+
+
+
+ +
+
+
+

YAML +

+
# ESMValTool
+# recipe_python.yml
+---
+# See https://docs.esmvaltool.org/en/latest/recipes/recipe_examples.html
+# for a description of this recipe.
+
+# See https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/overview.html
+# for a description of the recipe format.
+---
+documentation:
+  description: |
+    Example recipe that plots a map and timeseries of temperature.
+
+  title: Recipe that runs an example diagnostic written in Python.
+
+  authors:
+    - andela_bouwe
+    - righi_mattia
+
+  maintainer:
+    - schlund_manuel
+
+  references:
+    - acknow_project
+
+  projects:
+    - esmval
+    - c3s-magic
+
+datasets:
+  - {dataset: BCC-ESM1, project: CMIP6, exp: historical, ensemble: r1i1p1f1, grid: gn}
+  - {dataset: bcc-csm1-1, version: v1, project: CMIP5, exp: historical, ensemble: r1i1p1}
+
+preprocessors:
+# See https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/preprocessor.html
+# for a description of the preprocessor functions.
+
+  to_degrees_c:
+    convert_units:
+      units: degrees_C
+
+  annual_mean_amsterdam:
+    extract_location:
+      location: Amsterdam
+      scheme: linear
+    annual_statistics:
+      operator: mean
+    multi_model_statistics:
+      statistics:
+        - mean
+      span: overlap
+    convert_units:
+      units: degrees_C
+
+  annual_mean_global:
+    area_statistics:
+      operator: mean
+    annual_statistics:
+      operator: mean
+    convert_units:
+      units: degrees_C
+
+diagnostics:
+
+  map:
+    description: Global map of temperature in January 2000.
+    themes:
+      - phys
+    realms:
+      - atmos
+    variables:
+      tas:
+        mip: Amon
+        preprocessor: to_degrees_c
+        timerange: 2000/P1M
+        caption: |
+          Global map of {long_name} in January 2000 according to {dataset}.
+    scripts:
+      script1:
+        script: examples/diagnostic.py
+        quickplot:
+          plot_type: pcolormesh
+          cmap: Reds
+
+  timeseries:
+    description: Annual mean temperature in Amsterdam and global mean since 1850.
+    themes:
+      - phys
+    realms:
+      - atmos
+    variables:
+      tas_amsterdam:
+        short_name: tas
+        mip: Amon
+        preprocessor: annual_mean_amsterdam
+        timerange: 1850/2000
+        caption: Annual mean {long_name} in Amsterdam according to {dataset}.
+      tas_global:
+        short_name: tas
+        mip: Amon
+        preprocessor: annual_mean_global
+        timerange: 1850/2000
+        caption: Annual global mean {long_name} according to {dataset}.
+    scripts:
+      script1:
+        script: examples/diagnostic.py
+        quickplot:
+          plot_type: plot
+
+
+
+
+
+

Keys and values in recipe settings

+

The [ESMValTool pre-processors](https://docs.esmvaltool.org/projects/ESMValCore/en/latest/ +recipe/preprocessor.html?highlight=preprocessor) cover a broad range of +operations on the input data, like time manipulation, area manipulation, +land-sea masking, variable derivation, etc. Let’s add the preprocessor +extract_region to a new section +annual_mean_regional:

+
+

YAML +

+
preprocessors:
+  annual_mean_regional:
+    annual_statistics:
+      operator: mean
+    extract_region:
+      start_longitude: -10
+      end_longitude: 40
+      start_latitude: 27
+      end_latitude: 70
+
+

Also, we change the projects value esmval +to tutorial:

+
+

YAML +

+
projects:
+  - tutorial
+  - c3s-magic
+
+

Then, we save the file and run the recipe:

+
+

BASH +

+
  esmvaltool run recipe_python.yml
+
+
+

ERROR +

+
ValueError: Tag 'tutorial' does not exist in section 'projects' of
+esmvaltool/config-references.yml 2020-06-29 18:09:56,641 UTC [46055] INFO If you
+have a question or need help, please start a new discussion on
+https://github.com/ESMValGroup/ESMValTool/discussions If you suspect this is a
+bug, please open an issue on https://github.com/ESMValGroup/ESMValTool/issues To
+make it easier to find out what the problem is, please consider attaching the
+files run/recipe_*.yml and run/main_log_debug.txt from the output directory.
+
+

The values for the keys author, maintainer, +projects and references in the recipe should +be known by ESMValTool:

+
+

ESMValTool can’t locate the +data

+

You are assisting a colleague with ESMValTool. The colleague replaces +the CanESM2 entry in +dataset: CanESM2, project: CMIP5 to ACCESS1-3 +and runs the recipe. However, ESMValTool encounters an error like:

+
+

BASH +

+
ERROR   No input files found for variable {'short_name': 'tas', 'mip': 'Amon',
+'preprocessor': 'annual_mean_amsterdam', 'variable_group': 'tas_amsterdam',
+'diagnostic': 'timeseries', 'dataset': 'ACCESS1-3', 'project': 'CMIP5', 'exp':
+'historical', 'ensemble': 'r1i1p1', 'recipe_dataset_index': 1, 'institute':
+['CSIRO-BOM'], 'product': ['output1', 'output2'], 'timerange': '1850/2000',
+'alias': 'CMIP5', 'original_short_name': 'tas', 'standard_name':
+'air_temperature', 'long_name': 'Near-Surface Air Temperature', 'units': 'K',
+'modeling_realm': ['atmos'], 'frequency': 'mon', 'start_year': 1850,
+'end_year': 2000}
+ERROR   Looked for files matching
+['tas_Amon_ACCESS1-3_historical_r1i1p1*.nc'], but did not find any existing
+input directory
+ERROR   Set 'log_level' to 'debug' to get more information
+
+
+

What suggestions would you give the researcher for fixing the +error?

+
+
+ +
+
+

Challenge

+
+ +
+
+
+
+
+ +
+
+
  1. Check user-config.yml to see if the correct directory +for input data is introduced
  2. +
  3. Check the available data, regarding exp, mip, ensemble, start_year, +and end_year
  4. +
  5. Check the variable names in the diagnostics section in the +recipe
  6. +
+
+
+
+

Check pre-processed data

+

The setting save_intermediary_cubes in the configuration +file can be used to save the pre-processed data. More information about +this setting can be found at Configuration.

+
+
+ +
+
+

save_intermediary_cubes

+
+

Note that this setting should only be used for debugging, as it +significantly slows down the recipe and increases disk usage because a +lot of output files need to be stored.

+
+
+
+

Check diagnostic script path

+

The result of the pre-processor is passed to the +examples/diagnostic.py script, that is introduced in the +recipe as:

+
+

YAML +

+
scripts:
+  script1:
+    script:
+      script: examples/diagnostic.py
+
+

The diagnostic scripts are located in the folder +diag_scripts in the ESMValTool installation directory +<path_to_esmvaltool>. To find where ESMValTool is +located on your system, see Installation .

+

Let’s see what happens if we can change the script path as:

+
+

YAML +

+
scripts:
+  script1:
+    script:
+      script: diag_scripts/ocean/diagnostic_timeseries.py
+
+
+

BASH +

+
  esmvaltool run examples/recipe_python.yml
+
+
+

ERROR +

+
esmvalcore._task.DiagnosticError: Cannot execute script
+'diag_scripts/ocean/diagnostic_timeseries.py'
+(~/mambaforge/envs/esmvaltool2.6/lib/python3.10/site-packages/esmvaltool/
+diag_scripts/diag_scripts/ocean/diagnostic_timeseries.py):
+file does not exist. 2022-10-18 11:42:34,136 UTC [39323] INFO If you have a
+question or need help, please start a new discussion on
+https://github.com/ESMValGroup/ESMValTool/discussions If you suspect this is a
+bug, please open an issue on https://github.com/ESMValGroup/ESMValTool/issues To
+make it easier to find out what the problem is, please consider attaching the
+files run/recipe_*.yml and run/main_log_debug.txt from the output directory.
+
+

The script path should be relative to diag_scripts +directory. It means that the script +diagnostic_timeseries.py is located in +<path_to_esmvaltool>/diag_scripts/ocean/. +Alternatively, the script path can be an absolute path. To examine this, +we can download the script from the ESMValTool +repository:

+
+

BASH +

+
wget https://raw.githubusercontent.com/ESMValGroup/ESMValTool/main/esmvaltool/\
+diag_scripts/ocean/diagnostic_timeseries.py
+
+

One way to get the absolute path is to run:

+
+

BASH +

+
readlink -f diagnostic_timeseries.py
+
+

Then we can update the script path :

+
+

YAML +

+
scripts:
+  script1:
+    script:
+      script: <path_to_script>/diagnostic_timeseries.py
+
+

Then, run the recipe again and examine the output to see +Run was successful!

+
+
+ +
+
+

Available recipe and diagnostic scripts

+
+

ESMValTool provides a broad suite of recipes +and diagnostic scripts for different disciplines like atmosphere, +climate metrics, future projections, IPCC, land, ocean, ….

+
+
+
+
+

Re-running a diagnostic

+

Look at the main_log.txt file and answer the following +question: How to re-run the diagnostic script?

+
+

Solution

+

The main_log.txt file contains information on how to +re-run the diagnostic script without re-running the pre-processors:

+
+
+
+

BASH +

+
2020-06-29 20:36:32,844 UTC [52810] INFO    To re-run this diagnostic script, run:
+
+
+
+ +
+
+

Challenge

+
+ +
+
+
+
+
+ +
+
+

If you run the command in a terminal, you will be able to re-run the +diagnostic.

+
+
+
+
+
+
+ +
+
+

Memory issues

+
+

If you run out of memory, try setting max_parallel_tasks +to 1 in the configuration file. Then, check the amount of memory you +need for that by inspecting the file run/resource_usage.txt +in the output directory. Using the number, there you can increase the +number of parallel tasks again to a reasonable number for the amount of +memory available in your system.

+
+
+
+
+
+ +
+
+

Key Points

+
+
  • There are three different kinds of log files: +main_log.txt, and main_log_debug.txt and +log.txt.
  • +
+
+
+
+
+ + +
+
+ + + diff --git a/404.html b/404.html new file mode 100644 index 00000000..abdbdd8a --- /dev/null +++ b/404.html @@ -0,0 +1,453 @@ + +ESMValTool Tutorial: Page not found +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Page not found

+ +

Our apologies!

+

We cannot seem to find the page you are looking for. Here are some +tips that may help:

+
  1. try going back to the previous +page or
  2. +
  3. navigate to any other page using the navigation bar on the +left.
  4. +
  5. if the URL ends with /index.html, try removing +that.
  6. +
  7. head over to the home page of this +lesson +
  8. +

If you came here from a link in this lesson, please contact the +lesson maintainers using the links at the foot of this page.

+
+
+ + +
+
+ + + diff --git a/CODE_OF_CONDUCT.html b/CODE_OF_CONDUCT.html new file mode 100644 index 00000000..8df6c3cf --- /dev/null +++ b/CODE_OF_CONDUCT.html @@ -0,0 +1,465 @@ + +ESMValTool Tutorial: Contributor Code of Conduct +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Contributor Code of Conduct

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +

As contributors and maintainers of this project, we pledge to follow +the The +Carpentries Code of Conduct.

+

Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by following our reporting +guidelines.

+ + + +
+
+ + +
+
+ + + diff --git a/LICENSE.html b/LICENSE.html new file mode 100644 index 00000000..6e306701 --- /dev/null +++ b/LICENSE.html @@ -0,0 +1,513 @@ + +ESMValTool Tutorial: Licenses +
+ ESMValTool Tutorial +
+ +
+
+ + + + + +
+
+

Licenses

+

Last updated on 2024-11-26 | + + Edit this page

+ + + +
+ +
+ + + +

Instructional Material

+

All Carpentries (Software Carpentry, Data Carpentry, and Library +Carpentry) instructional material is made available under the Creative Commons +Attribution license. The following is a human-readable summary of +(and not a substitute for) the full legal +text of the CC BY 4.0 license.

+

You are free:

+
  • to Share—copy and redistribute the material in any +medium or format
  • +
  • to Adapt—remix, transform, and build upon the +material
  • +

for any purpose, even commercially.

+

The licensor cannot revoke these freedoms as long as you follow the +license terms.

+

Under the following terms:

+
  • Attribution—You must give appropriate credit +(mentioning that your work is derived from work that is Copyright (c) +The Carpentries and, where practical, linking to https://carpentries.org/), provide a link to the +license, and indicate if changes were made. You may do so in any +reasonable manner, but not in any way that suggests the licensor +endorses you or your use.

  • +
  • No additional restrictions—You may not apply +legal terms or technological measures that legally restrict others from +doing anything the license permits. With the understanding +that:

  • +

Notices:

+
  • You do not have to comply with the license for elements of the +material in the public domain or where your use is permitted by an +applicable exception or limitation.
  • +
  • No warranties are given. The license may not give you all of the +permissions necessary for your intended use. For example, other rights +such as publicity, privacy, or moral rights may limit how you use the +material.
  • +

Software

+

Except where otherwise noted, the example programs and other software +provided by The Carpentries are made available under the OSI-approved MIT +license.

+

Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +“Software”), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions:

+

The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software.

+

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+

Trademark

+

“The Carpentries”, “Software Carpentry”, “Data Carpentry”, and +“Library Carpentry” and their respective logos are registered trademarks +of Community Initiatives.

+
+
+ + +
+
+ + + diff --git a/aio.html b/aio.html new file mode 100644 index 00000000..338ba137 --- /dev/null +++ b/aio.html @@ -0,0 +1,6322 @@ + + + + + +ESMValTool Tutorial: All in One View + + + + + + + + + + + + +
+ ESMValTool Tutorial +
+ +
+
+ + + + + + +
+
+ + +

Content from Introduction

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • What is ESMValTool?
  • +
  • Who are the people behind ESMValTool?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Familiarize with ESMValTool
  • +
  • Synchronize expectations
  • +
+
+
+
+
+
+

What is ESMValTool? +

+
+

This tutorial is a first introduction to ESMValTool. Before diving +into the technical steps, let’s talk about what ESMValTool is all +about.

+
+
+ +
+
+

What is ESMValTool?

+
+

What do you already know about or expect from ESMValTool?

+
+
+
+
+
+ +
+
+

EMSValTool is many things, but in this tutorial we will focus on the +following traits:

+

A tool to analyse climate data

+

A collection of diagnostics for reproducible climate +science

+

A community effort

+
+
+
+
+

A tool to analyse climate data +

+
+

ESMValTool takes care of finding, opening, checking, fixing, +concatenating, and preprocessing CMIP data and several other supported +datasets.

+

The central component of ESMValTool that we will see in this tutorial +is the recipe. Any ESMValTool recipe is basically a set +of instructions to reproduce a certain result. The basic structure of a +recipe is as follows:

+
    +
  • +Documentation with relevant (citation) +information
  • +
  • +Datasets that should be analysed
  • +
  • +Preprocessor steps that must be applied
  • +
  • +Diagnostic scripts performing more specific +evaluation steps
  • +
+

An example recipe could look like this:

+
+

YAML +

+
documentation:
+  title: This is an example recipe.
+  description: Example recipe
+  authors:
+    - lastname_firstname
+
+datasets:
+  - {dataset: HadGEM2-ES, project: CMIP5, exp: historical, mip: Amon, 
+     ensemble: r1i1p1, start_year: 1960, end_year: 2005}
+
+preprocessors:
+  global_mean:
+    area_statistics:
+      operator: mean
+
+diagnostics:
+  hockeystick_plot:
+    description: plot of global mean temperature change
+    variables:
+      temperature:
+        short_name: tas
+        preprocessor: global_mean
+    scripts: hockeystick.py
+
+
+
+ +
+
+

Understanding the different section of the recipe

+
+

Try to figure out the meaning of the different dataset keys. Hint: +they can be found in the documentation of ESMValTool.

+
+
+
+
+
+ +
+
+

The keys are explained in the ESMValTool documentation, in the +Recipe section, under [datasets](https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/ +overview.html#recipe-section-datasets)

+
+
+
+
+

A collection of diagnostics for reproducible climate science +

+
+

More than a tool, ESMValTool is a collection of publicly available +recipes and diagnostic scripts. This makes it possible to easily +reproduce important results.

+
+
+ +
+
+

Explore the available recipes

+
+

Go to the ESMValTool +Documentation webpage and explore the Available recipes +section. Which recipe(s) would you like to try?

+
+
+
+

A community effort +

+
+

ESMValTool is built and maintained by an active community of +scientists and software engineers. It is an open source project to which +anyone can contribute. Many of the interactions take place on GitHub. +Here, we briefly introduce you to some of the most important pages.

+
+
+ +
+
+

Meet the ESMValGroup

+
+

Go to github.com/ESMValGroup. This +is the GitHub page of our ‘organization’. Have a look around. How many +collaborators are there? Do you know any of them?

+

Near the top of the page there are 2 pinned repositories: ESMValTool +and ESMValCore. Visit each of the repositories. How many people have +contributed to each of them? Can you also find out how many people have +contributed to this tutorial?

+
+
+
+
+
+ +
+
+

Issues and pull requests

+
+

Go back to the repository pages of ESMValTool or ESMValCore. There +are tabs for ‘issues’ and ‘pull requests’. You can use the labels to +navigate them a bit more. How many open issues are about enhancements of +ESMValTool? And how many bugs have been fixed in ESMValCore? There is +also an ‘insights’ tab, where you can see a summary of recent activity. +How many issues have been opened and closed in the past month?

+
+
+
+

Conclusion +

+
+

This concludes the introduction of the tutorial. You now have a basic +knowledge of ESMValTool and its community. The following episodes will +walk you through the installation, configuration and running your first +recipes.

+
+
+ +
+
+

Key Points

+
+
    +
  • ESMValTool provides a reliable interface to analyse and evaluate +climate data
  • +
  • A large collection of recipes and diagnostic scripts is already +available
  • +
  • ESMValTool is built and maintained by an active community of +scientists and developers
  • +
+
+
+
+

Content from Quickstart guide

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • What is the purpose of the quickstart guide?
  • +
  • How do I load and check the ESMValTool environment?
  • +
  • How do I configure ESMValTool?
  • +
  • How do I run a recipe?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Understand the purpose of the quickstart guide
  • +
  • Load and check the ESMValTool environment
  • +
  • Configure ESMValTool
  • +
  • Run a recipe
  • +
+
+
+
+
+
+
+
+ +
+
+

What is the purpose of the quickstart guide?

+
+
    +
  • The purpose of the quickstart guide is to enable a user of +ESMValTool to run ESMValTool as quickly as possible by making the bare +minimum number of changes.
  • +
+
+
+
+
+
+ +
+
+

How do I load and check the ESMValTool environment?

+
+
    +
  • For this quickstart guide, an assumption is made that ESMValTool +has already been installed at the site where ESMValTool will be run. If +this is not the case, see the [Installation][lesson-installation] +episode in this tutorial.

  • +
  • Load the ESMValTool environment by following the instructions at +[ESMValTool: Pre-installed versions on HPC clusters / other +servers][activate-environment].

  • +
  • +

    Check the ESMValTool environment by accessing the help for +ESMValTool:

    +
    +

    BASH +

    +
    esmvaltool --help
    +
    +
  • +
+
+
+
+
+
+ +
+
+

How do I configure ESMValTool?

+
+
    +
  • +

    Create the ESMValTool user configuration file (the file is +written by default to ~/.esmvaltool/config-user.yml):

    +
    +

    BASH +

    +
    esmvaltool config get_config_user
    +
    +
  • +
  • Edit the ESMValTool user configuration file using your favourite +text editor to uncomment the lines relating to the site where ESMValTool +will be run.

  • +
  • For more details about the ESMValTool user configuration file see +the [Configuration][lesson-configuration] episode in this +tutorial.

  • +
+
+
+
+
+
+ +
+
+

How do I run a recipe?

+
+
    +
  • +

    Run the example Python recipe:

    +
    +

    BASH +

    +
    esmvaltool run examples/recipe_python.yml 
    +
    +
  • +
  • +

    Wait for the recipe to complete. If the recipe completes +successfully, the last line printed to screen at the end of the log will +look something like:

    +
    +

    BASH +

    +
    YYYY-MM-DD HH:mm:SS, NNN UTC [NNNNN] INFO    Run was successful
    +
    +
  • +
  • +

    View the output of the recipe by opening the HTML file produced +by ESMValTool (the location of this file is printed to screen near the +end of the log):

    +
    +

    BASH +

    +
    YYYY-MM-DD HH:mm:SS, NNN UTC [NNNNN] INFO    Wrote recipe output to:
    +file:///$HOME/esmvaltool_output/recipe_python_<date>_<time>/index.html
    +
    +
  • +
  • For more details about running recipes see the [Running your +first recipe][lesson-recipe] episode in this tutorial.

  • +
+
+
+
+
+
+ +
+
+

Key Points

+
+
    +
  • The purpose of the quickstart guide is to enable a user of +ESMValTool to run ESMValTool as quickly as possible without having to go +through the whole tutorial
  • +
  • Use the module load command to load the ESMValTool +environment, see the \[Installation\]\[lesson-installation\] episode for more +details and use esmvaltool --help to check the ESMValTool +environment
  • +
  • Use esmvaltool config get_config_user to create the +ESMValTool user configuration file
  • +
  • Use esmvaltool run <recipe>.yml to run a +recipe
  • +
+
+
+

Content from Installation

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • What are the prerequisites for installing ESMValTool?
  • +
  • How do I confirm that the installation was successful?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Install ESMValTool
  • +
  • Demonstrate that the installation was successful
  • +
+
+
+
+
+
+

Overview +

+
+

The instructions help with the installation of ESMValTool on +operating systems like Linux/MacOSX/Windows. We use the Mamba +package manager to install the ESMValTool. Other installation methods +are also available; they can be found in the documentation. +We will first install Mamba, and then ESMValTool. We end this chapter by +testing that the installation was successful.

+

Before we begin, here are all the possible ways in which you can use +ESMValTool depending on your level of expertise or involvement with +ESMValTool and associated software such as GitHub and Mamba.

+
    +
  1. If you have access to a server where ESMValTool is already installed +as a module, for e.g., the [CEDA JASMIN](https://help.jasmin.ac.uk/article +/4955-community-software-esmvaltool) server, you can simply load the +module with the following command:
  2. +
+
+

BASH +

+
module load esmvaltool
+
+

After loading esmvaltool, we can start using ESMValTool +right away. Please see the next +lesson. 2. If you would like to install ESMValTool as a mamba +package, then this lesson will tell you how! 3. If you would like to +start experimenting with existing diagnostics or contributing to +ESMvalTool, please see the instructions for source installation in the +lesson Development and +contribution and in the documentation.

+
+
+ +
+
+

Install ESMValTool on Windows

+
+

ESMValTool does not directly support Windows, but successful usage +has been reported through the Windows Subsystem +for Linux(WSL), available in Windows 10. To install the WSL please +follow the instructions on the +Windows Documentation page. After installing the WSL, installation +can be done using the same instructions for Linux/MacOSX.

+
+
+
+

Install ESMValTool on Linux/MacOSX +

+
+
+

Install Mamba +

+

ESMValTool is distributed using Mamba. To +install mamba on Linux or MacOSX, follow the +instructions below:

+
    +
  1. Please download the installation file for the latest Mamba +version here.

  2. +
  3. Next, run the installer from the place where you downloaded +it:

  4. +
+

On Linux:

+
+

BASH +

+
bash Mambaforge-Linux-x86_64.sh
+
+

On MacOSX:

+
+

BASH +

+
bash Mambaforge-MacOSX-x86_64.sh
+
+
    +
  1. Follow the instructions in the installer. The defaults should +normally suffice.

  2. +
  3. You will need to restart your terminal for the changes to have +effect.

  4. +
  5. We recommend updating mamba before the esmvaltool installation. +To do so, run:

  6. +
+
+

BASH +

+
mamba update --name base mamba
+
+
    +
  1. Verify you have a working mamba installation by:
  2. +
+
+

BASH +

+
which mamba
+
+

This should show the path to your mamba executable, +e.g. ~/mambaforge/bin/mamba.

+

For more information about installing mamba, see [the mamba +installation documentation](https://docs.esmvaltool.org/en +/latest/quickstart/installation.html#mamba-installation).

+
+
+

Install the ESMValTool package +

+

The ESMValTool package contains diagnostics scripts in four +languages: R, Python, Julia and NCL. This introduces a lot of +dependencies, and therefore the installation can take quite long. It is, +however, possible to install ‘subpackages’ for each of the languages. +The following (sub)packages are available:

+
    +
  • esmvaltool-python
  • +
  • esmvaltool-ncl
  • +
  • esmvaltool-r
  • +
  • +esmvaltool –> the complete package, i.e. the +combination of the above.
  • +
+

For the tutorial, we will install the complete package. Thus, to +install the ESMValTool package, run

+
+

BASH +

+
mamba create --name esmvaltool esmvaltool 
+
+

On MacOSX ESMValTool functionalities in Julia, NCL, and R are not +supported. To install a Mamba environment on MacOSX, please refer to +specific information.

+

This will create a new [Mamba environment](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks +/manage-environments.html) called esmvaltool, with the +ESMValTool package and all of its dependencies installed in it.

+
+
+ +
+
+

Common issues

+
+

You find a list of common installation problems and their solutions +in the [documentation](https://docs.esmvaltool.org/en/latest/quickstart/installation +.html#common-installation-problems-and-their-solutions).

+
+
+
+
+
+

Install Julia +

+

Some ESMValTool diagnostics are written in the Julia programming +language. If you want a full installation of ESMValTool including Julia +diagnostics, you need to make sure Julia is installed before installing +ESMValTool.

+

In this tutorial, we will not use Julia, but for reference, we have +listed the steps to install Julia below. Complete instructions for +installing Julia can be found on the Julia +installation page.

+
+
+ +
+
+

First, open a bash terminal and activate the newly created +esmvaltool environment.

+
+

BASH +

+
conda activate esmvaltool
+
+

Next, to install Julia via mamba, you can use the +following command:

+
+

BASH +

+
mamba install julia
+
+

To check that the Julia executable can be found, run

+
+

BASH +

+
which julia
+
+

to display the path to the Julia executable, it should be

+
+

OUTPUT +

+
~/mambaforge/envs/esmvaltool/bin/julia
+
+

To test that Julia is installed correctly, run

+
+

BASH +

+
julia
+
+

to start the interactive Julia interpreter. Press Ctrl+D +to exit.

+
+
+
+
+
+
+

Test that the installation was successful +

+

To test that the installation was successful, run

+
+

BASH +

+
conda activate esmvaltool
+
+

to activate the conda environment called esmvaltool. In +the shell prompt the active conda environment should have been changed +from (base) to (esmvaltool).

+

Next, run

+
+

BASH +

+
esmvaltool --help
+
+

to display the command line help.

+
+
+ +
+
+

Version of ESMValTool

+
+

Can you figure out which version of ESMValTool has been +installed?

+
+
+
+
+
+ +
+
+

The esmvaltool --help command lists version +as a command to get the version

+

When you run

+
+

BASH +

+
esmvaltool version
+
+

The version of ESMValTool installed should be displayed on the screen +as:

+
+

OUTPUT +

+
ESMValCore: 2.11.0
+ESMValTool: 2.11.0
+
+

Note that on HPC servers such as JASMIN, sometimes a more recent +development version may be displayed for ESMValTool, for e.g. +ESMValTool: 2.11.0.dev71+g2c60b4d97

+
+
+
+
+
+
+ +
+
+

Key Points

+
+
    +
  • All the required packages can be installed using mamba.
  • +
  • You can find more information about installation in the documentation.
  • +
+
+
+
+
+

Content from Configuration

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • What is the user configuration file and how should I use it?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Understand the contents of the user-config.yml file
  • +
  • Prepare a personalized user-config.yml file
  • +
  • Configure ESMValTool to use some settings
  • +
+
+
+
+
+
+

The configuration file +

+
+

For the purposes of this tutorial, we will create a directory in our +home directory called esmvaltool_tutorial and use that as +our working directory. The following steps should do that:

+
+

BASH +

+
 mkdir esmvaltool_tutorial
+ cd esmvaltool_tutorial
+
+

The config-user.yml configuration file contains all the +global level information needed by ESMValTool to run. This is a YAML file.

+

You can get the default configuration file by running:

+
+

BASH +

+
  esmvaltool config get_config_user --path=<target_dir>
+
+

The default configuration file will be downloaded to the directory +specified with the --path variable. For instance, you can +provide the path to your working directory as the +target_dir. If this option is not used, the file will be +saved to the default location: +~/.esmvaltool/config-user.yml, where ~ is the +path to your home directory. Note that files and directories starting +with a period are “hidden”, to see the .esmvaltool +directory in the terminal use ls -la ~. Note that if a +configuration file by that name already exists in the default location, +the get_config_user command will not update the file as +ESMValTool will not overwrite the file. You will have to move the file +first if you want an updated copy of the user configuration file.

+

We run a text editor called nano to have a look inside +the configuration file and then modify it if needed:

+
+

BASH +

+
  nano ~/.esmvaltool/config-user.yml
+
+

Any other editor can be used, e.g.vim.

+

This file contains the information for:

+
    +
  • Output settings
  • +
  • Destination directory
  • +
  • Auxiliary data directory
  • +
  • Number of tasks that can be run in parallel
  • +
  • Rootpath to input data
  • +
  • Directory structure for the data from different projects
  • +
+
+
+ +
+
+

Text editor side note

+
+

No matter what editor you use, you will need to know where it +searches for and saves files. If you start it from the shell, it will +(probably) use your current working directory as its default location. +We use nano in examples here because it is one of the least +complex text editors. Press ctrl + O to save the +file, and then ctrl + X to exit +nano.

+
+
+
+

Output settings +

+
+

The configuration file starts with output settings that inform +ESMValTool about your preference for output. You can turn on or off the +setting by true or false values. Most of these +settings are fairly self-explanatory.

+
+
+ +
+
+

Saving preprocessed data

+
+

Later in this tutorial, we will want to look at the contents of the +preproc folder. This folder contains preprocessed data and +is removed by default when ESMValTool is run. In the configuration file, +which settings can be modified to prevent this from happening?

+
+
+
+
+
+ +
+
+

If the option remove_preproc_dir is set to +false, then the preproc/ directory contains +all the pre-processed data and the metadata interface files. If the +option save_intermediary_cubes is set to true +then data will also be saved after each preprocessor step in the folder +preproc. Note that saving all intermediate results to file +will result in a considerable slowdown, and can quickly fill your +disk.

+
+
+
+
+

Destination directory +

+
+

The destination directory is the rootpath where ESMValTool will store +its output folders containing e.g. figures, data, logs, etc. With every +run, ESMValTool automatically generates a new output folder determined +by recipe name, and date and time using the format: YYYYMMDD_HHMMSS.

+
+
+ +
+
+

Set the destination directory

+
+

Let’s name our destination directory esmvaltool_output +in the working directory. ESMValTool should write the output to this +path, so make sure you have the disk space to write output to this +directory. How do we set this in the config-user.yml?

+
+
+
+
+
+ +
+
+

We use output_dir entry in the +config-user.yml file as:

+
+

YAML +

+
output_dir: ./esmvaltool_output
+
+

If the esmvaltool_output does not exist, ESMValTool will +generate it for you.

+
+
+
+
+

Rootpath to input data +

+
+

ESMValTool uses several categories (in ESMValTool, this is referred +to as projects) for input data based on their source. The current +categories in the configuration file are mentioned below. For example, +CMIP is used for a dataset from the Climate Model Intercomparison +Project whereas OBS may be used for an observational dataset. More +information about the projects used in ESMValTool is available in the +[documentation](https://docs.esmvaltool.org/projects/esmvalcore/en/latest/ +quickstart/find_data.html). When using ESMValTool on your own machine, +you can create a directory to download climate model data or observation +data sets and let the tool use data from there. It is also possible to +ask ESMValTool to download climate model data as needed. This can be +done by specifying a download directory and by setting the option to +download data as shown below.

+
+

YAML +

+
# Directory for storing downloaded climate data
+download_dir: ~/climate_data
+search_esgf: always
+
+

If you are working offline or do not want to download the data then +set the option above to never. If you want to download data +only when the necessary files are missing at the usual location, you can +set the option to when_missing.

+

The rootpath specifies the directories where ESMValTool +will look for input data. For each category, you can define either one +path or several paths as a list. For example:

+
+

YAML +

+
rootpath:
+  CMIP5: [~/cmip5_inputpath1, ~/cmip5_inputpath2]
+  OBS: ~/obs_inputpath
+  RAWOBS: ~/rawobs_inputpath
+  default: ~/climate_data
+
+

These are typically available in the default configuration file you +downloaded, so simply removing the machine specific lines should be +sufficient to access input data.

+
+
+ +
+
+

Set the correct rootpath

+
+

In this tutorial, we will work with data from CMIP5 and CMIP6. How can we +modify the rootpath to make sure the data path is set +correctly for both CMIP5 and CMIP6? Note: to get the data, check the +instructions in Setup.

+
+
+
+
+
+ +
+
+
    +
  • Are you working on your own local machine? You need to add the root +path of the folder where the data is available to the +config-user.yml file as:
  • +
+
+

YAML +

+
  rootpath:
+  ...
+    CMIP5: ~/esmvaltool_tutorial/data
+    CMIP6: ~/esmvaltool_tutorial/data
+
+
    +
  • Are you working on your local machine and have downloaded data using +ESMValTool? You need to add the root path of the folder where the data +has been downloaded to as specified in the +download_dir.
  • +
+
+

YAML +

+
rootpath:
+...
+  CMIP5: ~/climate_data
+  CMIP6: ~/climate_data
+
+
    +
  • Are you working on a computer cluster like Jasmin or DKRZ? +Site-specific path to the data for JASMIN/DKRZ/ETH/IPSL are already +listed at the end of the config-user.yml file. You need to +uncomment the related lines. For example, on JASMIN:
  • +
+
+

YAML +

+
auxiliary_data_dir: /gws/nopw/j04/esmeval/aux_data/AUX
+rootpath: 
+ CMIP6: /badc/cmip6/data/CMIP6
+ CMIP5: /badc/cmip5/data/cmip5/output1
+ OBS: /gws/nopw/j04/esmeval/obsdata-v2
+ OBS6: /gws/nopw/j04/esmeval/obsdata-v2
+ obs4MIPs: /gws/nopw/j04/esmeval/obsdata-v2
+ ana4mips: /gws/nopw/j04/esmeval/obsdata-v2
+ default: /gws/nopw/j04/esmeval/obsdata-v2
+
+ +
+
+
+
+

Directory structure for the data from different projects +

+
+

Input data can be from various models, observations and reanalysis +data that adhere to the CF/CMOR +standard. The drs setting describes the file +structure.

+

The drs setting describes the file structure for several +projects (e.g. CMIP6, CMIP5, obs4mips, OBS6, OBS) on several key +machines (e.g. BADC, CP4CDS, DKRZ, ETHZ, SMHI, BSC). For more +information about drs, you can visit the ESMValTool +documentation on [Data Reference Syntax (DRS)](https://docs.esmvaltool.org/projects/esmvalcore/ +en/latest/quickstart/find_data.html#cmor-drs).

+
+
+ +
+
+

Set the correct drs

+
+

In this lesson, we will work with data from CMIP5 and CMIP6. How can we +set the correct drs?

+
+
+
+
+
+ +
+
+
    +
  • Are you working on your own local machine? You need to set the +drs of the data in the config-user.yml file +as:
  • +
+
+

YAML +

+
  drs:
+    CMIP5: default
+    CMIP6: default
+
+
    +
  • Are you asking ESMValTool to download the data for use with your +diagnostics? You need to set the drs of the data in the +config-user.yml file as:
  • +
+
+

YAML +

+
   drs:
+     CMIP5: ESGF
+     CMIP6: ESGF
+     CORDEX: ESGF
+     obs4MIPs: ESGF
+
+
    +
  • Are you working on a computer cluster like Jasmin or DKRZ? +Site-specific drs of the data are already listed at the end +of the config-user.yml file. You need to uncomment the +related lines. For example, on Jasmin:
  • +
+
+

YAML +

+
  # Site-specific entries: Jasmin
+  # Uncomment the lines below to locate data on JASMIN
+  drs:
+    CMIP6: BADC
+    CMIP5: BADC
+    OBS: default
+    OBS6: default
+    obs4mips: default
+    ana4mips: default
+
+
+
+
+
+
+
+ +
+
+

Explain the default drs (if working on local machine)

+
+
    +
  1. In the previous exercise, we set the drs of CMIP5 data +to default. Can you explain why?
  2. +
  3. Have a look at the directory structure of the OBS data. +There is a folder called Tier1. What does it mean?
  4. +
+
+
+
+
+
+ +
+
+
    +
  1. drs: default is one way to retrieve data from a ROOT +directory that has no DRS-like structure. default indicates +that all the files are in a folder without any structure.

  2. +
  3. Observational data are organized in Tiers depending on their +level of public availability. Therefore the default directory must be +structured accordingly with sub-directories TierX +e.g. Tier1, Tier2 or Tier3, even when drs: default. More +details can be found in the [documentation](https://docs.esmvaltool.org/projects/esmvalcore/en/latest/ +quickstart/find_data.html#observational-data).

  4. +
+
+
+
+
+

Other settings +

+
+
+
+ +
+
+

Auxiliary data directory

+
+

The auxiliary_data_dir setting is the path where any +required additional auxiliary data files are stored. This location +allows us to tell the diagnostic script where to find the files if they +can not be downloaded at runtime. This option should not be used for +model or observational datasets, but for data files (e.g. shape files) +used in plotting such as coastline descriptions and if you want to feed +some additional data (e.g. shape files) to your recipe.

+
+

YAML +

+
auxiliary_data_dir: ~/auxiliary_data
+
+

See more information in ESMValTool [document](https://docs.esmvaltool.org/projects/ESMValCore/en/latest/quickstart +/configure.html?highlight=auxiliary_data#user-configuration-file).

+
+
+
+
+
+ +
+
+

Number of parallel tasks

+
+

This option enables you to perform parallel processing. You can +choose the number of tasks in parallel as 1/2/3/4/… or you can set it to +null. That tells ESMValTool to use the maximum number of +available CPUs. For the purpose of the tutorial, please set ESMValTool +use only 1 cpu:

+
+

YAML +

+
max_parallel_tasks: 1
+
+

In general, if you run out of memory, try setting +max_parallel_tasks to 1. Then, check the amount of memory +you need for that by inspecting the file +run/resource_usage.txt in the output directory. Using the +number there you can increase the number of parallel tasks again to a +reasonable number for the amount of memory available in your system.

+
+
+
+
+
+ +
+
+

Make your own configuration file

+
+

It is possible to have several configuration files with different +purposes, for example: config-user_formalised_runs.yml, +config-user_debugging.yml. In this case, you have to pass the path of +your own configuration file as a command-line option when running the +ESMValTool. We will learn how to do this in the next lesson.

+
+
+
+
+
+ +
+
+

Key Points

+
+
    +
  • The config-user.yml tells ESMValTool where to find +input data.
  • +
  • +output_dir defines the destination directory.
  • +
  • +rootpath defines the root path of the data.
  • +
  • +drs defines the directory structure of the data.
  • +
+
+
+
+

Content from Running your first recipe

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • How to run a recipe?
  • +
  • What happens when I run a recipe?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Run an existing ESMValTool recipe
  • +
  • Examine the log information
  • +
  • Navigate the output created by ESMValTool
  • +
  • Make small adjustments to an existing recipe
  • +
+
+
+
+
+
+

This episode describes how ESMValTool recipes work, how to run a +recipe and how to explore the recipe output. By the end of this episode, +you should be able to run your first recipe, look at the recipe output, +and make small modifications.

+

Running an existing recipe +

+
+

The recipe format has briefly been introduced in the +[Introduction][lesson-introduction] episode. To see all the recipes that +are shipped with ESMValTool, type

+
+

BASH +

+
esmvaltool recipes list
+
+

We will start by running [examples/recipe_python.yml](https://docs.esmvaltool. +org/en/latest/recipes/recipe_examples.html)

+
esmvaltool run examples/recipe_python.yml
+

or if you have the user configuration file in your current directory +then

+
esmvaltool run --config_file ./config-user.yml examples/recipe_python.yml
+

If everything is okay, you should see that ESMValTool is printing a +lot of output to the command line. The final message should be “Run was +successful”. The exact output varies depending on your machine, but it +should look something like the example log output on terminal below.

+

{% include example_output.txt %}

+
+
+ +
+
+

Pro tip: ESMValTool search paths

+
+

You might wonder how ESMValTool was able find the recipe file, even +though it’s not in your working directory. All the recipe paths printed +from esmvaltool recipes list are relative to ESMValTool’s +installation location. This is where ESMValTool will look if it cannot +find the file by following the path from your working directory.

+
+
+
+

Investigating the log messages +

+
+

Let’s dissect what’s happening here.

+
+
+ +
+
+

Output files and directories

+
+

After the banner and general information, the output starts with some +important locations.

+
    +
  1. Did ESMValTool use the right config file?
  2. +
  3. What is the path to the example recipe?
  4. +
  5. What is the main output folder generated by ESMValTool?
  6. +
  7. Can you guess what the different output directories are for?
  8. +
  9. ESMValTool creates two log files. What is the difference?
  10. +
+
+
+
+
+
+ +
+
+
    +
  1. The config file should be the one we edited in the previous episode, +something like +/home/<username>/.esmvaltool/config-user.yml or +~/esmvaltool_tutorial/config-user.yml.
  2. +
  3. ESMValTool found the recipe in its installation directory, something +like +/home/users/username/mambaforge/envs/esmvaltool/bin/esmvaltool/recipes/examples/ +or if you are using a pre-installed module on a server, something like +/apps/jasmin/community/esmvaltool/ESMValTool_<version> /esmvaltool/recipes/examples/recipe_python.yml, +where <version> is the latest release.
  4. +
  5. ESMValTool creates a time-stamped output directory for every run. In +this case, it should be something like +recipe_python_YYYYMMDD_HHMMSS. This folder is made inside +the output directory specified in the previous episode: +~/esmvaltool_tutorial/esmvaltool_output.
  6. +
  7. There should be four output folders:
  8. +
+
    +
  • +plots/: this is where output figures are stored.
  • +
  • +preproc/: this is where pre-processed data are +stored.
  • +
  • +run/: this is where esmvaltool stores general +information about the run, such as log messages and a copy of the recipe +file.
  • +
  • +work/: this is where output files (not figures) are +stored.
  • +
+
    +
  1. The log files are:
  2. +
+
    +
  • +main_log.txt is a copy of the command-line output
  • +
  • +main_log_debug.txt contains more detailed information +that may be useful for debugging.
  • +
+
+
+
+
+
+
+ +
+
+

Debugging: No ‘preproc’ directory?

+
+

If you’re missing the preproc directory, then your +config-user.yml file has the value +remove_preproc_dir set to true (this is used +to save disk space). Please set this value to false and run +the recipe again.

+
+
+
+

After the output locations, there are two main sections that can be +distinguished in the log messages:

+
    +
  • Creating tasks
  • +
  • Executing tasks
  • +
+
+
+ +
+
+

Analyse the tasks

+
+

List all the tasks that ESMValTool is executing for this recipe. Can +you guess what this recipe does?

+
+
+
+
+
+ +
+
+

Just after all the ‘creating tasks’ and before ‘executing tasks’, we +find the following line in the output:

+
[134535] INFO    These tasks will be executed: map/tas, timeseries/tas_global,
+timeseries/script1, map/script1, timeseries/tas_amsterdam
+

So there are three tasks related to timeseries: global temperature, +Amsterdam temperature, and a script (tas: near-surface air +temperature). And then there are two tasks related to a map: something +with temperature, and again a script.

+
+
+
+
+

Examining the recipe file +

+
+

To get more insight into what is happening, we will have a look at +the recipe file itself. Use the following command to copy the recipe to +your working directory

+
+

BASH +

+
esmvaltool recipes get examples/recipe_python.yml
+
+

Now you should see the recipe file in your working directory (type +ls to verify). Use the nano editor to open +this file:

+
+

BASH +

+
nano recipe_python.yml
+
+

For reference, you can also view the recipe by unfolding the box +below.

+
+
+ +
+
+
+

YAML +

+
# ESMValTool
+# recipe_python.yml
+#
+# See https://docs.esmvaltool.org/en/latest/recipes/recipe_examples.html
+# for a description of this recipe.
+#
+# See https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/overview.html
+# for a description of the recipe format.
+---
+documentation:
+ description: |
+   Example recipe that plots a map and timeseries of temperature.
+
+ title: Recipe that runs an example diagnostic written in Python.
+
+ authors:
+   - andela_bouwe
+   - righi_mattia
+
+ maintainer:
+   - schlund_manuel
+
+ references:
+   - acknow_project
+
+ projects:
+   - esmval
+   - c3s-magic
+
+datasets:
+ - {dataset: BCC-ESM1, project: CMIP6, exp: historical, ensemble: r1i1p1f1, grid: gn}
+ - {dataset: bcc-csm1-1, project: CMIP5, exp: historical, ensemble: r1i1p1}
+
+preprocessors:
+ # See https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/preprocessor.html
+ # for a description of the preprocessor functions.
+
+ to_degrees_c:
+   convert_units:
+     units: degrees_C
+
+ annual_mean_amsterdam:
+   extract_location:
+     location: Amsterdam
+     scheme: linear
+   annual_statistics:
+     operator: mean
+   multi_model_statistics:
+     statistics:
+       - mean
+     span: overlap
+   convert_units:
+     units: degrees_C
+
+ annual_mean_global:
+   area_statistics:
+     operator: mean
+   annual_statistics:
+     operator: mean
+   convert_units:
+     units: degrees_C
+
+diagnostics:
+
+ map:
+   description: Global map of temperature in January 2000.
+   themes:
+     - phys
+   realms:
+     - atmos
+   variables:
+     tas:
+       mip: Amon
+       preprocessor: to_degrees_c
+       timerange: 2000/P1M
+       caption: |
+         Global map of {long_name} in January 2000 according to {dataset}.
+   scripts:
+     script1:
+       script: examples/diagnostic.py
+       quickplot:
+         plot_type: pcolormesh
+         cmap: Reds
+
+ timeseries:
+   description: Annual mean temperature in Amsterdam and global mean since 1850.
+   themes:
+     - phys
+   realms:
+     - atmos
+   variables:
+     tas_amsterdam:
+       short_name: tas
+       mip: Amon
+       preprocessor: annual_mean_amsterdam
+       timerange: 1850/2000
+       caption: Annual mean {long_name} in Amsterdam according to {dataset}.
+     tas_global:
+       short_name: tas
+       mip: Amon
+       preprocessor: annual_mean_global
+       timerange: 1850/2000
+       caption: Annual global mean {long_name} according to {dataset}.
+   scripts:
+     script1:
+       script: examples/diagnostic.py
+       quickplot:
+         plot_type: plot
+
+
+
+
+
+

Do you recognize the basic recipe structure that was introduced in +episode 1?

+
    +
  • +Documentation with relevant (citation) +information
  • +
  • +Datasets that should be analysed
  • +
  • +Preprocessors groups of common preprocessing +steps
  • +
  • +Diagnostics scripts performing more specific +evaluation steps
  • +
+
+
+ +
+
+

Analyse the recipe

+
+

Try to answer the following questions:

+
    +
  1. Who wrote this recipe?
  2. +
  3. Who should be approached if there is a problem with this +recipe?
  4. +
  5. How many datasets are analyzed?
  6. +
  7. What does the preprocessor called annual_mean_global +do?
  8. +
  9. Which script is applied for the diagnostic called +map?
  10. +
  11. Can you link specific lines in the recipe to the tasks that we saw +before?
  12. +
  13. How is the location of the city specified?
  14. +
  15. How is the temporal range of the data specified?
  16. +
+
+
+
+
+
+ +
+
+
    +
  1. The example recipe is written by Bouwe Andela and Mattia +Righi.

  2. +
  3. Manuel Schlund is listed as the maintainer of this +recipe.

  4. +
  5. Two datasets are analysed:

  6. +
+
    +
  • CMIP6 data from the model BCC-ESM1
  • +
  • CMIP5 data from the model bcc-csm1-1
  • +
+
    +
  1. The preprocessor annual_mean_global computes an area +mean as well as annual means

  2. +
  3. The diagnostic called map executes a script referred +to as script1. This is a python script named +examples/diagnostic.py

  4. +
  5. There are two diagnostics: map and +timeseries. Under the diagnostic map we find +two tasks:

  6. +
+
    +
  • a preprocessor task called tas, applying the +preprocessor called to_degrees_c to the variable +tas.
  • +
  • a diagnostic task called script1, applying the script +examples/diagnostic.py to the preprocessed data +(map/tas).
  • +
+

Under the diagnostic timeseries we find three tasks:

+
    +
  • a preprocessor task called tas_amsterdam, applying the +preprocessor called annual_mean_amsterdam to the variable +tas.
  • +
  • a preprocessor task called tas_global, applying the +preprocessor called annual_mean_global to the variable +tas.
  • +
  • a diagnostic task called script1, applying the script +examples/diagnostic.py to the preprocessed data +(timeseries/tas_global and +timeseries/tas_amsterdam).
  • +
+
    +
  1. The extract_location preprocessor is used to get +data for a specific location here. ESMValTool interpolates to the +location based on the chosen scheme. Can you tell the scheme used here? +For more ways to extract areas, see the [Area +operations][preproc-area-manipulation] page.

  2. +
  3. The timerange tag is used to extract data from a +specific time period here. The start time is 01/01/2000 and +the span of time to calculate means is 1 Month given by +P1M. For more options on how to specify time ranges, see +the [timerange documentation][timeranges].

  4. +
+
+
+
+
+
+
+ +
+
+

Pro tip: short names and variable groups

+
+

The preprocessor tasks in ESMValTool are called ‘variable groups’. +For the diagnostic timeseries, we have two variable groups: +tas_amsterdam and tas_global. Both of them +operate on the variable tas (as indicated by the +short_name), but they apply different preprocessors. For +the diagnostic map the variable group itself is named +tas, and you’ll notice that we do not explicitly provide +the short_name. This is a shorthand built into +ESMValTool.

+
+
+
+
+
+ +
+
+

Output files

+
+

Have another look at the output directory created by the ESMValTool +run.

+

Which files/folders are created by each task?

+
+
+
+
+
+ +
+
+
    +
  • +map/tas: creates /preproc/map/tas, +which contains preprocessed data for each of the input datasets, a file +called metadata.yml describing the contents of these +datasets and provenance information in the form of .xml +files.
  • +
  • +timeseries/tas_global: creates +/preproc/timeseries/tas_global, which contains preprocessed +data for each of the input datasets, a metadata.yml file +and provenance information in the form of .xml files.
  • +
  • +timeseries/tas_amsterdam: creates +/preproc/timeseries/tas_amsterdam, which contains +preprocessed data for each of the input datasets, plus a combined +MultiModelMean, a metadata.yml file and +provenance files.
  • +
  • +map/script1: creates /run/map/script1 +with general information and a log of the diagnostic script run. It also +creates /plots/map/script1/ and +/work/map/script1, which contain output figures and output +datasets, respectively. For each output file, there is also +corresponding provenance information in the form of .xml, +.bibtex and .txt files.
  • +
  • +timeseries/script1: creates +/run/timeseries/script1 with general information and a log +of the diagnostic script run. It also creates +/plots/timeseries/script1 and +/work/timeseries/script1, which contain output figures and +output datasets, respectively. For each output file, there is also +corresponding provenance information in the form of .xml, +.bibtex and .txt files.
  • +
+
+
+
+
+
+
+ +
+
+

Pro tip: diagnostic logs

+
+

When you run ESMValTool, any log messages from the diagnostic script +are not printed on the terminal. But they are written to the +log.txt files in the folder +/run/<diag_name>/log.txt.

+

ESMValTool does print a command that can be used to re-run a +diagnostic script. When you use this the output will be printed +to the command line.

+
+
+
+

Modifying the example recipe +

+
+

Let’s make a small modification to the example recipe. Notice that +now that you have copied and edited the recipe, you can use

+
esmvaltool run recipe_python.yml
+

to refer to your local file rather than the default version shipped +with ESMValTool.

+
+
+ +
+
+

Change your location

+
+

Modify and run the recipe to analyse the temperature for your own +location.

+
+
+
+
+
+ +
+
+

In principle, you only have to modify the location in the +preprocessor called annual_mean_amsterdam. However, it is +good practice to also replace all instances of amsterdam +with the correct name of your location. Otherwise the log messages and +output will be confusing. You are free to modify the names of +preprocessors or diagnostics.

+

In the diff file below you will see the changes we have +made to the file. The top 2 lines are the filenames and the lines like +@@ -39,9 +39,9 @@ represent the line numbers in the +original and modified file, respectively. For more info on this format, +see here.

+
+

DIFF +

+
--- recipe_python.yml	
++++ recipe_python_london.yml	
+@@ -39,9 +39,9 @@
+    convert_units:
+      units: degrees_C
+
+-  annual_mean_amsterdam:
++  annual_mean_london:
+    extract_location:
+-      location: Amsterdam
++      location: London
+      scheme: linear
+    annual_statistics:
+      operator: mean
+@@ -83,7 +83,7 @@
+          cmap: Reds
+
+  timeseries:
+-    description: Annual mean temperature in Amsterdam and global mean since 1850.
++    description: Annual mean temperature in London and global mean since 1850.
+    themes:
+      - phys
+    realms:
+@@ -92,9 +92,9 @@
+      tas_amsterdam:
+        short_name: tas
+        mip: Amon
+-        preprocessor: annual_mean_amsterdam
++        preprocessor: annual_mean_london
+        timerange: 1850/2000
+-        caption: Annual mean {long_name} in Amsterdam according to {dataset}.
++        caption: Annual mean {long_name} in London according to {dataset}.
+      tas_global:
+        short_name: tas
+        mip: Amon
+
+
+
+
+
+
+
+ +
+
+

Key Points

+
+
    +
  • ESMValTool recipes work ‘out of the box’ (if input data is +available)
  • +
  • There are strong links between the recipe, log file, and output +folders
  • +
  • Recipes can easily be modified to re-use existing code for your own +use case
  • +
+
+
+
+

Content from Conclusion of the basic tutorial

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • What do I do now?
  • +
  • Where can I get help?
  • +
  • What if I find a bug?
  • +
  • Where can I find more information about ESMValtool?
  • +
  • How can I cite ESMValtool?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Breathe - you’re finished now!
  • +
  • Congratulations & Thanks!
  • +
  • Find out about the mini-tutorials, and what to do next.
  • +
+
+
+
+
+
+

Congratulations! +

+
+

Congratulations on completing the ESMValTool tutorial! You should be +now ready to go and start using ESMValTool independently.

+

The rest of this tutorial contains individual mini-tutorials to help +work through a specific issue (not developed yet).

+
+

What next? +

+

From here, there are lots of ways that you can continue to use +ESMValTool.

+ +
+
+ +
+
+

Exercise: What do you want to do next?

+
+
    +
  • Think about what you want to do with ESMValTool. +
      +
    • Decide what datasets and variables you want to use.
    • +
    • Is any observational data available?
    • +
    • How will you preprocess the data?
    • +
    • What will your diagnostic script need to do?
    • +
    • What will your final figure show?
    • +
    +
  • +
+
+
+
+
+
+

Where can I get more information on ESMValTool? +

+ +
+
+

Where can I get more help? +

+

There are lots of resources available to assist you in using +ESMValTool.

+

The ESMValTool Discussions +page is a good place to find information on general issues, or check +if your question has already been addressed. If you have a GitHub +account, you can also post your questions there.

+

If you encounter difficulties, a great starting point is to visit +issues page issue page +to check whether your issues have already been reported or not. If they +have been reported before, suggestions provided by developers can help +you to solve the issues you encountered. Note that you will need a +GitHub account for this.

+

Additionally, there is an ESMValTool email list. Please see information +on how to subscribe to the user mailing list.

+
+
+

What if I find a bug? +

+

If you find a bug, please report it to the ESMValTool team. This will +help us fix issues, ensuring not only your uninterrupted workflow but +also contributing to the overall stability of ESMValTool for all +users.

+

To report a bug, please create a new issue using the issue +page.

+

In your bug report, please describe the problem as clearly and as +completely as possible. You may need to include a recipe or the output +log as well.

+
+
+

How do I cite the Tutorial? +

+

Please use citation information available at https://doi.org/10.5281/zenodo.3974591.

+
+
+ +
+
+

Key Points

+
+
    +
  • Individual mini-tutorials help work through a specific issue (not +developed yet).
  • +
  • We are constantly improving this tutorial.
  • +
+
+
+
+
+

Content from Writing your own recipe

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • How do I create a new recipe?
  • +
  • Can I use different preprocessors for different variables?
  • +
  • Can I use different datasets for different variables?
  • +
  • How can I combine different preprocessor functions?
  • +
  • Can I run the same recipe for multiple ensemble members?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Create a recipe with multiple preprocessors
  • +
  • Use different preprocessors for different variables
  • +
  • Run a recipe with variables from different datasets
  • +
+
+
+
+
+
+

Introduction +

+
+

One of the key strengths of ESMValTool is in making complex analyses +reusable and reproducible. But that doesn’t mean everything in +ESMValTool needs to be complex. Sometimes, the biggest challenge is in +keeping things simple. You probably know the ‘warming stripes’ +visualization by Professor Ed Hawkins. On the site https://showyourstripes.info{:target=“_blank”} you can +find the same visualization for many regions in the world.

+

Warming stripesShared by Ed Hawkins under a Creative Commons 4.0 Attribution +International licence. Source: https://showyourstripes.info

+

In this episode, we will reproduce and extend this functionality with +ESMValTool. We have prepared a small Python script that takes a NetCDF +file with timeseries data, and visualizes it in the form of our desired +warming stripes figure.

+

The diagnostic script that we will use is called +warming_stripes.py and can be downloaded here.

+

Download the file and store it in your working directory. If you +want, you may also have a look at the contents, but it is not necessary +to do so for this lesson.

+

We will write an ESMValTool recipe that takes some data, performs the +necessary preprocessing, and then runs this Python script.

+
+
+ +
+
+

Drawing up a plan

+
+

Previously, we saw that running ESMValTool executes a number of +tasks. What tasks do you think we will need to execute and what should +each of these tasks do to generate the warming stripes?

+
+
+
+
+
+ +
+
+

In this episode, we will need to do the following two tasks:

+
    +
  • A preprocessing task that converts the gridded temperature data to a +timeseries of global temperature anomalies
  • +
  • A diagnostic tasks that calls our Python script, taking our +preprocessed timeseries data as input.
  • +
+
+
+
+
+

Building a recipe from scratch +

+
+

The easiest way to make a new recipe is to start from an existing +one, and modify it until it does exactly what you need. However, in this +episode we will start from scratch. This forces us to think about all +the steps involved in processing the data. We will also deal with +commonly occurring errors through the development of the recipe.

+

Remember the basic structure of a recipe, and notice that each +component is extensively described in the documentation under the +section, [“Overview”][recipe-overview]{:target=“_blank”}:

+
    +
  • [documentation][recipe-section-documentation]{:target=“_blank”}
  • +
  • [datasets][recipe-section-datasets]{:target=“_blank”}
  • +
  • [preprocessors][recipe-section-preprocessors]{:target=“_blank”}
  • +
  • [diagnostics][recipe-section-diagnostics]{:target=“_blank”}
  • +
+

This is the first place to look for help if you get stuck.

+

Open a new file called recipe_warming_stripes.yml:

+
+

BASH +

+
nano recipe_warming_stripes.yml
+
+

Let’s add the standard header comments (these do not do anything), +and a first description.

+
+

YAML +

+
# ESMValTool
+# recipe_warming_stripes.yml
+---
+documentation:
+  description: Reproducing Ed Hawkins' warming stripes visualization
+  title: Reproducing Ed Hawkins' warming stripes visualization.
+
+

Notice that yaml always requires two spaces +indentation between the different levels. Pressing ctrl+o +will save the file. Verify the filename at the bottom and press enter. +Then use ctrl+x to exit the editor.

+

We will try to run the recipe after every modification we make, to +see if it (still) works!

+
+

BASH +

+
esmvaltool run recipe_warming_stripes.yml
+
+

In this case, it gives an error. Below you see the last few lines of +the error message.

+
+

ERROR +

+
...
+yamale.yamale_error.YamaleError:
+Error validating data '/home/users/username/esmvaltool_tutorial/recipe_warming_stripes.yml'
+with schema
+'/apps/jasmin/community/esmvaltool/miniconda3_py311_23.11.0-2/envs/esmvaltool/lib/python3.11/
+site-packages/esmvalcore/_recipe/recipe_schema.yml'
+	documentation.authors: Required field missing
+2024-05-27 13:21:23,805 UTC [41924] INFO
+If you have a question or need help, please start a new discussion on
+https://github.com/ESMValGroup/ESMValTool/discussions
+If you suspect this is a bug, please open an issue on
+https://github.com/ESMValGroup/ESMValTool/issues
+To make it easier to find out what the problem is, please consider attaching the
+files run/recipe_*.yml and run/main_log_debug.txt from the output directory.
+
+
+

We can use the the log message above, to understand why ESMValTool +failed. Here, this is because we missed a required field with author +names. The text +documentation.authors: Required field missing tells us +that. We see that ESMValTool always tries to validate the recipe at an +early stage. Note also the suggestion to open a GitHub issue if you need +help debugging the error message. This is something most users do when +they cannot understand the error or are not able to fix it on their +own.

+

Let’s add some additional information to the recipe. Open the recipe +file again, and add an authors section below the description. ESMValTool +expects the authors as a list, like so:

+
+

YAML +

+
authors:
+  - lastname_firstname
+
+

To bypass a number of similar error messages, add a minimal +diagnostics section below the documentation. The file should now look +like:

+
+

YAML +

+
# ESMValTool
+# recipe_warming_stripes.yml
+---
+documentation:
+  description: Reproducing Ed Hawkins' warming stripes visualization
+  title: Reproducing Ed Hawkins' warming stripes visualization.
+
+  authors:
+    - doe_john
+diagnostics:
+  dummy_diagnostic_1:
+    scripts: null
+
+

This is the minimal recipe layout that is required by ESMValTool. If +we now run the recipe again, you will probably see the following +error:

+
+

ERROR +

+
ValueError: Tag 'doe_john' does not exist in section
+'authors' of /apps/jasmin/community/esmvaltool/ESMValTool_2.10.0/esmvaltool/config-references.yml
+
+
+
+
+ +
+
+

Pro tip: config-references.yml

+
+

The error message above points to a file named +[config-references.yml][config-references] This is where ESMValTool +stores all its citation information. To add yourself as an author, add +your name in the form lastname_firstname in alphabetical +order following the existing entries, under the +# Development team section. See the [List of +authors][list-of-authors]{:target=“_blank”} section in the ESMValTool +documentation for more information.

+
+
+
+

For now, let’s just use one of the existing references. Change the +author field to righi_mattia, who cannot receive enough +credit for all the effort he put into ESMValTool. If you now run the +recipe again, you should see the final message

+
+

OUTPUT +

+
ERROR   No tasks to run!
+
+

Although there is no actual error in the recipe, ESMValTool assumes +you mistakenly left out a variable name to process and alerts you with +this error message.

+

Adding a dataset entry +

+
+

Let’s add a datasets section.

+
+
+ +
+
+

Filling in the dataset keys

+
+

Use the paths specified in the configuration file to explore the data +directory, and look at the explanation of the dataset entry in the +[ESMValTool documentation][recipe-section-datasets]{:target=“_blank”}. +For both the datasets, write down the following properties:

+
    +
  • project
  • +
  • variable (short name)
  • +
  • CMIP table
  • +
  • dataset (model name or obs/reanalysis dataset)
  • +
  • experiment
  • +
  • ensemble member
  • +
  • grid
  • +
  • start year
  • +
  • end year
  • +
+
+
+
+
+
+ +
+
+
+key | file 1 | +file 2 |
+project | CMIP6 | CMIP5 |
+short name | tas | tas |
+CMIP table | Amon | Amon |
+dataset | BCC-ESM1 | bcc-csm1-1|
+experiment | historical | historical |
+ensemble | r1i1p1f1 | r1i1p1 |
+grid | gn (native grid) | N/A |
+start year | 1850 | 1850 |
+end year | 2014 | 2005 |
+

Note that the grid key is only required for CMIP6 data, and that the +extent of the historical period has changed between CMIP5 and CMIP6.

+
+
+
+
+

Let us start with the BCC-ESM1 dataset and add a datasets section to +the recipe, listing this single dataset, as shown below. Note that key +fields such as mip or start_year are included +in the datasets section here but are part of the +diagnostic section in the recipe example seen in Running your first recipe.

+
+

YAML +

+
# ESMValTool
+# recipe_warming_stripes.yml
+---
+documentation:
+  description: Reproducing Ed Hawkins' warming stripes visualization
+  title: Reproducing Ed Hawkins' warming stripes visualization.
+
+  authors:
+    - doe_john
+datasets:
+  - {dataset: BCC-ESM1, project: CMIP6, mip: Amon, exp: historical,
+     ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2014}
+
+diagnostics:
+  dummy_diagnostic_1:
+    scripts: null
+
+

The recipe should run but produce the same message as in the previous +case since we still have not included a variable to actually process. We +have not included the short name of the variable in this dataset section +because this allows us to reuse this dataset entry with different +variable names later on. This is not really necessary for our simple use +case, but it is common practice in ESMValTool.

+
+
+ +
+
+

Pro-tip: Automatically populating a recipe with all available datasets

+
+

You can select all available models for processing using +glob patterns or wildcards. An example +datasets section that uses all available CMIP6 models and +ensemble members for the historical experiment is available +[here] [include-all-datasets]{:target=“_blank”}. Note that you will have +to set the search_esgf option in the +config_file to always so that you can download +data from ESGF nodes as needed.

+
+
+
+

Adding the preprocessor section +

+
+

Above, we already described the preprocessing task that needs to +convert the standard, gridded temperature data to a timeseries of +temperature anomalies.

+
+
+ +
+
+

Defining the preprocessor

+
+

Have a look at the available preprocessors in the +[documentation][preprocessor]{:target=“_blank”}. Write down

+
    +
  • Which preprocessor functions do you think we should use?
  • +
  • What are the parameters that we can pass to these functions?
  • +
  • What do you think should be the order of the preprocessors?
  • +
  • A suitable name for the overall preprocessor
  • +
+
+
+
+
+
+ +
+
+

We need to calculate anomalies and global means. There is an +anomalies preprocessor which takes in as arguments, a time +period, a reference period, and whether or not to standardize the data. +The global means can be calculated with the area_statistics +preprocessor, which takes an operator as argument (in our case we want +to compute the mean).

+

The default order in which these preprocessors are applied can be +seen [here][preprocessor-functions]{:target=“_blank”}: +area_statistics comes before anomalies. If you +want to change this, you can use the custom_order +preprocessor as described +[here][recipe-section-preprocessors]{:target=“_blank”}. For this +example, we will keep the default order..

+

Let’s name our preprocessor global_anomalies.

+
+
+
+
+

Add the following block to your recipe file between the +datasets and diagnostics block:

+
+

YAML +

+
preprocessors:
+  global_anomalies:
+    area_statistics:
+      operator: mean
+    anomalies:
+        period: month
+        reference:
+          start_year: 1981
+          start_month: 1
+          start_day: 1
+          end_year: 2010
+          end_month: 12
+          end_day: 31
+        standardize: false
+
+

Completing the diagnostics section +

+
+

We are now ready to finish our diagnostics section. Remember that we +want to create two tasks: a preprocessor task, and a diagnostic task. To +illustrate that we can also pass settings to the diagnostic script, we +add the option to specify a custom colormap.

+
+
+ +
+
+

Fill in the blanks

+
+

Extend the diagnostics section in your recipe by filling in the +blanks in the following template:

+
+

YAML +

+
diagnostics:
+  <... (suitable name for our diagnostic)>:
+    description: <...>
+    variables:
+      <... (suitable name for the preprocessed variable)>:
+        short_name: <...>
+        preprocessor: <...>
+    scripts:
+      <... (suitable name for our python script)>:
+        script: <full path to python script>
+        colormap: <... choose from matplotlib colormaps>
+
+
+
+
+
+
+ +
+
+
+

YAML +

+
diagnostics:
+  diagnostic_warming_stripes:
+    description: visualize global temperature anomalies as warming stripes
+    variables:
+      global_temperature_anomalies_global:
+        short_name: tas
+        preprocessor: global_anomalies
+    scripts:
+      warming_stripes_script:
+        script: ~/esmvaltool_tutorial/warming_stripes.py
+        colormap: 'bwr'
+
+
+
+
+
+

You should now be able to run the recipe to get your own warming +stripes.

+

Note: for the purpose of simplicity in this episode, we have not +added logging or provenance tracking in the diagnostic script. Once you +start to develop your own diagnostic scripts and want to add them to the +ESMValTool repositories, this will be required. Writing your own +diagnostic script is discussed in a later +episode.

+

Bonus exercises +

+
+

Below are a few exercises to practice modifying an ESMValTool recipe. +For your reference, here’s a copy of the recipe at this point. This +will be the point of departure for each of the modifications we’ll make +below.

+
+
+ +
+
+

Specific location selection

+
+

On showyourstripes.org, you can download stripes for specific +locations. Here we show how this can be done with ESMValTool. Instead of +the global mean, we can pick a location to plot the stripes for. Can you +find a suitable preprocessor to do this?

+
+
+
+
+
+ +
+
+

You can use extract_point or extract_region +to select a location. We used extract_point. Here’s a copy +of the recipe at this +point and this is the difference from the previous recipe:

+
+

DIFF +

+
--- recipe_warming_stripes.yml
++++ recipe_warming_stripes_local.yml
+@@ -10,9 +10,11 @@
+   - {dataset: BCC-ESM1, project: CMIP6, mip: Amon, exp: historical,
+      ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2014}
+
+ preprocessors:
+-  global_anomalies:
+-    area_statistics:
+-      operator: mean
++  anomalies_amsterdam:
++    extract_point:
++      latitude: 52.379189
++      longitude: 4.899431
++      scheme: linear
+     anomalies:
+       period: month
+       reference:
+@@ -27,9 +29,9 @@
+ diagnostics:
+   diagnostic_warming_stripes:
+     variables:
+-      global_temperature_anomalies:
++      temperature_anomalies_amsterdam:
+         short_name: tas
+-        preprocessor: global_anomalies
++        preprocessor: anomalies_amsterdam
+     scripts:
+       warming_stripes_script:
+         script: ~/esmvaltool_tutorial/warming_stripes.py
+
+
+
+
+
+
+
+ +
+
+

Different time periods

+
+

Split the diagnostic in two with two different time periods for the +same variable. You can choose the time periods yourself. In the example +below, we have chosen the recent past and the 20th century and have used +variable grouping.

+
+
+
+
+
+ +
+
+

Here’s a copy of the recipe at this point +and this is the difference with the previous recipe:

+
+

DIFF +

+
--- recipe_warming_stripes_local.yml
++++ recipe_warming_stripes_periods.yml
+@@ -7,7 +7,7 @@
+
+ datasets:
+-  - {dataset: BCC-ESM1, project: CMIP6, mip: Amon, exp: historical,
+-  	  ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2014}
++  - {dataset: BCC-ESM1, project: CMIP6, mip: Amon, exp: historical,
++     ensemble: r1i1p1f1, grid: gn}
+
+ preprocessors:
+   anomalies_amsterdam:
+@@ -29,9 +29,16 @@
+ diagnostics:
+   diagnostic_warming_stripes:
+     variables:
+-      temperature_anomalies_amsterdam:
++      temperature_anomalies_recent:
+         short_name: tas
+         preprocessor: anomalies_amsterdam
++        start_year: 1950
++        end_year: 2014
++      temperature_anomalies_20th_century:
++        short_name: tas
++        preprocessor: anomalies_amsterdam
++        start_year: 1900
++        end_year: 1999
+     scripts:
+       warming_stripes_script:
+         script: ~/esmvaltool_tutorial/warming_stripes.py
+
+
+
+
+
+
+
+ +
+
+

Different preprocessors

+
+

Now that you have different variable groups, we can also use +different preprocessors. Add a second preprocessor to add another +location of your choosing.

+
+

Solution

+

Here’s a copy of the recipe at +this point and this is the difference with the previous recipe:

+
+

DIFF +

+
--- recipe_warming_stripes_periods.yml
++++ recipe_warming_stripes_multiple_locations.yml
+@@ -15,7 +15,7 @@
+       latitude: 52.379189
+       longitude: 4.899431
+       scheme: linear
+-    anomalies:
++    anomalies: &anomalies
+       period: month
+       reference:
+         start_year: 1981
+@@ -25,18 +25,24 @@
+         end_month: 12
+         end_day: 31
+       standardize: false
++  anomalies_london:
++    extract_point:
++      latitude: 51.5074
++      longitude: 0.1278
++      scheme: linear
++    anomalies: *anomalies
+
+ diagnostics:
+   diagnostic_warming_stripes:
+     variables:
+-      temperature_anomalies_recent:
++      temperature_anomalies_recent_amsterdam:
+         short_name: tas
+         preprocessor: anomalies_amsterdam
+         start_year: 1950
+         end_year: 2014
+-      temperature_anomalies_20th_century:
++      temperature_anomalies_20th_century_london:
+         short_name: tas
+-        preprocessor: anomalies_amsterdam
++        preprocessor: anomalies_london
+         start_year: 1900
+         end_year: 1999
+     scripts:
+
+
+
+
+
+
+
+ +
+
+

Pro-tip: YAML anchors

+
+

If you want to avoid retyping the arguments used in your +preprocessor, you can use YAML anchors as seen in the +anomalies preprocessor specifications in the recipe +above.

+
+
+
+
+
+ +
+
+

Additional datasets

+
+

So far we have defined the datasets in the datasets section of the +recipe. However, it’s also possible to add specific datasets only for +specific variables or variable groups. Take a look at the documentation +to learn about the additional_datasets keyword +[here][additional-datasets]{:target=“_blank”}, and add a second dataset +only for one of the variable groups.

+
+
+
+
+
+ +
+
+

Here’s a copy of the recipe at +this point and this is the difference with the previous recipe:

+
+

DIFF +

+
--- recipe_warming_stripes_multiple_locations.yml
++++ recipe_warming_stripes_additional_datasets.yml
+@@ -45,6 +45,8 @@
+         preprocessor: anomalies_london
+         start_year: 1900
+         end_year: 1999
++        additional_datasets:
++          - {dataset: CanESM2, project: CMIP5, mip: Amon, exp: historical, ensemble: r1i1p1}
+     scripts:
+       warming_stripes_script:
+         script: ~/esmvaltool_tutorial/warming_stripes.py
+
+
+
+
+
+
+
+ +
+
+

Multiple ensemble members

+
+

You can choose data from multiple ensemble members for a model in a +single line.

+
+
+
+
+
+ +
+
+

The dataset section allows you to choose more than one +ensemble member Here’s a copy of the changed recipe +to do that. Changes made are shown in the diff output below:

+
+

DIFF +

+
--- recipe_warming_stripes.yml	2024-05-27 15:37:52.340358967 +0100
++++ recipe_warming_stripes_multiens.yml	2024-05-27 22:18:42.035558837 +0100
+@@ -10,7 +10,7 @@
+-     ensemble: r1i1p1f1, grid: gn, start_year: 1850, end_year: 2014}
++     ensemble: "r(1:2)i1p1f1", grid: gn, start_year: 1850, end_year: 2014}
+
+
+
+
+
+
+
+ +
+
+

Pro-tip: Concatenating datasets

+
+

Check out the section on a different way to use multiple ensemble +members or even multiple experiments at [Concatenating data +corresponding to multiple facets] +[concatenating-datasets]{:target=“_blank”}.

+
+
+
+
+
+ +
+
+

Key Points

+
+
    +
  • A recipe can work with different preprocessors at the same +time.
  • +
  • The setting additional_datasets can be used to add a +different dataset.
  • +
  • Variable groups are useful for defining different settings for +different variables.
  • +
  • Multiple ensemble members and experiments can be analysed in a +single recipe through concatenation.
  • +
+
+
+
+

Content from Development and contribution

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • What is a development installation?
  • +
  • How can I test new or improved code?
  • +
  • How can I incorporate my contributions into ESMValTool?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Execute a successful ESMValTool installation from the source +code.
  • +
  • Contribute to ESMValTool development.
  • +
+
+
+
+
+
+

We now know how ESMValTool works, but how do we develop it? +ESMValTool is an open-source project in ESMValGroup. We can contribute +to its development by:

+ +

In this lesson, we first show how to set up a development +installation of ESMValTool so you can make changes or additions. We then +explain how you can contribute these changes to the community.

+
+
+ +
+
+

Git knowledge

+
+

For this episode, you need some knowledge of Git. You can refresh your knowledge in +the corresponding Git carpentries +course.

+
+
+
+

Development installation +

+
+

We’ll explore how ESMValTool can be installed it in a +develop mode. Even if you aren’t collaborating with the +community, this installation is needed to run your new codes with +ESMValTool. Let’s get started.

+
+

1 Source code +

+

The ESMValTool source code is available on a public GitHub +repository: https://github.com/ESMValGroup/ESMValTool. To obtain the +code, there are two options:

+
    +
  1. Download the code from the repository. A ZIP file called +ESMValTool-main.zip is downloaded. To continue the +installation, unzip the file, move to the ESMValTool-main +directory and then follow the sequence of steps starting from the +section on ESMValTool dependencies below.
  2. +
  3. Clone the repository if you want to contribute to the ESMValTool +development:
  4. +
+
+

BASH +

+
git clone https://github.com/ESMValGroup/ESMValTool.git
+
+

This command will ask your GitHub username and a personal +token as password. Please follow instructions on +[GitHub token authentication +requirements][token-authentication-requirements] to create a personal +access token. Alternatively, you could [generate a new SSH +key][generate-ssh-key] and [add it to your GitHub account][add-ssh-key]. +After the authentication, the output might look like:

+
+

OUTPUT +

+
Cloning into 'ESMValTool'...
+remote: Enumerating objects: 163, done.
+remote: Counting objects: 100% (163/163), done.
+remote: Compressing objects: 100% (125/125), done.
+remote: Total 95049 (delta 84), reused 76 (delta 30), pack-reused 94886
+Receiving objects: 100% (95049/95049), 175.16 MiB | 5.48 MiB/s, done.
+Resolving deltas: 100% (68808/68808), done.
+
+

Now, a folder called ESMValTool has been created in your +working directory. This folder contains the source code of the tool. To +continue the installation, we move into the ESMValTool +directory:

+
+

BASH +

+
cd ESMValTool
+
+

Note that the main branch is checked out by default. We +can see this if we run:

+
+

BASH +

+
git status
+
+
+

OUTPUT +

+
On branch main
+Your branch is up to date with 'origin/main'.
+
+nothing to commit, working tree clean
+
+
+
+

2 ESMValTool dependencies +

+

Please don’t forget if an esmvaltool environment is already created +following the lesson Installation, we +should choose another name for the new environment in this lesson.

+

ESMValTool now uses mamba instead of conda +for the recommended installation. For a minimal mamba installation, see +section Install Mamba in lesson Installation.

+

It is good practice to update the version of mamba and conda on your +machine before setting up ESMValTool. This can be done as follows:

+
+

BASH +

+
mamba update --name base mamba conda
+
+

To simplify the installation process, an environment file +environment.yml is provided in the ESMValTool directory. We +create an environment by running:

+
+

BASH +

+
mamba env create --name esmvaltool --file environment.yml
+
+

The environment is called esmvaltool by default. If an +esmvaltool environment is already created following the +lesson Installation, we should choose +another name for the new environment in this lesson by:

+
+

BASH +

+
mamba env create --name a_new_name --file environment.yml
+
+

This will create a new conda environment and install ESMValTool (with +all dependencies that are needed for development purposes) into it with +a single command.

+

For more information see [conda managing +environments][manage-environments].

+

Now, we should activate the environment:

+
+

BASH +

+
conda activate esmvaltool
+
+

where esmvaltool is the name of the environment (replace +by a_new_name in case another environment name was +used).

+
+
+

3 ESMValTool installation +

+

ESMValTool can be installed in a develop mode by +running:

+
+

BASH +

+
pip install --editable '.[develop]'
+
+

This will add the esmvaltool directory to the Python +path in editable mode and install the development dependencies. We +should check if the installation works properly. To do this, run the +tool with:

+
+

BASH +

+
esmvaltool --help
+
+

If the installation is successful, ESMValTool prints a help message +to the console.

+
+
+ +
+
+

Checking the development installation

+
+

We can use the command mamba list to list installed +packages in the esmvaltool environment. Use this command to +check that ESMValTool is installed in a develop mode.

+

Tip: see the documentation +on conda list.

+
+
+
+
+
+ +
+
+

Run:

+
+

BASH +

+
mamba list esmvaltool
+
+
+

BASH +

+
# Name                    Version                   Build  Channel
+esmvaltool                2.10.0.dev3+g2dbc2cfcc    pypi_0   pypi
+
+
+
+
+
+
+
+

4 Updating ESMValTool +

+

The main branch has the latest features of ESMValTool. +Please make sure that the source code on your machine is up-to-date. If +you obtain the source code using git clone as explained in step +1 Source code, you can run git pull to +update the source code. Then ESMValTool installation will be updated +with changes from the main branch.

+
+

Contribution +

+
+

We have seen how to install ESMValTool in a develop +mode. Now, we try to contribute to its development. Let’s see how this +can be achieved. We first discuss our ideas in an issue +in ESMValTool repository. This can avoid disappointment at a later +stage, for example, if more people are doing the same thing. It also +gives other people an early opportunity to provide input and +suggestions, which results in more valuable contributions.

+

Then, we create a new branch locally and start +developing new codes. To create a new branch:

+
+

BASH +

+
git checkout -b your_branch_name
+
+

If needed, a link to a git tutorial can be found in Setup.

+

Once our development is finished, we can initiate a +pull request. To this end, we encourage you to join the +ESMValTool development team.

+

For more extensive documentation on contributing code, including a +section on the GitHubWorkflow, +please see the [Contributing code and documentation][code-documentation] +section in the ESMValtool documentation.

+
+

Review process +

+

The pull request will be tested, discussed and merged as part of the +“review process”. The process will take some effort and +time to learn. However, a few (command line) tools can get you a long +way, and we’ll cover those essentials in the next sections.

+

Tip: we encourage you to keep the pull requests +small. Reviewing small incremental changes is more efficient.

+
+
+

Working example +

+

We saw the ‘warming stripes’ diagnostic in lesson Writing your own recipe. Imagine the +following task: you want to contribute warming stripes recipe and +diagnostics to ESMValTool. You have to add the diagnostics warming_stripes.py and the recipe recipe_warming_stripes.yml +to their locations in ESMValTool directory. After these changes, you +should also check if everything works fine. This is where we take +advantage of the tools that are introduced later.

+

Let’s get started. Note that since this is an exercise to get +familiar with the development and contribution process, we will not +create a GitHub issue at this time but proceed as though it has been +done.

+
+
+

Check code quality +

+

We aim to adhere to best practices and coding standards. There are +several tools that check our code against those standards like:

+
    +
  • +flake8 for +checking against the PEP8 style guide
  • +
  • +yapf to ensure +consistent formatting for the whole project
  • +
  • +isort to consistently +sort the import statements
  • +
  • +yamllint +to ensure there are no syntax errors in our recipes and config +files
  • +
  • +lintr for +diagnostic scripts written in R
  • +
  • +codespell to check +grammar
  • +
+

The good news is that pre-commit has been already +installed when we chose development installation. +pre-commit is a command line and runs all of those tools. +It also fixes some of those errors. To explore other tools, have a look +at ESMValTool documentation on Code +style.

+
+
+ +
+
+

Using pre-commit

+
+

Let’s checkout our local branch and add the script warming_stripes.py to the +esmvaltool/diag_scripts directory.

+
+

BASH +

+
cd ESMValTool
+git checkout your_branch_name
+cp path_of_warming_stripes.py esmvaltool/diag_scripts/
+
+

By default, pre-commit only runs on the files that have +been staged in git:

+
+

BASH +

+
git status
+git add esmvaltool/diag_scripts/warming_stripes.py
+pre-commit run --files esmvaltool/diag_scripts/warming_stripes.py
+
+

Inspect the output of pre-commit and fix the remaining +errors.

+
+
+
+
+
+ +
+
+

The tail of the output of pre-commit:

+
+

BASH +

+
Check for added large files..............................................Passed
+Check python ast.........................................................Passed
+Check for case conflicts.................................................Passed
+Check for merge conflicts................................................Passed
+Debug Statements (Python)................................................Passed
+Fix End of Files.........................................................Passed
+Trim Trailing Whitespace.................................................Passed
+yamllint.............................................(no files to check)Skipped
+nclcodestyle.........................................(no files to check)Skipped
+style-files..........................................(no files to check)Skipped
+lintr................................................(no files to check)Skipped
+codespell................................................................Passed
+isort....................................................................Passed
+yapf.....................................................................Passed
+docformatter.............................................................Failed
+- hook id: docformatter
+- files were modified by this hook
+flake8...................................................................Failed
+- hook id: flake8
+- exit code: 1
+
+esmvaltool/diag_scripts/warming_stripes.py:20:5:
+F841 local variable 'nx' is assigned to but never used
+
+

As can be seen above, there are two Failed check:

+
    +
  1. +docformatter: it is mentioned that “files were modified +by this hook”. We run git diff to see the modifications. +The output includes the following:
  2. +
+
+

BASH +

+
+in the form of the popular warming stripes figure by Ed Hawkins."""
+
+

The syntax """ at the end of docstring is moved by one +line. Shifting it to the next line should fix this error.

+
    +
  1. +flake8: the error message is about an unused local +variable nx. We should check our codes regarding the usage +of nx. For now, let’s assume that it is added by mistake +and remove it. Note that you have to run git add again to +re-stage the file. Then rerun pre-commit and check that it passes.
  2. +
+
+
+
+
+
+
+

Run unit tests +

+

Previous section introduced some tools to check code style and +quality. There is lack of mechanism to determine whether or not our code +is getting the right answer. To achieve that, we need to write and run +tests for widely-used functions. ESMValTool comes with a lot of tests +that are in the folder tests.

+

To run tests, first we make sure that the working directory is +ESMValTool and our local branch is checked out. Then, we +can run tests using pytest locally:

+
+

BASH +

+
pytest
+
+

Tests will also be run automatically by CircleCI, when you submit a pull +request.

+
+
+ +
+
+

Running tests

+
+

Make sure our local branch is checked out and add the recipe recipe_warming_stripes.yml +to the esmvaltool/recipes directory:

+
+

BASH +

+
cp path_of_recipe_warming_stripes.yml esmvaltool/recipes/
+
+

Run pytest and inspect the results, this might take a +few minutes. If a test is failed, try to fix it.

+
+
+
+
+
+ +
+
+

Run:

+
+

BASH +

+
pytest
+
+

When pytest run is complete, you can inspect the test +reports that are printed in the console. Have a look at the second +section of the report FAILURES:

+
+

BASH +

+
================================ FAILURES ==========================================
+______________ test_recipe_valid[recipe_warming_stripes.yml] ______________
+
+

The test message shows that the recipe +recipe_warming_stripes.yml is not a valid recipe. Look for +a line that starts with an E in the rest of the +message:

+
+

BASH +

+

+E           esmvalcore._task.DiagnosticError: Cannot execute script
+'~/esmvaltool_tutorial/warming_stripes.py' (~/esmvaltool_tutorial/warming_stripes.py):
+file does not exist.
+
+

To fix the recipe, we need to edit the path of the diagnostic script +as warming_stripes.py:

+
+

YAML +

+
   scripts:
+     warming_stripes_script:
+       script: warming_stripes.py
+
+

For details, see lesson Writing your +own diagnostic script.

+
+
+
+
+
+
+

Build documentation +

+

When we add or update a code, we also update its corresponding +documentation. The ESMValTool documentation is available on docs.esmvaltool.org. +The source files are located in +ESMValTool/doc/sphinx/source/.

+

To build documentation locally, first we make sure that the working +directory is ESMValTool and our local branch is checked +out. Then, we run:

+
+

BASH +

+
sphinx-build -Ea doc/sphinx/source/ doc/sphinx/build/
+
+

Similar to code, documentation should be well written and adhere to +standards. If the documentation is built properly, the previous command +prints a message to the console:

+
+

OUTPUT +

+
build succeeded.
+
+The HTML pages are in doc/sphinx/build.
+
+

The main page of the documentation has been built into +index.html in doc/sphinx/build/ directory. To +preview this page locally, we open the file in a web browser:

+
+

BASH +

+
xdg-open doc/sphinx/build/index.html
+
+
+
+ +
+
+

Creating a documentation

+
+

In previous exercises, we added the recipe recipe_warming_stripes.yml +to ESMValTool. Now, we create a documentation file +recipe_warming_stripes.rst for this recipe:

+
+

BASH +

+
nano doc/sphinx/source/recipes/recipe_warming_stripes.rst
+
+

Add a reference i.e. .. _recipe_warming_stripes:, a +section title and some text about the recipe like:

+
.. _recipe_warming_stripes:
+
+Reproducing Ed Hawkins' warming stripes visualization
+======================================================
+
+This recipe produces warming stripes plots.
+

Save and close the file. We can think of this file as one page of a +book. Examples of documentation pages can be found in the folder +ESMValTool/doc/sphinx/source/recipes. Then, we need to +decide where this page should be located inside the book. The table of +content is defined by index.rst. Let’s have a look at the +content:

+
+

BASH +

+
nano doc/sphinx/source/recipes/index.rst
+
+

Add the recipe name i.e. recipe_warming_stripes to the +section Other in this file and preview the recipe +documentation page locally.

+
+
+
+
+
+ +
+
+

First, we add the recipe name recipe_warming_stripes to +the section Other:

+
Other
+^^^^^
+.. toctree::
+  :maxdepth: 1
+  ...
+  ...
+  recipe_warming_stripes
+

Then, we build and preview the documentation page:

+
+

BASH +

+
sphinx-build -Ea doc/sphinx/source/ doc/sphinx/build/
+xdg-open doc/sphinx/build/recipes/recipe_warming_stripes.html
+
+
+
+
+
+

Congratulations! You are now ready to make a pull request.

+
+
+ +
+
+

Key Points

+
+
    +
  • A development installation is needed if you want to incorporate your +code into ESMValTool.
  • +
  • Contributions include adding a new or improved script or helping +with a review process.
  • +
  • There are several tools to help improve the quality of your +code.
  • +
  • It is possible to run tests on your machine.
  • +
  • You can preview documentation pages locally.
  • +
+
+
+
+
+

Content from Writing your own diagnostic script

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • How do I write a new diagnostic in ESMValTool?
  • +
  • How do I use the preprocessor output in a Python diagnostic?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Write a new Python diagnostic script.
  • +
  • Explain how a diagnostic script reads the preprocessor output.
  • +
+
+
+
+
+
+

Introduction +

+
+

The diagnostic script is an important component of ESMValTool and it +is where the scientific analysis or performance metric is implemented. +With ESMValTool, you can adapt an existing diagnostic or write a new +script from scratch. Diagnostics can be written in a number of open +source languages such as Python, R, Julia and NCL but we will focus on +understanding and writing Python diagnostics in this lesson.

+

In this lesson, we will explain how to find an existing diagnostic +and run it using ESMValTool installed in editable/development mode. For +a development installation, see the instructions in the lesson Development and contribution. Also, +we will work with the recipe [recipe_python.yml][recipe] and the +diagnostic script [diagnostic.py][diagnostic] called by this recipe that +we have seen in the lesson Running your first +recipe .

+

Let’s get started!

+

Understanding an existing Python diagnostic +

+
+

If you clone the ESMValTool repository, a folder called +ESMValTool is created in your home/working directory, see +the instructions in the lesson Development and contribution .

+

The folder ESMValTool contains the source code of the +tool. We can find the recipe recipe_python.yml and the +python script diagnostic.py in these directories:

+
    +
  • ~/ESMValTool/esmvaltool/recipes/examples/recipe_python.yml
  • +
  • ~/ESMValTool/esmvaltool/diag_scripts/examples/diagnostic.py
  • +
+

Let’s have look at the code in diagnostic.py. For +reference, we show the diagnostic code in the dropdown box below. There +are four main sections in the script:

+
    +
  • A description i.e. the docstring (line 1).
  • +
  • Import statements (line 2-16).
  • +
  • Functions that implement our analysis (line 21-102).
  • +
  • A typical Python top-level script +i.e. if __name__ == '__main__' (line 105-108).
  • +
+
+
+ +
+
+
+

PYTHON +

+
 1:  """Python example diagnostic."""
+ 2:  import logging
+ 3:  from pathlib import Path
+ 4:  from pprint import pformat
+ 5:
+ 6:  import iris
+ 7:
+ 8:  from esmvaltool.diag_scripts.shared import (
+ 9:      group_metadata,
+10:      run_diagnostic,
+11:      save_data,
+12:      save_figure,
+13:      select_metadata,
+14:      sorted_metadata,
+15:  )
+16:  from esmvaltool.diag_scripts.shared.plot import quickplot
+17:
+18:  logger = logging.getLogger(Path(__file__).stem)
+19:
+20:
+21:  def get_provenance_record(attributes, ancestor_files):
+22:      """Create a provenance record describing the diagnostic data and plot."""
+23:      caption = caption = attributes['caption'].format(**attributes)
+24:
+25:      record = {
+26:          'caption': caption,
+27:          'statistics': ['mean'],
+28:          'domains': ['global'],
+29:          'plot_types': ['zonal'],
+30:          'authors': [
+31:              'andela_bouwe',
+32:              'righi_mattia',
+33:          ],
+34:          'references': [
+35:              'acknow_project',
+36:          ],
+37:          'ancestors': ancestor_files,
+38:      }
+39:      return record
+40:
+41:
+42:  def compute_diagnostic(filename):
+43:      """Compute an example diagnostic."""
+44:      logger.debug("Loading %s", filename)
+45:      cube = iris.load_cube(filename)
+46:
+47:      logger.debug("Running example computation")
+48:      cube = iris.util.squeeze(cube)
+49:      return cube
+50:
+51:
+52:  def plot_diagnostic(cube, basename, provenance_record, cfg):
+53:      """Create diagnostic data and plot it."""
+54:
+55:      # Save the data used for the plot
+56:      save_data(basename, provenance_record, cfg, cube)
+57:
+58:      if cfg.get('quickplot'):
+59:          # Create the plot
+60:          quickplot(cube, **cfg['quickplot'])
+61:          # And save the plot
+62:          save_figure(basename, provenance_record, cfg)
+63:
+64:
+65:  def main(cfg):
+66:      """Compute the time average for each input dataset."""
+67:      # Get a description of the preprocessed data that we will use as input.
+68:      input_data = cfg['input_data'].values()
+69:
+70:      # Demonstrate use of metadata access convenience functions.
+71:      selection = select_metadata(input_data, short_name='tas', project='CMIP5')
+72:      logger.info("Example of how to select only CMIP5 temperature data:\n%s",
+73:                  pformat(selection))
+74:
+75:      selection = sorted_metadata(selection, sort='dataset')
+76:      logger.info("Example of how to sort this selection by dataset:\n%s",
+77:                  pformat(selection))
+78:
+79:      grouped_input_data = group_metadata(input_data,
+80:                                          'variable_group',
+81:                                          sort='dataset')
+82:      logger.info(
+83:          "Example of how to group and sort input data by variable groups from "
+84:          "the recipe:\n%s", pformat(grouped_input_data))
+85:
+86:      # Example of how to loop over variables/datasets in alphabetical order
+87:      groups = group_metadata(input_data, 'variable_group', sort='dataset')
+88:      for group_name in groups:
+89:          logger.info("Processing variable %s", group_name)
+90:          for attributes in groups[group_name]:
+91:              logger.info("Processing dataset %s", attributes['dataset'])
+92:              input_file = attributes['filename']
+93:              cube = compute_diagnostic(input_file)
+94:
+95:              output_basename = Path(input_file).stem
+96:              if group_name != attributes['short_name']:
+97:                  output_basename = group_name + '_' + output_basename
+98:              if "caption" not in attributes:
+99:                  attributes['caption'] = input_file
+100:              provenance_record = get_provenance_record(
+101:                  attributes, ancestor_files=[input_file])
+102:              plot_diagnostic(cube, output_basename, provenance_record, cfg)
+103:
+104:
+105:  if __name__ == '__main__':
+106:
+107:      with run_diagnostic() as config:
+108:          main(config)
+
+
+
+
+
+
+
+ +
+
+

What is the starting point of a diagnostic?

+
+
    +
  1. Can you spot a function called main in the code +above?
  2. +
  3. What are its input arguments?
  4. +
  5. How many times is this function mentioned?
  6. +
+
+
+
+
+
+ +
+
+
    +
  1. The main function is defined in line 65 as +main(cfg).
  2. +
  3. The input argument to this function is the variable +cfg, a Python dictionary that holds all the necessary +information needed to run the diagnostic script such as the location of +input data and various settings. We will next parse this +cfg variable in the main function and extract +information as needed to do our analyses (e.g. in line 68).
  4. +
  5. The main function is called near the very end on line +108. So, it is mentioned twice in our code - once where it is called by +the top-level Python script and second where it is defined.
  6. +
+
+
+
+
+
+
+ +
+
+

The function run_diagnostic

+
+

The function run_diagnostic (line 107) is called a +context manager provided with ESMValTool and is the main entry point for +most Python diagnostics.

+
+
+
+

Preprocessor-diagnostic interface +

+
+

In the previous exercise, we have seen that the variable +cfg is the input argument of the main +function. The first argument passed to the diagnostic via the +cfg dictionary is a path to a file called +settings.yml. The ESMValTool documentation page provides an +overview of what is in this file, see [Diagnostic script +interfaces][interface].

+
+
+ +
+
+

What information do I need when writing a diagnostic script?

+
+

From the lesson Configuration, we +saw how to change the configuration settings before running a recipe. +First we set the option remove_preproc_dir to +false in the configuration file, then run the recipe +recipe_python.yml:

+
+

BASH +

+
esmvaltool run examples/recipe_python.yml
+
+
    +
  1. Find one example of the file settings.yml in the +run directory?
  2. +
  3. Open the file settings.yml and look at the +input_files list. It contains paths to some files +metadata.yml. What information do you think is saved in +those files?
  4. +
+
+
+
+
+
+ +
+
+
    +
  1. One example of settings.yml can be found in the +directory: +path_to_recipe_output/run/map/script1/settings.yml +
  2. +
  3. The metadata.yml files hold information about the +preprocessed data. There is one file for each variable having detailed +information on your data including project (e.g., CMIP6, CMIP5), dataset +names (e.g., BCC-ESM1, CanESM2), variable attributes (e.g., +standard_name, units), preprocessor applied and time range of the data. +You can use all of this information in your own diagnostic.
  4. +
+
+
+
+
+

Diagnostic shared functions +

+
+

Looking at the code in diagnostic.py, we see that +input_data is read from the cfg dictionary +(line 68). Now we can group the input_data according to +some criteria such as the model or experiment. To do so, ESMValTool +provides many functions such as select_metadata (line 71), +sorted_metadata (line 75), and group_metadata +(line 79). As you can see in line 8, these functions are imported from +esmvaltool.diag_scripts.shared that means these are shared +across several diagnostics scripts. A list of available functions and +their description can be found in [The ESMValTool Diagnostic API +reference][shared].

+
+

Extracting +information needed for analyses

+

We have seen the functions used for selecting, sorting and grouping +data in the script. What do these functions do?

+
+

Answer

+

There is a statement after use of select_metadata, +sorted_metadata and group_metadata that starts +with logger.info (lines 72, 76 and 82). These lines print +output to the log files. In the previous exercise, we ran the recipe +recipe_python.yml. If you look at the log file +recipe_python_#_#/run/map/script1/log.txt in +esmvaltool_output directory, you can see the output from +each of these functions, for example:

+
2023-06-28 12:47:14,038 [2548510] INFO     diagnostic,106	Example of how to
+group and sort input data by variable groups from the recipe:
+{'tas': [{'alias': 'CMIP5',
+         'caption': 'Global map of {long_name} in January 2000 according to '
+                    '{dataset}.\n',
+         'dataset': 'bcc-csm1-1',
+         'diagnostic': 'map',
+         'end_year': 2000,
+         'ensemble': 'r1i1p1',
+         'exp': 'historical',
+         'filename': '~/recipe_python_20230628_124639/preproc/map/tas/
+
+
+
            CMIP5_bcc-csm1-1_Amon_historical_r1i1p1_tas_2000-P1M.nc',
+
+
+
     'frequency': 'mon',
+     'institute': ['BCC'],
+     'long_name': 'Near-Surface Air Temperature',
+     'mip': 'Amon',
+     'modeling_realm': ['atmos'],
+     'preprocessor': 'to_degrees_c',
+     'product': ['output1', 'output2'],
+     'project': 'CMIP5',
+     'recipe_dataset_index': 1,
+     'short_name': 'tas',
+     'standard_name': 'air_temperature',
+     'start_year': 2000,
+     'timerange': '2000/P1M',
+     'units': 'degrees_C',
+     'variable_group': 'tas',
+     'version': 'v1'},
+    {'activity': 'CMIP',
+     'alias': 'CMIP6',
+     'caption': 'Global map of {long_name} in January 2000 according to '
+                '{dataset}.\n',
+     'dataset': 'BCC-ESM1',
+     'diagnostic': 'map',
+     'end_year': 2000,
+     'ensemble': 'r1i1p1f1',
+     'exp': 'historical',
+     'filename': '~/recipe_python_20230628_124639/preproc/map/tas/
+
+
+
            CMIP6_BCC-ESM1_Amon_historical_r1i1p1f1_tas_gn_2000-P1M.nc',
+
+
+ +
+
+

Challenge

+
+ +
+
+
+
+
+ +
+
+
     'frequency': 'mon',
+     'grid': 'gn',
+     'institute': ['BCC'],
+     'long_name': 'Near-Surface Air Temperature',
+     'mip': 'Amon',
+     'modeling_realm': ['atmos'],
+     'preprocessor': 'to_degrees_c',
+     'project': 'CMIP6',
+     'recipe_dataset_index': 0,
+     'short_name': 'tas',
+     'standard_name': 'air_temperature',
+     'start_year': 2000,
+     'timerange': '2000/P1M',
+     'units': 'degrees_C',
+     'variable_group': 'tas',
+     'version': 'v20181214'}]}
+

+This is how we can access preprocessed data within our diagnostic.
+
+
+
+
+

Diagnostic computation +

+
+

After grouping and selecting data, we can read individual attributes +(such as filename) of each item. Here, we have grouped the input data by +variables, so we loop over the variables (line 88). +Following this is a call to the function compute_diagnostic +(line 93). Let’s look at the definition of this function in line 42, +where the actual analysis of the data is done.

+

Note that output from the ESMValCore preprocessor is in the form of +NetCDF files. Here, compute_diagnostic uses Iris +to read data from a netCDF file and performs an operation +squeeze to remove any dimensions of length one. We can +adapt this function to add our own analysis. As an example, here we +calculate the bias using the average of the data using Iris cubes.

+
+

PYTHON +

+
def compute_diagnostic(filename):
+    """Compute an example diagnostic."""
+    logger.debug("Loading %s", filename)
+    cube = iris.load_cube(filename)
+
+    logger.debug("Running example computation")
+    cube = iris.util.squeeze(cube)
+
+    # Calculate a bias using the average of data
+    cube.data = cube.core_data() - cube.core_data.mean()
+    return cube
+
+
+
+ +
+
+

iris cubes

+
+

Iris reads data from NetCDF files into data structures called cubes. +The data in these cubes can be modified, combined with other cubes’ data +or plotted.

+
+
+
+
+
+ +
+
+

Reading data using xarray

+
+

Alternately, you can use xarrays to read the data +instead of Iris.

+
+
+
+
+
+ +
+
+

First, import xarray package at the top of the script +as:

+
+

PYTHON +

+
import xarray as xr
+
+

Then, change the compute_diagnostic as:

+
+

PYTHON +

+
def compute_diagnostic(filename):
+   """Compute an example diagnostic."""
+   logger.debug("Loading %s", filename)
+   dataset = xr.open_dataset(filename)
+
+   #do your analyses on the data here
+
+   return dataset
+
+

Caution: If you read data using xarray keep in mind to change +accordingly the other functions in the diagnostic which are dealing at +the moment with Iris cubes.

+
+
+
+
+
+
+ +
+
+

Reading data using the netCDF4 package

+
+

Yet another option to read the NetCDF file data is to use the +[netCDF-4 Python interface][netCDF] to the netCDF C library.

+
+
+
+
+
+ +
+
+

First, import the netCDF4 package at the top of the +script as:

+
+

PYTHON +

+
import netCDF4
+
+

Then, change compute_diagnostic as:

+
+

PYTHON +

+
def compute_diagnostic(filename):
+   """Compute an example diagnostic."""
+   logger.debug("Loading %s", filename)
+   nc_data = netCDF4.Dataset(filename,'r')
+
+   #do your analyses on the data here
+
+   return nc_data
+
+

Caution: If you read data using netCDF4 keep in mind to change +accordingly the other functions in the diagnostic which are dealing at +the moment with Iris cubes.

+
+
+
+
+

Diagnostic output +

+
+
+

Plotting the output +

+

Often, the end product of a diagnostic script is a plot or figure. +The Iris cube returned from the compute_diagnostic function +(line 93) is passed to the plot_diagnostic function (line +102). Let’s have a look at the definition of this function in line 52. +This is where we would plug in our plotting routine in the diagnostic +script.

+

More specifically, the quickplot function (line 60) can +be replaced with the function of our choice. As can be seen, this +function uses **cfg['quickplot'] as an input argument. If +you look at the diagnostic section in the recipe +recipe_python.yml, you see quickplot is a key +there:

+
+

YAML +

+
     script1:
+       script: examples/diagnostic.py
+        quickplot:
+          plot_type: pcolormesh
+          cmap: Reds
+
+

This way, we can pass arguments such as the type of plot +pcolormesh and the colormap cmap:Reds from the +recipe to the quickplot function in the diagnostic.

+
+
+ +
+
+

Passing arguments from the recipe to the diagnostic

+
+

Change the type of the plot and its colormap and inspect the output +figure.

+
+
+
+
+
+ +
+
+

In the recipe recipe_python.yml, you could change +plot_type and cmap. As an example, we choose +plot_type: pcolor and cmap: BuGn:

+
+

YAML +

+
    script1:
+      script: examples/diagnostic.py
+       quickplot:
+         plot_type: pcolor
+         cmap: BuGn
+
+

The plot can be found at +path_to_recipe_output/plots/map/script1/png.

+
+
+
+
+ +
+
+

Saving the output +

+

In our example, the function save_data in line 56 is +used to save the Iris cube. The saved files can be found under the +work directory in a .nc format. There is also +the function save_figure in line 62 to save the plots under +the plot directory in a .png format (or +preferred format specified in your configuration settings). Again, you +may choose your own method of saving the output.

+
+
+

Recording the provenance +

+

When developing a diagnostic script, it is good practice to record +provenance. To do so, we use the function +get_provenance_record (line 100). Let us have a look at the +definition of this function in line 21 where we describe the diagnostic +data and plot. Using the dictionary record, it is possible +to add custom provenance to our diagnostics output. Provenance is stored +in the W3C PROV +XML format and also in an SVG file under the +work and plot directory. For more information, +see [recording provenance][provenance].

+
+

Congratulations! +

+
+

You now know the basic diagnostic script structure and some available +tools for putting together your own diagnostics. Have a look at existing +recipes and diagnostics in the repository for more examples of functions +you can use in your diagnostics!

+
+
+ +
+
+

Key Points

+
+
    +
  • ESMValTool provides helper functions to interface a Python +diagnostic script with preprocessor output.
  • +
  • Existing diagnostics can be used as templates and modified to write +new diagnostics.
  • +
  • Helper functions can be imported from +esmvaltool.diag_scripts.shared and used in your own +diagnostic script.
  • +
+
+
+
+

Content from CMORization: adding new datasets to ESMValTool

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • CMORization: what is it and why do we need it?
  • +
  • How to use the existing CMORizer scripts shipped with +ESMValTool?
  • +
  • How to add support for new (observational) datasets?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Understand what CMORization is and why it is necessary.
  • +
  • Use existing scripts to CMORize your data.
  • +
  • Write a new CMORizer script to support additional data.
  • +
+
+
+
+
+
+
Data flow with ESMValTool

Introduction +

+
+

This episode deals with “CMORization”. ESMValTool is designed to work +with data that follow the CMOR standards. Unfortunately, not all +datasets follow these standards. In order to use such datasets in +ESMValTool we first need to reformat the data. This process is called +“CMORization”.

+
+
+ +
+
+

What are the CMOR standards?

+
+

The name “CMOR” originates from a tool: the Climate Model Output Rewriter. +This tool is used to create “CF-Compliant netCDF files for use in the +CMIP projects”. So CMOR extends the CF-standard with additional +requirements for the Coupled Model Intercomparison Projects (see e.g. here).

+

Concretely, the CMOR standards dictate e.g. the variable names and +units, coordinate information, how the data should be structured (e.g. 1 +variable per file), additional metadata requirements, and file naming +conventions a.k.a. the data reference syntax (DRS). +All this information is stored in so-called CMOR tables. For example, +the CMOR tables for the CMIP6 project can be found here.

+
+
+
+

ESMValTool offers two ways to CMORize data:

+
    +
  1. A reformatting script can be used to create a CMOR-compliant copy. +CMORizer scripts for several popular datasets are included in +ESMValTool, and ESMValTool also provides a convenient way to execute +them.
  2. +
  3. ESMValCore can execute CMOR fixes ‘[on the fly](https://docs.esmvaltool.org/projects/esmvalcore/en/latest/develop/ +fixing_data.html#fixing-data)’. The advantage is that you don’t need to +store an additional, reformatted copy of the data. The disadvantage is +that these fixes should be implemented inside ESMValCore, which is +beyond the scope of this tutorial.
  4. +
+

In this lesson, we will re-implement a CMORizer script for the +FLUXCOM dataset that contains observations of the Gross Primary +Production (GPP), a variable that is important for calculating +components of the global carbon cycle. See the next section on how to +obtain data.

+

As in the previous episode (Development and Contribution +episode ), we will be using the development installation of +ESMValTool.

+

Obtaining the data +

+
+

The data for this episode is available via the FluxCom Data +Portal. First you’ll need to register. After registration, in the +dropdown boxes, select FLUXCOM as the data choice and click download. +Three files will be displayed. Click the download button on the “FLUXCOM +(RS+METEO) Global Land Carbon Fluxes using CRUNCEP climate data”. You’ll +receive an email with the FTP address to access the server. Connect to +the server, follow the path in your email, and look for the file +raw/monthly/GPP.ANN.CRUNCEPv6.monthly.2000.nc. Download +that file and save it in a folder called +~/data/RAWOBS/Tier3/FLUXCOM.

+

Note: you’ll need a user-friendly ftp client. On Linux, +ncftp works okay.

+
+
+ +
+
+

What is the deal with those “tiers”?

+
+

Many datasets come with access restrictions. In this way the data +providers can keep track of how their data is used. In many cases +“restricted access” just means that one has to register with an email +address and accept the terms of use, which typically ask that you +acknowledge the data providers.

+

There are also datasets available that do not need a registration. +The “obs4MIPs” or “ana4MIPs” datasets, for example, are specifically +produced to facilitate comparisons with model simulations.

+

To reflect these different levels of access restriction, the +ESMValTool team has created a tier-system. The definition of the +different tiers are as follows:

+
    +
  • +Tier1: obs4MIPs and ana4MIPS datasets (can be used +directly with the ESMValTool)
  • +
  • +Tier2: other freely available datasets (most of +them will need some kind of cmorization)
  • +
  • +Tier3: datasets with access restrictions (most of +these datasets will also need some kind of cmorization)
  • +
+

These access restrictions are also why the ESMValTool developers +cannot distribute copies or automate downloading of all observations and +reanalysis data used in the recipes. As a compromise, we provide the +CMORization scripts so that each user can CMORize their own copy of the +access restricted datasets if needed.

+
+
+
+

Run the existing CMORizer script +

+
+

Before we develop our own CMORizer script, let’s first see what +happens when we run the existing one. There is a specific command +available in the ESMValTool to run the CMORizer scripts:

+
+

BASH +

+
esmvaltool data format --config_file <path to config-user.yml>  <dataset-name>
+
+

The config-user.yml is the file in which we define the +different data paths, see the episode on Configuration. In the +rootpath of your config-user.yml, make sure to +add the right directory for “RAWOBS” data in which you downloaded the +FLUXCOM dataset:

+
+

YAML +

+
rootpath:
+  RAWOBS: ~/data/RAWOBS
+
+

This enables ESMValTool to find the raw observational datasets stored +in the “RAWOBS” folder. The dataset-name needs to be +identical to the folder name that was created to store the raw +observation data files, i.e. RAWOBS/TierX/dataset-name. In +our case this would be “FLUXCOM”.

+

If everything is okay, the output should look something like +this:

+
+

OUTPUT +

+
...
+... Starting the CMORization Tool at time: 2022-07-26 14:02:16 UTC
+... ----------------------------------------------------------------------
+... input_dir  = /home/peter/data/RAWOBS
+... output_dir = /home/peter/esmvaltool_output/data_formatting_20220726_140216
+... ----------------------------------------------------------------------
+... Running the CMORization scripts.
+... Processing datasets ['FLUXCOM']
+... Input data from: /home/peter/data/RAWOBS/Tier3/FLUXCOM
+... Output will be written to: /home/peter/esmvaltool_output/
+      data_formatting_20220726_140216/Tier3/FLUXCOM
+... Reformat script: /home/peter/mambaforge/envs/esmvaltool/lib/python3.9/
+      site-packages/esmvaltool/cmorizers/data/formatters/datasets/fluxcom
+... CMORizing dataset FLUXCOM using Python script /home/peter/mambaforge/envs/
+      esmvaltool/lib/python3.9/site-packages/esmvaltool/cmorizers/data/formatters/
+      datasets/fluxcom.py
+... Found input file '/home/peter/data/RAWOBS/Tier3/FLUXCOM/GPP.ANN.CRUNCEPv6.monthly.*.nc'
+... CMORizing variable 'gpp'
+... Lmon
+... Var is gpp
+... ... UserWarning: Ignoring netCDF variable 'GPP' invalid units 'gC m-2 day-1'
+
+... Fixing time...
+... Fixing latitude...
+... Fixing longitude...
+... Flipping dimensional coordinate latitude...
+... Saving file
+... Saving: /home/peter/esmvaltool_output/data_formatting_20220726_140216/Tier3/
+      FLUXCOM/OBS_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_200001-200012.nc
+... Cube has lazy data [lazy is preferred]
+... CMORization of dataset FLUXCOM finished!
+... Formatting successful for dataset FLUXCOM
+
+

So you can see that several fixes are applied, and the CMORized file +is written to the ESMValTool output directory, i.e. +~/esmvaltool_output/data_formatting_YYYYMMDD_HHMMSS/TierX/dataset-name/filename.nc +In order to use it, we’ll have to copy it from the output directory to a +folder called ~/data/OBS/Tier3/FLUXCOM and make sure the +path to OBS is set correctly in our config-user file:

+
+

YAML +

+
rootpath:
+  OBS: ~/data/OBS
+
+

You can also see the path where ESMValTool stores the reformatting +script: +~/ESMValTool/esmvaltool/data/formatters/datasets/fluxcom.py. +You may have a look at this file if you want. The script also uses a +configuration file: +~/ESMValTool/esmvaltool/cmorizers/data/cmor_config/FLUXCOM.yml.

+

Make a test recipe +

+
+

To verify that the data is correctly CMORized, we will make a simple +test recipe. As illustrated in the figure at the top of this episode, +one of the steps that ESMValTool executes is a CMOR-check. If the data +is not correctly CMORized, ESMValTool will give a warning or error.

+
+
+ +
+
+

Create a test recipe

+
+

Create a simple recipe called recipe_check_fluxcom.yml that +loads the FLUXCOM data. It should include a datasets section with a +single entry for the “FLUXCOM” dataset with the correct dataset keys, +and a diagnostics section with two variables: gpp. We don’t need any +preprocessors or scripts (set scripts: null), but we have +to add a documentation section with a description, authors and +maintainer, otherwise the recipe will fail.

+

Use the following dataset keys:

+
    +
  • project: OBS
  • +
  • dataset: FLUXCOM
  • +
  • type: reanaly
  • +
  • version: ANN-v1
  • +
  • mip: Lmon
  • +
  • start_year: 2000
  • +
  • end_year: 2000
  • +
  • tier: 3
  • +
+

Some of these dataset keys are further explained in the callout boxes +in this episode.

+
+
+
+
+
+ +
+
+

Here’s an example recipe

+
+

YAML +

+
documentation:
+
+  description: Test recipe for FLUXCOM data
+  title: This is a test recipe for the FLUXCOM data.
+
+  authors:
+    - kalverla_peter
+
+  maintainer:
+    - kalverla_peter
+
+datasets:
+  - {project: OBS, dataset: FLUXCOM, mip: Lmon, tier: 3, start_year: 2000, 
+     end_year: 2000, type: reanaly, version: ANN-v1}
+
+diagnostics:
+  check_fluxcom:
+    description: Check that ESMValTool can load the cmorized fluxnet data without errors.
+    variables:
+      gpp:
+    scripts: null
+
+

To learn more about writing a recipe, please refer to Writing your own recipe.

+
+
+
+
+

Try to run the example recipe with

+
+

BASH +

+
esmvaltool run recipe_check_fluxcom.yml --config_file <path to config-user.yml> --log_level debug
+
+

If everything is okay, the recipe should run without problems.

+

Starting from scratch +

+
+

Now that you’ve seen how to use an existing CMORizer script, let’s +think about adding a new one. We will remove the existing CMORizer +script, and re-implement it from scratch. This exercise allows us to +point out all the details of what’s going on. We’ll also remove the +CMORized data that we’ve just created, so our test recipe will not be +able to use it anymore.

+
+

BASH +

+
rm ~/data/OBS/Tier3/FLUXCOM/OBS_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_200001-200012.nc
+rm ~/ESMValTool/esmvaltool/cmorizers/data/formatters/datasets/fluxcom.py
+rm ~/ESMValTool/esmvaltool/cmorizers/data/cmor_config/FLUXCOM.yml
+
+

If you now run the test recipe again it should fail, and somewhere in +the output you should find something like:

+
+

ERROR +

+
No input files found for ...
+Looked for files matching: /home/peter/data/OBS/Tier3/
+      FLUXCOM/OBS_FLUXCOM_reanaly_ANN-v1_Lmon_gpp[_.]*nc
+
+

From this we can see that the first thing our CMORizer should do is +to rename the file so that it follows the CMOR filename conventions.

+

Create a new CMORizer script and a corresponding config file +

+
+

The first step now is to create a new file in the right folder that +will contain our new CMORizer instructions. Create a file called +fluxcom.py

+
+

BASH +

+
nano ~/ESMValTool/esmvaltool/cmorizers/data/formatters/datasets/fluxcom.py
+
+

and fill it with the following boilerplate code:

+
+

PYTHON +

+
"""ESMValTool CMORizer for FLUXCOM GPP data.
+
+<We will add some useful info here later>
+"""
+import logging
+from esmvaltool.cmorizers.data import utilities as utils
+
+logger = logging.getLogger(__name__)
+
+def cmorization(in_dir, out_dir, cfg, cfg_user, start_date, end_date):
+    """Cmorize the dataset."""
+
+    # This is where you'll add the cmorization code
+    # 1. find the input data
+    # 2. apply the necessary fixes
+    # 3. store the data with the correct filename
+
+

Here, in_dir corresponds to the input directory of the +raw files, out_dir to the output directory of final +reformatted data set and cfg to a configuration dictionary +given by a configuration file that we will get to shortly. The last +three arguments will not be considered in this script but can be used in +other cases. cfg_user corresponds to the user configuration +file, start_date to the start of the period to format, and +end_date to the end of the period to format. When you type +the command esmvaltool data format in the terminal, +ESMValTool will call this function with the settings found in your +configuration files.

+

The ESMValTool CMORizer also needs a dataset configuration file. +Create a file called +~/ESMValTool/esmvaltool/cmorizers/data/cmor_config/FLUXCOM.yml +and fill it with the following boilerplate:

+
+

YAML +

+
---
+# filename: ???
+
+attributes:
+  project_id: OBS6
+#   dataset_id: ???
+#   version: ???
+#   tier: ???
+#   modeling_realm: ???
+#   source: ???
+#   reference: ???
+#   comment: ???
+
+# variables:
+#   ???:
+#     mip: ???
+
+

Note: the name of this file must be +identical to dataset-name.

+

As you can see, the configuration file contains information about the +original filename of the dataset, and some additional metadata that you +might recognize from the CMOR filename structure. It also contains a +list of variables that’s available for this dataset. We’ll add this +information step by step in the following sections.

+
+
+ +
+
+

RAWOBS, OBS, OBS6!?

+
+

In the configuration above we’ve already filled in the +project_id. ESMValTool uses these project IDs to find the +data on your hard drive, and also to find more information about the +data. The RAWOBS and OBS projects refer to +external data before and after CMORization, respectively. +Historically, most external data were observations, hence the +naming.

+

In going from CMIP5 to CMIP6, the CMOR standards changed a bit. For +example, some variables were renamed, which posed a dilemma: should +CMORization reformat to the CMIP5 or CMIP6 definition? To solve this, +the OBS6 project was created. So OBS6 data +follow the CMIP6 standards, and that’s what we’ll use for the new +CMORizer.

+
+
+
+

You can try running the CMORizer at this point, and it should work +without errors. However, it doesn’t produce any output yet:

+
+

BASH +

+
esmvaltool data format --config_file <path to config-user.yml> FLUXCOM
+
+
+

1. Find the input data +

+

First we’ll get the CMORizer script to locate our FLUXCOM data. We +can use the information from the in_dir and +cfg variables. Add the following snippet to your CMORizer +script:

+
+

PYTHON +

+
# 1. find the input data
+logger.info("in_dir: '%s'", in_dir)
+logger.info("cfg: '%s'", cfg)
+
+

If you run the CMORizer again, it will print out the content of these +variables and the output should contain something like this:

+
+

OUTPUT +

+
... in_dir: '/home/peter/data/RAWOBS/Tier3/FLUXCOM'
+... cfg: '{'attributes': {'project_id': 'OBS6', 'comment': ''},
+    'cmor_table': <esmvalcore.cmor.table.CMIP6Info object at 0x7fbd0a0f6bf0>}'
+
+
+
+ +
+
+

Load the data

+
+

Try to locate the input data inside the CMORizer script and load it +(we’ll use iris because ESMValTool includes helper +utilities for iris cubes). Confirm that you’ve loaded the data by +logging the correct path and (part of the) file content.

+
+
+
+
+
+ +
+
+

There are many ways to do it. In any case, you should have added the +original filename to the configuration file (and un-commented this +line): filename: 'GPP.ANN.CRUNCEPv6.monthly.*.nc'. Note the +*: this is a useful shorthand to find multiple files for +different years. In a similar way we can also look for multiple +variables, etc.

+

Here’s an example solution (inserted directly under the original +comment):

+
+

PYTHON +

+
# 1. find the input data
+filename_pattern = cfg['filename']
+matches = Path(in_dir).glob(filename_pattern)
+
+for match in matches:
+    input_file = str(match)
+    logger.info("found: %s", input_file)
+    cube = iris.load_cube(input_file)
+    logger.info("content: %s", cube)
+
+

To make this work we’ve added import iris and +from pathlib import Path at the top of the file. Note that +we’ve started a loop, since we may find multiple files if there’s more +than one year of data available.

+
+
+
+
+
+
+

2. Save the data with the correct filename +

+

Before we start adding fixes, we’ll first make sure that our CMORizer +can also write output files with the correct name. This will enable us +to use the test recipe for the CMOR compatibility check.

+

We can use the save function from the utils +that we imported at the top. The call signature looks like this: +utils.save_variables(cube, var, outdir, attrs, **kwargs).

+

We already have the cube and the outdir. +The variable short name (var) and attributes +(attrs) are set through the configuration file. So we need +to find out what the correct short name and attributes are.

+

The standard attributes for CMIP variables are defined in the CMIP +tables. These tables are differentiated according to the “MIP” they +belong to. The tables are a copy of the PCMDI guidelines.

+
+
+ +
+
+

Find the variable “gpp” in a CMOR table

+
+

Check the available CMOR tables to find the variable “gpp” with the +following characteristics:

+
    +
  • standard_name: +gross_primary_productivity_of_biomass_expressed_as_carbon +
  • +
  • frequency: mon +
  • +
  • modeling_realm: land +
  • +
+
+
+
+
+
+ +
+
+

The variable “gpp” belongs to the land variables. The temporal +resolution that we are looking for is “monthly”. This information points +to the “Lmon” CMIP table. And indeed, the variable “gpp” can be found in +the file [here](https://github.com/ESMValGroup/ESMValCore/blob/main/esmvalcore/ +cmor/tables/cmip6/Tables/CMIP6_Lmon.json).

+
+
+
+
+

If the variable you are interested in is not available in the +standard CMOR tables, you could write a custom CMOR table entry for the +variable. This, however, is beyond the scope of this tutorial.

+
+
+ +
+
+

Fill the configuration file

+
+

Uncomment the following entries in your configuration file and fill +them with appropriate values:

+
    +
  • dataset_id
  • +
  • version
  • +
  • tier
  • +
  • modeling_realm
  • +
  • short_name (the ??? immediately under variables)
  • +
  • mip
  • +
+
+
+
+
+
+ +
+
+

The configuration file now look something like this:

+
+

YAML +

+
---
+filename: 'GPP.ANN.CRUNCEPv6.monthly.*.nc'
+
+attributes:
+  project_id: OBS6
+  dataset_id: FLUXCOM
+  version: 'ANN-v1'
+  tier: 3
+  modeling_realm: reanaly
+  source: ''
+  reference: ''
+  comment: ''
+
+variables:
+  gpp:
+    mip: Lmon
+
+
+
+
+
+

Now that we have set this information correctly in the config file, +we can call the save function. Add the following python code to your +CMORizer script:

+
+

PYTHON +

+
# 3. store the data with the correct filename
+attributes = cfg['attributes']
+variables = cfg['variables']
+
+for short_name, variable_info in variables.items():
+    all_attributes = {**attributes, **variable_info}  # add the mip to the other attributes
+    utils.save_variable(cube=cube, var=short_name, outdir=out_dir, attrs=all_attributes)
+
+

Since we only have one variable (gpp), the loop is not strictly +necessary. However, this makes it possible to add more variables later +on.

+
+
+ +
+
+

Was the CMORization successful so far?

+
+

If you run the CMORizer again, you should see that it creates an +output file named +OBS6_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_xxxx01-xxxx12.nc +stored in your ESMValTool output directory +~/esmvaltool_output/data_formatting_YYYYMMDD_HHMMSS/Tier3/FLUXCOM/. +The “xxxx” and “yyyy” represent the start and end year of the data.

+
+
+
+

Great! So we have produced a NetCDF file with the CMORizer that +follows the naming convention for ESMValTool datasets. Let’s have a look +at the NetCDF file as it was written with the very basic CMORizer from +above.

+
+

BASH +

+
ncdump -h OBS6_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_200001-200012.nc
+
+
+

OUTPUT +

+
netcdf OBS6_FLUXCOM_reanaly_ANN-v1_Lmon_gpp_200001-200012 {
+dimensions:
+        time = 12 ;
+        lat = 360 ;
+        lon = 720 ;
+variables:
+        float GPP(time, lat, lon) ;
+                GPP:_FillValue = 1.e+20f ;
+                GPP:long_name = "GPP" ;
+        double time(time) ;
+                time:axis = "T" ;
+                time:units = "days since 1582-10-15 00:00:00" ;
+                time:standard_name = "time" ;
+                time:calendar = "gregorian" ;
+        double lat(lat) ;
+        double lon(lon) ;
+
+// global attributes:
+                :_NCProperties = "version=2,netcdf=4.7.4,hdf5=1.10.6" ;
+                :created_by = "Fabian Gans [fgans@bgc-jena.mpg.de], Ulrich Weber
+		  [uweber@bgc-jena.mpg.de]" ;
+                :flux = "GPP" ;
+                :forcing = "CRUNCEPv6" ;
+                :institution = "MPI-BGC-BGI" ;
+                :invalid_units = "gC m-2 day-1" ;
+                :method = "Artificial Neural Networks" ;
+                :provided_by = "Martin Jung [mjung@bgc-jena.mpg.de] on behalf of FLUXCOM team" ;
+                :reference = "Jung et al. 2016, Nature; Tramontana et al. 2016, Biogeosciences" ;
+                :temporal_resolution = "monthly" ;
+                :title = "GPP based on FLUXCOM RS+METEO with CRUNCEPv6 climate " ;
+                :version = "v1" ;
+                :Conventions = "CF-1.7" ;
+}
+
+

The file contains a variable named “GPP” that contains three +dimensions: “time”, “lat”, “lon”. Notice the strange time units, and the +invalid_units in the global attributes section. Also it +seems that there is not information available about the lat and lon +coordinates. These are just some of the things we’ll address in the next +section.

+
+
+

3. Implementing additional fixes +

+

Copy the output of the CMORizer to your folder +~/data/OBS6/Tier3/FLUXCOM/ and change the test recipe to +look for OBS6 data instead of OBS (note: we’re upgrading the CMORizer to +newer standards here!). Make sure the path to OBS6 is set +correctly in our config-user file:

+
+

YAML +

+
rootpath:
+  OBS6: ~/data/OBS6
+
+

If we now run the test recipe on our newly ‘CMORized’ data,

+
+

BASH +

+
esmvaltool run recipe_check_fluxcom.yml --config_file <path to config-user.yml> --log_level debug
+
+

it should be able to find the correct file, but it does not succeed +yet. The first thing that the ESMValTool CMOR checker brings up is:

+
+

ERROR +

+
iris.exceptions.UnitConversionError: Cannot convert from unknown units. The
+"units" attribute may be set directly.
+
+

If you look closely at the error messages, you can see that this +error concerns the units of the coordinates. ESMValTool tries to fix +them automatically, but since no units are defined on the coordinates, +this fails.

+

The cmorizer utilities also include a function called +fix_coords, but before we can use it, we’ll also need to +make sure the coordinates have the correct standard name. Add the +following code to your cmorizer:

+
+

PYTHON +

+
# 2. Apply the necessary fixes
+# 2a. Fix/add coordinate information and metadata
+cube.coord('lat').standard_name = 'latitude'
+cube.coord('lon').standard_name = 'longitude'
+utils.fix_coords(cube)
+
+

With some additional refactoring, our cmorization function might then +look something like this:

+
+

PYTHON +

+
def cmorization(in_dir, out_dir, cfg, cfg_user, start_date, end_date):
+    """Cmorize the dataset."""
+
+    # Get general information from the config file
+    attributes = cfg['attributes']
+    variables = cfg['variables']
+
+    for short_name, variable_info in variables.items():
+        logger.info("CMORizing variable: %s", short_name)
+
+        # 1a. Find the input data (one file for each year)
+        filename_pattern = cfg['filename']
+        matches = Path(in_dir).glob(filename_pattern)
+
+        for match in matches:
+            # 1b. Load the input data
+            input_file = str(match)
+            logger.info("found: %s", input_file)
+            cube = iris.load_cube(input_file)
+
+            # 2. Apply the necessary fixes
+            # 2a. Fix/add coordinate information and metadata
+            cube.coord('lat').standard_name = 'latitude'
+            cube.coord('lon').standard_name = 'longitude'
+            utils.fix_coords(cube)
+
+            # 3. Save the CMORized data
+            all_attributes = {**attributes, **variable_info}
+            utils.save_variable(cube=cube, var=short_name, outdir=out_dir, attrs=all_attributes)
+
+

Run the CMORizer script once more. Have a look at the netCDF file, +and confirm that the coordinates now have much more metadata added to +them. Then, run the test recipe again with the latest CMORizer output. +The next error is:

+
+

ERROR +

+
esmvalcore.cmor.check.CMORCheckError: There were errors in variable GPP:
+Variable GPP units unknown can not be converted to kg m-2 s-1 in cube:
+
+

Okay, so let’s fix the units of the “GPP” variable in the CMORizer. +Remember that you can find the correct units in the CMOR table. Add the +following three lines to our CMORizer:

+
+

PYTHON +

+
# 2b. Fix gpp units
+logger.info("Changing units for gpp from gc/m2/day to kg/m2/s")
+cube.data = cube.core_data() / (1000 * 86400)
+cube.units = 'kg m-2 s-1'
+
+

If everything is okay, the test recipe should now pass. We’re getting +there. Looking through the output though, there’s still a warning.

+
+

OUTPUT +

+
WARNING There were warnings in variable GPP:
+Standard name for GPP changed from None to gross_primary_productivity_of_biomass_expressed_as_carbon
+Long name for GPP changed from GPP to Carbon Mass Flux out of Atmosphere Due to
+      Gross Primary Production on Land [kgC m-2 s-1]
+
+

ESMValTool is able to apply automatic fixes here, but if we are +running a CMORizer script anyway, we might as well fix it +immediately.

+

Add the following snippet:

+
+

PYTHON +

+
# 2c. Fix metadata
+cmor_table = cfg['cmor_table']
+cmor_info = cmor_table.get_variable(variable_info['mip'], short_name)
+utils.fix_var_metadata(cube, cmor_info)
+
+

You can see that we’re using the CMOR table here. This was passed on +by ESMValTool as part of the CFG input variable. So here +we’re making sure that we’re updating the cubes metadata to conform to +the CMOR table.

+

Finally, the test recipe should run without errors or warnings.

+
+
+

4. Finalizing the CMORizer +

+

Once everything works as expected, there’s a couple of things that we +can still do.

+
    +
  • +Add download instructions. The header of the +CMORizer contains information about where to obtain the data, when it +was accessed the last time, which ESMValTool “tier” it is associated +with, and more detailed information about the necessary downloading and +processing steps.
  • +
+
+
+ +
+
+

Fill out the header for the “FLUXCOM” dataset

+
+

Fill out the header of the new CMORizer. The different parts that +need to be present in the header are the following:

+
    +
  • Caption: the first line of the docstring should summarize what the +script does.
  • +
  • Tier
  • +
  • Source
  • +
  • Last access
  • +
  • Download and processing instructions
  • +
+
+
+
+
+
+ +
+
+

The header for the “FLUXCOM” dataset could look something like +this:

+
+

PYTHON +

+
"""ESMValTool CMORizer for FLUXCOM GPP data.
+
+Tier
+    Tier 3: restricted dataset.
+
+Source
+    http://www.bgc-jena.mpg.de/geodb/BGI/Home
+
+Last access
+    20190727
+
+Download and processing instructions
+    From the website, select FLUXCOM as the data choice and click download.
+    Two files will be displayed. One for Land Carbon Fluxes and one for
+    Land Energy fluxes. The Land Carbon Flux file (RS + METEO) using
+    CRUNCEP data file has several data files for different variables.
+    The data for GPP generated using the
+    Artificial Neural Network Method will be in files with name:
+    GPP.ANN.CRUNCEPv6.monthly.\*.nc
+    A registration is required for downloading the data.
+    Users in the UK with a CEDA-JASMIN account may request access to the jules
+    workspace and access the data.
+    Note : This data may require rechunking of the netcdf files.
+    This constraint will not exist once iris is updated to
+    version 2.3.0 Aug 2019
+"""
+
+
+
+
+
+
    +
  • +Fill the dataset information list. The file +[datasets.yml](https://github.com/ESMValGroup/ESMValTool/blob/main/esmvaltool/ +cmorizers/data/datasets.yml) contains the ESMValTool “tier”, the data +source, the last access time and download instructions for all supported +datasets in ESMValTool. You can simply reuse the information written in +the header of the CMORizer.
  • +
+
+
+ +
+
+

Fill out the FLUXCOM entry in +datasets.yml +

+
+

Fill out the FLUXCOM entry in datasets.yml. The +different parts that need to be present in the entry are the +following:

+
    +
  • Dataset-name
  • +
  • Tier
  • +
  • Source
  • +
  • Last access
  • +
  • Download and processing instructions
  • +
+
+
+
+
+
+ +
+
+

The entry for the “FLUXCOM” dataset should look like:

+
+

YAML +

+
FLUXCOM:
+  tier: 3
+  source: http://www.bgc-jena.mpg.de/geodb/BGI/Home
+  last_access: 2019-07-27
+  info: |
+    From the website, select FLUXCOM as the data choice and click download.
+    Two files will be displayed. One for Land Carbon Fluxes and one for
+    Land Energy fluxes. The Land Carbon Flux file (RS + METEO) using
+    CRUNCEP data file has several data files for different variables.
+    The data for GPP generated using the
+    Artificial Neural Network Method will be in files with name:
+    GPP.ANN.CRUNCEPv6.monthly.*.nc
+    A registration is required for downloading the data.
+    Users in the UK with a CEDA-JASMIN account may request access to the jules
+    workspace and access the data.
+    Note : This data may require rechunking of the netcdf files.
+    This constraint will not exist once iris is updated to
+    version 2.3.0 Aug 2019
+
+
+
+
+
+

Once the datasets.yml file is filled, you can check that +ESMValTool can display information about the added dataset with:

+
+

BASH +

+
esmvaltool data info FLUXCOM
+
+

If everything is okay, the output should look something like +this:

+
+

OUTPUT +

+
 $ esmvaltool data info FLUXCOM
+FLUXCOM
+
+Tier: 3
+Source: http://www.bgc-jena.mpg.de/geodb/BGI/Home
+Automatic download: No
+
+From the website, select FLUXCOM as the data choice and click download.
+Two files will be displayed. One for Land Carbon Fluxes and one for
+Land Energy fluxes. The Land Carbon Flux file (RS + METEO) using
+CRUNCEP data file has several data files for different variables.
+The data for GPP generated using the
+Artificial Neural Network Method will be in files with name:
+GPP.ANN.CRUNCEPv6.monthly.*.nc
+A registration is required for downloading the data.
+Users in the UK with a CEDA-JASMIN account may request access to the jules
+workspace and access the data.
+Note : This data may require rechunking of the netcdf files.
+This constraint will not exist once iris is updated to
+version 2.3.0 Aug 2019
+
+

Note that Automatic download: No means that no automatic +downloading script is available in ESMValTool for this dataset. The +implementation of such a script is beyond the scope of this tutorial. To +find out which datasets come with an automatic download script, you can +run: esmvaltool data list to list all datasets supported in +ESMValTool. More information about the usage of automatic downloading +scripts can be found in the User +Guide.

+
    +
  • +Complete the metadata in the config file. We have +left a few fields empty in the configuration file, such as ‘source’. By +filling out these fields we can make sure the relevant metadata is +passed on as attributes in the CMORized data. To make this work, add the +following line to the CMORizer script:
  • +
+
+

PYTHON +

+
# 2d. Update the cubes metadata with all info from the config file
+utils.set_global_atts(cube, attributes)
+
+
    +
  • Add a reference. Make sure that there is a +reference file available for the dataset, see the instruction [here](https://docs.esmvaltool.org/en +/latest/community/diagnostic.html#adding-references).

  • +
  • Make a pull request. Since you have gone through +all the trouble to reformat the dataset so that the ESMValTool can work +with it, it would be great if you could provide the CMORizer, and +ultimately with that the dataset, to the rest of the community. For more +information, see the episode on Development and +contribution.

  • +
  • Add documentation. Make sure that you have added +the info of your dataset to the User Guide so that people know it is +available for the ESMValTool Obtaining +input data.

  • +
+
+

Some final comments +

+
+

Congratulations! You have just added support for a new dataset to +ESMValTool! Adding a new CMORizer is definitely already an advanced task +when working with the ESMValTool. You need to have a basic understanding +of how the ESMValTool works and how it’s internal structure looks like. +In addition, you need to have a basic understanding of NetCDF files and +a programming language. In our example we used python for the CMORizing +script since we advocate for focusing the code development on only a few +different programming languages. This helps to maintain the code and to +ensure the compatibility of the code with possible fundamental changes +to the structure of the ESMValTool and ESMValCore.

+

More information about adding observations to the ESMValTool can be +found in the documentation.

+
+
+ +
+
+

Key Points

+
+
    +
  • CMORizers are dataset-specific scripts that can be run once to +generate CMOR-compliant data.
  • +
  • ESMValTool comes with a set of CMORizers readily available, but you +can also add your own.
  • +
+
+
+
+

Content from Debugging

+
+

Last updated on 2024-11-26 | + + Edit this page

+
+ +
+
+

Overview

+
+
+
+
+

Questions

+
    +
  • How can I handle errors/warnings?
  • +
+
+
+
+
+
+
+

Objectives

+
    +
  • Fix a broken recipe
  • +
+
+
+
+
+
+

Every user encounters errors. Once you know why you get certain types +of errors, they become much easier to fix. The good news is that +ESMValTool creates a record of the output messages and stores them in +log files. They can be used for debugging or monitoring the process. +This lesson helps you understand the different types of errors and when +you are likely to encounter them.

+

Log files +

+
+

Each time we run ESMValTool, it will produce a new output directory. +This directory should contain the run folder that is +automatically generated by ESMValTool. To examine this, we run a +recipe_python.yml that can be found in lesson Running your first recipe . Check lesson Configuration on how to set paths.

+

In a new terminal, run the recipe:

+
+

BASH +

+
  cd esmvaltool_tutorial
+  esmvaltool run examples/recipe_python.yml
+
+
+

ERROR +

+
esmvaltool: command not found
+
+

ESMValTool encounters this error because the conda environment +esmvaltool has not been activated. To fix the error, before +running the recipe, activate the environment:

+
+

BASH +

+
conda activate esmvaltool
+
+
+
+ +
+
+

conda environment

+
+

More information about the conda environment can be found at Installation.

+
+
+
+

Let’s list the files in the run directory:

+
+

BASH +

+
  ls esmvaltool_output/recipe_python_#_#/run
+
+
+

OUTPUT +

+
main_log_debug.txt  main_log.txt  map  recipe_python.yml  resource_usage.txt
+timeseries
+
+

In the main_log_debug.txt and main_log.txt, +ESMValTool writes the output messages, warnings and possible errors that +might happen during pre-processings. To inspect them, we can look inside +the files. For example:

+
+

BASH +

+
  cat esmvaltool_output/recipe_python_#_#/run/main_log.txt
+
+

Now, let’s have a look inside the folder +timeseries/script1:

+
+

BASH +

+
  ls esmvaltool_output/recipe_python_#_#/run/timeseries/script1/
+
+
+

OUTPUT +

+
diagnostic_provenance.yml log.txt  resource_usage.txt  settings.yml
+
+

In the log.txt, ESMValTool writes the output messages, +warnings and possible errors that are related to the diagnostic +script.

+

If you encounter an error and don’t know what it means, it is +important to read the log information. Sometimes knowing where the error +occurred is enough to fix it, even if you don’t entirely understand the +message. However, note that you may not always be able to find the error +or fix it. In that case, ESMValTool community helps you figure out what +went wrong.

+
+
+ +
+
+

Different log files

+
+

In the run directory, there are two log files +main_log_debug.txt and main_log.txt. What are +their differences?

+
+
+
+
+
+ +
+
+

The main_log_debug.txt contains the output messages from +the pre-processor whereas the main_log.txt shows general +errors and warnings that might happen in running the recipe and +diagnostics script.

+
+
+
+
+

Let’s change some settings in the recipe to run a regional +pre-processor. We use a text editor called nano to open the +recipe file:

+
+

BASH +

+
  nano recipe_python.yml
+
+
+
+ +
+
+

Text editor side note

+
+

No matter what editor you use, you will need to know where it +searches for and saves files. If you start it from the shell, it will +(probably) use your current working directory as its default location. +We use nano in examples here because it is one of the least +complex text editors. Press ctrl + O to save the +file, and then ctrl + X to exit +nano.

+
+
+
+
+
+ +
+
+
+

YAML +

+
# ESMValTool
+# recipe_python.yml
+---
+# See https://docs.esmvaltool.org/en/latest/recipes/recipe_examples.html
+# for a description of this recipe.
+
+# See https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/overview.html
+# for a description of the recipe format.
+---
+documentation:
+  description: |
+    Example recipe that plots a map and timeseries of temperature.
+
+  title: Recipe that runs an example diagnostic written in Python.
+
+  authors:
+    - andela_bouwe
+    - righi_mattia
+
+  maintainer:
+    - schlund_manuel
+
+  references:
+    - acknow_project
+
+  projects:
+    - esmval
+    - c3s-magic
+
+datasets:
+  - {dataset: BCC-ESM1, project: CMIP6, exp: historical, ensemble: r1i1p1f1, grid: gn}
+  - {dataset: bcc-csm1-1, version: v1, project: CMIP5, exp: historical, ensemble: r1i1p1}
+
+preprocessors:
+# See https://docs.esmvaltool.org/projects/esmvalcore/en/latest/recipe/preprocessor.html
+# for a description of the preprocessor functions.
+
+  to_degrees_c:
+    convert_units:
+      units: degrees_C
+
+  annual_mean_amsterdam:
+    extract_location:
+      location: Amsterdam
+      scheme: linear
+    annual_statistics:
+      operator: mean
+    multi_model_statistics:
+      statistics:
+        - mean
+      span: overlap
+    convert_units:
+      units: degrees_C
+
+  annual_mean_global:
+    area_statistics:
+      operator: mean
+    annual_statistics:
+      operator: mean
+    convert_units:
+      units: degrees_C
+
+diagnostics:
+
+  map:
+    description: Global map of temperature in January 2000.
+    themes:
+      - phys
+    realms:
+      - atmos
+    variables:
+      tas:
+        mip: Amon
+        preprocessor: to_degrees_c
+        timerange: 2000/P1M
+        caption: |
+          Global map of {long_name} in January 2000 according to {dataset}.
+    scripts:
+      script1:
+        script: examples/diagnostic.py
+        quickplot:
+          plot_type: pcolormesh
+          cmap: Reds
+
+  timeseries:
+    description: Annual mean temperature in Amsterdam and global mean since 1850.
+    themes:
+      - phys
+    realms:
+      - atmos
+    variables:
+      tas_amsterdam:
+        short_name: tas
+        mip: Amon
+        preprocessor: annual_mean_amsterdam
+        timerange: 1850/2000
+        caption: Annual mean {long_name} in Amsterdam according to {dataset}.
+      tas_global:
+        short_name: tas
+        mip: Amon
+        preprocessor: annual_mean_global
+        timerange: 1850/2000
+        caption: Annual global mean {long_name} according to {dataset}.
+    scripts:
+      script1:
+        script: examples/diagnostic.py
+        quickplot:
+          plot_type: plot
+
+
+
+
+
+

Keys and values in recipe settings +

+
+

The [ESMValTool pre-processors](https://docs.esmvaltool.org/projects/ESMValCore/en/latest/ +recipe/preprocessor.html?highlight=preprocessor) cover a broad range of +operations on the input data, like time manipulation, area manipulation, +land-sea masking, variable derivation, etc. Let’s add the preprocessor +extract_region to a new section +annual_mean_regional:

+
+

YAML +

+
preprocessors:
+  annual_mean_regional:
+    annual_statistics:
+      operator: mean
+    extract_region:
+      start_longitude: -10
+      end_longitude: 40
+      start_latitude: 27
+      end_latitude: 70
+
+

Also, we change the projects value esmval +to tutorial:

+
+

YAML +

+
projects:
+  - tutorial
+  - c3s-magic
+
+

Then, we save the file and run the recipe:

+
+

BASH +

+
  esmvaltool run recipe_python.yml
+
+
+

ERROR +

+
ValueError: Tag 'tutorial' does not exist in section 'projects' of
+esmvaltool/config-references.yml 2020-06-29 18:09:56,641 UTC [46055] INFO If you
+have a question or need help, please start a new discussion on
+https://github.com/ESMValGroup/ESMValTool/discussions If you suspect this is a
+bug, please open an issue on https://github.com/ESMValGroup/ESMValTool/issues To
+make it easier to find out what the problem is, please consider attaching the
+files run/recipe_*.yml and run/main_log_debug.txt from the output directory.
+
+

The values for the keys author, maintainer, +projects and references in the recipe should +be known by ESMValTool:

+ +
+

ESMValTool can’t locate the +data

+

You are assisting a colleague with ESMValTool. The colleague replaces +the CanESM2 entry in +dataset: CanESM2, project: CMIP5 to ACCESS1-3 +and runs the recipe. However, ESMValTool encounters an error like:

+
+

BASH +

+
ERROR   No input files found for variable {'short_name': 'tas', 'mip': 'Amon',
+'preprocessor': 'annual_mean_amsterdam', 'variable_group': 'tas_amsterdam',
+'diagnostic': 'timeseries', 'dataset': 'ACCESS1-3', 'project': 'CMIP5', 'exp':
+'historical', 'ensemble': 'r1i1p1', 'recipe_dataset_index': 1, 'institute':
+['CSIRO-BOM'], 'product': ['output1', 'output2'], 'timerange': '1850/2000',
+'alias': 'CMIP5', 'original_short_name': 'tas', 'standard_name':
+'air_temperature', 'long_name': 'Near-Surface Air Temperature', 'units': 'K',
+'modeling_realm': ['atmos'], 'frequency': 'mon', 'start_year': 1850,
+'end_year': 2000}
+ERROR   Looked for files matching
+['tas_Amon_ACCESS1-3_historical_r1i1p1*.nc'], but did not find any existing
+input directory
+ERROR   Set 'log_level' to 'debug' to get more information
+
+
+

What suggestions would you give the researcher for fixing the +error?

+
+
+ +
+
+

Challenge

+
+ +
+
+
+
+
+ +
+
+
    +
  1. Check user-config.yml to see if the correct directory +for input data is introduced
  2. +
  3. Check the available data, regarding exp, mip, ensemble, start_year, +and end_year
  4. +
  5. Check the variable names in the diagnostics section in the +recipe
  6. +
+
+
+
+
+

Check pre-processed data +

+
+

The setting save_intermediary_cubes in the configuration +file can be used to save the pre-processed data. More information about +this setting can be found at Configuration.

+
+
+ +
+
+

save_intermediary_cubes

+
+

Note that this setting should only be used for debugging, as it +significantly slows down the recipe and increases disk usage because a +lot of output files need to be stored.

+
+
+
+

Check diagnostic script path +

+
+

The result of the pre-processor is passed to the +examples/diagnostic.py script, that is introduced in the +recipe as:

+
+

YAML +

+
scripts:
+  script1:
+    script:
+      script: examples/diagnostic.py
+
+

The diagnostic scripts are located in the folder +diag_scripts in the ESMValTool installation directory +<path_to_esmvaltool>. To find where ESMValTool is +located on your system, see Installation .

+

Let’s see what happens if we can change the script path as:

+
+

YAML +

+
scripts:
+  script1:
+    script:
+      script: diag_scripts/ocean/diagnostic_timeseries.py
+
+
+

BASH +

+
  esmvaltool run examples/recipe_python.yml
+
+
+

ERROR +

+
esmvalcore._task.DiagnosticError: Cannot execute script
+'diag_scripts/ocean/diagnostic_timeseries.py'
+(~/mambaforge/envs/esmvaltool2.6/lib/python3.10/site-packages/esmvaltool/
+diag_scripts/diag_scripts/ocean/diagnostic_timeseries.py):
+file does not exist. 2022-10-18 11:42:34,136 UTC [39323] INFO If you have a
+question or need help, please start a new discussion on
+https://github.com/ESMValGroup/ESMValTool/discussions If you suspect this is a
+bug, please open an issue on https://github.com/ESMValGroup/ESMValTool/issues To
+make it easier to find out what the problem is, please consider attaching the
+files run/recipe_*.yml and run/main_log_debug.txt from the output directory.
+
+

The script path should be relative to diag_scripts +directory. It means that the script +diagnostic_timeseries.py is located in +<path_to_esmvaltool>/diag_scripts/ocean/. +Alternatively, the script path can be an absolute path. To examine this, +we can download the script from the ESMValTool +repository:

+
+

BASH +

+
wget https://raw.githubusercontent.com/ESMValGroup/ESMValTool/main/esmvaltool/\
+diag_scripts/ocean/diagnostic_timeseries.py
+
+

One way to get the absolute path is to run:

+
+

BASH +

+
readlink -f diagnostic_timeseries.py
+
+

Then we can update the script path :

+
+

YAML +

+
scripts:
+  script1:
+    script:
+      script: <path_to_script>/diagnostic_timeseries.py
+
+

Then, run the recipe again and examine the output to see +Run was successful!

+
+
+ +
+
+

Available recipe and diagnostic scripts

+
+

ESMValTool provides a broad suite of recipes +and diagnostic scripts for different disciplines like atmosphere, +climate metrics, future projections, IPCC, land, ocean, ….

+
+
+
+
+

Re-running a diagnostic

+

Look at the main_log.txt file and answer the following +question: How to re-run the diagnostic script?

+
+

Solution

+

The main_log.txt file contains information on how to +re-run the diagnostic script without re-running the pre-processors:

+
+
+
+

BASH +

+
2020-06-29 20:36:32,844 UTC [52810] INFO    To re-run this diagnostic script, run:
+
+
+
+ +
+
+

Challenge

+
+ +
+
+
+
+
+ +
+
+

If you run the command in a terminal, you will be able to re-run the +diagnostic.

+
+
+
+
+
+
+ +
+
+

Memory issues

+
+

If you run out of memory, try setting max_parallel_tasks +to 1 in the configuration file. Then, check the amount of memory you +need for that by inspecting the file run/resource_usage.txt +in the output directory. Using the number, there you can increase the +number of parallel tasks again to a reasonable number for the amount of +memory available in your system.

+
+
+
+
+
+ +
+
+

Key Points

+
+
    +
  • There are three different kinds of log files: +main_log.txt, and main_log_debug.txt and +log.txt.
  • +
+
+
+
+
+
+
+
+ + +
+ + +
+ + + + + diff --git a/android-chrome-192x192.png b/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..ed3c210abf103fc3dc2e23fea7e3554c388f0e78 GIT binary patch literal 2103 zcmV-72*~$|P)Px#32;bRa{vHV*Z=@l*a3CjNqztT03=XMR7Ff_aR?Gx z7a(M`x6LRqZ(n7UR9lT9Dr5LvAn$Bh6001I?dPE!E>{1whmaOyFS zuO{QYvH$=FB1uF+RCwCuT+eG;M-UdgNR@M2aO%2yt%Dm~L|f9@r@Azykc-k9e2wE$ zokNMC_#%QK)Tbm4_>hZZUkpC22pD_+x$1}Cdpo z|8MPpBi9W8v9SL(nWF)(_o2lQqE{USK3-~ zB_U@NXG+=tN&Pm+JuLRNFF6UELK$P5C)EwMHGd`<_H9ZMpK-rLUVW+W#DKr zvHj0_SafdCrVJeEVO^3!&ofi#i&zTH9K%QojTCYn5j-F{-iystMOF$W09*Sf0RFzFqO%H%;V3TxZ1A^@crV2GlSIMj6H~@J+tXq{_*^2>CA4fz~<)_Oy zz$&R**$6BEOx7sPqa_x=4qn?~(Gm{uDPBFbt}=_37yw?~I-#XS0q*f+4Tm&vKQIA? z0of$Rr;7osaWg}?9t*G(mNkn4R7X@?61sIU0Qp%3a3He))Ngwn1_0L~3&7gH5x2Muo)aAdExno;05%YZ(qNdc2!Qd30RXF(hf)~+`E3FK*uv1G4)TQmTk?1@S{-c1M4HkuTY5Ad4Zq%bEn?{G$etDY|P9t!hCH(C(=OBM@!7%Vp$HM}b6{+gnzfIDNfvS&^_P%TBN&%d_<7A6mL-kr1|nY-X=v3S*#LAi1x1!SMe?#a!u5A z_!7(vu;=V_!?{4HDu3vVL%7YC&T~vh;!p2NPWw4s59@wV!WBs#`yrYZw%aB`q3`ab z#(lgaz!GfWuF0zb($IB9lDE%}=qb}3+}iy7DyjkF0^NQ@w)k3;=3poh+!62*n`OyI8ZX6}1%I zF#}vsh7oEIMDzPukPFJ>k(l&>2rD6kC1}brj64xqH$d16sSz}c+*P^>!m>|5J4aoz zJXKP+5$^j&K5SpDSbnz&fXn?}vace}od##*Rae+dPZ>tKJ^nTFsVj79u8KHsB-;R< z(Ut;L3suDVhI>Zd*(F?0R{2hwc}6bx2g%sTcP?1~-q9A{&{iD~UUAOIo4h9X-(+m zUjyxHGvf3agapx}X`$*JubedS;iv>-ZWCR;c}F1Yft=z`#gp2Wvh64`YgOJn@_oo(PK+ zYFO82xRyUCfbWnL&ms(r2L;>1VhoHD9-z<$9_Fpxbzx92#~qq@SYR?9s9@+}XORYW zRWLNvP{$hBCOlXII%0bmQJk%Vf+bQ^w1F+cvqg%GH}L$U_B!;R#fBg+Q_<*GY&;S_#0038dR9JLUVRs;Ka&Km7Y-J#Hd2nSQX>fF7 z004NL*c literal 0 HcmV?d00001 diff --git a/android-chrome-512x512.png b/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..c88d96c1cbdcffb4b7a9bce5bd6d7c9f84dff82a GIT binary patch literal 6231 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 F=6}Gj-Ddy* literal 0 HcmV?d00001 diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8044feefd6be2a4034404d83da154a6f895f6351 GIT binary patch literal 2096 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 A>;M1& literal 0 HcmV?d00001 diff --git a/assets/fonts/Mulish-Black.svg b/assets/fonts/Mulish-Black.svg new file mode 100644 index 00000000..0f2be050 --- /dev/null +++ b/assets/fonts/Mulish-Black.svg @@ -0,0 +1,8557 @@ + + + + +Created by FontForge 20201107 at Thu Sep 30 21:20:51 2021 + By +Copyright 2016 The Mulish Project Authors (https://github.com/googlefonts/mulish) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Mulish-Black.ttf b/assets/fonts/Mulish-Black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7c45457b315e1d35760ab47af9bddd783ae6a8b5 GIT binary patch literal 106576 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 z%0D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 literal 0 HcmV?d00001 diff --git a/assets/fonts/Mulish-Black.woff b/assets/fonts/Mulish-Black.woff new file mode 100644 index 0000000000000000000000000000000000000000..eab41f8facce015d096d1869208e6657b5dbea28 GIT binary patch literal 55084 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%0Dy7 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 literal 0 HcmV?d00001 diff --git a/assets/fonts/Mulish-BlackItalic.eot b/assets/fonts/Mulish-BlackItalic.eot new file mode 100644 index 0000000000000000000000000000000000000000..ec7447c35d1753cff3e578c225b0dce6afb93abf GIT binary patch literal 123372 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