Skip to content

[Option Appraisal Module] [Package 1: Measures] Split 1: Adds MeasureConfig dataclasses for serializable measure configuration.#1276

Open
spjuhel wants to merge 25 commits into
developfrom
feature/option-appraisal-dataclasses
Open

[Option Appraisal Module] [Package 1: Measures] Split 1: Adds MeasureConfig dataclasses for serializable measure configuration.#1276
spjuhel wants to merge 25 commits into
developfrom
feature/option-appraisal-dataclasses

Conversation

@spjuhel
Copy link
Copy Markdown
Collaborator

@spjuhel spjuhel commented Apr 7, 2026

Changes proposed in this PR:

This PR introduces a new module defining a family of dataclasses that encode adaptation measure parameters in a serializable, declarative form. This is a preparatory step for the broader Measure/MeasureSet rework.

The objective is to retain the possibility to define measures from modifiers in addition to the upcoming system that will use python functions directly, and provide a (better) user interface for this.

no existing code is changed.

New dataclasses:

  • MeasureConfig top-level configuration for a single measure, aggregating all modifier configs below.
  • ImpfsetModifierConfig parameters for modifying impact functions (intensity, MDD, PAA scaling, optional replacement)
  • HazardModifierConfig parameters for modifying hazard (intensity scaling, frequency cutoff, optional replacement)
  • ExposuresModifierConfig parameters for modifying exposures (impact function remapping, zeroing regions, optional replacement)
  • CostIncomeConfig serializable representation of cost/income cash flow parameters

All configs inherit from the abstract _ModifierConfig base, which provides shared to_dict/from_dict and a __repr__ that highlights non-default fields.

MeasureConfig supports full serialization to/from dict, YAML, and legacy Excel rows (from_row), which will serve as the migration path for the existing file-based I/O in MeasureSet.

PR Author Checklist

PR Reviewer Checklist

Comment thread climada/entity/measures/measure_config.py Outdated
Comment thread climada/entity/measures/measure_config.py
@chahank
Copy link
Copy Markdown
Member

chahank commented Apr 7, 2026

Excellent idea!

spjuhel added 7 commits April 7, 2026 10:22
Cleans-up, Docstringfies

Better to_dict, color parser, and post_inits

Removes duplicate docstring
…MADA-project/climada_python into feature/option-appraisal-dataclasses
@spjuhel spjuhel changed the title [Option Appraisal Module] - Adds MeasureConfig dataclasses for serializable measure configuration. [Option Appraisal Module] [Package 1: Measures] Split 1: Adds MeasureConfig dataclasses for serializable measure configuration. Apr 7, 2026
@spjuhel
Copy link
Copy Markdown
Collaborator Author

spjuhel commented Apr 8, 2026

@chahank

Question: Should measures (defined by MeasureConfig) be specific to a hazard? (I see reasons for both yes and no)

Currently, they are specific (thus configs require you to provide haz_type).

I see how you could want modifications of impact function to be for multiple hazards
But also you should probably not be able to apply a shift in intensity for multiple hazards?

Any opinion on that?

@spjuhel spjuhel marked this pull request as ready for review April 8, 2026 14:04
@spjuhel
Copy link
Copy Markdown
Collaborator Author

spjuhel commented Apr 8, 2026

This PR is officially open for review! Review is advised after #1274 is merged.

Actual changes to be reviewed are in:

  • climada/entity/measures/measure_config.py
  • climada/entity/measures/test/test_measure_config.py
  • doc/user-guide/climada_measure_config.ipynb (and a few other tutos where a target was added at the top)

@chahank
Copy link
Copy Markdown
Member

chahank commented Apr 9, 2026

@chahank

Question: Should measures (defined by MeasureConfig) be specific to a hazard? (I see reasons for both yes and no)

Currently, they are specific (thus configs require you to provide haz_type).

I see how you could want modifications of impact function to be for multiple hazards But also you should probably not be able to apply a shift in intensity for multiple hazards?

Any opinion on that?

Good question. I would then to keep it hazard type specific. But maybe best is to ask core users of the model :).
@ChrisFairless : any opinion?

Copy link
Copy Markdown
Member

@peanutfun peanutfun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed mostly on a technical level. Very nice code quality overall, nice class structure! I found a few issues. Sorry for the brevity 😬 ✌️

Comment thread climada/entity/measures/measure_config.py Outdated
Comment thread climada/entity/measures/measure_config.py Outdated
Comment thread climada/entity/measures/measure_config.py Outdated
Comment thread climada/entity/measures/measure_config.py Outdated
Comment thread climada/entity/measures/measure_config.py Outdated
Comment thread climada/entity/measures/measure_config.py
Comment thread climada/entity/measures/measure_config.py Outdated
Comment thread climada/entity/measures/measure_config.py Outdated
Comment thread climada/entity/measures/test/test_measure_config.py Outdated
Comment thread climada/entity/measures/test/test_measure_config.py Outdated
@spjuhel
Copy link
Copy Markdown
Collaborator Author

spjuhel commented May 13, 2026

@ValentinGebhart This has been reviewed on the technical aspects. Can you give a look at the guide here and tell me if its clear enough?

Note: The "measure tutorial" link at the end does not work yet and is a placeholder.

Copy link
Copy Markdown
Collaborator

@ValentinGebhart ValentinGebhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this very clear tutorial! I added only a few rephrasings and some comments at points where I thought it was not perfectly clear. However all are only minor things, and I find the explanations very clear about how to define the MeasureConfigs.

"\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",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really impossible to save python functions? That means that measures that do not correspond to MeasureConfigs cannot be saved?

"\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 handle the reading, writing and \"declarative\" defining of `Measure` objects.\n",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understood correctly, this sounds a bit misleading, as if MeasureConfig handle reading and writing of ALL Measure objects, while actually they can only read and write those that correspond to MeasureConfig objects, right?
Maybe rephrase to
"which can be read from and written to files, and which enable a "declarative" defining of Measure objects."

"- `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",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On first reading the meaning was not super clear to me. Maybe rephrase to
"Note that all effects of a MeasureConfig object can be directly defined using the MeasureConfig container, the underlying ones are there to keep things organized.

"| **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",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"| | `impact_rp_cutoff` | `float` | Return period (years) threshold; events below this are ignored. |\n",
"| | `impact_rp_cutoff` | `float` | Return period (years) threshold; events with impacts below this threshold are ignored. |\n",

" 0,\n",
" 25,\n",
" 78,\n",
" ], # Sets the value of exposure points with index 0, 25 and 78 to 0.\n",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems in conflict to what you wrote above. Here it sounds that you set to zero exposure points with specific index values, above it sounds that you set to zero exposure points with specific region_ids ("List of Region IDs where exposure value is set to 0."

" 0,\n",
" 25,\n",
" 78,\n",
" ], # Sets the value of exposure points with index 0, 25 and 78 to 0.\n",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems in conflict to what you wrote above. Here it sounds that you set to zero exposure points with specific index values, above it sounds that you set to zero exposure points with specific region_ids ("List of Region IDs where exposure value is set to 0."

" cost_yearly_growth_rate=0.02,\n",
" periodic_income=100_000.0,\n",
" income_yearly_growth_rate=0.03,\n",
" freq=\"Y\",\n",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The freq attribute is not shown in the below print, and it not entirely clear to me what it does. Above, it is written "frequecy of cash flows". That means that if one uses, e.g., freq="2Y", the growth rates etc are still yearly but the the costs and incomes occur only every two years?

"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",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe clarify what negative and positive values are, and above cost were given as positive values. I assume here negative values are one-time costs and negative values are one-time incomes?

"- 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",
Copy link
Copy Markdown
Collaborator

@ValentinGebhart ValentinGebhart May 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually wondering about this one: AFAIK you want to have each measure consist of three functions that modify each of the risk components. This parameter however looks like it takes into account all hazard, vulnerability and exposure, as it looks at the impacts per event, which seems a bit confusing with the three function structure. My main question is if this cutoff is applied at the start (computing the impact events to be excluded before applying all other stuff), or at the end (after all other effect were taken into account)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants