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