diff --git a/_toc.yml b/_toc.yml index 174698a8..712a20ee 100644 --- a/_toc.yml +++ b/_toc.yml @@ -30,6 +30,7 @@ chapters: - file: book/hydro sections: - file: notebooks/tutorials/hydrological_output + - file: notebooks/tutorials/runoff_sensitivity - url: https://oggm.org/oggm-edu-notebooks/oggm-edu/glacier_water_resources.html title: Glaciers as water resources (OGGM-Edu part 1 - idealized) - url: https://oggm.org/oggm-edu-notebooks/oggm-edu/glacier_water_resources_projections.html diff --git a/notebooks/tutorials/runoff_sensitivity.ipynb b/notebooks/tutorials/runoff_sensitivity.ipynb new file mode 100644 index 00000000..1d85d12d --- /dev/null +++ b/notebooks/tutorials/runoff_sensitivity.ipynb @@ -0,0 +1,735 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Runoff Sensitivity to Mass Balance Parameters" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "Goals of this notebook: To explore the sensitivity of the runoff and OGGM's hydrological components to the mass balance model parameters.\n", + "\n", + "Mass balance parameters directly control the glacier's mass balance (accumulation and ablation), but their impact extends beyond solid ice and snow. In OGGM, runoff is computed from multiple components: glacier melt, precipitation both on and off the glacier, and snow melt. Because runoff is a sum of these components (and some components are more sensitive to mass balance parameter changes than others), **runoff can exhibit even greater sensitivity to parameter variations than the mass balance itself**.\n", + "\n", + "Recent work ([Wimberly et al., 2025](https://tc.copernicus.org/articles/19/1491/2025/)) has shown that these sensitivities have important implications for glacier hydrology and projections. Understanding how mass balance parameters propagate through the hydrological cycle is critical for water resource assessments and future projections.\n", + "\n", + "This will allow us to understand the relationship between our parameters and model output, and to appreciate why careful parameter calibration is important for accurate hydrological predictions." + ] + }, + { + "cell_type": "markdown", + "id": "2", + "metadata": {}, + "source": [ + "It is recommended to consult the previous tutorials to gain an understanding first as to how runoff is computed in OGGM:\n", + "- [Glaciers as water resources: Part 1](https://edu-notebooks.oggm.org/oggm-edu/glacier_water_resources.html)\n", + "- [Glaciers as water resources: Part 2](https://edu-notebooks.oggm.org/oggm-edu/glacier_water_resources_projections.html)" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## Set Up\n", + "\n", + "First import the required packages to run this tutorial and initialize our glacier directories!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "import numpy as np\n", + "import xarray as xr\n", + "import matplotlib\n", + "\n", + "from oggm import cfg, utils, workflow, tasks, DEFAULT_BASE_URL\n", + "from oggm.core import massbalance\n", + "from oggm.core.massbalance import MultipleFlowlineMassBalance" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "cfg.initialize(logging_level='WARNING')\n", + "cfg.PATHS['working_dir'] = utils.gettempdir(dirname='OGGM-runoff', reset=True)\n", + "cfg.PARAMS['store_model_geometry'] = True" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "We start from a well known glacier in the Austrian Alps, Hintereisferner. But you can choose any other glacier, e.g. from [this list](https://github.com/OGGM/oggm-sample-data/blob/master/wgms/rgi_wgms_links_20220112.csv)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "# Hintereisferner\n", + "rgi_id = 'RGI60-11.00897'\n", + "\n", + "# We pick the elevation-bands glaciers because they run a bit faster -\n", + "# but they create more step changes in the area outputs\n", + "gdir_hef = workflow.init_glacier_directories([rgi_id], from_prepro_level=5, prepro_border=160,\n", + " prepro_base_url=DEFAULT_BASE_URL)[0]" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "# An Introduction to Sensitivity Analysis\n", + "\n", + "**Sensitivity Analysis** investigates how the variation in the output of a numerical model can be attributed to variations of its input factors ([Pianosi et al., (2016)](https://www.sciencedirect.com/science/article/pii/S1364815216300287)).\n", + "\n", + "In this tutorial, we perform a simple, exploratory one-at-a-time sensitivity analysis to investigate the effects of the mass balance parameters on the glaciohydrological model outputs. We will focus on the mass balance parameters: the melt factor, temperature bias and precipitation factor.\n", + "\n", + "**Important: unlike an \"operational\" OGGM run, the parameters below are varied without ensuring that observations are matched. This would lead to different results / conclusions and could be the topic of another tutorial.**" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "# Simple Sensitivity Analysis Experiment" + ] + }, + { + "cell_type": "markdown", + "id": "10", + "metadata": {}, + "source": [ + "### Temperature Bias" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "We will begin by investigating the sensitvity of the runoff to one parameter at a time, we will start with **temperature bias**. Below, we will vary only the temperature bias, and fix the melt factor and the precipitation factor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "temp_bias_df = pd.DataFrame()\n", + "file_id = '_sens'\n", + "\n", + "for temp_bias in np.arange(-5, 5.0, 0.5): # We are varying the temperature bias\n", + "\n", + " # For each temperature bias, we create a mass balance model with the same melt factor\n", + " # and precipitation factor, but with the new temperature bias\n", + " mb_model = MultipleFlowlineMassBalance(\n", + " gdir_hef,\n", + " mb_model_class=massbalance.MonthlyTIModel,\n", + " temp_bias=float(temp_bias), # Vary the temperature bias\n", + " melt_f=5.0, # Fix melt factor to 5.0 for all runs\n", + " prcp_fac=2.5, # Fix precipitation factor to 2.5 for all runs\n", + " check_calib_params=False, # We are forcing parameters which do not match observations\n", + " )\n", + "\n", + " # We are using the task run_with_hydro to store hydrological outputs along with\n", + " # the usual glaciological outputs\n", + " tasks.run_with_hydro(\n", + " gdir_hef, # Run on the selected glacier\n", + " run_task=tasks.run_from_climate_data, # Run from climate data\n", + " mb_model=mb_model, # Use the mass balance model with the new temp_bias\n", + " ys=2000, # Period which we will average and constantly repeat\n", + " init_model_yr=2000, # Start from spinup year 2000\n", + " init_model_filesuffix='_spinup_historical', # Use the previous run as initial state\n", + " store_monthly_hydro=True, # Monthly outputs provide additional information\n", + " output_filesuffix=file_id, # Identifier for the output file, to read it later\n", + " )\n", + "\n", + " # Now read the hydrological outputs for this run\n", + " with xr.open_dataset(gdir_hef.get_filepath('model_diagnostics', filesuffix=file_id)) as ds_sens:\n", + " # The last step of hydrological output is NaN (we can't compute it for this year)\n", + " ds_sens = ds_sens.isel(time=slice(0, -1)).load()\n", + "\n", + " sel_vars = [v for v in ds_sens.variables if 'month_2d' not in ds_sens[v].dims]\n", + " df_annual_sens = ds_sens[sel_vars].to_dataframe()\n", + "\n", + " # Store the runoff time series in a DataFrame, one column per temperature bias\n", + " temp_bias_df[temp_bias] = (\n", + " df_annual_sens['melt_off_glacier']\n", + " + df_annual_sens['melt_on_glacier']\n", + " + df_annual_sens['liq_prcp_off_glacier']\n", + " + df_annual_sens['liq_prcp_on_glacier']\n", + " ) * 1e-9 # Convert from kilograms to megatonnes per year (Mt/yr) for easier plotting\n", + "\n", + "temp_bias_df = temp_bias_df.sort_index(axis=1)" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "Now, for each temperature bias value, let's plot the mean runoff against the temperature bias, and plot the runoff time series to undestand how this varies across our range of temperature bias values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "# Now let's get a nice colormap centered at temp_bias=0\n", + "norm = matplotlib.colors.Normalize(vmin=-5, vmax=5.01)\n", + "colors_temp_bias = plt.get_cmap('coolwarm')\n", + "\n", + "fig, axs = plt.subplots(1, 2, figsize=(14, 6))\n", + "for temp_bias in temp_bias_df.columns:\n", + " axs[0].plot(\n", + " temp_bias,\n", + " temp_bias_df[temp_bias].mean(),\n", + " 'o',\n", + " color=colors_temp_bias(norm(temp_bias)),\n", + " )\n", + "axs[0].set_ylabel('Mean Runoff (Mt/yr)')\n", + "axs[0].set_xlabel('temp_bias (°C)')\n", + "axs[0].set_title('Mean Runoff vs Temperature Bias')\n", + "\n", + "for temp_bias in temp_bias_df.columns:\n", + " axs[1].plot(\n", + " temp_bias_df.index,\n", + " temp_bias_df[temp_bias].values,\n", + " '-',\n", + " color=colors_temp_bias(norm(temp_bias)),\n", + " label=temp_bias,\n", + " )\n", + "axs[1].set_ylabel('Runoff (Mt/yr)')\n", + "axs[1].set_xlabel('Year')\n", + "axs[1].legend(title='temp_bias:', bbox_to_anchor=(1, 1))\n", + "axs[1].set_xticks(temp_bias_df.index[::2])\n", + "axs[1].set_title('Runoff Time Series for Different Temperature Biases')\n", + "\n", + "fig.suptitle(f'Runoff and Temperature Bias - {gdir_hef.rgi_id}')\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "We can see that the runoff appears to be sensitive to the temperature bias! In the graph on the left, we can see the mean runoff increases as we increase the temperature bias (likely because of an increase of glacier melt), and on the graph on the right, we can see how this affects the runoff annually. The high temperature bias leads to a strong reduction of runoff towards the end of the period, likely because the glacier is melting very fast and cannot sustain high runoff for long." + ] + }, + { + "cell_type": "markdown", + "id": "16", + "metadata": {}, + "source": [ + "### Precipitation Factor" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "Now lets explore what happens when we vary the **precipitation factor**, and fix our other two mass balance parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "prcp_fac_df = pd.DataFrame()\n", + "\n", + "for prcp_fac in np.arange(0.1, 10, 0.5): # Now we are varying the precipitation factor\n", + "\n", + " # For each precipitation factor, we create a mass balance model with the same melt\n", + " # factor and temperature bias, but with the new precipitation factor\n", + " mb_model = MultipleFlowlineMassBalance(\n", + " gdir_hef,\n", + " mb_model_class=massbalance.MonthlyTIModel,\n", + " prcp_fac=float(prcp_fac),\n", + " melt_f=5.0, # Fix melt factor\n", + " temp_bias=0, # Fix the temperature bias to 0\n", + " check_calib_params=False, # We are forcing parameters which do not match observations\n", + " )\n", + "\n", + " # We are using the task run_with_hydro to store hydrological outputs along with\n", + " # the usual glaciological outputs\n", + " tasks.run_with_hydro(\n", + " gdir_hef, # Run on the selected glacier\n", + " run_task=tasks.run_from_climate_data,\n", + " mb_model=mb_model, # Use the mass balance model with the new prcp_fac\n", + " ys=2000, # Period which we will average and constantly repeat\n", + " init_model_yr=2000, # Start from spinup year 2000\n", + " init_model_filesuffix='_spinup_historical', # Use the previous run as initial state\n", + " store_monthly_hydro=True, # Monthly outputs provide additional information\n", + " output_filesuffix=file_id, # Identifier for the output file, to read it later\n", + " )\n", + "\n", + " # Reading the glaciological and hydrological outputs for this run again\n", + " with xr.open_dataset(gdir_hef.get_filepath('model_diagnostics', filesuffix=file_id)) as ds_sens:\n", + " # The last step of hydrological output is NaN (we can't compute it for this year)\n", + " ds_sens = ds_sens.isel(time=slice(0, -1)).load()\n", + "\n", + " sel_vars = [v for v in ds_sens.variables if 'month_2d' not in ds_sens[v].dims]\n", + " df_annual_sens = ds_sens[sel_vars].to_dataframe()\n", + "\n", + " # Store the runoff time series in a DataFrame, one column per precipitation factor\n", + " prcp_fac_df[prcp_fac] = (\n", + " df_annual_sens['melt_off_glacier']\n", + " + df_annual_sens['melt_on_glacier']\n", + " + df_annual_sens['liq_prcp_off_glacier']\n", + " + df_annual_sens['liq_prcp_on_glacier']\n", + " ) * 1e-9\n", + "\n", + "prcp_fac_df = prcp_fac_df.sort_index(axis=1)" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "Now let's plot to see our results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "# Now we can centre the colormap around prcp_fac\n", + "norm = matplotlib.colors.Normalize(vmin=0.1, vmax=10)\n", + "colors_prcp_fac = plt.get_cmap('coolwarm')\n", + "\n", + "fig, axs = plt.subplots(1, 2, figsize=(14, 6))\n", + "for prcp_fac in prcp_fac_df.columns:\n", + " axs[0].plot(\n", + " prcp_fac,\n", + " prcp_fac_df[prcp_fac].mean(),\n", + " 'o',\n", + " color=colors_prcp_fac(norm(prcp_fac)),\n", + " )\n", + "axs[0].set_ylabel('Mean Runoff (Mt/yr)')\n", + "axs[0].set_xlabel('Precipitation factor')\n", + "axs[0].set_title('Mean Runoff vs Precipitation Factor')\n", + "\n", + "for prcp_fac in prcp_fac_df.columns:\n", + " axs[1].plot(\n", + " prcp_fac_df.index,\n", + " prcp_fac_df[prcp_fac].values,\n", + " '-',\n", + " color=colors_prcp_fac(norm(prcp_fac)),\n", + " label=prcp_fac,\n", + " )\n", + "axs[1].set_ylabel('Runoff (Mt/yr)')\n", + "axs[1].set_xlabel('Year')\n", + "axs[1].legend(title='Precipitation factor:', bbox_to_anchor=(1, 1))\n", + "axs[1].set_xticks(prcp_fac_df.index[::2])\n", + "axs[1].set_title('Runoff Time Series for Different Precipitation Factors')\n", + "\n", + "fig.suptitle(f'Runoff and Precipitation Factor - {gdir_hef.rgi_id}')\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "We can see that again, the runoff appears sensitive to the precipitation factor! And that as the precipitation factor increases, as does the runoff. Note that the increase is not linear." + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "### Melt Factor" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "Finally, let's see what happens when we alter the melt factor and fix the remaining 2 mass balance parameters!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "melt_f_df = pd.DataFrame()\n", + "\n", + "for melt_f in np.arange(1.5, 17, 1.0): # Now we are varying the melt factor\n", + "\n", + " # For each melt factor, we create a mass balance model with the same precipitation\n", + " # factor and temperature bias, but now with the new melt factor\n", + " mb_model = MultipleFlowlineMassBalance(\n", + " gdir_hef,\n", + " mb_model_class=massbalance.MonthlyTIModel,\n", + " melt_f=float(melt_f),\n", + " prcp_fac=2.5,\n", + " temp_bias=0,\n", + " check_calib_params=False, # We are forcing parameters which do not match observations\n", + " )\n", + "\n", + " # Run this with our 2 fixed mass balance parameters and our varying melt factor\n", + " tasks.run_with_hydro(\n", + " gdir_hef, # Run on the selected glacier\n", + " run_task=tasks.run_from_climate_data, # Running from observed climate data\n", + " ys=2000, # Period which we will average and constantly repeat\n", + " init_model_yr=2000, # Start from spinup year 2000\n", + " init_model_filesuffix='_spinup_historical', # Use the previous run as initial state\n", + " mb_model=mb_model, # Use the mass balance model with the new melt_f\n", + " store_monthly_hydro=True, # Monthly outputs provide additional information\n", + " output_filesuffix=file_id, # Identifier for the output file, to read it later\n", + " )\n", + "\n", + " # Reading the glaciological and hydrological outputs\n", + " with xr.open_dataset(gdir_hef.get_filepath('model_diagnostics', filesuffix=file_id)) as ds_sens:\n", + " # The last step of hydrological output is NaN (we can't compute it for this year)\n", + " ds_sens = ds_sens.isel(time=slice(0, -1)).load()\n", + "\n", + " sel_vars = [v for v in ds_sens.variables if 'month_2d' not in ds_sens[v].dims]\n", + " df_annual_sens = ds_sens[sel_vars].to_dataframe()\n", + "\n", + " # Store the runoff time series in a DataFrame, one column per melt factor\n", + " melt_f_df[melt_f] = (\n", + " df_annual_sens['melt_off_glacier']\n", + " + df_annual_sens['melt_on_glacier']\n", + " + df_annual_sens['liq_prcp_off_glacier']\n", + " + df_annual_sens['liq_prcp_on_glacier']\n", + " ) * 1e-9\n", + "\n", + "melt_f_df = melt_f_df.sort_index(axis=1)" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "Now we plot:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "# let's get a nice colormap centered around melt_f\n", + "norm = matplotlib.colors.Normalize(vmin=1.5, vmax=17)\n", + "colors_melt_f = plt.get_cmap('coolwarm')\n", + "\n", + "fig, axs = plt.subplots(1, 2, figsize=(14, 6))\n", + "for melt_f in melt_f_df.columns:\n", + " axs[0].plot(\n", + " melt_f,\n", + " melt_f_df[melt_f].mean(),\n", + " 'o',\n", + " color=colors_melt_f(norm(melt_f)),\n", + " )\n", + "axs[0].set_ylabel('Runoff (Mt/yr)')\n", + "axs[0].set_xlabel('Melt factor')\n", + "axs[0].set_title('Mean Runoff vs Melt Factor')\n", + "\n", + "for melt_f in melt_f_df.columns:\n", + " axs[1].plot(\n", + " melt_f_df.index,\n", + " melt_f_df[melt_f].values,\n", + " '-',\n", + " color=colors_melt_f(norm(melt_f)),\n", + " label=melt_f,\n", + " )\n", + "axs[1].set_ylabel('Runoff (Mt/yr)')\n", + "axs[1].set_xlabel('Year')\n", + "axs[1].legend(title='Melt factor:', bbox_to_anchor=(1, 1))\n", + "axs[1].set_xticks(melt_f_df.index[::2])\n", + "axs[1].set_title('Runoff Time Series for Different Melt Factors')\n", + "\n", + "fig.suptitle(f'Runoff and Melt Factor - {gdir_hef.rgi_id}')\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "Again, we can see that the runoff is sensitive to the melt factor too! And that, as the melt factor increases, so does the runoff. Like with the temperature bias, high melt factors lead to a quick depletion of the glacier." + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "We have explored the relationship between the mass balance parameters and the runoff, but what about other glaciohydrological outputs?" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "# Exploring other Glaciohydrological outputs in OGGM" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "**First let's start with a definition!** In this next section we will be investigating the melt contribution to runoff, defined as: \n", + "\n", + "$ \\frac{\\text{melt on glacier}}{\\text{runoff}}$. \n", + "\n", + "This represents how much of the total runoff can be attributed to the glacial melt (i.e. the melt water produced from the currently glaciated area)." + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "Below we will investigate both the melt contribution, and `melt_on_glacier` sensitivity to the precipitation factor. \n", + "\n", + "We run a final set of simulations to obtain the glaciohydrological outputs from OGGM for our sensitivity study." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "# Varying prcp_fac between a range of values with a step of 0.5\n", + "pd_prcp_sens = pd.DataFrame(index=np.arange(0.1, 10.0, 0.5))\n", + "file_id = '_sens'\n", + "\n", + "for pf in pd_prcp_sens.index:\n", + "\n", + " mb_model = MultipleFlowlineMassBalance(\n", + " gdir_hef,\n", + " mb_model_class=massbalance.MonthlyTIModel,\n", + " prcp_fac=float(pf),\n", + " melt_f=5.0,\n", + " temp_bias=0,\n", + " check_calib_params=False,\n", + " )\n", + "\n", + " # We are using the task run_with_hydro to store hydrological outputs along with the usual glaciological outputs\n", + " # Run this again with the calibrated parameters\n", + " tasks.run_with_hydro(gdir_hef, # Run on the selected glacier\n", + " run_task=tasks.run_from_climate_data, # running from observed climate data\n", + " ys=2000, # Period which we will average and constantly repeat\n", + " init_model_yr=2000, # Start from spinup year 2000\n", + " init_model_filesuffix='_spinup_historical', # use the previous run as initial state\n", + " mb_model=mb_model, # use the mass balance model with the new melt_f\n", + " store_monthly_hydro=True, # Monthly outputs provide additional information\n", + " output_filesuffix=file_id); # an identifier for the output file, to read it later\n", + "\n", + " with xr.open_dataset(gdir_hef.get_filepath('model_diagnostics', filesuffix=file_id)) as ds_sens:\n", + " # The last step of hydrological output is NaN (we can't compute it for this year)\n", + " ds_sens = ds_sens.isel(time=slice(0, -1)).load()\n", + "\n", + " # Plot the runoff again for the calibrated melt_f parameter\n", + " sel_vars = [v for v in ds_sens.variables if 'month_2d' not in ds_sens[v].dims]\n", + " df_annual_sens = ds_sens[sel_vars].to_dataframe()\n", + "\n", + " pd_prcp_sens.loc[pf, 'melt_off_glacier'] = df_annual_sens['melt_off_glacier'].mean() * 1e-9\n", + " pd_prcp_sens.loc[pf, 'melt_on_glacier'] = df_annual_sens['melt_on_glacier'].mean() * 1e-9\n", + " pd_prcp_sens.loc[pf, 'liq_prcp_off_glacier'] = df_annual_sens['liq_prcp_off_glacier'].mean() * 1e-9\n", + " pd_prcp_sens.loc[pf, 'liq_prcp_on_glacier'] = df_annual_sens['liq_prcp_on_glacier'].mean() * 1e-9\n", + " pd_prcp_sens.loc[pf, 'runoff'] = pd_prcp_sens.loc[pf, 'melt_off_glacier'] + pd_prcp_sens.loc[pf, 'melt_on_glacier'] + pd_prcp_sens.loc[pf, 'liq_prcp_off_glacier'] + pd_prcp_sens.loc[pf, 'liq_prcp_on_glacier']" + ] + }, + { + "cell_type": "markdown", + "id": "33", + "metadata": {}, + "source": [ + "Now plotting to investigate the sensitivity of the `melt_on_glacier` and the melt contribution to the precipitation factor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], + "source": [ + "fig, axs = plt.subplots(1,2,figsize=(14,6))\n", + "\n", + "for pf in pd_prcp_sens.index:\n", + " axs[0].scatter(\n", + " pf,\n", + " pd_prcp_sens.loc[pf, 'melt_on_glacier'], # melt on glacier\n", + " color='blue'\n", + " )\n", + "\n", + " axs[1].scatter(\n", + " pf,\n", + " pd_prcp_sens.loc[pf, 'melt_on_glacier']/pd_prcp_sens.loc[pf, 'runoff'], # melt contribution\n", + " color='blue'\n", + " )\n", + "\n", + "\n", + "axs[0].set_xlabel('Precipitation factor')\n", + "axs[0].set_ylabel('Mean annual melt on glacier (Mt/yr)')\n", + "axs[0].set_title('Sensitivity of Melt on Glacier to Precipitation Factor')\n", + "\n", + "axs[1].set_xlabel('Precipitation factor')\n", + "axs[1].set_ylabel('Mean Glacial Melt Contribution')\n", + "axs[1].set_title('Sensitivity of Glacial Melt Contribution to the Precipitation Factor')\n", + "\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "35", + "metadata": {}, + "source": [ + "The above graph shows that these glaciohydrological outputs are both sensitive to the precipitation factor changing!\n", + "\n", + "We can see that as the precipitation factor increases, the mean annual melt on glacier increases. \n", + "\n", + "However, when we divide this value by the total runoff to derive the melt contribution, this results in a decrease in the melt contribution as the precipitation factor increases.\n", + "\n", + "Let's think about why there might be an increase in the melt on glacier with an increasing precipitation factor, but a decreasing glacial melt contribution? We can start by investigating how much the total runoff increases, compared to the melt on glacier, and this might help us answer the question!\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "fig, axs = plt.subplots()\n", + "\n", + "for i, pf in enumerate(pd_prcp_sens.index):\n", + " axs.scatter(\n", + " pf,\n", + " pd_prcp_sens.loc[pf, 'melt_on_glacier'],\n", + " color='blue',\n", + " label='Melt on glacier' if i == 0 else None\n", + " )\n", + " axs.scatter(\n", + " pf,\n", + " pd_prcp_sens.loc[pf, 'runoff'],\n", + " color='red',\n", + " label='Runoff' if i == 0 else None\n", + " )\n", + "\n", + "axs.set_xlabel('Precipitation factor')\n", + "axs.set_ylabel('Glaciohydrological components')\n", + "axs.set_title('Sensitivity of Melt on Glacier to Precipitation Factor')\n", + "\n", + "plt.legend()\n", + "\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "We can see that the total runoff is more sensitive to the changes in the precipitation factor and is increasing at a much more rapid rate when we increase the precipitation factor. Therefore this causes a decrease in the melt contribution makes sense.\n", + "\n", + "This shows us that different outputs can have different sensitivities to the same changing input parameters, and can lead to some interesting results!" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "# Tutorial take-aways\n", + "- Sensitivity analysis is a useful tool to understand the relationship between our model inputs and outputs\n", + "- One-at-a-time sensitivity analysis is an exploratory tool to help us better investigate model behaviour.\n", + "- Different outputs can exhibit different sensitivities for the same range of input values.\n", + "- The model sensitivity depends greatly on the chosen inputs (what they are, and their ranges) and the outputs we are considering.\n", + "\n", + "\n", + "This is a very simple application of sensitivity analysis applied to OGGM, but these insights can motivate further use of sensitivity analysis on a larger-scale. There are future plans to integrate the [Sensitivity Analysis For Everyone Toolbox (SAFE)](https://safetoolbox.github.io/) with OGGM for more complex sensitivity analysis applications!\n" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "# References" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "- Pianosi, F., Beven, K., Freer, J., Hall, J. W., Rougier, J., Stephenson, D. B., and Wagener, T.: Sensitivity analysis of environmental models: a systematic review with practical workflow, Environ. Model. Softw., 79, 214–232, https://doi.org/10.1016/j.envsoft.2016.02.008, 2016\n", + "- Wimberly, F., Ultee, L., Schuster, L., Huss, M., Rounce, D. R., Maussion, F., Coats, S., Mackay, J., and Holmgren, E.: Inter-model differences in 21st century glacier runoff for the world's major river basins, The Cryosphere, 19, 1491–1511, https://doi.org/10.5194/tc-19-1491-2025, 2025. " + ] + } + ], + "metadata": { + "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.14.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/welcome.ipynb b/notebooks/welcome.ipynb index 4a2399de..61a349a1 100644 --- a/notebooks/welcome.ipynb +++ b/notebooks/welcome.ipynb @@ -76,7 +76,12 @@ "source": [ "## Hydrological output\n", "\n", - "- [Hydrological mass-balance output](tutorials/hydrological_output.ipynb)" + "- [Hydrological mass-balance output](tutorials/hydrological_output.ipynb)\n", + "- [Sensitivity of glacier runoff to the mass balance parameters](tutorials/runoff_sensitivity.ipynb)\n", + "\n", + "You might find the following notebooks in OGGM-Edu interesting as well!\n", + "- [Glaciers as water resources: part 1 (idealized climate)](https://oggm.org/oggm-edu-notebooks/oggm-edu/glacier_water_resources.html)\n", + "- [Glaciers as water resources: part 2 (projections)](https://oggm.org/oggm-edu-notebooks/oggm-edu/glacier_water_resources_projections.html)" ] }, {