{ "cells": [ { "cell_type": "markdown", "id": "4ebf2aa0-a068-47ce-bbc4-03704b3c5d8d", "metadata": {}, "source": [ "# Session 2 — Climate Model: Carbon Cycle and Temperature Dynamics\n", "\n", "This notebook implements a **reduced-form climate model** inspired by DICE-type frameworks. \n", "It tracks the dynamics of atmospheric carbon, ocean carbon, radiative forcing, and temperature. \n", "The time step is `Δ` years (annual or 5-year), starting at year `t0`.\n", "\n", "---\n", "\n", "## Inputs, states and derived quantities\n", "- **E(t)** : Exogenous CO₂ emissions (GtCO₂ per year; input) \n", "- **M_AT(t)** : Carbon mass in the atmosphere (GtC) \n", "- **M_UP(t)** : Carbon mass in the upper ocean (GtC) \n", "- **M_LO(t)** : Carbon mass in the lower ocean (GtC) \n", "- **F(t)** : Radiative forcing (W/m²) \n", "- **T_AT(t)** : Atmospheric temperature anomaly (°C) \n", "- **T_LO(t)** : Lower-ocean temperature anomaly (°C) \n", "\n", "---\n", "\n", "## Carbon Cycle Equations\n", "\n", "$$\n", "M_{AT}(t) = (1 - \\Delta b_{12}) M_{AT}(t-1) \n", "+ \\Delta b_{12} \\frac{M_{AT0}}{M_{UP0}} M_{UP}(t-1) \n", "+ \\Delta \\xi E(t-1)\n", "$$\n", "\n", "$$\n", "M_{UP}(t) = \\Delta b_{12} M_{AT}(t-1) \n", "+ \\left(1 - \\Delta b_{12} \\tfrac{M_{AT0}}{M_{UP0}} - \\Delta b_{23}\\right) M_{UP}(t-1) \n", "+ \\Delta b_{23} \\frac{M_{UP0}}{M_{LO0}} M_{LO}(t-1)\n", "$$\n", "\n", "$$\n", "M_{LO}(t) = \\Delta b_{23} M_{UP}(t-1) \n", "+ \\left(1 - \\Delta b_{23} \\tfrac{M_{UP0}}{M_{LO0}}\\right) M_{LO}(t-1)\n", "$$\n", "\n", "---\n", "\n", "## Radiative Forcing\n", "\n", "$$\n", "F(t) = F_{2 \\times CO_2} \\cdot \n", "\\frac{\\ln\\!\\left(\\tfrac{M_{AT}(t-1)}{M_{AT}^{\\text{pre}}}\\right)}{\\ln(2)}\n", "$$\n", "\n", "where \n", "- $F_{2 \\times CO_2}$ is the forcing from doubling CO₂ (≈ 3.36 W/m²), \n", "- $M_{AT}^{\\text{pre}}$ is preindustrial atmospheric carbon mass. \n", "\n", "---\n", "\n", "## Temperature Dynamics\n", "\n", "$$\n", "T_{AT}(t) = T_{AT}(t-1) \n", "+ \\Delta c_1 \\, F(t) \n", "- \\Delta c_1 \\, \\frac{F_{2 \\times CO_2}}{T_{2 \\times CO_2}} \\, T_{AT}(t-1) \n", "- \\Delta c_1 c_3 \\, \\big( T_{AT}(t-1) - T_{LO}(t-1) \\big), $$\n", "\n", "$$\n", "T_{LO}(t) = T_{LO}(t-1) \n", "+ \\Delta c_4 \\, \\big( T_{AT}(t-1) - T_{LO}(t-1) \\big).\n", "$$\n", "\n", "---\n", "\n", "## Key Parameters\n", "- $\\Delta$: time step (years) \n", "- $F_{2 \\times CO_2}$: forcing for CO₂ doubling \n", "- $T_{2 \\times CO_2}$: equilibrium climate sensitivity (°C per doubling) \n", "- $b_{12}, b_{23}$: carbon transfer rates (atmosphere ↔ ocean) \n", "- $c_1, c_3, c_4$: heat transfer coefficients \n", "- $\\xi$: conversion factor CO2 to C.\n", "\n", "---\n", "\n", "## Purpose\n", "This notebook will:\n", "1. Initialize the state variables at year `t0`. \n", "2. Simulate carbon and temperature dynamics over time. \n", "3. Explore the effect of emissions scenarios on climate trajectories. \n", "\n", "---\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Session 2 — Objectives and prerequisites\n", "\n", "**Learning objectives**\n", "- Connect historical greenhouse-gas data to the original `Climate_Models.py` carbon-cycle and temperature blocks.\n", "- Check the model against observed temperature anomalies.\n", "- Use SSP emissions to compare forward-looking temperature paths.\n", "\n", "**Prerequisites.** Slides for Session 2; simple column selection with Pandas; basic Matplotlib; units GtCO₂ versus GtC.\n" ], "id": "session2-cell-1" }, { "cell_type": "markdown", "metadata": { "x3a_technical_cell": true }, "source": [ "## Technical setup\n", "\n", "Run the notebook from this session directory. The original course module and data files are stored next to it.\n", "\n", "**Convention.** The initial climate state is given. Emissions are prescribed annual inputs (not optimized controls): inputs in row t act on the transition from t to t+1.\n", "The mathematical reference is **climate_model.pdf**. Use calendar-year alignment: annual emissions in row t act over the interval starting at t. The final date requested in update_path is included.\n" ], "id": "x3a-setup-1-markdown" }, { "cell_type": "code", "execution_count": null, "metadata": { "x3a_technical_cell": true }, "outputs": [], "source": [ "# Install dependencies in the current notebook kernel before running imports.\n", "%pip install -r requirements.txt\n" ], "id": "x3a-setup-1-code" }, { "cell_type": "code", "execution_count": null, "id": "8079de98-5548-49b3-a8af-650d3b32d8c4", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "import Climate_Models as CM" ] }, { "cell_type": "markdown", "id": "c399c930-b58c-4a02-bab9-4630ddf7ebc1", "metadata": {}, "source": [ "# A — Historical Climate Dynamics\n", "\n", "In this section, we will work with **historical CO₂ emissions data** collected by *Our World in Data*. \n", "The dataset provides **annual CO₂ emissions by country** (measured in tonnes of CO₂) starting from the Industrial Revolution.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4cc6106d-14e2-4b77-a3a9-2c5a3248cd41", "metadata": {}, "outputs": [], "source": [ "# Read historical annual CO2 emissions.\n", "df = pd.read_csv(\"https://ourworldindata.org/grapher/annual-co2-emissions-per-country.csv?v=1&csvType=full&useColumnShortNames=true\", storage_options={'User-Agent': 'Our World In Data data fetch/1.0'})\n", "df = df.rename(columns={'entity': 'Entity', 'code': 'Code', 'year': 'Year'})" ] }, { "cell_type": "code", "execution_count": null, "id": "7d1c2c9b-e9e6-49c1-b019-9c036bab940a", "metadata": {}, "outputs": [], "source": [ "# Select World observations and order them by year.\n", "df_world = df[df[\"Code\"] == \"OWID_WRL\"].sort_values(\"Year\")\n", "\n", "# Convert tonnes of CO2 to GtCO2 per year.\n", "world_co2 = df_world[\"emissions_total\"] / 10**9\n", "year_co2 = df_world[\"Year\"]\n", "emissions_by_year = df_world.set_index(\"Year\")[\"emissions_total\"] / 10**9\n", "df_world.head()" ] }, { "cell_type": "markdown", "id": "c13c964b-c4ad-45dc-a0ea-cf61b75a4be0", "metadata": {}, "source": [ "### A-1) plot the paths of carbon emissions\n" ] }, { "cell_type": "code", "execution_count": null, "id": "03941f72-9a52-41a6-9abd-f26c7b1751da", "metadata": {}, "outputs": [], "source": [ "# Your code here\n", "pass" ] }, { "cell_type": "markdown", "id": "fccee437-a52c-4b27-95d1-efdd62c43a63", "metadata": {}, "source": [ "### A-2) We would like to simulate climate dynamics\n", "\n", "#### Initializing the Climate System\n", "\n", "Before we can simulate climate dynamics, we must **set the model at its pre-industrial equilibrium**. \n", "This means starting the system in the state it was around the year 1750, before large-scale industrial CO₂ emissions began.\n", "\n", "- **Carbon stocks** are initialized at their **pre-industrial levels**: \n", " - $M_{AT0} = M_{AT}^{\\text{pre}}$ (atmosphere) \n", " - $M_{UP0} = M_{UP}^{\\text{pre}}$ (upper ocean) \n", " - $M_{LO0} = M_{LO}^{\\text{pre}}$ (lower ocean) \n", "\n", "- **Temperature anomalies** are set to **zero** at the start: \n", " - $T_{AT0} = 0$ (no atmospheric anomaly) \n", " - $T_{LO0} = 0$ (no ocean anomaly) \n", "\n", "- The **time horizon** spans from 1749 to 2024 with annual steps.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "ac0c9109-92ca-4ab7-b984-1dce0d11a285", "metadata": {}, "outputs": [], "source": [ "# Historical experiment: start at preindustrial equilibrium.\n", "p = CM.Params()\n", "p.Delta = 1\n", "p.t0 = 1749\n", "p.tT = 2024\n", "p.nT = (p.tT - p.t0) // p.Delta + 1\n", "\n", "p.M_AT0 = p.mat\n", "p.M_UP0 = p.mup\n", "p.M_LO0 = p.mlo\n", "p.T_AT0 = 0\n", "p.T_LO0 = 0\n", "p.M_CH40 = p.mch4\n", "\n", "# No emissions in this first experiment.\n", "path = CM.init_states(p)\n", "path = CM.update_path(path, p, 1750, 2024)\n", "CM.mat_to_df(path, p)" ] }, { "cell_type": "markdown", "id": "62de7fb2-fe34-45e2-83ce-6b9931fd951e", "metadata": {}, "source": [ "**Exercise:**\n", "1. What is the path of atmospheric carbon `M_AT` and temperature `T_AT`? Use a plot.\n", "2. Quickly comment your outcome.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "f023f24b-1e78-44f1-85cb-706ed1e294e5", "metadata": {}, "outputs": [], "source": [ "# Your code\n", "pass" ] }, { "cell_type": "markdown", "id": "b5320c22-d250-4e44-b4a7-27263a8c3371", "metadata": {}, "source": [ "Write your answer here.\n" ] }, { "cell_type": "markdown", "id": "42391c2b-36e6-43a5-b1f1-ace399ba643a", "metadata": {}, "source": [ "### A-3) Feed the climate system with carbon emissions\n", "\n", "**Goal**\n", "\n", "We’ll (i) load historical CO₂ emissions, (ii) express them in GtCO₂/year (the module converts CO₂ to carbon internally), (iii) inject them into the simulation matrix `path[:, i_E]`, (iv) update the climate path, and (v) plot and comment.\n", "\n", "---\n", "\n", "**Exercise**\n", "\n", "1. Replace the emissions in `path[:, p.i_E]` by `world_co2` aligned by year (already done above) and run:\n", " ```python\n", " CM.update_path(path_pollution, p, 1750, 2024)\n", " ```\n", "2. Plot the result in term of carbon accumulation and global atmospheric temperature.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "bad3a12d-683e-4af5-b864-80204f1f27bf", "metadata": {}, "outputs": [], "source": [ "# Start a fresh path, then fill its emissions column by calendar year.\n", "path_pollution = CM.init_states(p)\n", "# Use emissions_by_year.reindex(path_pollution[:, p.i_time], fill_value=0).\n", "# Simulate 1750–2024 and plot the results." ] }, { "cell_type": "markdown", "id": "2eba7476-ddea-45dc-a918-fc19e50e2db2", "metadata": {}, "source": [ "### A-4) Plot and comment: global temperature anomalies\n", "\n", "**Goal**\n", "\n", "Compare observed global near-surface temperature anomalies with the model-simulated atmospheric temperature anomaly obtained from historical emissions. Discuss similarities and differences in terms of level, trend, and variability.\n", "\n", "---\n", "\n", "**Instructions**\n", "\n", "1. **Line plot (comparison).** \n", " Plot `world_temp` against `world_temp_yr` (observed series) together with `path_pollution[:, p.i_T_AT]` (simulated series under historical emissions). \n", " Make sure both are aligned by calendar year. \n", " Label axes, add a legend, title, and grid.\n", "\n", "2. **Basic diagnostics.** \n", " - Comment how well the simulated path reproduces the observed warming trend. \n", " - Discuss differences in the early period vs. recent decades. \n", " - Note that the observed series includes short-term variability (ENSO, volcanoes) not captured by the simple model. \n", " - Reflect on whether the model underestimates or overestimates the magnitude of warming.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "0a79353f-ecb2-49dc-953d-7d1e26641dd9", "metadata": {}, "outputs": [], "source": [ "# Read observed temperature anomalies.\n", "temperatures_data = pd.read_csv(\"https://ourworldindata.org/grapher/temperature-anomaly.csv?v=1&csvType=full&useColumnShortNames=true\", storage_options={'User-Agent': 'Our World In Data data fetch/1.0'})\n", "temperatures_data = temperatures_data.rename(columns={'entity': 'Entity', 'code': 'Code', 'year': 'Year'})\n", "temperatures_world = temperatures_data[temperatures_data[\"Code\"] == \"OWID_WRL\"]\n", "world_temp = temperatures_world[\"near_surface_temperature_anomaly\"]\n", "world_temp_yr = temperatures_world[\"Year\"]\n", "\n", "# Plot observed and simulated temperatures here." ] }, { "cell_type": "markdown", "id": "a964e5c7-8a89-43eb-b5bf-e31eaf8e5c3f", "metadata": {}, "source": [ "Write your answer here.\n" ] }, { "cell_type": "markdown", "id": "d3a92adf-2412-403a-9cb1-6d861af63e17", "metadata": {}, "source": [ "### A-5) Methane cycles\n", "\n", "**Goal**\n", "\n", "Extend the climate model to account for methane (CH₄), which has a shorter lifetime than CO₂ but a stronger warming potential per molecule. Methane dynamics can be represented by a simple decay equation and its radiative forcing contribution.\n", "\n", "---\n", "\n", "**Methane cycle (one-box model)**\n", "\n", "Methane accumulates through emissions and decays with a characteristic lifetime of 9 years in this course calibration:\n", "\n", "$$\n", "M_{CH4}(t) = m_{CH4} + (1 - \\delta \\Delta)(M_{CH4}(t-1)-m_{CH4}) + \\Delta E_{CH4}(t-1)\n", "$$\n", "\n", "- $M_{CH4}(t)$: atmospheric methane concentration (ppb). \n", "- $E_{CH4}(t)$: methane emissions (in ppb equivalents). \n", "- $\\delta = 1/9$: annual decay rate. \n", "- $\\Delta$: time step (1 year).\n", "- $m_{CH4}$: preindustrial level of methan. \n", "\n", "---\n", "\n", "**Radiative forcing from methane**\n", "\n", "Methane’s forcing is approximately proportional to the square root of its concentration:\n", "\n", "$$\n", "F_{CH4}(t) = \\alpha_{CH4}\\,\\Big(\\sqrt{M_{CH4}(t)} - \\sqrt{M_{CH4,0}}\\Big)\n", "$$\n", "\n", "- $M_{CH4,0} \\approx 722$ ppb (pre-industrial baseline). \n", "- $\\alpha_{CH4} \\approx 0.036 \\,\\text{W/m}^2 / \\sqrt{\\text{ppb}}$. \n", "\n", "---\n", "\n", "**Total forcing**\n", "\n", "The climate system now responds to combined forcing:\n", "\n", "$$\n", "F(t) = F_{CO2}(t) + F_{CH4}(t) + F_{\\text{ex}}(t)\n", "$$\n", "\n", "where $F_{\\text{ex}}$ (set to zero) can capture other greenhouse gases or exogenous forcing.\n", "\n", "---\n", "\n", "---\n", "\n", "**Questions**\n", "\n", "- Compare Global temperature, atmospheric methan and carbon into a subplot `1*3`.\n", "- Comment your results.\n", "\n", "---\n" ] }, { "cell_type": "code", "execution_count": null, "id": "0065d479-feea-4287-9954-4029947f1a0d", "metadata": {}, "outputs": [], "source": [ "# Download Methan data.\n", "# Feed the model with methan data.\n", "# Call CM.update_path, which already includes the methane stock and its forcing.\n", "\n", "# Read global methane emissions.\n", "df_ch4 = pd.read_csv(\"https://ourworldindata.org/grapher/ghg-emissions-by-gas.csv?v=1&csvType=full&useColumnShortNames=true\", storage_options={'User-Agent': 'Our World In Data data fetch/1.0'})\n", "df_ch4 = df_ch4.rename(columns={'entity': 'Entity', 'code': 'Code', 'year': 'Year'})\n", "df_ch4_world = df_ch4[df_ch4[\"Entity\"] == \"World\"].sort_values(\"Year\")\n", "\n", "# Course conversion: tonnes CO2-equivalent -> Mt CH4 -> ppb per year.\n", "methane_by_year = df_ch4_world.set_index(\"Year\")[\"annual_emissions_ch4_total_co2eq\"] / (10**6 * 29.8 * 2.78)\n", "path_ch4_co2 = path_pollution.copy()\n", "\n", "# Fill the methane input by year, simulate and plot the three panels." ] }, { "cell_type": "markdown", "id": "167e477f-e26c-447a-a7de-bfdb6b1ea44b", "metadata": {}, "source": [ "Write your answer here.\n" ] }, { "cell_type": "markdown", "id": "26184603-d322-4a84-a915-5e8fb71a0047", "metadata": {}, "source": [ "# B — Forecasting temperatures based on SSP scenarios\n", "\n", "Use the five **Shared Socioeconomic Pathways (SSPs)** as alternative CO₂ emissions inputs.\n", "\n", "Start all five simulations in **2015**, with the same DICE initial carbon stocks and temperatures supplied by `CM.Params()`. Do not reuse the final states of the historical experiment in Part A.\n", "\n", "The local CSV already contains **annual** global emissions in **GtCO₂/year**, from **2020 to 2100**. Use the observed global emissions for **2015–2019**, identically in every scenario, then the SSP column from 2020 onwards. No extrapolation or new interpolation is needed.\n", "\n", "- SSP1: Sustainability\n", "- SSP2: Middle of the road\n", "- SSP3: Regional rivalry\n", "- SSP4: Inequality\n", "- SSP5: Fossil-fueled development\n", "\n", "These are baseline emissions paths, not optimized climate policies. The historical bridge uses the same CO₂ series as Part A; it is a teaching convention, not a recalibration of DICE.\n", "\n", "Source supplied with the course: [Our World in Data — IPCC scenarios explorer](https://ourworldindata.org/explorers/ipcc-scenarios).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "7db357bb-275a-487b-8e13-30e3fd3b09b3", "metadata": { "scrolled": true }, "outputs": [], "source": [ "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# Load\n", "df = pd.read_csv(\"Notebook_ClimateModels_SSP_data.csv\",sep=\";\",decimal=\",\")\n", "\n", "# Pick year + SSP columns\n", "year = df[\"Year\"]\n", "\n", "# Plot\n", "plt.figure()\n", "plt.plot(year, df[\"SSP1\"], label=\"SSP1\",color=\"#1B9E77\")\n", "plt.plot(year, df[\"SSP2\"], label=\"SSP2\",color=\"#D95F02\")\n", "plt.plot(year, df[\"SSP3\"], label=\"SSP3\",color=\"#8C6D31\")\n", "plt.plot(year, df[\"SSP4\"], label=\"SSP4\",color=\"#2CA25F\")\n", "plt.plot(year, df[\"SSP5\"], label=\"SSP5\",color=\"#6F42C1\")\n", "plt.grid(True, alpha=0.3)\n", "plt.legend(title=\"Scenario\", ncol=2, frameon=False)\n", "plt.title(\"Global CO₂ emissions — SSP baselines\")\n", "plt.xlabel(\"Year\"); plt.ylabel(\"GtCO₂/year\")\n", "plt.tight_layout();\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "26050a06-323d-4352-9fd0-74e2a3f16687", "metadata": {}, "source": [ "### B-1) Simulate temperature paths under SSP scenarios\n", "\n", "1. Set an annual grid from **2015 to 2100**. Keep the DICE initial states from a fresh `CM.Params()`: atmospheric carbon 851 GtC, upper-ocean carbon 460 GtC, deep-ocean carbon 1740 GtC, atmospheric temperature 0.85 °C and deep-ocean temperature 0.007 °C.\n", "2. Create five separate paths. Put the historical emissions for 2015–2019 in their first five rows and the corresponding SSP emissions for 2020–2100 in the remaining rows.\n", "3. Simulate **2016 to 2100**: the 2015 row is the given initial state; emissions in 2015 drive the first transition to 2016.\n", "4. Plot the five temperature paths and carbon stocks. Display the temperatures in 2100 and interpret the differences.\n", "\n", "Write the SSP1, SSP2, SSP3, SSP4 and SSP5 calculations separately, following the same simple steps. Use explicit assignments and plotting commands.\n", "\n", "**Methane convention.** Keep the module's initial concentration (1860 ppb) in all five paths. This file supplies CO₂ only: additional methane emissions are set to zero, so methane relaxes towards its preindustrial level identically in every scenario. These are comparisons within the course model, not complete IPCC projections.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "8334e121-a77d-4194-86d2-1cfe2b513df0", "metadata": {}, "outputs": [], "source": [ "# A NEW calibration: do not copy the preindustrial states from Part A.\n", "p2 = CM.Params()\n", "p2.Delta = 1\n", "p2.t0 = 2015\n", "p2.tT = 2100\n", "p2.nT = (p2.tT - p2.t0) // p2.Delta + 1\n", "\n", "# Keep the DICE initial carbon stocks and temperatures from CM.Params().\n", "path_SSP = CM.init_states(p2)\n", "\n", "# The SSP file begins in 2020. Historical data fill 2015–2019.\n", "historical_2015_2019 = emissions_by_year.loc[2015:2019]\n", "ssp = df.set_index(\"Year\").loc[2020:2100]\n", "\n", "# Same historical bridge and methane assumption in every scenario.\n", "path_SSP[:5, p2.i_E] = historical_2015_2019\n", "path_SSP[:, p2.i_E_CH4] = 0\n", "\n", "CM.mat_to_df(path_SSP, p2).head(1)\n", "\n", "# Create path_SSP1 as a copy of path_SSP.\n", "# Fill path_SSP1[5:, p2.i_E] with ssp[\"SSP1\"].\n", "# Run CM.update_path(path_SSP1, p2, 2016, 2100).\n", "# Write the same three steps explicitly for SSP2, SSP3, SSP4 and SSP5.\n", "# Plot the five results and display temperatures in 2100." ] }, { "cell_type": "markdown", "id": "33ecd8f6-7109-4cb5-9629-ab3dfebccd01", "metadata": {}, "source": [ "Write your answer here.\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.12.7" }, "x3a_merged_sources": [ "Session0/Notebook_ClimateModels.ipynb" ], "x3a_provenance": { "session": 2, "source_files": [ "Session0/Notebook_ClimateModels.ipynb" ], "allowed_changes": [ "file rename", "session title", "local-path normalization", "optional installation cell", "stored-output normalization" ] } }, "nbformat": 4, "nbformat_minor": 5 }