Skip to content

Add tools.impacts: GMT-to-impact prediction toolkit via RIME emulators#479

Open
Wegatriespython wants to merge 19 commits into
iiasa:mainfrom
Wegatriespython:feat/physical-climate-impacts
Open

Add tools.impacts: GMT-to-impact prediction toolkit via RIME emulators#479
Wegatriespython wants to merge 19 commits into
iiasa:mainfrom
Wegatriespython:feat/physical-climate-impacts

Conversation

@Wegatriespython

@Wegatriespython Wegatriespython commented Feb 20, 2026

Copy link
Copy Markdown
Contributor

This PR introduces the tools.impacts library and implements it for SPARCCLE project workflows.

This PR extracts does two things : A) introduces the tools.impacts module, adds impacts.py files for domains buildings, cooling_impacts, water. Compared to #466, it refreshes the source data files for rime, replacing downscaled data from Jones et al 2025, with grid level computed impacts data based on Li et al 2025 for wet cooling, and Qin et al 2023 for dry cooling.

B) It implements SPARRCLE project specific workflows using the workflow class, covering physical-impact integration into a range of SPARRCLE scenarios.

How to review

Five commits on main:

Commit Scope
2e5d5c6b1 tools/impacts/ and tools/iamc/ helpers — toolkit only, no domain code.
0c106e9f0 model/buildings/impacts.py (full implementation) plus per-SSP theta_*, rc_sector_fractions_*, and correction_coefficients_* calibration data.
dc20285f5 model/water/data/cooling_impacts.py — wet cooling via relation_activity, dry cooling via capacity_factor.
2445a7dbd model/water/data/impacts.py and basin geometry helpers. Water is opt-in (code path exists; not wired into the active workflow).
d124e411c project/sparrcle/{cli.py, scenario_config.yaml, workflow.py}Workflow-driven graph and mix-models sparrcle run CLI.

Toolkit layer (tools/impacts/, tools/iamc/)

Module Change
rime.py GWL-binned nearest-neighbor lookup. Adapted reimplementation of iiasa/rime (GPL-3.0); attribution in module docstring. Single cached open_rime_dataset(filename) entry. Linearity check gates ensemble-mean reliability.
climate.py GmtArray NamedTuple, gmt_ensemble / gmt_expectation, load_magicc_gmt(magicc_dir, n_runs) (reads upstream MAGICC ensembles), persist_gmt_mean(scen, gmt).
temporal.py sample_to_model_years — resamples annual data to MESSAGE non-uniform timesteps via match-dispatched methods (point / average / forward-fill / interpolate); accepts wide or long input.
tools/iamc/__init__.py frame_to_iamc(df, variable, unit, *, region_col) shared (region, year, value) → IAMC helper used by both buildings and cooling.

Domain layer (model/buildings/, model/water/data/)

Module Change
model/buildings/impacts.py Full buildings CID kernel. Computes rc_spec/rc_therm demand reshaping under warming via theta transfer functions and SSP-specific correction coefficients (SSP2, SSP3). apply_building_cids writes the reshaped demand into the scenario and persists `Final Energy
model/water/data/cooling_impacts.py apply_cooling_cids — wet cooling via wet_cooling_cf_{parent} relation_activity rows on *__ot_fresh / *__cl_fresh variants and dry cooling via capacity_factor derating. IAMC ratios persisted under `Physical Climate Impact
model/water/data/impacts.py Water CID transforms including 157 → 217 basin expansion via split_basin_macroregion. Water is a code path only; not wired into the active SPARRCLE workflow.

SPARRCLE project (project/sparrcle/)

File Change
workflow.py generate(...) builds a Workflow.add_step graph: per-starter base → cooling (Phase 1, subprocess mix-models water-ix cooling) → CI_b / CI_p / CI_bp (Phase 2 CID variants). validate_inputs(config) preflight raises a single FileNotFoundError listing every missing MAGICC output, buildings calibration file, or RIME dataset before any clone is made.
cli.py mix-models sparrcle group; run selects a target subgraph, --from truncates, --go executes.
scenario_config.yaml Strict-validated; seven _GDP_CI_50_ensemble_* starter rows across SSP1/SSP2/SSP3.

PR checklist

  • Continuous integration checks all pass (ruff, ruff-format)
  • Add or expand tests; coverage checks pass
  • Add, expand, or update documentation (doc/api/tools-impacts.rst, docstrings, citations)
  • Update doc/whatsnew.rst

@Wegatriespython Wegatriespython self-assigned this Feb 20, 2026
@Wegatriespython Wegatriespython added the safe to test PRs from forks that do not pose security risks label Feb 20, 2026
Wegatriespython added a commit to Wegatriespython/message-ix-models that referenced this pull request Feb 20, 2026
@codecov

codecov Bot commented Feb 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 58.48708% with 450 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.6%. Comparing base (6110b41) to head (0fc75ae).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
message_ix_models/project/sparccle/workflow.py 0.0% 115 Missing ⚠️
message_ix_models/model/buildings/impacts.py 30.5% 100 Missing ⚠️
...sage_ix_models/model/water/data/cooling_impacts.py 63.2% 75 Missing ⚠️
message_ix_models/tools/impacts/climate.py 45.0% 39 Missing ⚠️
...e_ix_models/tests/model/water/data/test_impacts.py 41.3% 34 Missing ⚠️
message_ix_models/model/water/data/impacts.py 0.0% 28 Missing ⚠️
message_ix_models/model/water/utils.py 40.0% 21 Missing ⚠️
message_ix_models/tests/tools/impacts/test_rime.py 80.5% 15 Missing ⚠️
message_ix_models/tools/impacts/rime.py 76.5% 15 Missing ⚠️
message_ix_models/tools/impacts/temporal.py 89.8% 7 Missing ⚠️
... and 1 more
Additional details and impacted files
@@           Coverage Diff           @@
##            main    #479     +/-   ##
=======================================
- Coverage   74.2%   73.6%   -0.7%     
=======================================
  Files        320     335     +15     
  Lines      25655   26737   +1082     
=======================================
+ Hits       19047   19680    +633     
- Misses      6608    7057    +449     
Files with missing lines Coverage Δ
message_ix_models/cli.py 90.0% <ø> (ø)
...essage_ix_models/model/water/data/water_for_ppl.py 91.6% <100.0%> (+<0.1%) ⬆️
message_ix_models/project/sparccle/cli.py 100.0% <100.0%> (ø)
...age_ix_models/tests/model/test_building_impacts.py 100.0% <100.0%> (ø)
...els/tests/model/water/data/test_cooling_impacts.py 100.0% <100.0%> (ø)
...sage_ix_models/tests/tools/impacts/test_climate.py 100.0% <100.0%> (ø)
...age_ix_models/tests/tools/impacts/test_temporal.py 100.0% <100.0%> (ø)
message_ix_models/tools/iamc/__init__.py 94.0% <100.0%> (+0.4%) ⬆️
message_ix_models/tools/impacts/__init__.py 100.0% <100.0%> (ø)
message_ix_models/util/node.py 100.0% <100.0%> (ø)
... and 11 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@adrivinca

adrivinca commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

[edited]
thanks Vignesh, a lot of work!

I was wondering if inthe guidelines on how to run existing scenarios with impacts you could include what the requirements, etc. are... I understand that you need a specific scenario (ScenarioMIP BMT) to run impacts in building and other requirements (existing water module) to add/edit water impacts. right? you also need to have run MAGICC (also FAIR?), which should very clear upfront.

What is a user wanted to run the water module on an existing scenario and directly use this impact implementation? from my understanding, this is currently not ready-to-use with the current water cli commands. it is fine if it will be in another PR.

In particular for the water module impacts (impacts.py and cooling_impacts.py), we should think if we want to retire previous implementations, like this

@Wegatriespython

Copy link
Copy Markdown
Contributor Author

Actually I think it would be nice to get some user feedback before designing pre-maturely. Because that would give some valuable insights into how workflows other than this specific project are organised. Maybe you can give a proposed goal and we can see how it would work

Wegatriespython added a commit to Wegatriespython/message-ix-models that referenced this pull request Apr 3, 2026
Wegatriespython added a commit to Wegatriespython/message-ix-models that referenced this pull request Apr 30, 2026
@Wegatriespython Wegatriespython force-pushed the feat/physical-climate-impacts branch from 0d0274b to 715b617 Compare April 30, 2026 15:48
@Wegatriespython Wegatriespython force-pushed the feat/physical-climate-impacts branch from 715b617 to d124e41 Compare May 1, 2026 08:04

@adrivinca adrivinca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the impressive work. It might take a bit to digest and polish.
I maybe would like to clarify some doubts about data, the overal structure and usability of the impacts workflow (also for possible projects beyond sparccle), and the interaction with other modules, like water.

Data:

  • SSP1 & SSP2 only for building impacts? (data coefficients files)
  • Data/Impact/rime maybe subdivide by sectoral subfolders?

Conceptual questions:

  • Why is build_wet_cooling_constraints implementing impact differently than for water cooling system? is because the source data is different?
  • What is the different if you implement the thermal cooling impacts on a scenario without water wrt to a scenario with water contraints? Might clue is that there are differences and the secound route should also be possible in this framework

Structure:

  • it would be nice if there was a complete generic workflow like the one in sparccle (btw you misspelled with "sparrcle") project now that is not under the project, but that it could be used by any project. basically I suggest moving the sparccle worksflow to a generic impact worksflow when possible and just leave sparcle project specific config.

Documentation:

  • please see my previous comment on prerequirites (MAGICC), existing scenarios, ect
  • explain how to run individual impacts: building, power plants, water
  • add information on the reporting possibilities for each impact stream
  • explain what SSPs are covered and gaps
  • based on the suggestion to chang structure. document how to run specific impacts, not just magicc-rime generically.

Then there are some utils that improve or partially replace existing ones. Maybe worth if @khaeru also gives a look and you decide if these utils should cohexist or some should be updated:

  • tools/impact/temporal.py/sample_to_model_years partially reinvents util/compat/message_data/update_fix_and_inv_cost.py/add_missing_years

  • _demand_to_final_energy_iamc calls pint directly — util.convert_units exists

    Existing: util/_convert_units.py:16 — convert_units(data, unit_info, store) is a public utility that does exactly registry.Quantity(factor * data.values, unit_in).to(unit_out).magnitude on a pandas Series, using iam_units.registry.
    New code (buildings/impacts.py:449-450):
    registry.Quantity(df["value"].to_numpy(), "GWa/year")
    .to(_FINAL_ENERGY_UNIT).magnitude
    The APIs differ (convert_units is Series-oriented with a dict; the new code is a one-liner on an array), so the existing one can't be dropped in directly. But this is worth noting: the project already has a pint-based unit conversion utility and the new code essentially reimplements the same pint call. If _FINAL_ENERGY_UNIT or the source unit ever needs changing, there are now two divergent patterns to maintain.

if not ts.empty:
scen.check_out(timeseries_only=True)
try:
scen.add_timeseries(ts)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this a safe approach also in the case the legacy reporting, the water reporting (here) or others are run afterwards. There are cases where this timeseries addition might be removed.
Let's maybe explore the possible use-cases for impacts and make sure reporting is consistent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So for this, besides passing files manually I think we have to rely on subsequent caller hygiene to not remove timeseries blanket. For water reporting's own removal we can address but in general I think this might be a potential issue.

# MESSAGE demand parameter expects GWa
_EJ_TO_GWA = registry("1 EJ").to("GW * year").magnitude

_REFERENCE_SCENARIO = "SSP2"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this should not be hard-coded
maybe more to a workflow setup

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So the scenario is no longer hardcoded, however for gamma (which calibrates between STURM and CHILLED) I only have SSP2 runs so for now this has to be hardcoded here till we get more STURM runs.

Comment thread message_ix_models/model/water/data/cooling_impacts.py
Comment thread message_ix_models/model/buildings/impacts.py Outdated
Comment thread message_ix_models/model/water/data/cooling_impacts.py Outdated
@khaeru

khaeru commented May 8, 2026

Copy link
Copy Markdown
Member

Maybe worth if @khaeru also gives a look and you decide if these utils should cohexist or some should be updated

Without yet diving into the big diff, the summary Adriano wrote does seem to be on-point. Please reach out if I can help discuss how to improve existing utils to support the applications you have in this PR.

@Wegatriespython

Wegatriespython commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @adrivinca and @khaeru Khaeru. Follow-up pass to address the comments
Changes:

  • Renamed sparrcle -> sparccle (CLI group mix-models sparccle, package path).
  • New doc/impacts/index.rst splits tools.impacts (generic GMT/RIME prediction), model.*.impacts (MESSAGE scenario edits), and project.sparccle (protocol, starter selection, variant naming, orchestration).
  • doc/project/sparccle.rst stays high-level: SSP starters, economic/GDP-impact placeholder, physical climate workflow, prerequisites, CI_b/CI_p/CI_bp targets.
  • MAGICC clarified as the current file-reader/input format (climate-assessment workbook), not a requirement of the prediction layer, which only needs a GmtArray. FAIR not required.
  • Buildings calibration documented: gamma/correction_coeff reconciles CHILLED/RIME EI × STURM floor area with STURM baseline energy at the calibration temperature; theta is a second calibration against the SSP2023/WP-RC demand target; rc_sector_fractions identify the buildings component already in aggregate rc_spec/rc_therm so replacement demand does not double-count.
  • Wet vs dry cooling use different MESSAGE-side edits: wet constrains freshwater-cooled activity via relations; dry derates capacity_factor for __air technologies. Saline cooling not in the considered set of impacts.
  • Basin-level water availability added as a valid domain consumer in impacts docs. Still not a part of the active SPARCCLE physical workflow.
  • Node codelist threaded through the workflow (context.model.regions) into buildings and cooling impacts; will fail loudly for non-R12 since packaged RIME, STURM floor-area, and cooling-share data are R12-coded.
  • reference_scenario made explicit in the buildings public API and workflow. SSP-specific theta and rc_sector_fractions are selected by workflow SSP; gamma/correction-coefficient tables stay SSP2-derived (shipped artifacts).

Not done:

  • SPARCCLE workflow orchestrates starter scenarios with specific dependencies for emission and gmt files, and scenario protocol for layering impacts. The workflow itself uses the domain impacts.py files for the actual impact implementation and doesn't encode impact logic, only scenario protocol and file pathing. For now its not clear what the abstraction should be for cross project workflows, since climate-assessment has not merged PR 88, which gives the specific magicc file format we need.
  • Basin-level water impacts not wired into the water-ix CLI or SPARCCLE workflow. Transforms exist; arbitrary-scenario CLI use still needs a separate workflow design.
  • sample_to_model_years not merged with add_missing_years, nor _demand_to_final_energy_iamc with convert_units. APIs are close but not drop-in: sample_to_model_years handles point/average/forward-fill/interpolate over MESSAGE periods and long-form inputs; convert_units is Series/dict-oriented vs a small array conversion here.
  • data/impacts/rime/ not subdivided by sector. Few packaged emulator files today; new impacts docs name the consuming domain modules. Deeper hierarchy when the file set warrants it.

Reporting/timeseries: buildings and cooling CID inputs persisted as — Final Energy|Residential and Commercial|{Cooling,Heating}, Physical Climate Impact|Surface Temperature (GSAT)|Mean, Physical Climate Impact|Thermoelectric Cooling|{Freshwater Cooling Activity Limit Ratio, Dry Cooling Capacity Factor Ratio}. Ideally downstream reporting retrieves via scenario.timeseries() and does not strip the namespaces.

Comment thread doc/impacts/index.rst
Comment thread doc/impacts/index.rst

@adrivinca adrivinca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

just minor comments on the doc

GmtArray + load_magicc_gmt + persist_gmt_mean in climate; cached
open_rime_dataset, clip_gmt, and predict_rime with linearity guard
in rime; sample_to_model_years (point/average/interpolate, long-form)
in temporal; frame_to_iamc helper in tools/iamc;
extract_region_code in util/node (callers in water_for_ppl and
water/report migrate from inline split).

API doc page tools-impacts.rst.
compute_building_cids and apply_building_cids drive the RIME
EI-vs-GWL emulator into MESSAGE residential/commercial demand,
with theta-calibrated reference scenarios and sector_fractions
from STURM. CID timeseries persist as Final Energy|Residential
and Commercial|{Cooling,Heating} in EJ/yr.

Packaged data: theta_{cool,heat}_SSP{2,3}.csv,
rc_sector_fractions_SSP{2,3}.csv, sturm_floor_area_R12_{resid,comm}.csv,
correction_coefficients_{cool,heat}_SSP2_{resid,comm}.csv,
region_EI_{cool,heat}_gwl_binned.nc.
build_wet_cooling_constraints emits relation_activity rows that bound
freshwater-using thermoelectric variants by per-region capacity-factor
ratios under warming (Li et al. 2025); build_dry_cooling_factors
rescales air-cooled capacity factors (Qin et al. 2023). Wet relations
named wet_cooling_cf_{parent}; CID timeseries persist as Physical
Climate Impact|Thermoelectric Cooling|{Freshwater,Dry}*.

Packaged: r12_thermoelectric_gwl.nc, r12_capacity_gwl_ensemble.nc.
model/water/data/impacts.py: predict_water_rime drives basin-level
RIME emulators, with seasonal2step bifurcation when applicable, and
expands to MESSAGE basin-region rows.

model/water/utils.py: load_basin_mapping (cached), split_basin_macroregion,
NAN_BASIN_IDS / N_RIME_BASINS / N_MESSAGE_BASINS constants.

Packaged: joint_bifurcation_mapping_CWatM_2step.csv,
rime_regionarray_local_temp_CWatM_seasonal2step_window11.nc,
rime_regionarray_qr_CWatM_annual_window11.nc.
project/sparrcle wires Phase 1 (water-ix cooling subprocess) and
Phase 2 (buildings + cooling CIDs) into a Workflow with one
base/cooling/CI_b/CI_p/CI_bp step set per starter. validate_inputs
preflights MAGICC outputs, RIME datasets, and per-SSP buildings
calibration files so a misconfigured run fails before any clone.
CLI: mix-models sparrcle run TARGET (--from / --go).
Drop test_sparrcle.py (trivial click registration check) and
test_sparrcle_workflow.py (graph-key assertions with load_config and
validate_inputs both monkeypatched away).

Replace the permanently-skipped theta calibration test in
test_building_impacts.py with two in-memory tests of
prepare_building_demand that run unconditionally in CI:
- test_prepare_building_demand_substitution_arithmetic: verifies the
  substitution formula new = old*(1-frac) + cid against known inputs
- test_prepare_building_demand_missing_cid_zerofills: verifies that an
  empty CID frame contributes zero rather than NaN

Add netCDF4 and plotnine as dev dependencies (needed to run the
remaining predict_building_ei and package-import paths in tests).
CI was failing on tests that open RIME NetCDF files via xarray
(test_ratio_* in test_cooling_impacts.py, test_warming_* in
test_building_impacts.py) because no NetCDF backend was installed.
The files exist in the repo via LFS; the skip guard only checked
existence, not openability.
@adrivinca

Copy link
Copy Markdown
Contributor

I am happy with the changes. We agreed that we decide on a clearer split of code vs content documentation in #442 or similar.
@khaeru could you please check if we can merge this PR? thanks!

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

Labels

safe to test PRs from forks that do not pose security risks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants