{ "cells": [ { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ClimateMatchAcademy/course-content/blob/main/tutorials/W2D1_FutureClimate-IPCCIPhysicalBasis/student/W2D1_Tutorial5.ipynb)   \"Open\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# **Tutorial 5: Internal Climate Variability**\n", "\n", "**Week 2, Day 1, Future Climate: The Physical Basis**\n", "\n", "**Content creators:** Brodie Pearson, Julius Busecke, Tom Nicholas\n", "\n", "**Content reviewers:** Younkap Nina Duplex, Zahra Khodakaramimaghsoud, Sloane Garelick, Peter Ohue, Jenna Pearson, Derick Temfack, Peizhen Yang, Cheng Zhang, Chi Zhang, Ohad Zivan\n", "\n", "**Content editors:** Jenna Pearson, Ohad Zivan, Chi Zhang\n", "\n", "**Production editors:** Wesley Banfield, Jenna Pearson, Chi Zhang, Ohad Zivan\n", "\n", "**Our 2023 Sponsors:** NASA TOPS, Google DeepMind, and CMIP\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Tutorial Objectives\n", "\n", "In this tutorial, we will learn about the concept of internal climate variability, how it influences the predictability of climate phenomena and how it contributes to uncertainty in CMIP6 models. We will work with a _single-model ensemble_, which utilizes the _MPI-ESM1-2-LR_ model from CMIP6, to isolate and quantify internal climate variability.\n", "\n", "By the end of this tutorial, you would be able to:\n", "\n", "- Understand the importance of internal climate variability and its role in climate prediction and model uncertainty.\n", "- Create and evaluate a single-model ensemble using IPCC uncertainty bands, providing a visual representation of model uncertainty.\n", "- Contrast the uncertainty due to internal variability against the uncertainty within a multi-model ensemble (which includes internal variability _and_ the impacts of human/coding choices).\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Setup\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 93464, "status": "ok", "timestamp": 1683413540687, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [ "colab" ] }, "outputs": [], "source": [ "# installations ( uncomment and run this cell ONLY when using google colab or kaggle )\n", "\n", "# !pip install condacolab &> /dev/null\n", "# import condacolab\n", "# condacolab.install()\n", "\n", "# # Install all packages in one call (+ use mamba instead of conda), this must in one line or code will fail\n", "# !mamba install xarray-datatree intake-esm gcsfs xmip aiohttp nc-time-axis cf_xarray xarrayutils &> /dev/null\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "tags": [] }, "outputs": [], "source": [ "# imports\n", "import time\n", "\n", "tic = time.time()\n", "\n", "import intake\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import xarray as xr\n", "\n", "from xmip.preprocessing import combined_preprocessing\n", "from xarrayutils.plotting import shaded_line_plot\n", "\n", "from datatree import DataTree\n", "from xmip.postprocessing import _parse_metric" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [] }, "outputs": [], "source": [ "# @title Figure settings\n", "import ipywidgets as widgets # interactive display\n", "\n", "plt.style.use(\n", " \"https://raw.githubusercontent.com/ClimateMatchAcademy/course-content/main/cma.mplstyle\"\n", ")\n", "\n", "%matplotlib inline\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [] }, "outputs": [], "source": [ "# @title Helper functions\n", "\n", "\n", "def global_mean(ds: xr.Dataset) -> xr.Dataset:\n", " \"\"\"Global average, weighted by the cell area\"\"\"\n", " return ds.weighted(ds.areacello.fillna(0)).mean([\"x\", \"y\"], keep_attrs=True)\n", "\n", "\n", "# Calculate anomaly to reference period\n", "def datatree_anomaly(dt):\n", " dt_out = DataTree()\n", " for model, subtree in dt.items():\n", " # for the coding exercise, ellipses will go after sel on the following line\n", " ref = dt[model][\"historical\"].ds.sel(time=slice(\"1950\", \"1980\")).mean()\n", " dt_out[model] = subtree - ref\n", " return dt_out\n", "\n", "\n", "def plot_historical_ssp126_combined(dt):\n", " for model in dt.keys():\n", " datasets = []\n", " for experiment in [\"historical\", \"ssp126\"]:\n", " datasets.append(dt[model][experiment].ds.tos)\n", "\n", " da_combined = xr.concat(datasets, dim=\"time\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [] }, "outputs": [], "source": [ "# @title Video 1: Internal Climate Variability\n", "\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == \"Bilibili\":\n", " src = f\"https://player.bilibili.com/player.html?bvid={id}&page={page}\"\n", " elif source == \"Osf\":\n", " src = f\"https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render\"\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == \"Youtube\":\n", " video = YouTubeVideo(\n", " id=video_ids[i][1], width=W, height=H, fs=fs, rel=0\n", " )\n", " print(f\"Video available at https://youtube.com/watch?v={video.id}\")\n", " else:\n", " video = PlayVideo(\n", " id=video_ids[i][1],\n", " source=video_ids[i][0],\n", " width=W,\n", " height=H,\n", " fs=fs,\n", " autoplay=False,\n", " )\n", " if video_ids[i][0] == \"Bilibili\":\n", " print(\n", " f\"Video available at https://www.bilibili.com/video/{video.id}\"\n", " )\n", " elif video_ids[i][0] == \"Osf\":\n", " print(f\"Video available at https://osf.io/{video.id}\")\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [(\"Youtube\", \"YcIAaljLRh4\"), (\"Bilibili\", \"BV1HF41197qn\")]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "pycharm": { "name": "#%%\n" }, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @markdown\n", "from ipywidgets import widgets\n", "from IPython.display import IFrame\n", "\n", "link_id = \"cy5bh\"\n", "\n", "download_link = f\"https://osf.io/download/{link_id}/\"\n", "render_link = f\"https://mfr.ca-1.osf.io/render?url=https://osf.io/{link_id}/?direct%26mode=render%26action=download%26mode=render\"\n", "# @markdown\n", "out = widgets.Output()\n", "with out:\n", " print(f\"If you want to download the slides: {download_link}\")\n", " display(IFrame(src=f\"{render_link}\", width=730, height=410))\n", "display(out)\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Section 1: Internal Climate Variability & Single-model Ensembles\n", "\n", "One of the CMIP6 models we are using in today's tutorials, _MPI-ESM1-2-LR_, is part of single-model ensemble, where its modelling centre carried out multiple simulations of the model for many of the CMIP6 experiments. To create a single-model ensemble, the modelling centre will run a model using the same forcing data, but with small changes in the initial conditions. Due to the chaotic nature of the climate system, these small changes in initial conditions lead to differences in the modelled climate as time progresses. These differences are often referred to as internal variability. By running this single-model ensemble and comparing the results to simulations using different forcing datasets, it allows us to separate the internal variability from the externally-forced variability. If you are interested in learning more about large ensemble climate models, you can [read this paper](https://esd.copernicus.org/articles/12/401/2021/).\n", "\n", "Let's take advantage of this single-model ensemble to quantify the internal variability of this model's simulated climate, and contrast this against the multi-model uncertainty we diagnosed in the previous tutorial.\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Coding Exercise 1.1\n", "\n", "Complete the following code to:\n", "\n", "1. Load 5 different _realizations_ of the _MPI-ESM1-2-LR_ experiments (_r1i1p1f1_ through _r5i1p1f1_). This numbering convention means they were each initialized using a different time-snapshot of the base/spin-up simulation.\n", "2. Plot the _historical_ and _SSP1-2.6_ experiment data for each realization, using a distinct color for each realization, but keeping that color the same across both the historical period and future period for a given realization.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 420, "status": "error", "timestamp": 1683413710306, "user": { "displayName": "Sloane Garelick", "userId": "04706287370408131987" }, "user_tz": 240 }, "tags": [] }, "outputs": [], "source": [ "col = intake.open_esm_datastore(\n", " \"https://storage.googleapis.com/cmip6/pangeo-cmip6.json\"\n", ") # open an intake catalog containing the Pangeo CMIP cloud data\n", "\n", "cat_ensemble = col.search(\n", " source_id=\"MPI-ESM1-2-LR\",\n", " variable_id=\"tos\",\n", " table_id=\"Omon\",\n", " # select the 5 ensemble members described above\n", " member_id=...,\n", " grid_label=\"gn\",\n", " experiment_id=[\"historical\", \"ssp126\", \"ssp585\"],\n", " require_all_on=[\"source_id\", \"member_id\"],\n", ")\n", "\n", "# convert the sub-catalog into a datatree object, by opening each dataset into an xarray.Dataset (without loading the data)\n", "kwargs = dict(\n", " preprocess=combined_preprocessing, # apply xMIP fixes to each dataset\n", " xarray_open_kwargs=dict(\n", " use_cftime=True\n", " ), # ensure all datasets use the same time index\n", " storage_options={\n", " \"token\": \"anon\"\n", " }, # anonymous/public authentication to google cloud storage\n", ")\n", "# hopefully we can implement https://github.com/intake/intake-esm/issues/562 before the\n", "# actual tutorial, so this would be a lot cleaner\n", "cat_ensemble.esmcat.aggregation_control.groupby_attrs = [\n", " \"source_id\", \"experiment_id\"]\n", "dt_ensemble = cat_ensemble.to_datatree(**kwargs)\n", "\n", "# add the area (we can reuse the area from before, since for a given model the horizontal are does not vary between members)\n", "dt_ensemble_with_area = DataTree()\n", "for model, subtree in dt_ensemble.items():\n", " metric = dt_area[\"MPI-ESM1-2-LR\"][\"historical\"].ds[\"areacello\"].squeeze()\n", " dt_ensemble_with_area[model] = subtree.map_over_subtree(\n", " _parse_metric, metric)\n", "\n", "# global average\n", "# average every dataset in the tree globally\n", "dt_ensemble_gm = dt_ensemble_with_area.map_over_subtree(global_mean)\n", "\n", "# calculate anomaly\n", "dt_ensemble_gm_anomaly = datatree_anomaly(dt_ensemble_gm)\n", "\n", "\n", "def plot_historical_ssp126_ensemble_combined(dt, ax):\n", " for model in dt.keys():\n", " datasets = []\n", " for experiment in [\"historical\", \"ssp126\"]:\n", " datasets.append(\n", " dt[model][experiment].ds.coarsen(time=12).mean().tos)\n", "\n", " # concatenate the historical and ssp126 timeseries for each ensemble member\n", " da_combined = ...\n", " # plot annual averages\n", " da_combined.plot(hue=\"member_id\", ax=ax)\n", "\n", "\n", "fig, ax = plt.subplots()\n", "plot_historical_ssp126_ensemble_combined(dt_ensemble_gm_anomaly, ax)\n", "\n", "ax.set_title(\n", " \"Global Mean SST Anomaly in SSP1-2.6 from a 5-member single-model ensemble\"\n", ")\n", "ax.set_ylabel(\"Global Mean SST Anomaly [$^\\circ$C]\")\n", "ax.set_xlabel(\"Year\")\n" ] }, { "cell_type": "markdown", "metadata": { "id": "1CAXFSbJGsHq" }, "source": [ "### **Coding Exercise 1.2**\n", "\n", "Complete the following code to:\n", "\n", "1. Repeat the final figure of the last tutorial, except now display means and uncertainty bands of the single-model ensemble that you just loaded, rather than the multi-model ensemble analyzed in the previous tutorial.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {}, "executionInfo": { "elapsed": 313, "status": "error", "timestamp": 1682795255008, "user": { "displayName": "Brodie Pearson", "userId": "05269028596972519847" }, "user_tz": 420 }, "id": "BLqAj58IG8Hz", "outputId": "9f8d704a-df5f-4710-8491-b4d78d5352ae" }, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "for experiment, color in zip([\"historical\", \"ssp126\", \"ssp585\"], [\"C0\", \"C1\", \"C2\"]):\n", " da = (\n", " dt_ensemble_gm_anomaly[\"MPI-ESM1-2-LR\"][experiment]\n", " .ds.tos.coarsen(time=12)\n", " .mean()\n", " .load()\n", " )\n", " # calculate the mean across ensemble members\n", " da.mean(...).plot(color=color, label=experiment, ax=ax)\n", "\n", " # shading representing spread between members\n", " x = da.time.data\n", " # diagnose the lower range of the likely bounds\n", " da_lower = ...\n", " # diagnose the upper range of the likely bounds\n", " da_upper = ...\n", " ax.fill_between(x, da_lower, da_upper, alpha=0.5, color=color)\n", "\n", "ax.set_title(\n", " \"Global Mean SST Anomaly in SSP1-2.6 from a 5-member single-model ensemble\"\n", ")\n", "ax.set_ylabel(\"Global Mean SST Anomaly [$^\\circ$C]\")\n", "ax.set_xlabel(\"Year\")\n", "ax.legend()\n" ] }, { "cell_type": "markdown", "metadata": { "id": "ZHkokcHf_nj9" }, "source": [ "### **Question 1.2: Climate Connection**\n", "\n", "1. How does this figure compare to the multi-model ensemble figure from the previous tutorial (included below)? Can you interpret differences using the science we have discussed today?\n", "\n", "![tutorial 4 solution](https://github.com/ClimateMatchAcademy/course-content/blob/main/tutorials/W2D1_FutureClimate-IPCCIPhysicalBasis/img/W2D1_Tutorial_5_Insert_Figure.png?raw=true)\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Summary\n", "\n", "In this tutorial, we explored the internal climate variability and its implications for climate modeling and prediction. We discussed the utility of single-model ensembles for isolating the effects of internal variability by contrasting simulations with identical physics, numerics, and discretization. We quantified the internal variability of _MPI-ESM1-2-LR_ model's simulated climate and compared it to the uncertainty introduced by multi-model ensembles. Through this tutorial, we better understand the boundaries of climate prediction and the different sources of uncertainty in CMIP6 models.\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Resources\n", "\n", "This tutorial uses data from the simulations conducted as part of the [CMIP6](https://wcrp-cmip.org/) multi-model ensemble.\n", "\n", "For examples on how to access and analyze data, please visit the [Pangeo Cloud CMIP6 Gallery](https://gallery.pangeo.io/repos/pangeo-gallery/cmip6/index.html)\n", "\n", "For more information on what CMIP is and how to access the data, please see this [page](https://github.com/ClimateMatchAcademy/course-content/blob/main/tutorials/CMIP/CMIP_resource_bank.md).\n", "\n", "For more information about large ensemble climate modelling [see this paper](https://esd.copernicus.org/articles/12/401/2021/).\n" ] } ], "metadata": { "colab": { "collapsed_sections": [], "include_colab_link": true, "machine_shape": "hm", "name": "W2D1_Tutorial_5", "provenance": [ { "file_id": "1WfT8oN22xywtecNriLptqi1SuGUSoIlc", "timestamp": 1680298239014 } ], "toc_visible": true }, "gpuClass": "standard", "kernel": { "display_name": "Python 3", "language": "python", "name": "python3" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.8" } }, "nbformat": 4, "nbformat_minor": 4 }