Skip to content

fix(fair2): idealised conc-driven PI ERF + empty-bundle splice#106

Merged
benmsanderson merged 5 commits into
feat/fair2-ciceroscmpy2-adapters-and-runmode-nonforkfrom
fix/fair2-idealised-pi-erf
Jun 22, 2026
Merged

fix(fair2): idealised conc-driven PI ERF + empty-bundle splice#106
benmsanderson merged 5 commits into
feat/fair2-ciceroscmpy2-adapters-and-runmode-nonforkfrom
fix/fair2-idealised-pi-erf

Conversation

@benmsanderson

Copy link
Copy Markdown
Collaborator

What

Fixes the two FaIR2 idealised-experiment issues surfaced while wiring the AR7 WG1 Ch5 ensemble onto the conc-driven adapters, plus docs and a regression test. Stacked on feat/fair2-ciceroscmpy2-adapters-and-runmode-nonfork.

Issue 1 — _splice_bundle_with_user KeyError on empty bundle

Idealised scenarios (abrupt-4xCO2, 1pctCO2, esm-flat10*) have no RCMIP3 emissions baseline, so _rcmip3_to_fair_emissions_df returns an empty, column-less frame. When the user also supplied Emissions|* rows, _splice_bundle_with_user hit KeyError: 'scenario' at bundle_df["scenario"].isin(...) (the if spliced_df.empty fallback is unreachable — the KeyError fires first). Short-circuit to the user's rows when the bundle is empty. Two unit regressions added (no FaIR run).

Issue 2 — +1.53 W/m² preindustrial ERF residual

Decomposed the residual on an abrupt-4xCO2 conc-driven run: it is a constant aerosol ERF offset (+0.64 W/m² in the mini calibration; +1.53 with the AR7 1.4.1 calibration), not leaking non-CO2 emissions.

Root cause: FaIR evaluates emissions-driven forcing (aerosols especially) relative to each species' baseline_emissions. The idealised path left non-CO2 species at zero emissions (the co2_only zeroing / _zero_fill_fair_arrays), so the anomaly was 0 − baseline_emissions → a constant spurious forcing. Zero emissions is not the PI reference.

Fix: for idealised scenarios, pin non-CO2 emissions-mode species to baseline_emissions (CO2 sources excluded — they are concentration-driven). The anomaly, and hence the PI forcing, becomes ~0.

Verified (mini calibration, abrupt-4xCO2 conc-driven), non-CO2 ERF at the 1850 branch point:

component before after
Aerosols +0.644 +0.003
CH4 −0.065 +0.000
non-CO2 (total − CO2) +0.414 +0.001

Non-idealised runs are untouched — the existing ssp245 modern-adapter snapshots are unchanged.

Docs

New "Idealised experiments and the non-conc-species baseline" page documenting how each adapter (MAGICC7 / FaIR2 / CICEROSCMPY2) treats non-conc species in conc-driven runs, and recommending the explicit esm-piControl emissions overlay for reproducible cross-model idealised runs.

Tests

  • test_rcmip3.py: empty-bundle splice + end-to-end build_emissions_df for an idealised scenario with a user overlay (CO2 survives, non-CO2 zeroed).
  • test_idealised_pi_erf.py: FaIR2 idealised non-CO2 ERF ~0 at the PI branch point (env-gated on the full RCMIP3 bundle).

Notes / follow-ups

  • Fix approach: chose hold-at-baseline (true zero PI anomaly) over zero-the-forcing or accept-an-offset. This changes the idealised non-CO2 inputs vs the old (buggy) zeroing — relevant to ECS diagnostics.
  • Cross-adapter PI-ERF test is FaIR2-only. MAGICC7 needs an explicit esm-piControl overlay for idealised runs (no auto-detection); CICEROSCMPY2 exposes only component-wise ERF (no plain total/CO2 pair). A true cross-adapter assertion is a follow-up.
  • A smaller, separate ozone term drifts to ~−0.15 W/m² by 2000 on abrupt-4xCO2 (EESC/precursor chemistry from the bundle's prescribed non-PI concentrations) — not the PI residual; worth a follow-up look.

🤖 Generated with Claude Code

benmsanderson and others added 5 commits June 22, 2026 20:12
Idealised scenarios (abrupt-4xCO2, 1pctCO2, esm-flat10*) have no RCMIP3
emissions baseline, so _rcmip3_to_fair_emissions_df returns an empty,
column-less DataFrame. When the user also supplies Emissions|* rows,
_splice_bundle_with_user hit `KeyError: 'scenario'` at
bundle_df["scenario"].isin(...) — the existing `if spliced_df.empty`
fallback is unreachable because the KeyError fires first, and the
caller build_emissions_df only short-circuits the all-empty case.

Short-circuit to the user's rows when the bundle is empty. They still
flow through build_emissions_df's co2_only_scenarios zeroing and the
year-column stringify, so idealised treatment is preserved.

Adds two unit regressions (no FaIR run): the direct empty-bundle splice,
and an end-to-end build_emissions_df for an idealised scenario with a
user overlay asserting CO2 survives and non-CO2 is zeroed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Idealised experiments (abrupt-4xCO2, 1pctCO2, esm-flat10*) hold
everything but CO2 at pre-industrial. The adapter previously left the
non-CO2 emissions-mode species at *zero* emissions (the co2_only
zeroing in build_emissions_df and the _zero_fill_fair_arrays safety
net). But FaIR evaluates emissions-driven forcing -- aerosols in
particular -- relative to each species' baseline_emissions, so zero
emissions is NOT the PI reference: it leaves a constant spurious ERF
(anomaly 0 - baseline_emissions).

Decomposed on an abrupt-4xCO2 conc-driven run (mini calibration): the
non-CO2 ERF residual is +0.41 W/m^2 at the PI branch point, dominated by
aerosols (+0.64 W/m^2). With the AR7 1.4.1 calibration this is the
reported +1.53 W/m^2.

Fix: after the fills, pin non-CO2 emissions-mode species to
baseline_emissions for idealised scenarios (CO2 sources excluded -- they
are concentration-driven / back-calculated). This makes the anomaly, and
hence the PI forcing, ~0. Verified: non-CO2 ERF +0.41 -> +0.001 at 1850;
aerosols +0.64 -> +0.003; CH4 -> 0.000. Non-idealised runs are untouched
(the existing ssp245 modern-adapter snapshots are unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document how each adapter treats the non-concentration-driven species
(aerosol / ozone precursors and any GHG not supplied as a concentration)
in conc-driven runs, and how that differs for idealised experiments
(abrupt-4xCO2, 1pctCO2, esm-flat10*):

- MAGICC7 requires explicit Emissions|* for those species (no idealised
  auto-detection),
- FaIR2 zeroes natural/land-use forcing and holds non-CO2 emissions-mode
  species at baseline_emissions for idealised scenarios,
- CICEROSCMPY2 zeroes unsupplied emissions / holds unsupplied
  concentrations at PI.

Recommends supplying the esm-piControl emissions overlay explicitly for
reproducible cross-model idealised runs (the only way to get correct PI
behaviour from MAGICC7).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regression guard for the idealised baseline fix: drive abrupt-4xCO2 by
CO2 concentration only and assert the non-CO2 ERF (total minus CO2) at
the 1850 branch point is ~0. Before the fix this carried a constant
~+0.6 W/m^2 aerosol offset (the +1.53 W/m^2 PI residual with the AR7
1.4.1 calibration).

Env-gated on the full RCMIP3 bundle (the mini fixture lacks abrupt-4xCO2
concentrations); FaIR2-only because MAGICC7 needs an explicit
esm-piControl overlay for idealised runs and CICEROSCMPY2 exposes only
component-wise ERF (cross-adapter assertion is a follow-up).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@benmsanderson benmsanderson merged commit dbbcb55 into feat/fair2-ciceroscmpy2-adapters-and-runmode-nonfork Jun 22, 2026
22 of 29 checks passed
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.

1 participant