From 9c83f4343cf9e329015b8f2f528e92073dc0d28a Mon Sep 17 00:00:00 2001 From: Fabien Maussion Date: Wed, 16 Apr 2025 14:14:55 +0100 Subject: [PATCH] some updates to the edu notebooks including plausible --- .github/workflows/build-pages.yml | 4 +- _config.yml | 12 +- _static/custom.css | 6 + img/build_info.ipynb | 6 +- oggm-edu/edu_intro.ipynb | 2 +- oggm-edu/glacier_water_resources.ipynb | 2 +- oggm-edu/low_pass_climate.ipynb | 146 +-- oggm-edu/mass_balance_gradients.ipynb | 5 +- .../pre16/glacier_water_resources_pre16.ipynb | 861 ------------------ ...er_water_resources_projections_pre16.ipynb | 806 ---------------- oggm-edu/temperature_index_model.ipynb | 724 --------------- welcome.ipynb | 3 - 12 files changed, 97 insertions(+), 2480 deletions(-) create mode 100644 _static/custom.css delete mode 100644 oggm-edu/pre16/glacier_water_resources_pre16.ipynb delete mode 100644 oggm-edu/pre16/glacier_water_resources_projections_pre16.ipynb delete mode 100644 oggm-edu/temperature_index_model.ipynb diff --git a/.github/workflows/build-pages.yml b/.github/workflows/build-pages.yml index 890a0b0..f90baf5 100644 --- a/.github/workflows/build-pages.yml +++ b/.github/workflows/build-pages.yml @@ -15,7 +15,7 @@ jobs: container: ghcr.io/oggm/oggm:latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Fix Git-Protection run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Install Dependencies @@ -27,7 +27,7 @@ jobs: run: | jupyter-book build . - name: Upload Build Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: build-output path: _build/ diff --git a/_config.yml b/_config.yml index f5e4e52..d4d7315 100755 --- a/_config.yml +++ b/_config.yml @@ -1,27 +1,28 @@ ####################################################################################### # A default configuration that will be loaded for all jupyter books -# See the documentation for help and more options: +# See the documentation for help and more options: # https://jupyterbook.org/customize/config.html ####################################################################################### # Book settings title: OGGM-Edu notebooks # The title of the book. Will be placed in the left navbar. author: OGGM e.V. and OGGM Contributors # The author of the book -copyright: "2014-2021" # Copyright year to be placed in the footer +copyright: "2014-2025" # Copyright year to be placed in the footer logo: img/logo.png # A path to the book logo repository: url: https://github.com/OGGM/oggm-edu-notebooks - path_to_book: . + path_to_book: . branch: master launch_buttons: notebook_interface: "jupyterlab" binderhub_url: "https://mybinder.org" jupyterhub_url: "https://classroom.oggm.org" html: + extra_css: + - _static/custom.css use_repository_button: true use_issues_button: true use_edit_page_button: true - google_analytics_id: UA-106829797-2 extra_footer: |

These notebooks are licensed under a BSD-3-Clause license. @@ -31,7 +32,10 @@ html: sphinx: config: html_show_copyright: false + html_last_updated_fmt: '%b %d, %Y' nb_merge_streams: true + html_js_files: + - ['https://plausible.oggm.org/js/script.js', {'defer': 'defer', 'data-domain': 'edu.oggm.org'}] execute: execute_notebooks: auto timeout: -1 diff --git a/_static/custom.css b/_static/custom.css new file mode 100644 index 0000000..5e1bd8d --- /dev/null +++ b/_static/custom.css @@ -0,0 +1,6 @@ +html { + --pst-font-size-h1: 2.2em; /* Default: 2.625 */ + --pst-font-size-h2: 1.7em; /* Default: 2.125 */ + --pst-font-size-h3: 1.3em; /* Default: 1.75 */ + --pst-font-size-h4: 1.0em; /* Default: ? */ + } diff --git a/img/build_info.ipynb b/img/build_info.ipynb index f58d6a4..692b5bd 100644 --- a/img/build_info.ipynb +++ b/img/build_info.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "c5fb7d5b-688d-4102-817a-0f5b1e751c64", + "id": "0", "metadata": {}, "source": [ "# Build information & package versions" @@ -11,7 +11,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1ebc35f3-6446-4d5b-972b-589130c009d1", + "id": "1", "metadata": {}, "outputs": [], "source": [ @@ -22,7 +22,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39094c00-8ea2-4592-9f45-cc2627759612", + "id": "2", "metadata": {}, "outputs": [], "source": [ diff --git a/oggm-edu/edu_intro.ipynb b/oggm-edu/edu_intro.ipynb index bcb7d4f..91978ea 100644 --- a/oggm-edu/edu_intro.ipynb +++ b/oggm-edu/edu_intro.ipynb @@ -702,7 +702,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.4" }, "toc": { "base_numbering": 1, diff --git a/oggm-edu/glacier_water_resources.ipynb b/oggm-edu/glacier_water_resources.ipynb index ab373c9..296d5ae 100644 --- a/oggm-edu/glacier_water_resources.ipynb +++ b/oggm-edu/glacier_water_resources.ipynb @@ -809,7 +809,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.12.4" }, "toc": { "base_numbering": 1, diff --git a/oggm-edu/low_pass_climate.ipynb b/oggm-edu/low_pass_climate.ipynb index 28c943d..6bbda0f 100644 --- a/oggm-edu/low_pass_climate.ipynb +++ b/oggm-edu/low_pass_climate.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "d6e0ab93-5e66-459a-a4a5-85d74db9e372", + "id": "0", "metadata": {}, "source": [ "# Glaciers as a low-pass filter of climate variations" @@ -10,7 +10,7 @@ }, { "cell_type": "markdown", - "id": "7a40466d-6682-4e57-a345-da24762dc7a9", + "id": "1", "metadata": {}, "source": [ "In a previous notebook we have talked about the response time of a glacier and how it takes some time for the glacier to respond to changes in its climate.\n", @@ -24,7 +24,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2912398a-b50d-492d-bd5f-5cbe3e73dc61", + "id": "2", "metadata": {}, "outputs": [], "source": [ @@ -33,7 +33,7 @@ }, { "cell_type": "markdown", - "id": "188dc7e4-0339-4303-8f2e-e6823e79615a", + "id": "3", "metadata": {}, "source": [ "Then we create a bed. For these examples we can use a fairly simply bed with a single slope.\n", @@ -43,7 +43,7 @@ { "cell_type": "code", "execution_count": null, - "id": "1bcaa3e6-e0a5-4479-98b5-d48b36a550a8", + "id": "4", "metadata": {}, "outputs": [], "source": [ @@ -56,7 +56,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6075e2f6-03b2-48a9-9c14-63da9659e0bc", + "id": "5", "metadata": {}, "outputs": [], "source": [ @@ -65,7 +65,7 @@ }, { "cell_type": "markdown", - "id": "4125090d-1140-48f0-b851-b6e8d7143384", + "id": "6", "metadata": {}, "source": [ "Next step is the mass balance.\n", @@ -75,7 +75,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6b9c7582-75e0-47da-837d-08cd35bb7688", + "id": "7", "metadata": {}, "outputs": [], "source": [ @@ -87,7 +87,7 @@ }, { "cell_type": "markdown", - "id": "7341d8db-fd13-45d0-84f9-6ec7473c2439", + "id": "8", "metadata": {}, "source": [ "Now we are ready to create the glacier." @@ -96,7 +96,7 @@ { "cell_type": "code", "execution_count": null, - "id": "22132c99-93e6-46b9-838a-92d9074cde7c", + "id": "9", "metadata": {}, "outputs": [], "source": [ @@ -105,7 +105,7 @@ }, { "cell_type": "markdown", - "id": "87fff22a-9179-4f4b-b563-df4dcc4c69c4", + "id": "10", "metadata": {}, "source": [ "Let's take a look at the mass balance profile." @@ -114,7 +114,7 @@ { "cell_type": "code", "execution_count": null, - "id": "5b19c938-774d-4a02-9e2a-ee5a2c978482", + "id": "11", "metadata": {}, "outputs": [], "source": [ @@ -123,7 +123,7 @@ }, { "cell_type": "markdown", - "id": "e8b88efb-8843-4100-b88d-22d205a64929", + "id": "12", "metadata": {}, "source": [ "## Adding a random climate" @@ -131,7 +131,7 @@ }, { "cell_type": "markdown", - "id": "d90c6faf-0e00-468b-973c-e261eae61086", + "id": "13", "metadata": {}, "source": [ "Before we start progressing the glacier assign a random climate to the future of the glacier.\n", @@ -145,7 +145,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6055c625-8f07-4164-a60c-3ff27a4697d0", + "id": "14", "metadata": {}, "outputs": [], "source": [ @@ -155,7 +155,7 @@ }, { "cell_type": "markdown", - "id": "7a3733ba-722c-4934-baa7-90bc3f6b790d", + "id": "15", "metadata": {}, "source": [ " We can take a look at the random climate through the `temperature_bias_series` attribute of the `MassBalance`." @@ -164,7 +164,7 @@ { "cell_type": "code", "execution_count": null, - "id": "952d34d8-388e-4e32-b608-d5c85cf9dd1e", + "id": "16", "metadata": {}, "outputs": [], "source": [ @@ -174,7 +174,7 @@ }, { "cell_type": "markdown", - "id": "5318f9d0-3bee-4130-8c9c-6c884a49649b", + "id": "17", "metadata": {}, "source": [ "And also quickly plot it.\n", @@ -184,7 +184,7 @@ { "cell_type": "code", "execution_count": null, - "id": "e31e4892-246b-45e5-abdb-fa8b5e4f6969", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -193,7 +193,7 @@ }, { "cell_type": "markdown", - "id": "4acb7a66-f1f8-48cd-9a70-b36135cc1832", + "id": "19", "metadata": {}, "source": [ "

\n", @@ -206,7 +206,7 @@ }, { "cell_type": "markdown", - "id": "ba4241d6-06ac-4874-bdc2-a4efe917347b", + "id": "20", "metadata": {}, "source": [ "Now we can progress the glacier as usual." @@ -215,7 +215,7 @@ { "cell_type": "code", "execution_count": null, - "id": "98605ac3-3cb8-4917-88f7-c3d53675d424", + "id": "21", "metadata": {}, "outputs": [], "source": [ @@ -226,7 +226,7 @@ { "cell_type": "code", "execution_count": null, - "id": "9247566d-54be-426b-86ad-a8e96009a6d0", + "id": "22", "metadata": {}, "outputs": [], "source": [ @@ -235,7 +235,7 @@ }, { "cell_type": "markdown", - "id": "4fad4986-692d-40ea-8cd9-aac7e587b058", + "id": "23", "metadata": {}, "source": [ "## A look at the history" @@ -243,7 +243,7 @@ }, { "cell_type": "markdown", - "id": "773dafa7-aa25-457d-b164-1eefa5b8e6b2", + "id": "24", "metadata": {}, "source": [ "We plot the history of the glacier length, volume and area with the `.plot_history()` method.\n", @@ -253,7 +253,7 @@ { "cell_type": "code", "execution_count": null, - "id": "4ddd99f2-97ec-4a52-8c52-5cf8781ceb18", + "id": "25", "metadata": {}, "outputs": [], "source": [ @@ -262,7 +262,7 @@ }, { "cell_type": "markdown", - "id": "206edbc8-006b-499a-9d69-8aee3c3a0569", + "id": "26", "metadata": {}, "source": [ "Since the random climate is just random, it is difficult to distinguish any similarities between the noisy bias and the glacier history.\n", @@ -285,7 +285,7 @@ { "cell_type": "code", "execution_count": null, - "id": "eea519b4-c896-47b2-8578-ba02af115b25", + "id": "27", "metadata": {}, "outputs": [], "source": [ @@ -296,7 +296,7 @@ }, { "cell_type": "markdown", - "id": "2c48847e-b70c-4f6f-96c5-52e5e7f21ba5", + "id": "28", "metadata": {}, "source": [ "## Creating a custom climate" @@ -304,7 +304,7 @@ }, { "cell_type": "markdown", - "id": "f594e7ec-1488-4d13-92ff-10b9135dc0e3", + "id": "29", "metadata": {}, "source": [ "
\n", @@ -320,7 +320,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41b44746-0770-46b8-b5d5-2c1e455d32f0", + "id": "30", "metadata": {}, "outputs": [], "source": [ @@ -329,7 +329,7 @@ }, { "cell_type": "markdown", - "id": "11024cb1-07d8-46a4-b8c3-e5a4c456cd67", + "id": "31", "metadata": {}, "source": [ "For this exercise we will create a sinusoidal bias, not because it is very realistic but to show one example of how one can use convenient functions to generate the values.\n", @@ -339,7 +339,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fa1f5c46-2823-449d-8051-13ae7d3d2ad3", + "id": "32", "metadata": {}, "outputs": [], "source": [ @@ -355,7 +355,7 @@ }, { "cell_type": "markdown", - "id": "fb81739c-a449-4e28-9b66-951f3f3f68d4", + "id": "33", "metadata": {}, "source": [ "We reset the glacier before adding the new climate, to start fresh." @@ -364,7 +364,7 @@ { "cell_type": "code", "execution_count": null, - "id": "777bbf1f-7dda-4ee8-ae3d-50f1e17c5398", + "id": "34", "metadata": {}, "outputs": [], "source": [ @@ -373,7 +373,7 @@ }, { "cell_type": "markdown", - "id": "30b67656-35d6-4541-980b-f18d1d618de1", + "id": "35", "metadata": {}, "source": [ "Then we can assign the `bias_data` to the `.temp_bias_series`." @@ -382,7 +382,7 @@ { "cell_type": "code", "execution_count": null, - "id": "d553b2c6-161f-4bb1-b855-5c75b7a7146f", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -391,7 +391,7 @@ }, { "cell_type": "markdown", - "id": "e6f43138-db11-4169-a78d-621d1bf5cf1c", + "id": "37", "metadata": {}, "source": [ "Progress the glacier as usual." @@ -400,7 +400,7 @@ { "cell_type": "code", "execution_count": null, - "id": "875ce1d0-7b91-4af4-9efe-1d5d5ce98c18", + "id": "38", "metadata": {}, "outputs": [], "source": [ @@ -409,7 +409,7 @@ }, { "cell_type": "markdown", - "id": "33ee2f58-a65a-4b31-a63d-e023d7c295d8", + "id": "39", "metadata": {}, "source": [ "Take a look at the history of the glacier" @@ -418,7 +418,7 @@ { "cell_type": "code", "execution_count": null, - "id": "73a35b8c-71ab-4de6-ba47-8f228c377c38", + "id": "40", "metadata": {}, "outputs": [], "source": [ @@ -427,7 +427,7 @@ }, { "cell_type": "markdown", - "id": "8f94aa57-7544-46f0-96da-131a1b33c53b", + "id": "41", "metadata": {}, "source": [ "It is also possible to add a future climate to a glacier that already has some history.\n", @@ -438,7 +438,7 @@ { "cell_type": "code", "execution_count": null, - "id": "be861f86-a23a-42d5-b691-04a552053028", + "id": "42", "metadata": {}, "outputs": [], "source": [ @@ -448,7 +448,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ce1923aa-e8d6-4803-8638-39f201cfc358", + "id": "43", "metadata": {}, "outputs": [], "source": [ @@ -458,7 +458,7 @@ { "cell_type": "code", "execution_count": null, - "id": "915c7276-235b-4fd0-aeed-675390bff3a7", + "id": "44", "metadata": {}, "outputs": [], "source": [ @@ -468,7 +468,7 @@ { "cell_type": "code", "execution_count": null, - "id": "be47cf43-724b-46ba-911f-f7fb9ce2d8c5", + "id": "45", "metadata": {}, "outputs": [], "source": [ @@ -477,7 +477,7 @@ }, { "cell_type": "markdown", - "id": "02c82bc1-9018-42d4-b2c4-4143eb536e67", + "id": "46", "metadata": {}, "source": [ "We can then assign more data to the future climate, just as easy as before.\n", @@ -487,7 +487,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63de6f34-285e-4217-8764-ff4ac1323c51", + "id": "47", "metadata": {}, "outputs": [], "source": [ @@ -503,7 +503,7 @@ }, { "cell_type": "markdown", - "id": "0f12e4c4-b498-40ed-b22f-da2ca5ae76c0", + "id": "48", "metadata": {}, "source": [ "Assign it to the `.temp_bias_series`.\n", @@ -513,7 +513,7 @@ { "cell_type": "code", "execution_count": null, - "id": "80be6329-27fe-4d2a-a419-b856b830e8b8", + "id": "49", "metadata": {}, "outputs": [], "source": [ @@ -523,7 +523,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8ef676bf-3467-4f16-b07f-aad08488991d", + "id": "50", "metadata": {}, "outputs": [], "source": [ @@ -534,7 +534,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34615a88-e419-44fa-90c2-91a7a87d53e5", + "id": "51", "metadata": {}, "outputs": [], "source": [ @@ -543,7 +543,7 @@ }, { "cell_type": "markdown", - "id": "4d91ea65-2923-4aa1-87d8-48418b16a3df", + "id": "52", "metadata": {}, "source": [ "### Adding noise to clean data" @@ -551,7 +551,7 @@ }, { "cell_type": "markdown", - "id": "8dd07ff8-9d99-4127-9829-943d02931dd6", + "id": "53", "metadata": {}, "source": [ "Until now, we have either created a completely random climate or very clean and predictable trends.\n", @@ -563,7 +563,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c41427a7-922a-4ba2-b2cd-b5917c72ef82", + "id": "54", "metadata": {}, "outputs": [], "source": [ @@ -573,7 +573,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61d71ede-f8d8-4f57-992a-a572f0f3467f", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -587,7 +587,7 @@ }, { "cell_type": "markdown", - "id": "5b850aab-9a87-491b-9d30-6dc335112b9d", + "id": "56", "metadata": {}, "source": [ "We first add only some noise to get a noisy \"spin up\" of the glacier." @@ -596,7 +596,7 @@ { "cell_type": "code", "execution_count": null, - "id": "c22efdc2-2302-43fa-abe3-f3254b34eb9d", + "id": "57", "metadata": {}, "outputs": [], "source": [ @@ -606,7 +606,7 @@ }, { "cell_type": "markdown", - "id": "353053f2-e630-4b29-ad87-e17b2131377d", + "id": "58", "metadata": {}, "source": [ "Then lets add the trend, remember that this will append to the future of the climate." @@ -615,7 +615,7 @@ { "cell_type": "code", "execution_count": null, - "id": "34430b8e-3d51-4051-a1bc-5c87b31cf33c", + "id": "59", "metadata": {}, "outputs": [], "source": [ @@ -624,7 +624,7 @@ }, { "cell_type": "markdown", - "id": "534bb50e-5e7e-43ce-bf95-572bf7b56d57", + "id": "60", "metadata": {}, "source": [ "Progress to the end of the climate data." @@ -633,7 +633,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8a9d0bcb-8096-438a-b36e-cf7e0314a73d", + "id": "61", "metadata": {}, "outputs": [], "source": [ @@ -642,7 +642,7 @@ }, { "cell_type": "markdown", - "id": "334a8d88-c31b-470b-9506-9b799c51a654", + "id": "62", "metadata": {}, "source": [ "
\n", @@ -653,7 +653,7 @@ { "cell_type": "code", "execution_count": null, - "id": "febafb35-33da-43b9-9f52-df2f75653cdb", + "id": "63", "metadata": {}, "outputs": [], "source": [ @@ -662,7 +662,7 @@ }, { "cell_type": "markdown", - "id": "80230675-fda6-488f-bef4-3f6d79b87b71", + "id": "64", "metadata": {}, "source": [ "Conveniently, the `.add_temperature_bias` has an argument which adds noise to the trend." @@ -671,7 +671,7 @@ { "cell_type": "code", "execution_count": null, - "id": "073f7de2-92c5-490b-b2fd-44c38117e2f8", + "id": "65", "metadata": {}, "outputs": [], "source": [ @@ -681,7 +681,7 @@ { "cell_type": "code", "execution_count": null, - "id": "681ad1b8-8c71-4f85-bac6-93ed297ff31d", + "id": "66", "metadata": {}, "outputs": [], "source": [ @@ -691,7 +691,7 @@ { "cell_type": "code", "execution_count": null, - "id": "82fcb879-a239-49de-9680-68cbf1a55e1c", + "id": "67", "metadata": {}, "outputs": [], "source": [ @@ -701,7 +701,7 @@ { "cell_type": "code", "execution_count": null, - "id": "db72073d-f39a-41f7-86b3-fc740df51381", + "id": "68", "metadata": {}, "outputs": [], "source": [ @@ -710,7 +710,7 @@ }, { "cell_type": "markdown", - "id": "eeee1f9d-9af5-4783-8482-c3e4679f81f2", + "id": "69", "metadata": {}, "source": [ "Now you should have the tools to create your own climates for OGGM-Edu glaciers and see how they filter the climate." @@ -718,7 +718,7 @@ }, { "cell_type": "markdown", - "id": "b8542b3c-cb83-4c4b-968d-3e99b963e10c", + "id": "70", "metadata": {}, "source": [ "## What's next?" @@ -726,7 +726,7 @@ }, { "cell_type": "markdown", - "id": "43edc509-8e07-4cec-813b-7a0839e4d719", + "id": "71", "metadata": {}, "source": [ "[Back to the table of contents](../welcome.ipynb)" @@ -749,7 +749,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.4" } }, "nbformat": 4, diff --git a/oggm-edu/mass_balance_gradients.ipynb b/oggm-edu/mass_balance_gradients.ipynb index eb3d8ea..a165dfe 100644 --- a/oggm-edu/mass_balance_gradients.ipynb +++ b/oggm-edu/mass_balance_gradients.ipynb @@ -550,7 +550,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## References" + "(References)=\n", + "## References " ] }, { @@ -604,7 +605,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.4" }, "toc": { "base_numbering": 1, diff --git a/oggm-edu/pre16/glacier_water_resources_pre16.ipynb b/oggm-edu/pre16/glacier_water_resources_pre16.ipynb deleted file mode 100644 index 0ee911f..0000000 --- a/oggm-edu/pre16/glacier_water_resources_pre16.ipynb +++ /dev/null @@ -1,861 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Glaciers as water resources: part 1 (idealized climate) - OGGM versions pre 1.6" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our previous notebooks showed how to use OGGM to answer theoretical questions about glaciers, using idealized experiments: the effect of a different slope, the concept of the equilibrium line altitude, the mass balance gradient, etc. Now, how do we use OGGM to explore real glaciers? This notebook gives us some insight." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Goals of this notebook:\n", - "\n", - "- prepare a model run for a real world glacier\n", - "- run simulations using different idealized climate scenarios to explore the role of glaciers as water resources\n", - "- understand the concept of \"peak water\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - " \n", - " This version of the notebook works for OGGM versions before 1.6. We will keep this notebook here for a while longer (e.g.: for classroom.oggm.org). If you are running OGGM 1.6, you can visit the updated notebook at glacier_water_resources.ipynb.\n", - " \n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setting the scene: glacier runoff and \"peak water\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "*\"If glaciers melt, there won't be water in mountain anymore\"*.\n", - "\n", - "This is a sentence that we hear often from people we meet, or sometimes even in news articles. In fact, the role of glaciers in the hydrological cycle is more complex than that. In this notebook, we will explore this question using idealized climate scenarios applied to real glaciers!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Before we continue, let's have a look at the expected contribution of glaciers to local streamflow. The chart below shows an idealized scenario where the climate is first constant (t$_0$-t$_1$, and the glacier is in equilibrium with its climate) and then a warming occurs (t$_1$-t$_2$). This graph makes a few very important points, which we will explore together in this notebook. \n", - "\n", - "1. When a glacier is in equilibrium, a glacier does not contribute to the annual runoff at all. \n", - "2. When the climate is warming, glaciers are losing mass. This water contributes to downstream runoff, and the runoff increases.\n", - "3. If climate warms even more, glaciers will continue to lose mass and become significantly smaller. When there isn't much ice left to melt each year (or when climate stabilizes), their contribution will become smaller until becoming zero again.\n", - "4. In the new equilibrium, the annual runoff is the same as before, but the seasonal contribution changed.\n", - "\n", - "We will now get back to all these points together, using OGGM!\n", - "\n", - "\"Fig\n", - "\n", - "*Graphic from [Huss & Hock (2018)](https://www.nature.com/articles/s41558-017-0049-x)*" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "sns.set_context('notebook') # plot defaults" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Temporarily ignore warnings from shapely\n", - "import warnings\n", - "from shapely.errors import ShapelyDeprecationWarning\n", - "warnings.filterwarnings('ignore', category=ShapelyDeprecationWarning)\n", - "warnings.filterwarnings('ignore', message='Unpickling a shapely <2.0 geometry')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import xarray as xr\n", - "import salem\n", - "import numpy as np\n", - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import oggm.cfg\n", - "from oggm import utils, workflow, tasks, graphics\n", - "from oggm_edu.legacy import run_constant_climate_with_bias" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# OGGM options\n", - "oggm.cfg.initialize(logging_level='WARNING')\n", - "oggm.cfg.PATHS['working_dir'] = utils.gettempdir(dirname='WaterResources')\n", - "oggm.cfg.PARAMS['min_ice_thick_for_length'] = 1 # a glacier is when ice thicker than 1m\n", - "oggm.cfg.PARAMS['store_model_geometry'] = True" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define the glacier we will play with" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this notebook we use the Hintereisferner, Austria. Some other possibilities to play with:\n", - "\n", - "- Hintereisferner, Austria: RGI60-11.00897 \n", - "- Artesonraju, Peru: RGI60-16.02444\n", - "- Rikha Samba, Nepal: RGI60-15.04847\n", - "- Parlung No. 94, China: RGI60-15.11693\n", - "\n", - "And virtually any glacier you can find the RGI Id from, e.g. in the [GLIMS viewer](https://www.glims.org/maps/glims)! Large glaciers may need longer simulations to see changes though. For less uncertain calibration parameters, we also recommend to pick one of the many reference glaciers [in this list](https://github.com/OGGM/oggm-sample-data/blob/master/wgms/rgi_wgms_links_20200415.csv), where we make sure that observations of mass balance are better matched. \n", - "\n", - "\n", - "Let's start with Hintereisferner first and you'll be invited to try out your favorite glacier at the end of this notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Hintereisferner\n", - "rgi_id = 'RGI60-11.00897'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Preparing the glacier data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This can take up to a few minutes on the first call because of the download of the required data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# We pick the elevation-bands glaciers because they run a bit faster - but they create more step changes in the area outputs\n", - "base_url = 'https://cluster.klima.uni-bremen.de/~oggm/gdirs/oggm_v1.4/L3-L5_files/CRU/elev_bands/qc3/pcp2.5/no_match'\n", - "gdir = workflow.init_glacier_directories([rgi_id], from_prepro_level=5, prepro_border=80, prepro_base_url=base_url)[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Interactive glacier map " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A first glimpse on the glacier of interest.\n", - "\n", - "*Tip: You can use the mouse to pan and zoom in the map*" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# One interactive plot below requires Bokeh\n", - "# The rest of the notebook works without this dependency - comment if needed\n", - "import holoviews as hv\n", - "hv.extension('bokeh')\n", - "import geoviews as gv\n", - "import geoviews.tile_sources as gts\n", - "\n", - "sh = salem.transform_geopandas(gdir.read_shapefile('outlines'))\n", - "(gv.Polygons(sh).opts(fill_color=None, color_index=None) *\n", - " gts.tile_sources['EsriImagery'] * gts.tile_sources['StamenLabels']).opts(width=800, height=500, active_tools=['pan', 'wheel_zoom'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For OGGM, glaciers are \"1.5\" dimensional along their flowline:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fls = gdir.read_pickle('model_flowlines')\n", - "graphics.plot_modeloutput_section(fls);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generating a glacier in equilibrium with climate " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's prepare a run with the `run_constant_climate_with_bias` tasks from the `oggm_edu` package. It allows us to run idealized temperature and precipitation correction scenarios in an easy way. \n", - "\n", - "First, let's decide on a temperature evolution:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "years = np.arange(400)\n", - "temp_bias_ts = pd.Series(years * 0. - 2, index=years)\n", - "temp_bias_ts.plot(); plt.xlabel('Year'); plt.ylabel('Temperature bias (°C)');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Not much to see here! The `temp_bias_ts` variable describes a temperature bias that will be applied to the standard climate (see below). \n", - "\n", - "Here the bias is -2° all along because we want to run a so-called \"spinup\" run, to let the glacier grow and make sure that our glacier is in dynamical equilibrium with its climate at the end of the simulation. Let's go: " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# file identifier where the model output is saved\n", - "file_id = '_spinup'\n", - "\n", - "# We are using the task run_with_hydro to store hydrological outputs along with the usual glaciological outputs\n", - "tasks.run_with_hydro(gdir, # Run on the selected glacier\n", - " temp_bias_ts=temp_bias_ts, # the temperature bias to apply to the average climate\n", - " run_task=run_constant_climate_with_bias, # which climate scenario? See following notebook for other examples\n", - " y0=2009, halfsize=10, # Period which we will average and constantly repeat\n", - " store_monthly_hydro=True, # Monthly ouptuts provide additional information\n", - " output_filesuffix=file_id); # an identifier for the output file, to read it later" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "OK so there is quite some new material in the cell above. Let's focus on the most important points:\n", - "- we run the model for 400 years (as defined by our control temperature timeseries)\n", - "- the model runs with a constant climate averaged over 21 years (2 times `halfsize` + 1) for the period 1999-2019\n", - "- we apply a cold bias of -2°C. Indeed, Hintereisferner is in strong disequilibrium with the current climate and such a bias is needed to make the glacier grow (more on this later)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now Let's have a look at the output now:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "with xr.open_dataset(gdir.get_filepath('model_diagnostics', filesuffix=file_id)) as ds:\n", - " # The last step of hydrological output is NaN (we can't compute it for this year)\n", - " ds = ds.isel(time=slice(0, -1)).load()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are plenty of variables in this dataset! We can list them with:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "*Tip: you can click on a variable and show it's attribute with the \"page\" button on the right.*\n", - "\n", - "The `time` and `month_2d` variables are coordinates, and the other variables are either provided as additional information (e.g. `calendar_month`, we will get back to this), or they are providing the actual data. For instance, we can plot the annual evolution of the volume and length of our glacier:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, axs = plt.subplots(nrows=2, figsize=(10, 7), sharex=True)\n", - "ds.volume_m3.plot(ax=axs[0]);\n", - "ds.length_m.plot(ax=axs[1]);\n", - "axs[0].set_xlabel(''); axs[0].set_title(f'{rgi_id}'); axs[1].set_xlabel('Years');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The glacier grows and almost doubles its volume. After 400 years, it is in equilibrium.\n", - "\n", - "This spinup simulation won't be analyzed further: let's jump to the heart of the topic: **what happens with glacier runoff when climate is warming**?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Climate change simulation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We start by generating a temperature change scenario. We define a short period of constant temperature (at -2°C, to be consistent with above), followed by a linear increase (2.5 degrees in about 150 years) and a stabilization:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ny_s = 50 # Start\n", - "ny_t = 150 # Trend\n", - "ny_e = 102 # Stabilisation\n", - "temp_bias_ts = np.concatenate([np.full(ny_s, -2.), np.linspace(-2, 0.5, ny_t), np.full(ny_e, 0.5)])\n", - "temp_bias_ts = pd.Series(temp_bias_ts, index=np.arange(ny_s + ny_t + ny_e))\n", - "temp_bias_ts.plot(); plt.xlabel('Year'); plt.ylabel('Temperature bias (°C)');" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# file identifier where the model output is saved\n", - "file_id = '_lin_temp'\n", - "\n", - "# We are using the task run_with_hydro to store hydrological outputs along with the usual glaciological outputs\n", - "tasks.run_with_hydro(gdir,\n", - " temp_bias_ts=temp_bias_ts, # the temperature bias timeseries we just created\n", - " run_task=run_constant_climate_with_bias, # which climate scenario? See following notebook for other examples\n", - " y0=2009, halfsize=10, # Period which we will average and constantly repeat\n", - " store_monthly_hydro=True, # Monthly ouptuts provide additional information\n", - " init_model_filesuffix='_spinup', # We want to start from the glacier in equibrium we created earlier\n", - " output_filesuffix=file_id); # an identifier for the output file, to read it later" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's have a look at our glacier evolution:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "with xr.open_dataset(gdir.get_filepath('model_diagnostics', filesuffix=file_id)) as ds:\n", - " # The last step of hydrological output is NaN (we can't compute it for this year)\n", - " ds = ds.isel(time=slice(0, -1)).load()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, axs = plt.subplots(nrows=3, figsize=(10, 7), sharex=True)\n", - "ds.volume_m3.plot(ax=axs[0]);\n", - "ds.length_m.plot(ax=axs[1]);\n", - "temp_bias_ts.plot(ax=axs[2], c='C3');\n", - "axs[0].set_xlabel(''); axs[0].set_title(f'{rgi_id}'); axs[1].set_xlabel(''); axs[2].set_xlabel('Years'); axs[2].set_ylabel('Temperature bias (°C)');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this scenario, the glacier would melt almost entirely (it looses about 90% of its volume). Note also that the glacier continues to adjust after the temperature has stabilized. **What are the implications for downstream runoff?**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Annual runoff" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As glaciers retreat, they contribute to sea-level rise (visit the [World Glaciers Explorer](https://edu.oggm.org/en/latest/explorer.html) OGGM-Edu app for more information!). This is not what we are interested in here. Indeed, they will also have important local impacts: in this notebook, we will have a look at their impact on streamflow. \n", - "\n", - "Let's take a look at some of the hydrological outputs computed by OGGM. We start by creating a pandas DataFrame of all \"1D\" (annual) variables in the output dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sel_vars = [v for v in ds.variables if 'month_2d' not in ds[v].dims]\n", - "df_annual = ds[sel_vars].to_dataframe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then we can select the hydrological variables and sum them to get the total annual runoff:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Select only the runoff variables\n", - "runoff_vars = ['melt_off_glacier', 'melt_on_glacier', 'liq_prcp_off_glacier', 'liq_prcp_on_glacier']\n", - "# Convert them to megatonnes (instead of kg)\n", - "df_runoff = df_annual[runoff_vars] * 1e-9\n", - "# We smooth the output, which is otherwize noisy because of area discretization\n", - "df_runoff = df_runoff.rolling(31, center=True, min_periods=1).mean()\n", - "fig, ax = plt.subplots(figsize=(10, 3.5), sharex=True)\n", - "df_runoff.sum(axis=1).plot(ax=ax);\n", - "plt.ylabel('Runoff (Mt)'); plt.xlabel('Years'); plt.title(f'Total annual runoff for {rgi_id}');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The hydrological variables are computed on the largest possible area that was covered by glacier ice during the simulation. This is equivalent to the runoff that would be measured at a fixed-gauge hydrological station at the glacier terminus.\n", - "\n", - "The total annual runoff consists of the following components:\n", - "- melt off-glacier: snow melt on areas that are now glacier free (i.e. 0 in the year of largest glacier extent, in this example at the start of the simulation)\n", - "- melt on-glacier: ice + seasonal snow melt on the glacier\n", - "- liquid precipitaton on- and off-glacier (the latter being zero at the year of largest glacial extent, in this example at start of the simulation)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f, ax = plt.subplots(figsize=(10, 6));\n", - "df_runoff.plot.area(ax=ax, color=sns.color_palette('rocket')); plt.xlabel('Years'); plt.ylabel('Runoff (Mt)'); plt.title(rgi_id);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The glacier length and volume decrease from year 50 onwards until about year 250 - this is the **glacier retreat** phase. Afterwards, length and volume stabilize at a constant value indicating that the glacier has **reached equilibrium**. \n", - "\n", - "Now study the graph above and it's four main components.\n", - "\n", - "**Questions to address:**\n", - "- When is \"peak water\" reached? Does this have anything to do with a change in the temperature trend itself? If not, what is the reason for \"peak water\" to occur?\n", - "- Verify that the total fixed-gauge runoff (remember what this means?) is the same at the beginning and the end of the simulation. Why is that so? What changed in between?\n", - "- The temperature stabilizes at year 200, yet all hydrological variables continue to change after that. What is happening there?\n", - "- What is the contribution of liquid precipitation at the beginning and the end of the simulation? What changed?\n", - "- Can you verify that the net glacier contribution to runoff (i.e. - $\\Delta M / \\Delta t$) is zero at the beginning and the end of the simulation and positive in between? Why do we expect this behavior?\n", - "\n", - "*Hint: compare the results to the idealised runoff graphic that we introduced at the beginning of this chapter*" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Solution of the last question above\n", - "glacier_mass = ds.volume_m3.to_series() * oggm.cfg.PARAMS['ice_density'] * 1e-9 # In Megatonnes, Mt\n", - "glacier_mass = glacier_mass.rolling(31, center=True, min_periods=1).mean()\n", - "(- glacier_mass.diff()).plot()\n", - "plt.axhline(y=0, color='k', ls=':')\n", - "plt.ylabel('Annual glacier mass change (Mt yr$^{-1}$)')\n", - "plt.xlabel('Years'); plt.title('Glacier contribution to annual runoff');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Monthly runoff" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The \"2D\" variables contain the same hydrological data, but at monthly resolution (dimensions [time, month]). For example, monthly runoff can be computed as:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Select only the runoff variables and convert them to megatonnes (instead of kg)\n", - "monthly_runoff = ds['melt_off_glacier_monthly'] + ds['melt_on_glacier_monthly'] + ds['liq_prcp_off_glacier_monthly'] + ds['liq_prcp_on_glacier_monthly']\n", - "monthly_runoff = monthly_runoff.rolling(time=31, center=True, min_periods=1).mean() * 1e-9\n", - "monthly_runoff.clip(0).plot(cmap='Blues', cbar_kwargs={'label': 'Runoff (Mt)'}); plt.xlabel('Months'); plt.ylabel('Years');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But be aware, something is a bit wrong with this plot (some of you may have noticed: maximum melt happens in Fall, which is unusual): that's because the OGGM coordinates are hydrological months - let's make this better:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This should work in both hemispheres maybe?\n", - "ds_roll = ds.roll(month_2d=ds['calendar_month_2d'].data[0] - 1, roll_coords=True)\n", - "ds_roll['month_2d'] = ds_roll['calendar_month_2d']\n", - "\n", - "# Select only the runoff variables and convert them to megatonnes (instead of kg)\n", - "monthly_runoff = ds_roll['melt_off_glacier_monthly'] + ds_roll['melt_on_glacier_monthly'] + ds_roll['liq_prcp_off_glacier_monthly'] + ds_roll['liq_prcp_on_glacier_monthly']\n", - "monthly_runoff = monthly_runoff.rolling(time=31, center=True, min_periods=1).mean() * 1e-9\n", - "monthly_runoff.clip(0).plot(cmap='Blues', cbar_kwargs={'label': 'Runoff (Mt)'}); plt.xlabel('Months'); plt.ylabel('Years');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see, the runoff is approximately zero during the winter months, while relatively high during the summer months. \n", - "\n", - "Now let's compare the actual runoff to total precipitation over the basin:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Compute total precipitation (Snow + Liquid)\n", - "tot_precip = ds_roll['liq_prcp_off_glacier_monthly'] + ds_roll['liq_prcp_on_glacier_monthly'] + ds_roll['snowfall_off_glacier_monthly'] + ds_roll['snowfall_on_glacier_monthly']\n", - "tot_precip *= 1e-9 # in Mt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot these data at year 0\n", - "yr = 0\n", - "r = monthly_runoff.sel(time=yr)\n", - "p = tot_precip.sel(time=yr)\n", - "\n", - "f, ax = plt.subplots(figsize=(10, 6));\n", - "r.plot(ax=ax, color='C3', label='Monthly runoff', linewidth=3);\n", - "p.plot(ax=ax, color='C0', label='Monthly precipitation', linewidth=3);\n", - "ax.fill_between(r.month_2d, r, p, where=(p >= r), facecolor='C0', interpolate=True, alpha=0.5)\n", - "ax.fill_between(r.month_2d, r, p, where=(r > p), facecolor='C3', interpolate=True, alpha=0.5)\n", - "plt.ylabel('Mt yr$^{-1}$'); plt.legend(loc='best');\n", - "plt.xlabel('Month'); plt.title(f'Total monthly runoff and precipitation at year {yr}');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At the begining of the simulation, the glacier is in equilibrium and its annual contribution to runoff is zero. In fact, this means that the blue area in the plot above is equal to the red area: **the glaciated basin releases water during the summer, that accumulated in form of snow in the winter months**.\n", - "\n", - "In this region (Ötztal Alps, Austria), precipitation is relatively constant throughout the year. **Discuss what the implications could be in other climates of the world, in particular in climates with a strong seasonality of precipitation.**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's have a look at the seasonal change in runoff with time:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f, ax = plt.subplots(figsize=(10, 6));\n", - "cmap = sns.color_palette('magma', 3)\n", - "for i, yr in enumerate([0, 120, 300]):\n", - " monthly_runoff.sel(time=yr).plot(ax=ax, color=cmap[i], label=f'Year {yr}')\n", - "plt.ylabel('Mt yr$^{-1}$'); plt.legend(loc='best');\n", - "plt.xlabel('Month'); plt.title('Total monthly runoff change with time');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Questions to explore:**\n", - "- verify that the annual runoff is the same at year 0 than at year 300. What changed in between?\n", - "- what is responsible for the switch from summer melt to spring melt?\n", - "- how does the runoff change at \"peak water\" (approx. year 120)?\n", - "- discuss the implications of this shift in seasonality for Hintereisferner and for other regions of the world.\n", - "\n", - "**Now repeat this notebook (ideally by copying it first) with other regions of the world, where climate might be very different. **\n", - "\n", - "Note that our explanations are very glacier-specific. So if you change the glacier by using another rgi_id, you also might need to adapt the explanations! \n", - "\n", - "**You can also change the parameters of the simulations!**\n", - "\n", - "What happens when the temperature change is larger/faster, smaller/slower? What are the implications for \"peak water\"?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Wrapping up" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's conclude this notebook by reproducing the famous plot by [Huss & Hock (2018)](https://www.nature.com/articles/s41558-017-0049-x) with our data.\n", - "\n", - "\"Fig\n", - "\n", - "*Graphic from [Huss & Hock (2018)](https://www.nature.com/articles/s41558-017-0049-x)*" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Make a dataframe out of the xarray dataset for simplicity\n", - "df_monthly_runoff = pd.DataFrame(monthly_runoff.data, index=ds.time, columns=monthly_runoff.month_2d)\n", - "\n", - "# Create new columns for each season (we define seasons from the perspective of the northern Hemisphere)\n", - "df_monthly_runoff['Spring (Apr-Jun)'] = df_monthly_runoff[[4, 5, 6]].sum(axis=1)\n", - "df_monthly_runoff['Summer (Jul-Sep)'] = df_monthly_runoff[[7, 8, 9]].sum(axis=1)\n", - "df_monthly_runoff['Winter (Oct-Mar)'] = df_monthly_runoff[[10, 11, 12, 1, 2, 3]].sum(axis=1)\n", - "df_monthly_runoff.columns.name = 'Season'\n", - "\n", - "# Plot it\n", - "# Attention the seasons and labels might need to be adapted if a glacier in a different region is used!\n", - "f, ax = plt.subplots(figsize=(10, 6));\n", - "df_monthly_runoff[['Spring (Apr-Jun)', 'Summer (Jul-Sep)', 'Winter (Oct-Mar)']].plot.area(ax=ax, color=sns.color_palette('rocket'));\n", - "plt.xlabel('Years'); plt.ylabel('Runoff (Mt)'); plt.title('Runoff by season');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And now the actual plot:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [2, 1]}, figsize=(10, 8), sharex=True)\n", - "\n", - "p1 = df_monthly_runoff[['Spring (Apr-Jun)', 'Summer (Jul-Sep)', 'Winter (Oct-Mar)']].sum(axis=1)\n", - "p2 = df_monthly_runoff['Summer (Jul-Sep)'] # July to September is here the melt season\n", - "p1.plot(ax=ax1, color='C0', label='Annual runoff', linewidth=3)\n", - "p2.plot(ax=ax1, color='C3', label='Melt-season runoff', linewidth=3);\n", - "ax1.hlines([p1.loc[0], p2.loc[0]], 0, 300, color=['C0', 'C3'], linestyle=[':']);\n", - "ax1.legend(); ax1.set_xlabel(''); ax1.set_ylabel('Runoff (Mt)');\n", - "\n", - "(temp_bias_ts + 2).plot(ax=ax2, color='C2', label='Temperature', linewidth=3);\n", - "ax2.legend(loc='lower right'); ax2.set_xlabel('Years'); ax2.set_ylabel('$\\Delta T (°C)$');\n", - "sns.despine();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that the two plots above are very glacier-specific. If you use a glacier with another climate, you might need to adapt the plots, because we are always interested in those months where the runoff from the glacier contributes most to the total runoff. Example given, for Artesonraju in Peru, we are most interested in June, July, August and September as this is the dry season, where it almost does not rain at all. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Take home points" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Glaciers in equilibrium are *not* net water resources: they gain as much mass as they release\n", - "- However, they have a seasonal buffer role, releasing water during the melt months\n", - "- When glaciers melt, they become net water resources. \"Peak water\" is the point in time when glacier melt supply reaches its maximum, i.e. when the maximum runoff occurs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Huss, M. and Hock, R.: Global-scale hydrological response to future glacier mass loss, Nat. Clim. Chang., 8(2), 135–140, [doi:10.1038/s41558-017-0049-x](https://doi.org/10.1038/s41558-017-0049-x), 2018.\n", - "- A [recent blog post](https://blogs.egu.eu/divisions/cr/2021/03/05/glaciers-water-supply-climate-change/) about the role of glaciers in the hydrological cycle" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## What's next?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Back to the table of contents](../welcome.ipynb)" - ] - } - ], - "metadata": { - "hide_input": false, - "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" - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": false, - "sideBar": true, - "skip_h1_title": true, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": true, - "toc_window_display": false - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/oggm-edu/pre16/glacier_water_resources_projections_pre16.ipynb b/oggm-edu/pre16/glacier_water_resources_projections_pre16.ipynb deleted file mode 100644 index 939e2cd..0000000 --- a/oggm-edu/pre16/glacier_water_resources_projections_pre16.ipynb +++ /dev/null @@ -1,806 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Glaciers as water resources: part 2 (projections) - OGGM versions pre 1.6" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Goals of this notebook:\n", - "\n", - "- run simulations using climate projections to explore the role of glaciers as water resources" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - " \n", - " This version of the notebook works for OGGM versions before 1.6. We will keep this notebook here for a while longer (e.g.: for classroom.oggm.org). If you are running OGGM 1.6, you can visit the updated notebook at glacier_water_resources_projections.ipynb.\n", - " \n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setting the scene: glacier runoff and \"peak water\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We strongly recommend to run [Part 1](glacier_water_resources_pre16.ipynb) of this notebook before going on!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "sns.set_context('notebook') # plot defaults" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Temporarily ignore warnings from shapely\n", - "import warnings\n", - "from shapely.errors import ShapelyDeprecationWarning\n", - "warnings.filterwarnings('ignore', category=ShapelyDeprecationWarning)\n", - "warnings.filterwarnings('ignore', message='Unpickling a shapely <2.0 geometry')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import xarray as xr\n", - "import salem\n", - "import numpy as np\n", - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import oggm.cfg\n", - "from oggm import utils, workflow, tasks, graphics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# OGGM options\n", - "oggm.cfg.initialize(logging_level='WARNING')\n", - "oggm.cfg.PATHS['working_dir'] = utils.gettempdir(dirname='WaterResources-Proj')\n", - "oggm.cfg.PARAMS['min_ice_thick_for_length'] = 1 # a glacier is when ice thicker than 1m\n", - "oggm.cfg.PARAMS['store_model_geometry'] = True" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define the glacier we will play with" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this notebook we use the Hintereisferner, Austria. Some other possibilities to play with:\n", - "\n", - "- Hintereisferner, Austria: RGI60-11.00897 \n", - "- Artesonraju, Peru: RGI60-16.02444\n", - "- Rikha Samba, Nepal: RGI60-15.04847\n", - "- Parlung No. 94, China: RGI60-15.11693\n", - "\n", - "And virtually any glacier you can find the RGI Id from, e.g. in the [GLIMS viewer](https://www.glims.org/maps/glims)! Large glaciers may need longer simulations to see changes though. For less uncertain calibration parameters, we also recommend to pick one of the many reference glaciers [in this list](https://github.com/OGGM/oggm-sample-data/blob/master/wgms/rgi_wgms_links_20200415.csv), where we make sure that observations of mass-balance are better matched. \n", - "\n", - "\n", - "Let's start with Hintereisferner first and you'll be invited to try out your favorite glacier at the end of this notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Hintereisferner\n", - "rgi_id = 'RGI60-11.00897'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Preparing the glacier data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This can take up to a few minutes on the first call because of the download of the required data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# We pick the elevation-bands glaciers because they run a bit faster - but they create more step changes in the area outputs\n", - "base_url = 'https://cluster.klima.uni-bremen.de/~oggm/gdirs/oggm_v1.4/L3-L5_files/CRU/elev_bands/qc3/pcp2.5/no_match'\n", - "gdir = workflow.init_glacier_directories([rgi_id], from_prepro_level=5, prepro_border=80, prepro_base_url=base_url)[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Interactive glacier map " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A first glimpse on the glacier of interest.\n", - "\n", - "*Tip: You can use the mouse to pan and zoom in the map*" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# One interactive plot below requires Bokeh\n", - "# The rest of the notebook works without this dependency - comment if needed\n", - "import holoviews as hv\n", - "hv.extension('bokeh')\n", - "import geoviews as gv\n", - "import geoviews.tile_sources as gts\n", - "\n", - "sh = salem.transform_geopandas(gdir.read_shapefile('outlines'))\n", - "(gv.Polygons(sh).opts(fill_color=None, color_index=None) *\n", - " gts.tile_sources['EsriImagery'] * gts.tile_sources['StamenLabels']).opts(width=800, height=500, active_tools=['pan', 'wheel_zoom'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For OGGM, glaciers are \"1.5\" dimensional along their flowline:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fls = gdir.read_pickle('model_flowlines')\n", - "graphics.plot_modeloutput_section(fls);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## \"Commitment run\" " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are now ready to run our first simulation. This is a so called \"commitment run\": how much ice loss is \"already committed\" for this glacier, even if climate change would stop today? This is a useful but purely theoretical experiment: climate change won't stop today, unfortunately. To learn more about committed mass-loss at the global scale, read [Marzeion et al., 2018](https://www.nature.com/articles/s41558-018-0093-1).\n", - "\n", - "Here, we run a simulation for 100 years with a constant climate based on the last 11 years:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# file identifier where the model output is saved\n", - "file_id = '_ct'\n", - "\n", - "# We are using the task run_with_hydro to store hydrological outputs along with the usual glaciological outputs\n", - "tasks.run_with_hydro(gdir,\n", - " run_task=tasks.run_constant_climate, # which climate? See below for other examples\n", - " nyears=100, # length of the simulation\n", - " y0=2014, halfsize=5, # For the constant climate, period over which the climate is taken from\n", - " store_monthly_hydro=True, # Monthly ouptuts provide additional information\n", - " output_filesuffix=file_id); # an identifier for the output file to read it later" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then we can take a look at the output:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "with xr.open_dataset(gdir.get_filepath('model_diagnostics', filesuffix=file_id)) as ds:\n", - " # The last step of hydrological output is NaN (we can't compute it for this year)\n", - " ds = ds.isel(time=slice(0, -1)).load()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are plenty of variables in this dataset! We can list them with:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ds" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "*Tip: you can click on a variable and show it's attribute with the \"page\" button on the right.*\n", - "\n", - "The `time` and `month_2d` variables are coordinates, and the other variables are either provided as additional information (e.g. `calendar_month`, we will get back to this), or they are providing the actual data. For instance, we can plot the annual evolution of the volume and length of our glacier:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, axs = plt.subplots(nrows=2, figsize=(10, 7), sharex=True)\n", - "ds.volume_m3.plot(ax=axs[0]);\n", - "ds.length_m.plot(ax=axs[1]);\n", - "axs[0].set_xlabel(''); axs[0].set_title(f'{rgi_id}'); axs[1].set_xlabel('Years');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The glacier length and volume decrease during the first ~40 years of the simulation - this is the **glacier retreat** phase. Afterwards, both length and volume oscillate around a more or less constant value indicating that the glacier has **reached equilibrium**. The difference between the starting volume and the equilibrium volume is called the **committed mass loss**. It can be quite high in the Alps, and depends on many factors (such as glacier size, location, and the reference climate period), " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Annual runoff" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As glaciers retreat, they contribute to sea-level rise (visit the [World Glaciers Explorer](https://edu.oggm.org/en/latest/explorer.html) OGGM-Edu app for more information!). This is not what we are interested in here. Indeed, they will also have important local impacts: in this notebook, we will have a look at their impact on streamflow. \n", - "\n", - "Let's take a look at some of the hydrological outputs computed by OGGM. We start by creating a pandas DataFrame of all \"1D\" (annual) variables:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sel_vars = [v for v in ds.variables if 'month_2d' not in ds[v].dims]\n", - "df_annual = ds[sel_vars].to_dataframe()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then we can select the hydrological varialbes and sum them to get the total annual runoff:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Select only the runoff variables\n", - "runoff_vars = ['melt_off_glacier', 'melt_on_glacier', 'liq_prcp_off_glacier', 'liq_prcp_on_glacier']\n", - "# Convert them to megatonnes (instead of kg)\n", - "df_runoff = df_annual[runoff_vars] * 1e-9\n", - "fig, ax = plt.subplots(figsize=(10, 3.5), sharex=True)\n", - "df_runoff.sum(axis=1).plot(ax=ax);\n", - "plt.ylabel('Mt'); plt.xlabel('Years'); plt.title(f'Total annual runoff for {rgi_id}');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The hydrological variables are computed on the largest possible area that was covered by glacier ice during the simulation. This is equivalent to the runoff that would be measured at a fixed-gauge hydrological station at the glacier terminus.\n", - "\n", - "The total annual runoff consists of the following components:\n", - "- melt off-glacier: snow melt on areas that are now glacier free (i.e. 0 in the year of largest glacier extent, in this example at the start of the simulation)\n", - "- melt on-glacier: ice + seasonal snow melt on the glacier\n", - "- liquid precipitaton on- and off-glacier (the latter being zero at the year of largest glacial extent, in this example at start of the simulation)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f, ax = plt.subplots(figsize=(10, 6));\n", - "df_runoff.plot.area(ax=ax, color=sns.color_palette(\"rocket\")); plt.xlabel('Years'); plt.ylabel('Runoff (Mt)'); plt.title(rgi_id);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Before we continue, let's remember ourselves the expected contribution of glaciers to runoff.\n", - "\n", - "\"Fig\n", - "\n", - "*Graphic from [Huss & Hock (2018)](https://www.nature.com/articles/s41558-017-0049-x)*\n", - "\n", - "\n", - "**Questions to explore:**\n", - "- **where approximately on this graph is the studied glacier?**\n", - "- **can you explain the relative contribution of each component, based on the previous notebook?**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The total runoff out of a glacier basin is the sum of the four contributions above. To show that the glacier total contribution is indeed zero ($\\Delta M = 0$) **when in equilibrium**, we can compute it from the glacier mass change: " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "glacier_mass = ds.volume_m3.to_series() * oggm.cfg.PARAMS['ice_density'] * 1e-9 # In Megatonnes, Mt\n", - "\n", - "glacier_mass.diff().plot()\n", - "plt.axhline(y=0, color='k', ls=':')\n", - "plt.ylabel('Annual glacier mass change (Mt yr$^{-1}$)')\n", - "plt.xlabel('Years'); plt.title('Glacier contribution to annual runoff');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that this doesn't mean that ice is not melting! At equilibrium, this means that the ice that melts each year over the glacier is replaced by snowfall in the accumulation area of the glacier. This illustrates well that **glaciers in equilibrium are not net water resources on the annual average**: in the course of the year they gain as much mass as they release. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Monthly runoff" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The \"2D\" variables contain the same data but at monthly resolution, with the dimension (time, month). For example, runoff can be computed the \n", - "same way:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Select only the runoff variables and convert them to megatonnes (instead of kg)\n", - "monthly_runoff = (ds['melt_off_glacier_monthly'] + ds['melt_on_glacier_monthly'] +\n", - " ds['liq_prcp_off_glacier_monthly'] + ds['liq_prcp_on_glacier_monthly'])\n", - "monthly_runoff *= 1e-9\n", - "monthly_runoff.clip(0).plot(cmap='Blues', cbar_kwargs={'label': 'Mt'}); plt.xlabel('Months'); plt.ylabel('Years');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But be aware, something is a bit wrong with this: the coordinates are hydrological months - let's make this better:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This should work in both hemispheres maybe?\n", - "ds_roll = ds.roll(month_2d=ds['calendar_month_2d'].data[0] - 1, roll_coords=True)\n", - "ds_roll['month_2d'] = ds_roll['calendar_month_2d']\n", - "\n", - "# Select only the runoff variables and convert them to megatonnes (instead of kg)\n", - "monthly_runoff = (ds_roll['melt_off_glacier_monthly'] + ds_roll['melt_on_glacier_monthly'] +\n", - " ds_roll['liq_prcp_off_glacier_monthly'] + ds_roll['liq_prcp_on_glacier_monthly'])\n", - "monthly_runoff *= 1e-9\n", - "monthly_runoff.clip(0).plot(cmap='Blues', cbar_kwargs={'label': 'Mt'}); plt.xlabel('Months'); plt.ylabel('Years');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see, the runoff is approximately zero during the winter months, while relatively high during the summer months. This implies that the glacier is a source of water in the summer when it releases the water accumulated in the winter.\n", - "\n", - "The annual cycle changes as the glacier retreats:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "monthly_runoff.sel(time=[0, 30, 99]).plot(hue='time');\n", - "plt.title('Annual cycle');\n", - "plt.xlabel('Month');\n", - "plt.ylabel('Runoff (Mt)');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Not only does the total runoff during the summer months decrease as the simulation progresses, the month of maximum runoff is also shifted to earlier in the summer." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### CMIP5 projection runs\n", - "\n", - "You have now learned how to simulate and analyse a specific glacier under a constant climate. We will now take this a step further and simulate two different glaciers, located in different climatic regions, forced with CMIP5 climate projections.\n", - "\n", - "We begin by initializing the glacier directories:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# We keep Hintereisferner from earlier, but also add a new glacier\n", - "rgi_ids = [rgi_id, 'RGI60-15.02420']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gdirs = workflow.init_glacier_directories(rgi_ids, from_prepro_level=5, prepro_border=80, prepro_base_url=base_url)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`gdirs` now contain two glaciers, one in Central Europe and one in the Eastern Himlayas:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gdirs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can take a quick look at the new glacier:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sh = salem.transform_geopandas(gdirs[1].read_shapefile('outlines'))\n", - "(gv.Polygons(sh).opts(fill_color=None, color_index=None) *\n", - " gts.tile_sources['EsriImagery'] * gts.tile_sources['StamenLabels']).opts(width=800, height=500, active_tools=['pan', 'wheel_zoom'])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Climate downscaling\n", - "Before we run our simulation we have to process the climate data for the glaicer i.e. downscale it: (This can take some time)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from oggm.shop import gcm_climate\n", - "bp = 'https://cluster.klima.uni-bremen.de/~oggm/cmip5-ng/pr/pr_mon_CCSM4_{}_r1i1p1_g025.nc'\n", - "bt = 'https://cluster.klima.uni-bremen.de/~oggm/cmip5-ng/tas/tas_mon_CCSM4_{}_r1i1p1_g025.nc'\n", - "for rcp in ['rcp26', 'rcp45', 'rcp60', 'rcp85']:\n", - " # Download the files\n", - " ft = utils.file_downloader(bt.format(rcp))\n", - " fp = utils.file_downloader(bp.format(rcp))\n", - " workflow.execute_entity_task(gcm_climate.process_cmip_data, gdirs,\n", - " # Name file to recognize it later\n", - " filesuffix='_CCSM4_{}'.format(rcp),\n", - " # temperature projections\n", - " fpath_temp=ft,\n", - " # precip projections\n", - " fpath_precip=fp,\n", - " );" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Projection run\n", - "With the downscaling complete, we can run the forced simulations:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for rcp in ['rcp26', 'rcp45', 'rcp60', 'rcp85']:\n", - " rid = '_CCSM4_{}'.format(rcp)\n", - " workflow.execute_entity_task(tasks.run_with_hydro, gdirs,\n", - " run_task=tasks.run_from_climate_data, ys=2020,\n", - " # use gcm_data, not climate_historical\n", - " climate_filename='gcm_data',\n", - " # use the chosen scenario\n", - " climate_input_filesuffix=rid,\n", - " # this is important! Start from 2020 glacier\n", - " init_model_filesuffix='_historical',\n", - " # recognize the run for later\n", - " output_filesuffix=rid,\n", - " # add monthly diagnostics\n", - " store_monthly_hydro=True);" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create the figure\n", - "f, ax = plt.subplots(figsize=(18, 7), sharex=True)\n", - "# Loop over all scenarios\n", - "for i, rcp in enumerate(['rcp26', 'rcp45', 'rcp60', 'rcp85']):\n", - " file_id = f'_CCSM4_{rcp}'\n", - " # Open the data, gdirs[0] correspond to Hintereisferner.\n", - " with xr.open_dataset(gdirs[0].get_filepath('model_diagnostics', filesuffix=file_id)) as ds:\n", - " # Load the data into a dataframe\n", - " ds = ds.isel(time=slice(0, -1)).load()\n", - "\n", - " # Select annual variables\n", - " sel_vars = [v for v in ds.variables if 'month_2d' not in ds[v].dims]\n", - " # And create a dataframe\n", - " df_annual = ds[sel_vars].to_dataframe()\n", - "\n", - " # Select the variables relevant for runoff.\n", - " runoff_vars = ['melt_off_glacier', 'melt_on_glacier', 'liq_prcp_off_glacier', 'liq_prcp_on_glacier']\n", - " # Convert to mega tonnes instead of kg.\n", - " df_runoff = df_annual[runoff_vars].clip(0) * 1e-9\n", - " # Sum the variables each year \"axis=1\", take the 11 year rolling mean and plot it.\n", - " df_roll = df_runoff.sum(axis=1).rolling(window=11, center=True).mean()\n", - " df_roll.plot(ax=ax, label=rcp, color=sns.color_palette(\"rocket\")[i])\n", - "\n", - "ax.set_ylabel('Annual runoff (Mt)'); ax.set_xlabel('Year'); plt.title(gdirs[0].rgi_id); plt.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For Hintereisferner, runoff continues to decrease throughout the 21st-century for all scenarios, indicating that **peak water** has already been reached sometime in the past. This is the case for many European glaciers. What about our unnamed glacier in the Himalayas?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create the figure\n", - "f, ax = plt.subplots(figsize=(18, 7), sharex=True)\n", - "# Loop over all scenarios\n", - "for i, rcp in enumerate(['rcp26', 'rcp45', 'rcp60', 'rcp85']):\n", - " file_id = f'_CCSM4_{rcp}'\n", - " # Open the data, gdirs[1] correspond to the unnamed glacier.\n", - " with xr.open_dataset(gdirs[1].get_filepath('model_diagnostics', filesuffix=file_id)) as ds:\n", - " # Load the data into a dataframe\n", - " ds = ds.isel(time=slice(0, -1)).load()\n", - "\n", - " # Select annual variables\n", - " sel_vars = [v for v in ds.variables if 'month_2d' not in ds[v].dims]\n", - " # And create a dataframe\n", - " df_annual = ds[sel_vars].to_dataframe()\n", - "\n", - " # Select the variables relevant for runoff.\n", - " runoff_vars = ['melt_off_glacier', 'melt_on_glacier',\n", - " 'liq_prcp_off_glacier', 'liq_prcp_on_glacier']\n", - " # Convert to mega tonnes instead of kg.\n", - " df_runoff = df_annual[runoff_vars].clip(0) * 1e-9\n", - " # Sum the variables each year \"axis=1\", take the 11 year rolling mean and plot it.\n", - " df_roll = df_runoff.sum(axis=1).rolling(window=11, center=True).mean()\n", - " df_roll.plot(ax=ax, label=rcp, color=sns.color_palette(\"rocket\")[i])\n", - "\n", - "ax.set_ylabel('Annual runoff (Mt)'); ax.set_xlabel('Year'); plt.title(gdirs[1].rgi_id); plt.legend();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Unlike for Hintereisferner, these projections indicate that the annual runoff will increase in all the scenarios for the first half of the century. The higher RCP scenarios can reach **peak water** later in the century, since the excess melt can continue to increase. For the lower RCP scenarios on the other hand, the glacier might be approaching a new equilibirum, which reduces the runoff earlier in the century ([Rounce et. al., 2020](https://www.frontiersin.org/articles/10.3389/feart.2019.00331/full)). After **peak water** is reached (RCP2.6: ~2055, RCP8.5: ~2070 in these projections), the annual runoff begins to decrease. This decrease occurs because the shrinking glacier is no longer able to support the high levels of melt." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Another projection run with temperature bias, or precipitation?\n", - "TODO: include something about how temperature bias affect the size of the glacier and the meltwater output." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Take home points " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Glaciers in equilibrium are *not* net water resources: they gain as much mass as they release over a year. \n", - " - However, they have a seasonal buffer role: they release water during the melt months. \n", - " - The size of a glacier has an influence on the water availability downstream during the dry season. The impact is most important if the (warm) melt season coincides with the dry season (see [Kaser et al., 2010](https://www.pnas.org/content/107/47/20223)).\n", - "- When glaciers melt, they become net water resources over the year. \"Peak water\" is the point in time when glacier melt supply reaches its maximum, i.e. when the maximum runoff occurs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Kaser, G., Großhauser, M., and Marzeion, B.: Contribution potential of glaciers to water availability in different climate regimes, PNAS, 07 (47) 20223-20227, [doi:10.1073/pnas.1008162107](https://doi.org/10.1073/pnas.1008162107), 2010\n", - "- Huss, M. and Hock, R.: Global-scale hydrological response to future glacier mass loss, Nat. Clim. Chang., 8(2), 135–140, [doi:10.1038/s41558-017-0049-x](https://doi.org/10.1038/s41558-017-0049-x), 2018.\n", - "- Marzeion, B., Kaser, G., Maussion, F. and Champollion, N.: Limited influence of climate change mitigation on short-term glacier mass loss, Nat. Clim. Chang., 8, [doi:10.1038/s41558-018-0093-1](https://doi.org/10.1038/s41558-018-0093-1), 2018.\n", - "- Rounce, D. R., Hock, R., McNabb, R. W., Millan, R., Sommer, C., Braun, M. H., Malz, P., Maussion, F., Mouginot, J., Seehaus, T. C. and Shean, D. E.: Distributed global debris thickness estimates reveal debris significantly impacts glacier mass balance, Geophys. Res. Lett., [doi:10.1029/2020GL091311](https://doi.org/10.1029/2020GL091311), 2021." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## What's next?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Back to the table of contents](../welcome.ipynb)" - ] - } - ], - "metadata": { - "hide_input": false, - "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" - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": false, - "sideBar": true, - "skip_h1_title": true, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": true, - "toc_window_display": false - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/oggm-edu/temperature_index_model.ipynb b/oggm-edu/temperature_index_model.ipynb deleted file mode 100644 index d6347d3..0000000 --- a/oggm-edu/temperature_index_model.ipynb +++ /dev/null @@ -1,724 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Temperature index models" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Goals of this notebook:\n", - "\n", - "- Gain a basic understanding of temperature index models \n", - "- Implement OGGM's temperature index model for a glacier of interest\n", - "\n", - "
\n", - " \n", - " This version of the notebook works for OGGM versions before 1.6. We will keep this notebook here for a while longer (e.g.: for classroom.oggm.org), and we will replace it with an updated notebook at a later stage. If you are running OGGM 1.6, you should ignore this notebook.\n", - " \n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plotting libraries and plot style\n", - "import matplotlib.pyplot as plt\n", - "\n", - "import seaborn as sns\n", - "sns.set_context('notebook')\n", - "sns.set_style('ticks')\n", - "\n", - "import numpy as np\n", - "import oggm\n", - "from oggm import utils, cfg, workflow, graphics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cfg.initialize()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some settings:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# define a temporary directory to store the required data to\n", - "cfg.PATHS['working_dir'] = utils.gettempdir('ti_model')\n", - "\n", - "# set the size of the local glacier map: number of grid points outside the\n", - "# glacier boundaries\n", - "# increasing this parameter will (significantly!) increase the amount of data\n", - "# that needs to be downloaded\n", - "cfg.PARAMS['border'] = 10" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Background" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Glacier melt significantly influences catchment hydrology. Hence, it is useful to have accurate predictions of runoff from glacierized areas. Generally, there are two classes of melt models:\n", - "\n", - "- energy balance models\n", - "- temperature index models" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Energy balance models are physical models quantifying melt as the residual of the energy balance equation. These models require measurements of net radiation, wind speed, temperature and surface properties to predict melt. On a glacier, spatially well resolved measurements are demanding and hard to maintain. Hence, a simpler model, the temperature index model, is the most common approach to model glacier melt." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Temperature index models assume an empirical relationship between air temperatures and melt rates and are a simplification of the energy balance models. The reasoning is that melt is predominantly influenced by the longwave atmospheric radiation and the sensible heat flux - energy balance components that are highly influenced by air temperature [(Hock, 2003)](https://www.sciencedirect.com/science/article/pii/S0022169403002579). The main reason(s) why temperature index models are commonly used are the wide availability of air temperature measurements and computational efficiency." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Model setup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The simplest temperature index model relates the amount of ice or snow melt $M$ (mm) to the sum of positive air temperatures $T^+$ ($^\\circ$C) by a proportionality factor $DDF$, the *degree-day factor*, for each $n$ time intervals $\\Delta t$:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\\sum_i^{n} M = DDF \\sum_i^{n} T^+ \\Delta t$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Commonly, $\\Delta t = 1$ day is used - hence the name *degree-day factor*. However, any other time interval $\\Delta t$, e.g. hourly or monthly, can be used to determine $DDF$. In practice, the model requires measurements of air temperature and glacier mass balance to estimate $DDF$ - once calculated, $DDF$ can be used to predict melt by only measuring air temperature [(Hock, 2003)](https://www.sciencedirect.com/science/article/pii/S0022169403002579). However, this temperature index model, also called [*degree-day model*](http://www.antarcticglaciers.org/glaciers-and-climate/numerical-ice-sheet-models/modelling-glacier-melt/), is not able to predict glacier surface mass balance." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To model glacier surface mass balance, a more sophisticated temperature index model was developed by [Marzeion et al., (2012)](https://www.the-cryosphere.net/6/1295/2012/tc-6-1295-2012.html). The monthly mass balance $B_i$ at elevation $z$ is computed as" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$B_i(z) = P_i^{solid}(z) - \\mu^* \\text{max}(T_i(z) - T_{melt}, 0) - \\epsilon$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "where $P_i^{Solid}$ is the monthly solid precipitation, $T_i$ the monthly average temperature, $T_{Melt}$ the monthly average temperature above which ice melt is assumed and $\\epsilon$ the residual. $\\epsilon$ is assumed to be a random error taking account for uncertainties associated with unresolved physical processes. $\\mu^*$ is the temperature sensitivity of the glacier and it depends on many parameters, mostly glacier specific (e.g., avalanches, topographical shading, cloudiness, ...)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Degrees of freedom" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Among others, the temperature sensitivity $\\mu^*$, the threshold for melt $T_{Melt}$ and the implicit threshold for solid precipitation $T_{Solid}$ are important degrees of freedom of the model - $T_{Solid}$ is the monthly average temperature below which precipitation is assumed to be solid." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Generally, $T_{Melt}$ and $T_{Solid}$ can vary both spatially and temporally on a specific glacier. However, commonly the two thresholds $T_{Melt}$ and $T_{Solid}$ are assumed to be constant. $T_{Melt}$ and $T_{Solid}$ significantly influence the predicted mass balance $B$ by determining the months which are taken into account in the calculation. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Both $T_{Melt}$ and $T_{Solid}$ can be determined by a physical reasoning: we know that both snow melts and precipitation becomes solid at around 0$^{\\circ}$C. Hence, the two thresholds $T_{Melt}$ and $T_{Solid}$ are within a natural range that depends on the climatological conditions at a specific glacier site." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In OGGM, $T_{Melt}$ and $T_{Solid}$ are constants and you can access the default values via the ``cfg`` module:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# the default temperature below which solid precipitation is assumed\n", - "print('T_solid = {}°C'.format(cfg.PARAMS['temp_all_solid']))\n", - "# the default temperature above which melt is assumed to occur\n", - "print('T_melt = {}°C'.format(cfg.PARAMS['temp_melt']))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similarly, you can use your own $T_{Melt}$ and $T_{Solid}$ if you feel like it:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# don't run this ...\n", - "# cfg.PARAMS['temp_all_solid'] = 100\n", - "# cfg.PARAMS['temp_melt'] = - 273.15" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The temperature sensitivity $\\mu^*$ is glacier specific and mostly determined using statistical error minimization techniques, e.g. [ordinary least squares](https://en.wikipedia.org/wiki/Ordinary_least_squares) (OLS). Such statistical techniques are very sensitive to the sample size - a general issue in glaciology is that the sample size of annual mass balance records is poor for many glaciers." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, assume that a $100$ year long mass balance record together with temperature and precipitation measurements is available for a specific glacier (this is a best case example and only very few glaciers actually have such long records). OLS will find a statistically significant $\\mu^*$ which you can happily use to model mass balance. But what happens if you only use $30$ years out of the $100$ year record? OLS will find another statistically significant $\\mu^*$ that is different from the one determined by the $100$ year record - and another statistically significant $\\mu^*$ can be found for each reasonable subset of the original $100$ year record. This implies that $\\mu^*$ is generally a time dependent temperature sensitivity $\\mu^*(t)$." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this reason, OGGM implements a calibration procedure, introduced by [Marzeion et al., (2012)](https://www.the-cryosphere.net/6/1295/2012/tc-6-1295-2012.html), to determine a constant glacier specific $\\mu^*$ out of the time dependent $\\mu^*(t)$ candidates. This calibration is beyond the scope of this notebook and you can read about it in detail [here](https://www.the-cryosphere.net/6/1295/2012/tc-6-1295-2012.html) and check out an example implementation in OGGM [here](https://docs.oggm.org/en/stable/mass-balance.html#calibration)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Implementation in OGGM" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we need to define a glacier directory:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# this may take a while\n", - "gdir = workflow.init_glacier_directories([utils.demo_glacier_id('hef')], from_prepro_level=3)[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you want to look at your model domain, you can plot it using:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# graphics.plot_domain(gdir)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In OGGM, the calibrated temperature index model for each glacier is accessible via the ``PastMassBalance`` class of the ``massbalance`` module:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from oggm.core import massbalance" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# this class is the calibrated temperature index model\n", - "mb_cal = massbalance.PastMassBalance(gdir)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this case," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('the glacier selected is {},'.format(gdir.name))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and its calibrated temperature sensitivity $\\mu^*$ is" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('mu_star = {:2f} mm K^-1 yr^-1.'.format(mb_cal.mu_star))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similarly, the residual $\\epsilon$ is" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('epsilon = {:2f} mm.'.format(mb_cal.bias))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Climate data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Per default, the temperature index model is driven by the $0.5^{\\circ} \\times 0.5^{\\circ}$ gridded global [CRU TS](https://crudata.uea.ac.uk/cru/data/hrg/) climate dataset. These climate data are then downscaled to a higher resolution grid to allow for an elevation-dependent dataset. The climate data at the reference height used to drive the temperature index model and to determine the calibrated $\\mu^*$ of the selected glacier can be accessed via the glacier directory:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fpath = gdir.get_filepath('climate_historical')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(fpath)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is the temporary path where OGGM stored its climate data on your machine. You can read the climate data using ``xarray``:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import xarray as xr" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "climate = xr.open_dataset(fpath)\n", - "climate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The climate dataset has two variables, the monthly total precipitation ``prcp`` and the monthly average temperature ``temp``. Let's calculate the mean annual cycle of average temperature and total precipitation," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "annual_cycle = climate.groupby('time.month').mean(dim='time')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and plot it, to get an intuitive view on the climate conditions at the selected glacier site." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import calendar" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 2, figsize=(16, 9))\n", - "ax[0].plot(annual_cycle.month, annual_cycle.temp); ax[1].plot(annual_cycle.month, annual_cycle.prcp);\n", - "ax[0].set_title('Average temperature / (°C)'); ax[1].set_title('Total precipitation / (mm)');\n", - "for a in ax:\n", - " a.set_xticks(annual_cycle.month.values)\n", - " a.set_xticklabels([calendar.month_abbr[m] for m in annual_cycle.month.values])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Reference mass balance data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "OGGM uses in-situ mass balance data from the World Glacier Monitoring Service Fluctuations of Glaciers Database [(WGMS FoGD)](https://wgms.ch/data_databaseversions/). The Fluctuations of Glaciers (FoG) database contains annual mass-balance records for several hundreds of glaciers worldwide. Currently, 254 mass balance time series are available." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These data are shipped automatically with OGGM and can be accessed via the glacier directory:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Get the reference mass-balance from the WGMS FoG Database\n", - "ref_mb = gdir.get_ref_mb_data()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ref_mb[['ANNUAL_BALANCE']].plot(title='Annual mass balance: {}'.format(gdir.name), legend=False);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Predict mass balance!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we are set to calculate glacier mass balance using the temperature index model - we have the model parameters $\\mu^*$ and $\\epsilon$, the thresholds for melt and solid precipitation $T_{Melt}$ and $T_{Solid}$ and the climate dataset. The last thing we need to define are the heights at which we want to calculate the mass balance. Here, we use glacier flowlines along which the mass balance is computed:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fls = gdir.read_pickle('inversion_flowlines')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will calculate the specific mass balance in mm w.e. yr$^{-1}$ for the years where in-situ mass balance data is available:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(ref_mb.index.values)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The specific mass balance along the given flowlines is computed by" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ref_mb['OGGM (calib)'] = mb_cal.get_specific_mb(fls=fls, year=ref_mb.index.values)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this calculation we assumed an average ice density of" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('rho_ice = {} kg m^-3.'.format(cfg.PARAMS['ice_density']))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can compare the actual in-situ mass balance with the modelled mass balance:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 1)\n", - "ax.plot(ref_mb['ANNUAL_BALANCE'], label='Observed')\n", - "ax.plot(ref_mb['OGGM (calib)'], label='Modelled')\n", - "ax.set_ylabel('Specific mass balance / (mm w.e. y$^{-1}$)')\n", - "ax.legend(frameon=False);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Does not look too bad, does it? To assess model performance, it is helpful to plot the data in a scatter plot:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 1)\n", - "ax.plot(ref_mb['ANNUAL_BALANCE'], ref_mb['OGGM (calib)'], 'ok');\n", - "ax.plot(ref_mb['ANNUAL_BALANCE'], ref_mb['ANNUAL_BALANCE'], '-r');\n", - "ax.set_xlim(-3000, 2000)\n", - "ax.set_ylim(-3000, 2000)\n", - "ax.set_xlabel('Observed');\n", - "ax.set_ylabel('OGGM (calib)');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If the points were aligned along the red line, the model would perfectly predict mass balance. Generally, the model overestimates mass balance in magnitude - the scatter plot shows a steeper slope than the 1 to 1 red line. This is due to specific mass balance beeing dependent not only on the climate but also on the glacier surface area." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "OGGM computes the specific mass balance as a glacier area-weighted average using a constant glacier geometry fixed at the [Randolph Glacier Inventory](https://www.glims.org/RGI/) date, e.g. $2003$ for most glaciers in the European Alps. Glacier geometry is itself a function of climate and may change significantly over time. Hence, assuming a constant glacier geometry over a time period of different climatic conditions can result in a systematic model bias:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "bias = ref_mb['OGGM (calib)'] - ref_mb['ANNUAL_BALANCE']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 1)\n", - "ax.plot(bias);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The bias is positive at the beginning of the in-situ measurements and shows a negative trend. When keeping the glacier area constant, a positive (negative) bias means, that the calibrated temperature sensitivity $\\mu^*$ of the glacier is too low (high) during time periods of colder (warmer) climates. You can find a simple experiment about the sensitivity of the specific mass balance on climate change and glacier surface area in this [blog post](https://oggm.org/2017/10/01/specmb-ela/)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Take home points" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- There are two different types of melt models: the energy balance model and the temperature index model\n", - "- The temperature index model is the computationally efficient simplification of the energy balance model\n", - "- Temperature index models assume an empirical relationship between air temperature and melt rates\n", - "- Temperature index models can be extended to model glacier mass balance by adding solid precipitation as a model parameter\n", - "- The model outcome is significantly influenced by the choice of $T_{Melt}$ and $T_{Solid}$\n", - "- The temperature sensitivity of a glacier is not constant in time $\\mu^* = \\mu^*(t)$\n", - "- The specific mass balance is a function of the climate and the glacier surface area" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- Hock R., (2003). Temperature index melt modelling in mountain areas. *Journal of Hydrology*, 281, 104-115. https://doi.org/10.1016/S0022-1694(03)00257-9\n", - "- Marzeion B., Jarosch A. H. & Hofer M. (2012). Past and future sea-level change from the surface mass balance of glaciers. *The Cryosphere*, 6, 1295-1322. https://doi.org/10.5194/tc-6-1295-2012" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## What's next?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Back to the table of contents](../welcome.ipynb)" - ] - } - ], - "metadata": { - "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.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/welcome.ipynb b/welcome.ipynb index 4f4d6ea..e60c6bf 100644 --- a/welcome.ipynb +++ b/welcome.ipynb @@ -33,7 +33,6 @@ "- [Glacier surging experiments](oggm-edu/surging_experiment.ipynb)\n", "\n", "**Glacier surface mass balance, advance and retreat processes with simplified glacier geometry:**\n", - "- [Temperature index model](oggm-edu/temperature_index_model.ipynb)\n", "- [Accumulation and ablation processes](oggm-edu/accumulation_and_ablation.ipynb)\n", "- [Glacier advance and retreat](oggm-edu/advance_and_retreat.ipynb)\n", "- [Glaciers as low-pass filters of climate variations](oggm-edu/low_pass_climate.ipynb)\n", @@ -42,8 +41,6 @@ "- [Glaciers as water resources: part 1 (idealized climate)](oggm-edu/glacier_water_resources.ipynb)\n", "- [Glaciers as water resources: part 2 (projections)](oggm-edu/glacier_water_resources_projections.ipynb)\n", "\n", - "*Same notebooks for OGGM versions < 1.6: [Part 1](oggm-edu/pre16/glacier_water_resources_pre16.ipynb), [Part 2](oggm-edu/pre16/glacier_water_resources_projections_pre16.ipynb)*\n", - "\n", "**Educational graphics:**\n", "- [ELA changes and response time](graphics/ela_changes_response_time.ipynb)" ]