Defining Adaptation Measures with configurations#

Introduction#

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’).

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.

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.

Measure objects can be instantiated from MeasureConfig objects using Measure.from_config(<MeasureConfig object>).

Summary of Measure vs MeasureConfig#

Measure

MeasureConfig

Is used for the actual computation

Is transformed into a Measure for actual computation

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.)

Accepts any possible effect as long as it can be defined as a python function

Is restricted to a set of defined effects

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)

Configuration classes#

The definition of measures via MeasureConfig is organized into a hierarchy of specialized classes:

  • MeasureConfig: The top-level container for a single measure.

  • HazardModifierConfig: Defines how the hazard is changed (e.g., shifting intensity).

  • ImpfsetModifierConfig: Adjusts impact functions (e.g., scaling vulnerability curves).

  • ExposuresModifierConfig: Modifies exposure data (e.g., reassigning IDs or zeroing regions).

  • CostIncomeConfig: Handles the financial aspects, including initial costs and recurring income.

Note that everything can be defined and accessed directly from the MeasureConfig container, the underlying ones are there to keep things organized.

In the following we present each of these subclasses and the possibilities they offer.

Quickstart#

You can directly define a MeasureConfig object with a dictionary, using MeasureConfig.from_dict().

Below are the possible parameters:

Scope

Parameter

Type

Description

Top-Level

name (required)

str

Unique identifier for the measure.

haz_type (required)

str

The hazard type this measure targets (e.g., “TC”, “FL”).

implementation_duration

str

Pandas offset alias (e.g., “2Y”) for implementation time.

color_rgb

tuple

RGB triple (0-1 range) for plotting and visualization.

Hazard

haz_int_mult

float

Multiplier for hazard intensity (default: 1.0).

haz_int_add

float

Additive offset for hazard intensity (default: 0.0).

new_hazard_path

Path to an HDF5 file to replace the current hazard.

impact_rp_cutoff

float

Return period (years) threshold; events below this are ignored.

Impact Function

impf_ids

list

Specific impact function IDs to modify (None = all).

impf_mdd_mult / _add

float

Scale or shift the Mean Damage Degree curve.

impf_paa_mult / _add

float

Scale or shift the Percentage of Assets Affected curve.

impf_int_mult / _add

float

Scale or shift the intensity axis of the function.

new_impfset_path

Path to an Excel file to replace the impact function set.

Exposures

reassign_impf_id

dict

Mapping {haz_type: {old_id: new_id}} for reclassification.

set_to_zero

list

List of Region IDs where exposure value is set to 0.

new_exposures_path

Path to an HDF5 file to replace the current exposures.

Cost & Income

init_cost

float

One-time investment cost (absolute value).

periodic_cost

float

Recurring maintenance/operational costs.

periodic_income

float

Recurring income generated by the measure.

mkt_price_year

int

Reference year for pricing (default: current year).

freq

str

Frequency of cash flows (e.g., “Y” for yearly).

custom_cash_flows

list[dict]

Explicit list of dates and values for complex cash flows. (See the cost income tutorial)

from climada.entity.measures.measure_config import MeasureConfig

measure_dict = {
    "name": "Tutorial measure",
    "haz_type": "TC",
    "impf_ids": [1, 2],
    "impf_mdd_mult": 0.8,
    "new_hazard_path": "path/to/new_hazard.h5",
    "reassign_impf_id": {"TC": {1: 2}},
    "color_rgb": [0.1, 0.5, 0.3],
    "init_cost": 10000,
    "periodic_cost": 500,
}

meas_config = MeasureConfig.from_dict(measure_dict)

print(meas_config)
MeasureConfig(
	name='Tutorial measure'
	haz_type='TC'
	impfset_modifier=ImpfsetModifierConfig(
		Non default fields:
			impf_ids=[1, 2]
			impf_mdd_mult=0.8
)
	hazard_modifier=HazardModifierConfig(
		Non default fields:
			new_hazard_path='path/to/new_hazard.h5'
)
	exposures_modifier=ExposuresModifierConfig(
		Non default fields:
			reassign_impf_id={'TC': {1: 2}}
)
	cost_income=CostIncomeConfig(
		Non default fields:
			init_cost=10000
			periodic_cost=500
)
	implementation_duration=None
	color_rgb=(0.1, 0.5, 0.3))

Modifying Impact Functions: ImpfsetModifierConfig#

The ImpfsetModifierConfig is used to define how an adaptation measure changes the vulnerability (refer to the impact functions tutorial).

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.

Note

Modifications are always applied to a specific hazard type (haz_type parameter).

ImpfsetModifierConfig allows you to modify the main components of an impact function set, as well as to replace it entirely:

  • The MDD (Mean Damage Degree) array: via impf_mdd_mult to scale it and impf_mdd_add to shift it.

  • The PAA (Percentage of Assets Affected) array: via impf_paa_mult to scale it and impf_paa_add to shift it.

  • The intensity array: via impf_int_mult to scale it and impf_int_add to shift it.

  • Replacing the set: via providing the new_impfset_path parameter. It needs to be a valid .xlsx file readable by ImpactFuncSet.from_excel()

See below for code examples.

Warning

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).

Note

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.

from climada.entity.measures.measure_config import ImpfsetModifierConfig

# 1. Scaling existing Impact Functions
# Let's say we want to simulate a 20% reduction in MDD
# and a slight shift in the intensity threshold for Hazard 'TC'.
impf_mod_scaling = ImpfsetModifierConfig(
    haz_type="TC",
    impf_ids=[1, 2],  # Apply only to specific function IDs
    impf_mdd_mult=0.8,  # Reduce Mean Damage Degree by 20%
    impf_int_add=5.0,  # Shift intensity axis by 5 units (e.g., higher resistance)
)

print("--- Scaling Config ---")
print(impf_mod_scaling)

# 2. Replacing the Impact Function Set from a file
# Useful for measures that implement completely new building standards.
impf_mod_replace = ImpfsetModifierConfig(
    haz_type="TC", new_impfset_path="path/to/new_impact_functions.xlsx"
)

print("\n--- Replacement Config ---")
print(impf_mod_replace)
--- Scaling Config ---
ImpfsetModifierConfig(
		Non default fields:
			impf_ids=[1, 2]
			impf_mdd_mult=0.8
			impf_int_add=5.0
)

--- Replacement Config ---
ImpfsetModifierConfig(
		Non default fields:
			new_impfset_path='path/to/new_impact_functions.xlsx'
)

Modifying Hazards: HazardModifierConfig#

The HazardModifierConfig is used to define how an adaptation measure changes the hazard (refer to the hazard tutorial).

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.

Note

Modifications are always applied to a specific hazard type (haz_type parameter).

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:

  • The intensity matrix: via haz_int_mult to scale it and haz_int_add to shift it.

  • The frequency array: via haz_freq_mult to scale it and haz_freq_add to shift it.

  • Replacing the hazard: via providing the new_hazard_path parameter. It needs to be a valid hazard HDF5 file readable by Hazard.from_hdf5()

  • Applying a cutoff on frequency based on impacts: via impact_rp_cutoff (see the note).

Note

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.

For instance providing 1/20, would remove all events whose impacts have a return period below 20 years.

In that case the function changing the hazard (Measure.hazard_change) will be a function with the following signature:

f(hazard: Hazard,           # The hazard to apply on
         exposures: Exposures,     # The exposure for the impact computation
         impfset: ImpactFuncSet,   # The impfset for the impact computation
         base_hazard: Hazard,      # The hazard for the impact computation
         exposures_region_id: Optional[list[int]] = None, # Region id to filter to
         ) -> Hazard

Warning

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).

from climada.entity.measures.measure_config import HazardModifierConfig

# 1. Scaling existing hazard
# Let's say we want to simulate a 20% reduction in frequency
# and a reduction by 10m/s in the intensity for our tropical cyclones.
haz_mod = HazardModifierConfig(
    haz_type="TC",
    haz_int_add=-10,  # Reduce hazard intensity by 10 units
    haz_freq_mult=0.8,  # Scale hazard frequency by 20%
)

print("--- Scaling Config ---")
print(haz_mod)

# 2. Replacing the hazard from a file
# Useful for measures that correspond to a different hazard modelling.
# E.g., a dike leading to a change in (physical) flood modelling.
haz_mod_new = HazardModifierConfig(
    haz_type="FL", new_hazard_path="path/to/new_floods.h5"
)

print("\n--- Replacement Config ---")
print(haz_mod_new)

# 3. Applying a cutoff on the return period of the impacts
# Useful when measures are defined to avoid damage for a specific RP (exceedance frequency).
# Note that it looks a the distribution of the impacts, not the hazard intensity!
haz_mod_cutoff = HazardModifierConfig(
    haz_type="TC",
    impact_rp_cutoff=1
    / 20,  # Set intensity to 0 for events with impacts with a return period below 20 years
)

print("\n--- Cutoff Config ---")
print(haz_mod_cutoff)
--- Scaling Config ---
HazardModifierConfig(
		Non default fields:
			haz_int_add=-10
			haz_freq_mult=0.8
)

--- Replacement Config ---
HazardModifierConfig(
		Non default fields:
			new_hazard_path='path/to/new_floods.h5'
)

--- Cutoff Config ---
HazardModifierConfig(
		Non default fields:
			impact_rp_cutoff=0.05
)

Modifying Exposures: ExposuresModifierConfig#

The ExposuresModifierConfig is used to define how an adaptation measure changes the exposure (refer to the exposure tutorial).

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.

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:

  • Remapping the impact function: via reassign_impf_id with a dictionary of the form {haz_type: {old_id: new_id}}.

  • Setting values to zero: via set_to_zero with a list of indices of the exposure GeoDataFrame.

  • Replacing the exposure: via providing the new_exposures_path parameter. It need to be a valid HDF5 exposure file readable by Exposures.from_hdf5()

Warning

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).

from climada.entity.measures.measure_config import ExposuresModifierConfig

# 1. Changing existing Exposures
exp_mod = ExposuresModifierConfig(
    reassign_impf_id={"TC": {1: 2}},  # Remaps exposures points with impf_TC == 1 to 2.
    set_to_zero=[
        0,
        25,
        78,
    ],  # Sets the value of exposure points with index 0, 25 and 78 to 0.
)

print("--- First Config ---")
print(exp_mod)

# 2. Replacing the expoosure from a file
exp_mod_new = ExposuresModifierConfig(new_exposures_path="path/to/exposures.h5")

print("\n--- Replacement Config ---")
print(exp_mod_new)
--- First Config ---
ExposuresModifierConfig(
		Non default fields:
			reassign_impf_id={'TC': {1: 2}}
			set_to_zero=[0, 25, 78]
)

--- Replacement Config ---
ExposuresModifierConfig(
		Non default fields:
			new_exposures_path='path/to/exposures.h5'
)

Defining the financial aspects of the measure#

For in depth description of CostIncome objects, refer to the related tutorial.

Note

The default for mkt_price_year if not provided is the current year.

You can easily define the CostIncome object to be associated with the measure using CostIncomeConfig:

from climada.entity.measures.measure_config import CostIncomeConfig

# This models a measure where costs increase by 2% annually,
# but it generates 100k in yearly income which grows by 3%.
growth_finance = CostIncomeConfig(
    init_cost=500_000.0,
    periodic_cost=20_000.0,
    cost_yearly_growth_rate=0.02,
    periodic_income=100_000.0,
    income_yearly_growth_rate=0.03,
    freq="Y",
)

print("\n--- Growth & Income Config ---")
print(growth_finance)


# Custom Cash Flow
# If the investment isn't linear (e.g., a major retrofit in year 5),
# you can define a list of specific events.
custom_schedule = [
    {"date": "2024-01-01", "value": -1000000},  # Initial cost
    {"date": "2029-01-01", "value": -200000},  # Mid-term overhaul
    {"date": "2034-01-01", "value": 500000},  # Terminal value
]

custom_finance = CostIncomeConfig(custom_cash_flows=custom_schedule)

print("\n--- Custom Schedule Config ---")
print(custom_finance)
--- Growth & Income Config ---
CostIncomeConfig(
		Non default fields:
			init_cost=500000.0
			periodic_cost=20000.0
			periodic_income=100000.0
			cost_yearly_growth_rate=0.02
			income_yearly_growth_rate=0.03
)

--- Custom Schedule Config ---
CostIncomeConfig(
		Non default fields:
			custom_cash_flows=[{'date': '2024-01-01', 'value': -1000000}, {'date': '2029-01-01', 'value': -200000}, {'date': '2034-01-01', 'value': 500000}]
)

Reading from and writing to#

You can easily write/read measure configurations from YAML, as well as from pandas Series.

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) 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. See the measure tutorial for more details on that.

import pandas as pd
from climada.entity.measures.measure_config import MeasureConfig

# 1. Exporting to YAML
# Assuming 'my_measure_config' is a MeasureConfig object created previously
my_measure_config.to_yaml("seawall_config.yaml")

# 2. Loading from YAML
loaded_measure_config = MeasureConfig.from_yaml("seawall_config.yaml")

# 3. Loading from Pandas
row_data = pd.Series({
    "name": "Mangrove_Restoration",
    "haz_type": "TC",
    "impf_mdd_mult": 0.7,
    "init_cost": 250000,
    "color_rgb": (0.1, 0.8, 0.1)
})

pandas_measure_config = MeasureConfig.from_row(row_data)

# 4. Measure object directly
measure = Measure.from_yaml("seawall_config.yaml")