{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "56da5c86",
   "metadata": {},
   "source": [
    "<style>\n",
    ".jp-Notebook { max-width: 1180px; margin: auto; }\n",
    ".jp-RenderedHTMLCommon h1 { color: #17365d; }\n",
    ".jp-RenderedHTMLCommon h2 { color: #245b86; margin-top: 1.4em; }\n",
    ".jp-RenderedHTMLCommon table { font-size: 0.95em; }\n",
    "</style>\n",
    "\n",
    "# Deeply Temporalised Life Cycle Assessment with Climate Emulator Coupling\n",
    "\n",
    "**Add timing to LCA, from a simple car model to direct air capture and storage using `Premise` data package**\n",
    "\n",
    "Romain Sacchi · Laboratory for Energy Systems Analysis · PSI  \n",
    "BrightCon 2026\n",
    "\n",
    "> Our research question: When do life-cycle exchanges occur, and which background system supplies them?\n",
    "\n",
    "Upcoming publication:\n",
    "\n",
    "> *A graph-matrix hybrid approach for deep temporalisation in time-explicit LCA*  \n",
    "> Romain Sacchi, Tom Terlouw, Arthur Jakobs, Karin Treyer, Alvaro Hahn-Menacho, Christian Bauer  \n",
    "> [Pre-print](https://doi.org/10.21203/rs.3.rs-10139523/v1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f99b581",
   "metadata": {},
   "source": [
    "## Another tool for dynamic LCA?\n",
    "\n",
    "TRAILS stands for **Temporal Routing And Aggregation of Impacts across Life-cycle Systems**.  \n",
    "It combines the timing of life-cycle exchanges with a product system that changes over time.  \n",
    "\n",
    "### Current tools\n",
    "\n",
    "| Tool | Approach | How it works |\n",
    "|---|---|---|\n",
    "| **DyPLCA** | **Graph** | Follows the supply-chain network to calculate how much each process contributes and when its emissions occur. Matrices provide the starting data. **Deep temporalisation**, but does not consider time-evolving systems. |\n",
    "| **Prosperdyn** | **Mainly matrix** | Follows foreground connections to build a model for each year, then uses matrix calculations to solve each yearly model. Considers time-evolving systems, but not supply chain timing. |\n",
    "| **Temporalis / bw_temporalis** | **Mainly graph** | Follows **supply-chain paths**, combining time delays and distributions along the way to calculate when emissions occur. Matrix calculations help guide this search. Does not consider time-evolving systems.|\n",
    "| **bw_timex** | **Both** | Follows the supply-chain network to determine when processes occur. It then builds **larger matrices with separate entries for different times** and solves the resulting system. **Temporalization** and **time-evolving systems**, but larger matrices imply a limit to temporalisation depth. |\n",
    "| **Trails** | **Both** | Follows the supply-chain network to distribute demands over time. Where this tracing stops, it uses **separate matrix calculations for each year** to account for the remaining supply chain. **Deep temporalisation** and **time-evolving** systems.|\n",
    "\n",
    "Its main strength: `Trails` breaks down the problem into smaller ones, to handle both **deeply-temporalised** and **time-evolving** systems while remaining **computationally efficient**.\n",
    "\n",
    "\n",
    "\n",
    "There are three main steps:\n",
    "\n",
    "```text\n",
    "Functional unit and reference year\n",
    "      ↓\n",
    "temporal_routing() — assign exchanges to calendar years\n",
    "      ↓\n",
    "lci() — solve the supply-chain demands for each year\n",
    "      ↓\n",
    "Inventory by activity, elementary flow and year\n",
    "      ├── lcia(), e.g., with GWP100\n",
    "      ├── FaIR: radiative forcing and temperature change\n",
    "      └── lcia() with EDGES (exchange-based characterisation): regional water scarcity\n",
    "```\n",
    "\n",
    "By the end, we should be able to:\n",
    "\n",
    "1. explain how TRAILS places exchanges in time,\n",
    "2. why this can change an LCA result,\n",
    "3. and timed inventory can be used in three impact assessments approaches."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65fe3797",
   "metadata": {
    "tags": [
     "setup"
    ]
   },
   "source": [
    "### Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "646d36e4",
   "metadata": {
    "tags": [
     "setup",
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "import json\n",
    "import warnings\n",
    "from importlib.resources import files\n",
    "from pathlib import Path\n",
    "\n",
    "import pandas as pd\n",
    "from datapackage import Package\n",
    "\n",
    "import edges\n",
    "import trails as trails_package\n",
    "from trails import Trails, plot_temp, plot_temporal_scores, search_activity\n",
    "from trails.fair_rf import run_fair_delta_rf\n",
    "\n",
    "warnings.filterwarnings(\"ignore\", category=FutureWarning)\n",
    "print(f\"TRAILS {trails_package.__version__}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "461a967e",
   "metadata": {
    "tags": [
     "setup",
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "# define some paths\n",
    "ROOT = Path.cwd().resolve()\n",
    "DATA = ROOT / \"data\"\n",
    "SMALL_PACKAGE = DATA / \"example_data_package\" / \"datapackage.json\"\n",
    "PREMISE_PACKAGE = DATA / \"trails_remind_SSP2-PkBudg1000.zip\"\n",
    "DACCS_INVENTORY = DATA / \"lci-case-study-daccs_storage_risk.xlsx\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43ec59dc",
   "metadata": {
    "tags": [
     "setup",
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "def prospective_aware_method(ssp: str = \"SSP126\") -> dict:\n",
    "    path = files(\"edges\").joinpath(\n",
    "        \"data/AWARE 2.0 prospective_Country_all_yearly.json\"\n",
    "    )\n",
    "    method = json.loads(path.read_text(encoding=\"utf-8\"))\n",
    "    if ssp not in method[\"parameters\"]:\n",
    "        raise ValueError(f\"Unknown AWARE scenario {ssp!r}\")\n",
    "    method[\"parameters\"] = {ssp: method[\"parameters\"][ssp]}\n",
    "    method[\"name\"] = f\"AWARE 2.0 prospective | Country | all | yearly | {ssp}\"\n",
    "    return method"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3b766bf",
   "metadata": {},
   "source": [
    "# 1. A simple car example\n",
    "\n",
    "The data package contains **21 activities**, with background data for **2005, 2020, 2050 and 2100**.\n",
    "TRAILS interpolates between these years to obtain annual background matrices.\n",
    "\n",
    "**Functional unit:** 200,000 vehicle-kilometres, with vehicle use starting in 2050.\n",
    "This is a small teaching model, not a representative assessment of a real car.\n",
    "\n",
    "We load the package and choose GWP100. The full list of available methods is for reference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ea1dc76e",
   "metadata": {},
   "outputs": [],
   "source": [
    "car = Trails(\n",
    "    Package(str(SMALL_PACKAGE)),\n",
    "    ei_version=\"3.12\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3dfa2f44-3706-4917-98f1-706524d6542c",
   "metadata": {},
   "outputs": [],
   "source": [
    "CAR_GWP = \"IPCC 2021 (incl. biogenic CO2) - climate change: total (incl. biogenic CO2) - global warming potential (GWP100)\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42ae6c84-d5bf-4ac8-84e4-153231a5d5d9",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "from trails import get_lcia_method_names\n",
    "get_lcia_method_names()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ab92d95",
   "metadata": {},
   "source": [
    "### Inspect the background matrices\n",
    "\n",
    "`A` contains technosphere exchanges; `B` contains elementary flows.\n",
    "Each has an extra axis for the background scenario year. The other axes describe\n",
    "activities and products (`A`), or activities and elementary flows (`B`).\n",
    "\n",
    "The number of years shown below includes interpolation; it is not the number of original input years."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ec917d09",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"A[year, activity, activity]:\", car.A.shape)\n",
    "print(\"B[year, activity, flow]:    \", car.B.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62c73726",
   "metadata": {},
   "source": [
    "### Find the car activity\n",
    "\n",
    "Search by name and check which activity represents the functional unit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4794eace",
   "metadata": {},
   "outputs": [],
   "source": [
    "search_activity(car, name=\"passenger car\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dba65e58",
   "metadata": {},
   "source": [
    "### Read the car's exchange timing\n",
    "\n",
    "In this example data package, activity `13` is the internal-combustion-engine car.\n",
    "The table below shows its exchanges and their timing. Offsets are measured **in years** from the activity's reference year.\n",
    "\n",
    "Focus on three rows: fuel use is spread over offsets **0–16**; vehicle production has no explicit delay;\n",
    "end-of-life is split between offsets **17 and 18**, with weights **0.3 and 0.7**.\n",
    "For a 2050 start, those end-of-life exchanges occur in 2067 and 2068.\n",
    "\n",
    "`port` spreads the reference-year exchange amount over time. `matrix` reads the exchange coefficient\n",
    "from the background matrix for each pulse year. These choices can affect both the timing and the total inventory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f22f63da",
   "metadata": {},
   "outputs": [],
   "source": [
    "car_idx = 13\n",
    "car.print_exchange_table(year=2050, act_idx=car_idx)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b53ed0f7",
   "metadata": {},
   "source": [
    "## Place the life cycle in time\n",
    "\n",
    "`temporal_routing()` follows the exchanges and assigns demands and direct emissions to calendar years.  \n",
    "The next cell uses GWP100 to decide how far to follow each branch in detail.\n",
    "\n",
    "The cutoff controls this detailed traversal. A branch that stops is still included in the subsequent matrix solve;\n",
    "its remaining supply chain is not discarded.  \n",
    "The graph cell then writes an HTML view that you can open to inspect the timing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d5e07583",
   "metadata": {},
   "outputs": [],
   "source": [
    "car.temporal_routing(\n",
    "    start_year=2050,\n",
    "    start_act_idx=car_idx,\n",
    "    amount=200_000,\n",
    "    adaptive_methods=[CAR_GWP],\n",
    "    adaptive_relative_score_cutoff=0.00001\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8f69822-c2f0-42a2-a421-0f3b5131453d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from trails.plotting import plot_temporal_graph\n",
    "\n",
    "plot_temporal_graph(\n",
    "    car,\n",
    "    filename='trails_graph.html',\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd1e5d38",
   "metadata": {},
   "source": [
    "### Calculate the inventory\n",
    "\n",
    "`lci()` solves the demands for each active year using the corresponding background matrix.\n",
    "It retains the resulting inventory by year. No characterization factors are applied yet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b7863222",
   "metadata": {},
   "outputs": [],
   "source": [
    "car.lci()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e13b147",
   "metadata": {},
   "source": [
    "### Apply GWP100\n",
    "\n",
    "`lcia()` applies GWP100 to the inventory. We can apply another method later\n",
    "without calculating the inventory again."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d3ea37ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "car_gwp_scores = car.lcia(methods=[CAR_GWP])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1670270",
   "metadata": {},
   "source": [
    "### Calculate a static reference\n",
    "\n",
    "For comparison, we calculate the same functional unit using the 2050 background for the whole life cycle.\n",
    "This is a prospective static reference: “static” does not mean “present-day”."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7cbc216c",
   "metadata": {},
   "outputs": [],
   "source": [
    "car.static_lca(\n",
    "    year=2050,\n",
    "    act_idx=car_idx,\n",
    "    methods=[CAR_GWP],\n",
    "    amount=200_000,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d01487f",
   "metadata": {},
   "source": [
    "### Compare the temporal and static results\n",
    "\n",
    "The plot shows GWP100 contributions by inventory year and their cumulative total.\n",
    "The red dotted line is the static 2050 result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41eedc74",
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_temporal_scores(\n",
    "    trails=car,\n",
    "    stacked=False,\n",
    "    legend_top_n=7,\n",
    "    show_flow_contributions=False,\n",
    "    title=\"Passenger car: temporal versus static GWP\",\n",
    "    method_label=\"kg CO₂-eq\",\n",
    "    year_range=(2020, 2080),\n",
    "    year_tick=10,\n",
    "    reference_year=2050,\n",
    "    show_cumulative_axis=True,\n",
    "    static_score=car.static_score,\n",
    "    static_score_dash=\"dot\",\n",
    "    static_score_color=\"red\",\n",
    "    width=850,\n",
    "    height=480,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c936501c",
   "metadata": {},
   "source": [
    "# 2. Apply the same workflow to a larger system: DACCS (data package not provided)\n",
    "\n",
    "**DACCS** means direct air capture with carbon storage.\n",
    "\n",
    "`Premise` can now produce data packages containing time-sliced technosphere and biosphere systems (from 2000 to 2100) with temporal distributions \"baked-in\". Currently, branch [`trails`](https://github.com/polca/premise/tree/trals), upcoming release `2.6.0`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b99a5c7-df34-4d48-b33a-9b305e846ddb",
   "metadata": {},
   "source": [
    "![Premise temporal distributions](assets/fig_premise.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7328bb11-7b6b-4169-8160-8c4de917dd4e",
   "metadata": {},
   "source": [
    "To produce such a package with `Premise`:\n",
    "\n",
    "```python\n",
    "    package = TrailsDataPackage(\n",
    "      scenario={\n",
    "          \"model\": \"remind\",\n",
    "          \"pathway\": \"SSP1-PkBudg1000\",\n",
    "      },\n",
    "      source_db=\"ecoinvent-3.12-cutoff\",\n",
    "      source_version=\"3.12\",\n",
    "      system_model=\"cutoff\",\n",
    "      biosphere_name=\"biosphere-3.12\",\n",
    "      key=os.environ[\"PREMISE_KEY\"],\n",
    "    )\n",
    "    \n",
    "    package.create_datapackage()\n",
    "````\n",
    "\n",
    "Hence, we now use a larger background package produced with `Premise`, based on **REMIND's SSP2-PkBudg1000** scenario, and import an Excel inventory for solvent-based DACCS.  \n",
    "\n",
    "**Functional unit:** 20 Mt of CO₂ captured over a nominal 20-year operating period, starting in 2035.  \n",
    "This is gross capture, not net climate benefit: life-cycle emissions also enter the assessment.  \n",
    "The supplied inventory assigns operation to annual offsets **0–20**, or 2035–2055 inclusive.\n",
    "\n",
    "The workflow is the same as for the car: load → route → calculate the inventory → assess impacts."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1970dfeb",
   "metadata": {},
   "source": [
    "### Load the future background\n",
    "\n",
    "TRAILS loads the scenario package and, by default, interpolates the background matrices to annual resolution.\n",
    "The `Trails?` cell opens the API help in Jupyter; it is optional during the talk."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bcb75e30-e423-4048-bb50-a7b722cee5c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "Trails?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dfe9e036",
   "metadata": {
    "tags": [
     "slow"
    ]
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "PREMISE_PACKAGE=\"/Users/romain/GitHub/trails/dev/publication/trails_remind_SSP2-PkBudg1000.zip\"\n",
    "daccs = Trails(\n",
    "    Package(str(PREMISE_PACKAGE))\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8dc017eb-6c19-4c3e-8c23-a8f7864e15a2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# let's check the shape of A and B\n",
    "print(\"Shape of A\", daccs.A.shape)\n",
    "print(\"Shape of B\", daccs.B.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e33cd4a-fc4e-4e23-a423-2d237dd49bba",
   "metadata": {},
   "outputs": [],
   "source": [
    "# let's check the number of temporal distribution\n",
    "print(\"Temporalised biosphere exchanges\", len(daccs.temporal_biosphere_exchanges))\n",
    "print(\"Temporalised technosphere exchanges\", len(daccs.temporal_technosphere_exchanges))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9dc32cec",
   "metadata": {},
   "source": [
    "### Add the DACCS inventory\n",
    "\n",
    "The Excel file supplies foreground exchanges and their timing.\n",
    "TRAILS links these exchanges to activities and elementary flows in the background package."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "894dbbac",
   "metadata": {
    "tags": [
     "slow"
    ]
   },
   "outputs": [],
   "source": [
    "daccs.import_excel_inventory(str(DACCS_INVENTORY))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbb94405",
   "metadata": {},
   "source": [
    "### Find the DACCS activity\n",
    "\n",
    "Search by dataset name and location, then check the returned activity before using its index.\n",
    "The search below filters by text; it does not enforce a unique exact match."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e88e1a86-42a3-420d-82c6-0c5441e3c028",
   "metadata": {},
   "outputs": [],
   "source": [
    "search_activity(\n",
    "    daccs,\n",
    "    name=\"carbon dioxide, captured, with a solvent-based direct air capture system, 1MtCO2\",\n",
    "    location=\"Europe\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55b719ef",
   "metadata": {},
   "source": [
    "### Check the selected activity\n",
    "\n",
    "The saved example uses activity index `42300`. Check it against the search result if the package or import changes.  \n",
    "In the exchange table, construction occurs before the 2035 start, operation is spread over offsets 0–20,\n",
    "and end-of-life occurs at offset 21. Check the production amount as well as the exchange amounts."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6609e4a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "daccs_idx=42300\n",
    "daccs.print_exchange_table(\n",
    "    year=2035,\n",
    "    act_idx=daccs_idx\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd29c6d0",
   "metadata": {},
   "source": [
    "### Place the DACCS exchanges in time\n",
    "\n",
    "We use the same routing step as for the car. GWP100 guides the detailed traversal.\n",
    "`attribute_to_roots=True` keeps track of contributions associated with the starting activity's direct exchanges,\n",
    "such as its heat and electricity supply. The later plots use this grouping.\n",
    "\n",
    "The following graph cell exports an HTML view of the routed system."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44119520",
   "metadata": {},
   "outputs": [],
   "source": [
    "GWP = \"IPCC 2021 (incl. biogenic CO2) - climate change: total (incl. biogenic CO2) - global warming potential (GWP100)\"\n",
    "\n",
    "START_YEAR = 2035\n",
    "DACCS_AMOUNT_KG = 20_000_000_000.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cb23f951",
   "metadata": {
    "tags": [
     "slow"
    ]
   },
   "outputs": [],
   "source": [
    "daccs.temporal_routing(\n",
    "    start_year=START_YEAR,\n",
    "    start_act_idx=daccs_idx,\n",
    "    amount=DACCS_AMOUNT_KG,\n",
    "    attribute_to_roots=True,\n",
    "    adaptive_methods=[GWP],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a84c4ca-61af-4c81-b0f6-25b45a650253",
   "metadata": {},
   "outputs": [],
   "source": [
    "from trails.plotting import plot_temporal_graph\n",
    "\n",
    "plot_temporal_graph(\n",
    "    daccs,\n",
    "    filename='trails_graph_daccs.html',\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64edc0d4",
   "metadata": {},
   "source": [
    "### Calculate and keep the inventory\n",
    "\n",
    "We solve the annual demands once and retain the inventory for GWP100, FaIR and AWARE.\n",
    "This larger calculation can take time; use the saved results when presenting if necessary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38e918cd",
   "metadata": {
    "tags": [
     "slow"
    ]
   },
   "outputs": [],
   "source": [
    "daccs.lci(\n",
    "    solver_mode=\"iterative\",\n",
    "    iterative_rtol=1e-3,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f787f245",
   "metadata": {},
   "source": [
    "### Apply GWP100 and calculate a static reference\n",
    "\n",
    "We first apply fixed GWP100 factors to the annual inventory.\n",
    "We then calculate a static reference using the 2035 background for the whole life cycle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ab328a2",
   "metadata": {
    "tags": [
     "slow"
    ]
   },
   "outputs": [],
   "source": [
    "gwp_scores = daccs.lcia(methods=[GWP])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f778548-e1fc-489a-b1b7-476f65e6bfda",
   "metadata": {},
   "outputs": [],
   "source": [
    "daccs.static_lca(\n",
    "    year=2035,\n",
    "    act_idx=daccs_idx,\n",
    "    methods=[GWP],\n",
    "    amount=DACCS_AMOUNT_KG,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7a93fd8",
   "metadata": {},
   "source": [
    "### Read the DACCS climate result\n",
    "\n",
    "The plot groups annual GWP100 contributions by their first-level supply-chain origin.\n",
    "Negative contributions reduce the total; positive contributions increase it.  \n",
    "Compare the cumulative result with the static reference.\n",
    "\n",
    "The displayed range ends in 2070. It is a view of the results, not a definition of the full inventory horizon."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eb5f5fea-7450-445e-bcd8-1677eac35c4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "static_gwp_score = float(daccs.static_score[0])\n",
    "static_gwp_score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a03d4dfc",
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_temporal_scores(\n",
    "    trails=daccs,\n",
    "    stacked=True,\n",
    "    legend_top_n=7,\n",
    "    show_flow_contributions=False,\n",
    "    title=\"DACCS: life-cycle GWP through time\",\n",
    "    method_label=\"kg CO₂-eq\",\n",
    "    year_range=(2020, 2070),\n",
    "    year_tick=5,\n",
    "    reference_year=START_YEAR,\n",
    "    show_cumulative_axis=True,\n",
    "    width=850,\n",
    "    height=480,\n",
    "    static_score=daccs.static_score\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "270f1c74",
   "metadata": {},
   "source": [
    "# 3. But GWP isn't a very fitting indicator here...\n",
    "\n",
    "The sliding time window of the GWP indicator is not ideal for decision-making."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5570f407",
   "metadata": {},
   "source": [
    "## A. Calculate the climate response with FaIR\n",
    "\n",
    "GWP100 gives a fixed 100-year equivalence for each gas.  \n",
    "It does not show the resulting temperature trajectory.\n",
    "\n",
    "Instead, we use the climate model `FaIR`.  \n",
    "Here, it compares a **background emissions pathway** with that pathway **plus the TRAILS inventory**.  \n",
    "The difference is the climate response attributed to the inventory.  \n",
    "\n",
    "We provide the climate model with the background emissions as calculated in the REMIND pathway used by `Premise` to ensure consistency."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91fcd7c4",
   "metadata": {
    "tags": [
     "slow"
    ]
   },
   "outputs": [],
   "source": [
    "run_fair_delta_rf(\n",
    "    daccs,\n",
    "    scenario=\"REMIND|SSP2-PkBudg1000\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d98867d",
   "metadata": {},
   "source": [
    "### Read the radiative-forcing and temperature plots\n",
    "\n",
    "The first plot shows the change in **radiative forcing** (W/m²); the second shows the change in **temperature** (°C).  \n",
    "Both show median responses through 2200, grouped by first-level contribution.  \n",
    "Negative values indicate lower forcing or temperature than in the background run.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f8197600-0ced-4d56-90c8-4f4b62b09394",
   "metadata": {},
   "outputs": [],
   "source": [
    "from trails.plotting import plot_rf\n",
    "\n",
    "plot_rf(\n",
    "    daccs,\n",
    "    #by=\"root activity\",\n",
    "    by=\"flow\",\n",
    "    flow_groupby_name=True,\n",
    "    title=\"DACCS: RF response of timed emissions and removals\",\n",
    "    method_label=\"W/m²\",\n",
    "    year_range=(2020, 2200),\n",
    "    year_tick=20,\n",
    "    width=850,\n",
    "    height=480,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "186b1639",
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_temp(\n",
    "    daccs,\n",
    "    by=\"root activity\",\n",
    "    title=\"DACCS: temperature response of timed emissions and removals\",\n",
    "    method_label=\"°C\",\n",
    "    year_range=(2020, 2200),\n",
    "    year_tick=20,\n",
    "    width=850,\n",
    "    height=480,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "975b6c4c-ccf9-4500-9349-fed3dd29318b",
   "metadata": {},
   "source": [
    "### Optional extension: express the response as a CO₂ pulse equivalent"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "rf-co2-equivalence-explanation",
   "metadata": {},
   "source": [
    "How large would a single CO₂ pulse need to be to produce the same **integrated radiative forcing** as this inventory?  \n",
    "We compare the area under the inventory's forcing curve with the area under the curve for a known CO₂ pulse:\n",
    "\n",
    "$$M_{\\mathrm{CO_2,eq}} = M_{\\mathrm{ref}}\\,\\frac{\\int_{t_0}^{t_1} \\Delta RF_{\\mathrm{LCA}}(t)\\,dt}{\\int_{t_0}^{t_1} \\Delta RF_{\\mathrm{CO_2\\ pulse}}(t)\\,dt}.$$\n",
    "\n",
    "The code below places the reference pulse in **2026** and integrates both responses over **2000–2100**.  \n",
    "These are metric choices; the DACCS activity still starts in **2035**.  \n",
    "The **1 Mt CO₂** reference pulse sets the scale of the comparison.\n",
    "\n",
    "TRAILS calculates a ratio for each FaIR configuration before summarizing the ensemble.  \n",
    "A negative equivalent means a net reduction in integrated forcing over this window.  \n",
    "This is a window-specific RF metric, not GWP100."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b37b8169-c188-4008-92bd-449c99f459d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from trails.fair_rf import run_fair_co2_pulse_equivalents\n",
    "\n",
    "co2_pulse_result = run_fair_co2_pulse_equivalents(\n",
    "    daccs,\n",
    "    scenario=\"REMIND|SSP2-PkBudg1000\",\n",
    "    reference_pulse_year=2026,\n",
    "    window_start=2000,\n",
    "    window_end=2100,\n",
    "    reference_pulse_mass_kg=1.0e9,  # 1 Mt CO2\n",
    ")\n",
    "\n",
    "rf_equivalent = co2_pulse_result[\"co2_pulse_equivalent\"][\"integrated_rf\"]\n",
    "rf_equivalence_summary = pd.DataFrame(\n",
    "    {\n",
    "        \"median\": [rf_equivalent[\"median\"] / 1.0e9],\n",
    "        \"2.5%\": [rf_equivalent[\"p025\"] / 1.0e9],\n",
    "        \"97.5%\": [rf_equivalent[\"p975\"] / 1.0e9],\n",
    "    },\n",
    "    index=[\"Integrated RF CO2 pulse-equivalent [Mt CO2]\"],\n",
    ")\n",
    "rf_equivalence_summary"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "rf-one-tonne-question",
   "metadata": {},
   "source": [
    "#### Estimate gross capture per tonne of RF-equivalent removal\n",
    "\n",
    "For a negative RF equivalent, we can estimate how much gross capture corresponds to the benefit\n",
    "of a **one-tonne CO₂ removal pulse in 2026**, assessed over **2000–2100**:\n",
    "\n",
    "$$M_{\\mathrm{DACCS,required}} = 1\\ \\mathrm{t\\ CO_2}\\times\\frac{M_{\\mathrm{DACCS,modelled}}}{\\left|M_{\\mathrm{CO_2,eq}}\\right|}.$$\n",
    "\n",
    "This scaling assumes that the inventory and its climate benefit scale approximately linearly with DACCS deployment.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b1a41329-0180-4c22-ac6f-9c235f6baf05",
   "metadata": {},
   "outputs": [],
   "source": [
    "target_co2_equivalent_kg = 1_000.0  # 1 tonne CO2\n",
    "modelled_daccs_kg = DACCS_AMOUNT_KG\n",
    "\n",
    "required_daccs_summary = pd.DataFrame(\n",
    "    {\n",
    "        \"median\": [\n",
    "            target_co2_equivalent_kg\n",
    "            * modelled_daccs_kg\n",
    "            / abs(rf_equivalent[\"median\"])\n",
    "            / 1_000\n",
    "        ],\n",
    "        \"2.5%\": [\n",
    "            target_co2_equivalent_kg\n",
    "            * modelled_daccs_kg\n",
    "            / abs(rf_equivalent[\"p025\"])\n",
    "            / 1_000\n",
    "        ],\n",
    "        \"97.5%\": [\n",
    "            target_co2_equivalent_kg\n",
    "            * modelled_daccs_kg\n",
    "            / abs(rf_equivalent[\"p975\"])\n",
    "            / 1_000\n",
    "        ],\n",
    "    },\n",
    "    index=[\"Gross DACCS required per 1 t RF-equivalent removal [t CO2]\"],\n",
    ")\n",
    "required_daccs_summary"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "rf-one-tonne-interpretation",
   "metadata": {},
   "source": [
    "We need to interepret this as **tonnes of gross CO₂ capture per tonne of RF-equivalent removal**.  \n",
    "Alos, rememebr that the result depends on the reference pulse year and the integration window.  \n",
    "\n",
    "It is not a physical storage efficiency or a GWP100 result. It includes the effects of life-cycle emissions,\n",
    "removals, non-CO₂ gases and their timing.   \n",
    "The percentile range describes variation across the FaIR configurations.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35734754",
   "metadata": {},
   "source": [
    "## B. Towards a spatio-temporal impact assessment framework? \n",
    "\n",
    "### Assess water scarcity with AWARE and EDGES\n",
    "\n",
    "**AWARE** assesses the potential to deprive other users of water.  \n",
    "**EDGES** applies characterization factors to exchanges, using information about the flow and the associated activity.  \n",
    "This lets water-use factors vary with the activity's location and the inventory year.  \n",
    "\n",
    "Here we select country-level prospective AWARE 2.0 factors under **SSP2-RCP 2.6**, to align with the foreground inventory scenario (**REMIND SSP2-PkBudg1000**)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17baf5c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "aware_method = prospective_aware_method(\"SSP126\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ab62207",
   "metadata": {},
   "source": [
    "### Compare water-scarcity factors\n",
    "\n",
    "The table compares non-irrigation water factors for **Switzerland, Spain and Denmark** in **2024 and 2044**, acoording to AWARE 2.0: SSP1-RCP 2.6.\n",
    "For the same volume of water, a larger factor gives a larger water-scarcity score."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "362ea3d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "parameters = aware_method[\"parameters\"][\"SSP126\"]\n",
    "\n",
    "pd.DataFrame(\n",
    "    {\n",
    "        \"Switzerland\": [parameters[f\"cf_non_irri_ch\"][str(year)] for year in (2024, 2044)],\n",
    "        \"Spain\": [parameters[f\"cf_non_irri_es\"][str(year)] for year in (2024, 2044)],\n",
    "        \"Denmark\": [parameters[f\"cf_non_irri_dk\"][str(year)] for year in (2024, 2044)],\n",
    "    },\n",
    "    index=pd.Index([2024, 2044], name=\"year\"),\n",
    ").rename_axis(columns=\"m³ deprived / m³ water\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f794e7b9",
   "metadata": {},
   "source": [
    "### Apply AWARE to the inventory\n",
    "\n",
    "We call `lcia()` again, this time using EDGES and the selected AWARE factors.\n",
    "The annual inventory is reused; we do not repeat the supply-chain solves."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9a56914",
   "metadata": {
    "tags": [
     "slow"
    ]
   },
   "outputs": [],
   "source": [
    "aware_scores = daccs.lcia(\n",
    "    methods=[aware_method],\n",
    "    method_backend=\"edges\",\n",
    "    reuse_mappings=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfd2f789",
   "metadata": {},
   "source": [
    "### Read the water-scarcity result\n",
    "\n",
    "The plot groups water-scarcity contributions by year and first-level supply-chain origin.  \n",
    "Both water use and the **country-** and **year-** specific factors can affect the score.  \n",
    "The score does not reflect a physical volume of freshwater consumed.  \n",
    "Rather, itis a characterized scarcity impact (i.e., m3-deprived-eq.)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8143ee3d",
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_temporal_scores(\n",
    "    trails=daccs,\n",
    "    stacked=True,\n",
    "    legend_top_n=7,\n",
    "    show_flow_contributions=False,\n",
    "    title=\"DACCS: time- and location-sensitive impact assessment using AWARE 2.0\",\n",
    "    method_label=\"m³ deprived water-eq.\",\n",
    "    year_range=(2030, 2060),\n",
    "    year_tick=5,\n",
    "    reference_year=START_YEAR,\n",
    "    show_cumulative_axis=True,\n",
    "    width=850,\n",
    "    height=480,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d086da7-31d4-4e99-b324-11a1070a7fd2",
   "metadata": {},
   "source": [
    "## What to take away\n",
    "\n",
    "TRAILS combines **exchange timing** (regardless how deep) with **systems that change over time**.  \n",
    "Its central workflow is `temporal_routing()` → `lci()`.  \n",
    "The same annual tmporalised inventory can then be used for different impact assessment approaches:\n",
    "\n",
    "* conventional LCIA\n",
    "* support a `FaIR` climate-response calculation\n",
    "* or an integration with `Edges` for regionalised and/or time-sensitive characterisation factors.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "tool-publication-references",
   "metadata": {
    "tags": [
     "references"
    ]
   },
   "source": [
    "## References for the tools and methods used\n",
    "\n",
    "- **TRAILS — temporal life-cycle modelling.** Sacchi, R., et al. (2026). [A graph-matrix hybrid approach for deep temporalisation in time-explicit LCA](https://doi.org/10.21203/rs.3.rs-10139523/v1). *Research Square*, preprint, version 1.\n",
    "\n",
    "- **EDGES — exchange-level impact assessment.** Sacchi, R., et al. (2025). [Contextual LCIA without the overhead: an exchange-based framework for flexible impact assessment](https://doi.org/10.1007/s11367-025-02551-7). *The International Journal of Life Cycle Assessment*, **30**, 3087–3101.\n",
    "\n",
    "- **FaIR — radiative forcing and temperature response.** Leach, N. J., et al. (2021). [FaIRv2.0.0: a generalized impulse response model for climate uncertainty and future scenario exploration](https://doi.org/10.5194/gmd-14-3007-2021). *Geoscientific Model Development*, **14**, 3007–3036.\n",
    "\n",
    "- **AWARE 2.0 — prospective water-scarcity factors.** Seitfudem, G., Berger, M., & Boulay, A.-M. (2026). [Harnessing model ensembles to assess uncertainty and provide prospective characterization factors for AWARE2.0](https://doi.org/10.1007/s11367-026-02648-7). *The International Journal of Life Cycle Assessment*, **31**, article 98.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (trails)",
   "language": "python",
   "name": "trails"
  },
  "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.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
