{ "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 close that gap, the `measure` module now ships `MeasureConfig` objects which can be read from and written to files, and which enable \"declarative\" defining of `Measure` objects.\n", "As such `MeasureConfig` objects re-implement and improve the workflow from the old `Measure` objects and handle the translation to the new `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 functions to define what change to apply to the `Exposures`, `ImpactFuncSet`, `Hazard` objects | Defines the changes to apply using the (former) declarative 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 all effects of a `MeasureConfig` can be directly defined from the `MeasureConfig` container (with `from_dict()`, 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 with impacts below this threshold 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 exposures 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": "37a913e2-6517-45ee-b011-58de8d740d77", "metadata": {}, "source": [ "```{note}\n", "The string representation of a `MeasureConfig` object filters out the default values to avoid clutter.\n", "\n", "You can see all fields using `MeasureConfig.to_dict(omit_default=False)`\n", "```" ] }, { "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 horziontal translation of the impact function 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 array by 5 units, i.e. move impact function by 5 units to the right (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", " def hazard_change(\n", " hazard: Hazard, # Hazard to apply change to\n", " base_exposures: Exposures, # Exposure to use for impact computation\n", " base_impfset: ImpactFuncSet, # Impfset to use for impact computation\n", " base_hazard: Hazard, # Hazard to use for impact computation\n", " exposures_region_id: Optional[list[int]] = None, # Exposures id to restrict the effect to\n", " ) -> Hazard\n", "\n", "The base_<> triplet is provided by default when the measure is applied so you are not required to supply them if they should indeed be the base triplet before the measure is applied.\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={\n", " \"TC\": {1: 2}\n", " }, # Reassigns exposures points with impact function id 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', 'cost': 1000000.0, 'income': 0.0}, {'date': '2029-01-01', 'cost': 200000.0, 'income': 0.0}, {'date': '2034-01-01', 'cost': 10000.0, 'income': 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", "# By default, the periodicity of costs and income is yearly.\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", ")\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 custom cash flow, with negative values representing\n", "# net costs and positive ones net benefits.\n", "custom_schedule = [\n", " {\"date\": \"2024-01-01\", \"cost\": 1000000.0, \"income\": 0.0},\n", " {\"date\": \"2029-01-01\", \"cost\": 200000.0, \"income\": 0.0},\n", " {\"date\": \"2034-01-01\", \"cost\": 10000.0, \"income\": 500000},\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 }