{ "cells": [ { "cell_type": "markdown", "id": "659605a5-d601-47d3-89f7-b606e3e39c93", "metadata": {}, "source": [ "(measure-config-tutorial)=\n", "\n", "# Defining Adaptation Measures with configurations" ] }, { "cell_type": "markdown", "id": "c68c6cf1-d0a1-40ae-ae45-838741988ac6", "metadata": {}, "source": [ "## Introduction\n", "\n", "CLIMADA uses `Measure` objects to model the effects of adaptation measures. `Measure` objects were formerly defined declaratively (via for instance, a shifting or scaling of the hazard intensity or a change of impact function), and are now defined as python functions to enable more flexibility on the possible changes (see the [tutorial on measure objects](measure-tutorial)'). \n", "\n", "The caveat of defining measure effects as python functions is that it cannot be serialized (written to a file), and also makes reading from a file a challenge.\n", "\n", "In order to retain close that gap, the `measure` module now ships `MeasureConfig` objects, which handle the reading, writing and \"declarative\" defining of `Measure` objects.\n", "\n", "`Measure` objects can be instantiated from `MeasureConfig` objects using `Measure.from_config()`.\n", "\n", "### Summary of `Measure` vs `MeasureConfig`\n", "\n", "| `Measure` | `MeasureConfig` |\n", "|-----------|--------------------|\n", "| Is used for the actual computation | Is transformed into a `Measure` for actual computation |\n", "| Uses python function to define what change to apply to the `Exposures`, `ImpactFuncSet`, `Hazard` objects | Define the changes (functions) to apply via the former way (scaling/shifting effect, alternate file loading, etc.) |\n", "| Accepts any possible effect as long as it can be defined as a python function | Is restricted to a set of defined effects |\n", "| Cannot be written to a file (unless it was created by a `MeasureConfig`) | Can easily be read from/written to a file (`.xlsx` or `.yaml`) |" ] }, { "cell_type": "markdown", "id": "6d786faa-5b8c-4ee6-83cd-5fdafc1b2c29", "metadata": {}, "source": [ "### Configuration classes\n", "\n", "The definition of measures via `MeasureConfig` is organized into a hierarchy of specialized classes:\n", "\n", "- `MeasureConfig`: The top-level container for a single measure.\n", "- `HazardModifierConfig`: Defines how the hazard is changed (e.g., shifting intensity).\n", "- `ImpfsetModifierConfig`: Adjusts impact functions (e.g., scaling vulnerability curves).\n", "- `ExposuresModifierConfig`: Modifies exposure data (e.g., reassigning IDs or zeroing regions).\n", "- `CostIncomeConfig`: Handles the financial aspects, including initial costs and recurring income.\n", "\n", "Note that everything can be defined and accessed directly from the `MeasureConfig` container, the underlying ones are there to keep things organized.\n", "\n", "In the following we present each of these subclasses and the possibilities they offer." ] }, { "cell_type": "markdown", "id": "4887d2a6-8295-4fda-8442-cbcbd3b16fea", "metadata": {}, "source": [ "## Quickstart" ] }, { "cell_type": "markdown", "id": "5c40640d-50a4-4102-8e45-0dc8b9a770f2", "metadata": {}, "source": [ "You can directly define a `MeasureConfig` object with a dictionary, using `MeasureConfig.from_dict()`.\n", "\n", "Below are the possible parameters:\n", "\n", "| Scope | Parameter | Type | Description |\n", "| :--- | :--- | :--- | :--- |\n", "| **Top-Level** | `name` (required) | `str` | Unique identifier for the measure. |\n", "| | `haz_type` (required) | `str` | The hazard type this measure targets (e.g., \"TC\", \"FL\"). |\n", "| | `implementation_duration` | `str` | Pandas offset alias (e.g., \"2Y\") for implementation time. |\n", "| | `color_rgb` | `tuple` | RGB triple (0-1 range) for plotting and visualization. |\n", "| **Hazard** | `haz_int_mult` | `float` | Multiplier for hazard intensity (default: 1.0). |\n", "| | `haz_int_add` | `float` | Additive offset for hazard intensity (default: 0.0). |\n", "| | `new_hazard_path` | | Path to an HDF5 file to replace the current hazard. |\n", "| | `impact_rp_cutoff` | `float` | Return period (years) threshold; events below this are ignored. |\n", "| **Impact Function**| `impf_ids` | `list` | Specific impact function IDs to modify (None = all). |\n", "| | `impf_mdd_mult` / `_add` | `float` | Scale or shift the Mean Damage Degree curve. |\n", "| | `impf_paa_mult` / `_add` | `float` | Scale or shift the Percentage of Assets Affected curve. |\n", "| | `impf_int_mult` / `_add` | `float` | Scale or shift the intensity axis of the function. |\n", "| | `new_impfset_path` | | Path to an Excel file to replace the impact function set. |\n", "| **Exposures** | `reassign_impf_id` | `dict` | Mapping `{haz_type: {old_id: new_id}}` for reclassification. |\n", "| | `set_to_zero` | `list` | List of Region IDs where exposure value is set to 0. |\n", "| | `new_exposures_path` | | Path to an HDF5 file to replace the current exposures. |\n", "| **Cost & Income** | `init_cost` | `float` | One-time investment cost (absolute value). |\n", "| | `periodic_cost` | `float` | Recurring maintenance/operational costs. |\n", "| | `periodic_income` | `float` | Recurring income generated by the measure. |\n", "| | `mkt_price_year` | `int` | Reference year for pricing (default: current year). |\n", "| | `freq` | `str` | Frequency of cash flows (e.g., \"Y\" for yearly). |\n", "| | `custom_cash_flows` | `list[dict]`| Explicit list of dates and values for complex cash flows. (See the [cost income tutorial](cost-income-tutorial)) |" ] }, { "cell_type": "code", "execution_count": 1, "id": "62cf6502-7765-452c-be32-eb49a363b4a8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MeasureConfig(\n", "\tname='Tutorial measure'\n", "\thaz_type='TC'\n", "\timpfset_modifier=ImpfsetModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\timpf_ids=[1, 2]\n", "\t\t\timpf_mdd_mult=0.8\n", ")\n", "\thazard_modifier=HazardModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\tnew_hazard_path='path/to/new_hazard.h5'\n", ")\n", "\texposures_modifier=ExposuresModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\treassign_impf_id={'TC': {1: 2}}\n", ")\n", "\tcost_income=CostIncomeConfig(\n", "\t\tNon default fields:\n", "\t\t\tinit_cost=10000\n", "\t\t\tperiodic_cost=500\n", ")\n", "\timplementation_duration=None\n", "\tcolor_rgb=(0.1, 0.5, 0.3))\n" ] } ], "source": [ "from climada.entity.measures.measure_config import MeasureConfig\n", "\n", "measure_dict = {\n", " \"name\": \"Tutorial measure\",\n", " \"haz_type\": \"TC\",\n", " \"impf_ids\": [1, 2],\n", " \"impf_mdd_mult\": 0.8,\n", " \"new_hazard_path\": \"path/to/new_hazard.h5\",\n", " \"reassign_impf_id\": {\"TC\": {1: 2}},\n", " \"color_rgb\": [0.1, 0.5, 0.3],\n", " \"init_cost\": 10000,\n", " \"periodic_cost\": 500,\n", "}\n", "\n", "meas_config = MeasureConfig.from_dict(measure_dict)\n", "\n", "print(meas_config)" ] }, { "cell_type": "markdown", "id": "ac98393f-575f-4580-ac4a-dae578638916", "metadata": {}, "source": [ "## Modifying Impact Functions: `ImpfsetModifierConfig`\n", "\n", "The `ImpfsetModifierConfig` is used to define how an adaptation measure changes the vulnerability (refer to the [impact functions tutorial](impact-functions-tutorial)).\n", "\n", "When \"translated\" to a `Measure` object the `ImpfsetModifierConfig` populates the `impfset_change` attribute with a function that takes an `ImpactFuncSet` and returns a modified one, according to the specifications.\n", "\n", "```{note}\n", "Modifications are always applied to a specific hazard type (`haz_type` parameter).\n", "```\n", "\n", "`ImpfsetModifierConfig` allows you to modify the main components of an impact function set, as well as to replace it entirely:\n", "\n", "- The MDD (Mean Damage Degree) array: via `impf_mdd_mult` to scale it and `impf_mdd_add` to shift it.\n", "- The PAA (Percentage of Assets Affected) array: via `impf_paa_mult` to scale it and `impf_paa_add` to shift it.\n", "- The intensity array: via `impf_int_mult` to scale it and `impf_int_add` to shift it.\n", "- Replacing the set: via providing the `new_impfset_path` parameter. It needs to be a valid `.xlsx` file readable by `ImpactFuncSet.from_excel()`\n", "\n", "See below for code examples.\n", "\n", "```{warning}\n", "If you provide a new_impfset_path and other modifiers, CLIMADA will load the new file first and then apply the modifiers to it. (A warning will be issued to ensure this sequence is intended).\n", "```\n", "\n", "```{note}\n", "By default the changes are applied to all the impact functions in the set, but you can provide the `impf_ids` parameter to apply the changes to a selection of impact function ids.\n", "```" ] }, { "cell_type": "code", "execution_count": 2, "id": "5ffb447b-1b8f-4e40-9d1c-7db33a11255e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--- Scaling Config ---\n", "ImpfsetModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\timpf_ids=[1, 2]\n", "\t\t\timpf_mdd_mult=0.8\n", "\t\t\timpf_int_add=5.0\n", ")\n", "\n", "--- Replacement Config ---\n", "ImpfsetModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\tnew_impfset_path='path/to/new_impact_functions.xlsx'\n", ")\n" ] } ], "source": [ "from climada.entity.measures.measure_config import ImpfsetModifierConfig\n", "\n", "# 1. Scaling existing Impact Functions\n", "# Let's say we want to simulate a 20% reduction in MDD\n", "# and a slight shift in the intensity threshold for Hazard 'TC'.\n", "impf_mod_scaling = ImpfsetModifierConfig(\n", " haz_type=\"TC\",\n", " impf_ids=[1, 2], # Apply only to specific function IDs\n", " impf_mdd_mult=0.8, # Reduce Mean Damage Degree by 20%\n", " impf_int_add=5.0, # Shift intensity axis by 5 units (e.g., higher resistance)\n", ")\n", "\n", "print(\"--- Scaling Config ---\")\n", "print(impf_mod_scaling)\n", "\n", "# 2. Replacing the Impact Function Set from a file\n", "# Useful for measures that implement completely new building standards.\n", "impf_mod_replace = ImpfsetModifierConfig(\n", " haz_type=\"TC\", new_impfset_path=\"path/to/new_impact_functions.xlsx\"\n", ")\n", "\n", "print(\"\\n--- Replacement Config ---\")\n", "print(impf_mod_replace)" ] }, { "cell_type": "markdown", "id": "234ebc89-83b0-42b1-8b97-734016306b84", "metadata": {}, "source": [ "## Modifying Hazards: `HazardModifierConfig`\n", "\n", "The `HazardModifierConfig` is used to define how an adaptation measure changes the hazard (refer to the [hazard tutorial](hazard-tutorial)).\n", "\n", "When \"translated\" to a `Measure` object the `HazardModifierConfig` populates the `hazard_change` attribute with a function that takes a `Hazard` (possibly additional arguments, see below) and returns a modified one, according to the specifications.\n", "\n", "```{note}\n", "Modifications are always applied to a specific hazard type (`haz_type` parameter).\n", "```\n", "\n", "`HazardModifierConfig` allows you to modify the intensity and frequency of the hazard, to apply a cutoff on the return period of impacts, as well as to replace it entirely:\n", "\n", "- The intensity matrix: via `haz_int_mult` to scale it and `haz_int_add` to shift it.\n", "- The frequency array: via `haz_freq_mult` to scale it and `haz_freq_add` to shift it.\n", "- Replacing the hazard: via providing the `new_hazard_path` parameter. It needs to be a valid hazard HDF5 file readable by `Hazard.from_hdf5()`\n", "- Applying a cutoff on frequency based on impacts: via `impact_rp_cutoff` (see the note).\n", "\n", "```{note}\n", "Providing a value for `impact_rp_cutoff` \"removes\" (it sets their intensity to 0.) events from the hazard, for which the exceedance frequency (inverse of return period) of impacts is below the given threshold.\n", "\n", "For instance providing 1/20, would remove all events whose impacts have a return period below 20 years.\n", "\n", "In that case the function changing the hazard (`Measure.hazard_change`) will be a function with the following signature:\n", "\n", " f(hazard: Hazard, # The hazard to apply on\n", " exposures: Exposures, # The exposure for the impact computation\n", " impfset: ImpactFuncSet, # The impfset for the impact computation\n", " base_hazard: Hazard, # The hazard for the impact computation\n", " exposures_region_id: Optional[list[int]] = None, # Region id to filter to\n", " ) -> Hazard\n", "```\n", "\n", "```{warning}\n", "If you provide a new_hazard_path and other modifiers, CLIMADA will load the new file first and then apply the modifiers to it. (A warning will be issued to ensure this sequence is intended).\n", "```" ] }, { "cell_type": "code", "execution_count": 3, "id": "f6061c1c-b21f-4aef-a394-c172784a25ab", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--- Scaling Config ---\n", "HazardModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\thaz_int_add=-10\n", "\t\t\thaz_freq_mult=0.8\n", ")\n", "\n", "--- Replacement Config ---\n", "HazardModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\tnew_hazard_path='path/to/new_floods.h5'\n", ")\n", "\n", "--- Cutoff Config ---\n", "HazardModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\timpact_rp_cutoff=0.05\n", ")\n" ] } ], "source": [ "from climada.entity.measures.measure_config import HazardModifierConfig\n", "\n", "# 1. Scaling existing hazard\n", "# Let's say we want to simulate a 20% reduction in frequency\n", "# and a reduction by 10m/s in the intensity for our tropical cyclones.\n", "haz_mod = HazardModifierConfig(\n", " haz_type=\"TC\",\n", " haz_int_add=-10, # Reduce hazard intensity by 10 units\n", " haz_freq_mult=0.8, # Scale hazard frequency by 20%\n", ")\n", "\n", "print(\"--- Scaling Config ---\")\n", "print(haz_mod)\n", "\n", "# 2. Replacing the hazard from a file\n", "# Useful for measures that correspond to a different hazard modelling.\n", "# E.g., a dike leading to a change in (physical) flood modelling.\n", "haz_mod_new = HazardModifierConfig(\n", " haz_type=\"FL\", new_hazard_path=\"path/to/new_floods.h5\"\n", ")\n", "\n", "print(\"\\n--- Replacement Config ---\")\n", "print(haz_mod_new)\n", "\n", "# 3. Applying a cutoff on the return period of the impacts\n", "# Useful when measures are defined to avoid damage for a specific RP (exceedance frequency).\n", "# Note that it looks a the distribution of the impacts, not the hazard intensity!\n", "haz_mod_cutoff = HazardModifierConfig(\n", " haz_type=\"TC\",\n", " impact_rp_cutoff=1\n", " / 20, # Set intensity to 0 for events with impacts with a return period below 20 years\n", ")\n", "\n", "print(\"\\n--- Cutoff Config ---\")\n", "print(haz_mod_cutoff)" ] }, { "cell_type": "markdown", "id": "c7499c1a-2491-42c4-bdbb-d224090b85fb", "metadata": {}, "source": [ "## Modifying Exposures: `ExposuresModifierConfig`\n", "\n", "The `ExposuresModifierConfig` is used to define how an adaptation measure changes the exposure (refer to the [exposure tutorial](exposure-tutorial)).\n", "\n", "When \"translated\" to a `Measure` object the `ExposuresModifierConfig` populates the `exposures_change` attribute with a function that takes an `Exposures` and returns a modified one, according to the specifications.\n", "\n", "`ExposuresModifierConfig` allows you to modify the impact function assigned to different hazard, to set a list of points to 0 value, or to load a different Exposures:\n", "\n", "- Remapping the impact function: via `reassign_impf_id` with a dictionary of the form `{haz_type: {old_id: new_id}}`.\n", "- Setting values to zero: via `set_to_zero` with a list of indices of the exposure GeoDataFrame.\n", "- Replacing the exposure: via providing the `new_exposures_path` parameter. It need to be a valid HDF5 exposure file readable by `Exposures.from_hdf5()`\n", "\n", "```{warning}\n", "If you provide a new_exposures_path and other modifiers, CLIMADA will load the new file first and then apply the modifiers to it. (A warning will be issued to ensure this sequence is intended).\n", "```" ] }, { "cell_type": "code", "execution_count": 4, "id": "5237930d-a18c-4498-afe5-373c5dadf882", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--- First Config ---\n", "ExposuresModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\treassign_impf_id={'TC': {1: 2}}\n", "\t\t\tset_to_zero=[0, 25, 78]\n", ")\n", "\n", "--- Replacement Config ---\n", "ExposuresModifierConfig(\n", "\t\tNon default fields:\n", "\t\t\tnew_exposures_path='path/to/exposures.h5'\n", ")\n" ] } ], "source": [ "from climada.entity.measures.measure_config import ExposuresModifierConfig\n", "\n", "# 1. Changing existing Exposures\n", "exp_mod = ExposuresModifierConfig(\n", " reassign_impf_id={\"TC\": {1: 2}}, # Remaps exposures points with impf_TC == 1 to 2.\n", " set_to_zero=[\n", " 0,\n", " 25,\n", " 78,\n", " ], # Sets the value of exposure points with index 0, 25 and 78 to 0.\n", ")\n", "\n", "print(\"--- First Config ---\")\n", "print(exp_mod)\n", "\n", "# 2. Replacing the expoosure from a file\n", "exp_mod_new = ExposuresModifierConfig(new_exposures_path=\"path/to/exposures.h5\")\n", "\n", "print(\"\\n--- Replacement Config ---\")\n", "print(exp_mod_new)" ] }, { "cell_type": "markdown", "id": "2c2d4488-28e5-4ced-b9cf-e4d5c0cade3e", "metadata": {}, "source": [ "## Defining the financial aspects of the measure\n", "\n", "For in depth description of CostIncome objects, refer to the [related tutorial](cost-income-tutorial).\n", "\n", "```{note}\n", "The default for mkt_price_year if not provided is the current year.\n", "```\n", "\n", "You can easily define the CostIncome object to be associated with the measure using `CostIncomeConfig`:" ] }, { "cell_type": "code", "execution_count": 5, "id": "7c107fc5-606b-4904-8b8e-059f846c2e39", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "--- Growth & Income Config ---\n", "CostIncomeConfig(\n", "\t\tNon default fields:\n", "\t\t\tinit_cost=500000.0\n", "\t\t\tperiodic_cost=20000.0\n", "\t\t\tperiodic_income=100000.0\n", "\t\t\tcost_yearly_growth_rate=0.02\n", "\t\t\tincome_yearly_growth_rate=0.03\n", ")\n", "\n", "--- Custom Schedule Config ---\n", "CostIncomeConfig(\n", "\t\tNon default fields:\n", "\t\t\tcustom_cash_flows=[{'date': '2024-01-01', 'value': -1000000}, {'date': '2029-01-01', 'value': -200000}, {'date': '2034-01-01', 'value': 500000}]\n", ")\n" ] } ], "source": [ "from climada.entity.measures.measure_config import CostIncomeConfig\n", "\n", "# This models a measure where costs increase by 2% annually,\n", "# but it generates 100k in yearly income which grows by 3%.\n", "growth_finance = CostIncomeConfig(\n", " init_cost=500_000.0,\n", " periodic_cost=20_000.0,\n", " cost_yearly_growth_rate=0.02,\n", " periodic_income=100_000.0,\n", " income_yearly_growth_rate=0.03,\n", " freq=\"Y\",\n", ")\n", "\n", "print(\"\\n--- Growth & Income Config ---\")\n", "print(growth_finance)\n", "\n", "\n", "# Custom Cash Flow\n", "# If the investment isn't linear (e.g., a major retrofit in year 5),\n", "# you can define a list of specific events.\n", "custom_schedule = [\n", " {\"date\": \"2024-01-01\", \"value\": -1000000}, # Initial cost\n", " {\"date\": \"2029-01-01\", \"value\": -200000}, # Mid-term overhaul\n", " {\"date\": \"2034-01-01\", \"value\": 500000}, # Terminal value\n", "]\n", "\n", "custom_finance = CostIncomeConfig(custom_cash_flows=custom_schedule)\n", "\n", "print(\"\\n--- Custom Schedule Config ---\")\n", "print(custom_finance)" ] }, { "cell_type": "markdown", "id": "ab4216dd-fd0b-4939-844d-56bd5ea49504", "metadata": {}, "source": [ "## Reading from and writing to\n", "\n", "You can easily write/read measure configurations from YAML, as well as from pandas Series.\n", "\n", "You can also create `Measures`/`MeasureSet` directly, using the same methods (these methods first load the file as a `MeasureConfig` and convert it directly to a `Measure`)\n", "Similarly you can still create `MeasureSet` from legacy Excel or matlab files using `MeasureSet.from_excel()` which takes care of remapping the legacy parameter names to the new ones.\n", "See the [measure tutorial](measure-tutorial) for more details on that." ] }, { "cell_type": "markdown", "id": "63132690-dd6f-4f45-96a9-5519fa2dec07", "metadata": {}, "source": [ "\n", "```python\n", "import pandas as pd\n", "from climada.entity.measures.measure_config import MeasureConfig\n", "\n", "# 1. Exporting to YAML\n", "# Assuming 'my_measure_config' is a MeasureConfig object created previously\n", "my_measure_config.to_yaml(\"seawall_config.yaml\")\n", "\n", "# 2. Loading from YAML\n", "loaded_measure_config = MeasureConfig.from_yaml(\"seawall_config.yaml\")\n", "\n", "# 3. Loading from Pandas\n", "row_data = pd.Series({\n", " \"name\": \"Mangrove_Restoration\",\n", " \"haz_type\": \"TC\",\n", " \"impf_mdd_mult\": 0.7,\n", " \"init_cost\": 250000,\n", " \"color_rgb\": (0.1, 0.8, 0.1)\n", "})\n", "\n", "pandas_measure_config = MeasureConfig.from_row(row_data)\n", "\n", "# 4. Measure object directly\n", "measure = Measure.from_yaml(\"seawall_config.yaml\")\n", "```" ] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:climada_env_dev]", "language": "python", "name": "conda-env-climada_env_dev-py" }, "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.15" } }, "nbformat": 4, "nbformat_minor": 5 }