From cb6be3e01048446df3e27a6f07cf3707ee0d34d7 Mon Sep 17 00:00:00 2001 From: benmsanderson Date: Thu, 25 Jun 2026 17:17:29 +0200 Subject: [PATCH 1/2] fix(ciceroscmpy2): hold aerosols at PI for idealised conc-driven runs On the concentration-driven path (abrupt-4xCO2, 1pctCO2) _build_scendata_list sets emstart=nyend so CO2 stays concentration-driven. That also gated the user-supplied emissions overlay in _build_hybrid_emissions_data at `year >= nyend`, so the PI-flat aerosol / ozone-precursor emissions were applied only at the final year and every prior year fell back to the rising historical_em baseline. The result is a spurious growing aerosol cooling that drifts ERF down ~0.6 W/m2 across a 4xCO2 step (GSAT peaks ~1950 then cools) instead of holding flat. Decouple the overlay-application year from the model emstart via a new overlay_from argument, and pass nystart for idealised runs so the supplied PI emissions apply across the whole window. CO2 is unaffected: it is not supplied on the conc-driven path and stays concentration-driven via conc_run. The emis-driven esm-flat10 family already used emstart=1850 and is unchanged. Verified on abrupt-4xCO2: ERF now flat to +/-0.000 W/m2 (was -0.64) and GSAT rises monotonically with no spurious mid-run cooling. Co-Authored-By: Claude Opus 4.8 --- .../ciceroscmpy2_adapter.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/openscm_runner/adapters/ciceroscm_py2_adapter/ciceroscmpy2_adapter.py b/src/openscm_runner/adapters/ciceroscm_py2_adapter/ciceroscmpy2_adapter.py index 403416aa..e84c7b1c 100644 --- a/src/openscm_runner/adapters/ciceroscm_py2_adapter/ciceroscmpy2_adapter.py +++ b/src/openscm_runner/adapters/ciceroscm_py2_adapter/ciceroscmpy2_adapter.py @@ -685,6 +685,13 @@ def _build_scendata_list( nyend=nyend, emstart=scen_emstart, zero_unsupplied=idealised, + # Idealised runs hold every non-CO2 forcer at PI. On the + # conc-driven path scen_emstart is nyend (keeping CO2 + # concentration-driven), but the supplied PI-flat aerosol / + # ozone-precursor emissions must apply across the whole + # window rather than fall back to the rising historical + # baseline. Pin the overlay to nystart for idealised runs. + overlay_from=nystart if idealised else None, ) conc_data = None @@ -839,6 +846,7 @@ def _build_hybrid_emissions_data( nyend: int, emstart: int, zero_unsupplied: bool = False, + overlay_from: int | None = None, ): """ Build an RCMIP-format emissions DataFrame for one scenario. @@ -859,6 +867,18 @@ def _build_hybrid_emissions_data( Parameters ---------- + overlay_from + Year from which the user's supplied emissions overwrite the + baseline trajectory. Defaults to ``emstart``. For idealised + concentration-driven runs the model ``emstart`` is ``nyend`` + (so CO2 stays concentration-driven), but the supplied PI-flat + aerosol / ozone-precursor emissions must still apply across the + whole window -- otherwise the species fall back to the rising + historical baseline (a spurious growing aerosol cooling that + drifts ERF down through abrupt-4xCO2 / 1pctCO2). Pass + ``nystart`` in that case to pin the supplied emissions from the + run start; CO2 is unaffected because it is not supplied on the + conc-driven path (it stays concentration-driven via conc_run). zero_unsupplied When ``True``, every species column the user does NOT supply is overwritten with ``0.0`` across the whole ``[nystart, nyend]`` @@ -881,6 +901,8 @@ def _build_hybrid_emissions_data( from ..utils.cicero_utils.make_scenario_common import cicero_comp_dict + apply_from = emstart if overlay_from is None else overlay_from + df = ( pd.read_csv( baseline_em_file, delimiter="\t", index_col=0, skiprows=[1, 2, 3] @@ -965,7 +987,7 @@ def _build_hybrid_emissions_data( ) continue for year, val in user_row.items(): - if year in df.index and year >= emstart and not pd.isna(val): + if year in df.index and year >= apply_from and not pd.isna(val): df.at[year, col] = val * convfactor overlaid.append(cicero_species) overlaid_df_cols.add(col) From 66e127942eb315bd400279a04c47dfec82c44f71 Mon Sep 17 00:00:00 2001 From: benmsanderson Date: Thu, 25 Jun 2026 17:19:25 +0200 Subject: [PATCH 2/2] docs(changelog): add fragment for #108 Co-Authored-By: Claude Opus 4.8 --- changelog/108.fix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/108.fix.md diff --git a/changelog/108.fix.md b/changelog/108.fix.md new file mode 100644 index 00000000..687157c6 --- /dev/null +++ b/changelog/108.fix.md @@ -0,0 +1 @@ +Fixed CICEROSCMPY2 holding aerosol / ozone-precursor emissions at the rising historical baseline on the concentration-driven idealised path (`abrupt-4xCO2`, `1pctCO2`). Because `emstart` is `nyend` there (to keep CO2 concentration-driven), the supplied PI-flat emissions were applied only at the final year and every prior year fell back to the `historical_em` baseline, producing a spurious growing aerosol cooling (~0.6 W/m² ERF drift across a 4xCO2 step). `_build_hybrid_emissions_data` now takes an `overlay_from` argument and applies the supplied emissions from `nystart` for idealised runs, so non-CO2 forcers stay at preindustrial; CO2 is unaffected (concentration-driven via `conc_run`).