Cross-model, cross-scenario summary of NSe’s future open-boundary conditions: delta-change temperature/salinity (CMIP6, bias-corrected against the CMEMS Northwest Shelf reanalysis), tidal elevation/currents (TPXO9 plus the CMIP6 ensemble-mean sea-level trend), and (as of 2026-09-22) delta-change biogeochemistry – nitrate, phosphate, silicate, oxygen, dissolved inorganic carbon, and alkalinity – across all three CMIP6 models and both scenarios (see the bug-fix campaign section below for how that came together).

Delta-change (temperature / salinity)

Temperature (thetao)

Source: AMM7/AMM15 delta-change: GFDL-ESM4/ssp126 [1985-01-01–2014-12-31 vs 2015-01-01–2100-12-31]

  • Method: OLS linear trend per month
  • Historical period: 1985-01-01 to 2014-12-31
  • Future period: 2015-01-01 to 2100-12-31

thetao period summary

Validation: CMEMS reference vs CMIP6-historical climatology (1985-2014) at the same boundary points – confirms the bias-correction’s own historical baseline is sound before trusting the future delta above.

thetao CMEMS vs CMIP6 historical

Salinity (so)

Source: AMM7/AMM15 delta-change: GFDL-ESM4/ssp126 [1985-01-01–2014-12-31 vs 2015-01-01–2100-12-31]

  • Method: OLS linear trend per month
  • Historical period: 1985-01-01 to 2014-12-31
  • Future period: 2015-01-01 to 2100-12-31

so period summary

Validation: CMEMS reference vs CMIP6-historical climatology (1985-2014) at the same boundary points – confirms the bias-correction’s own historical baseline is sound before trusting the future delta above.

so CMEMS vs CMIP6 historical

Tidal + CMIP6-mean (elevation / currents)

Sea Surface Height (zos)

Source: TPXO9 + (GFDL-ESM4/ssp126 minus 1985-01-01 to 2014-12-31 historical mean, MSL datum) (tables=[‘Oday’, ‘Omon’])

zos period summary

Eastward Velocity (uo)

Source: TPXO9 + (GFDL-ESM4/ssp126 minus 1985-01-01 to 2014-12-31 historical mean, MSL datum) (tables=[‘Oday’, ‘Omon’])

uo period summary

Northward Velocity (vo)

Source: TPXO9 + (GFDL-ESM4/ssp126 minus 1985-01-01 to 2014-12-31 historical mean, MSL datum) (tables=[‘Oday’, ‘Omon’])

vo period summary

Biogeochemistry — feasibility check (not a boundary condition)

Surface, boundary-mean monthly climatology: the real CMEMS NWS BGC reference (black) against CMIP6-historical (1985-2014) climatology from the same 3 models used for physics, sampled at the identical boundary points – a diagnostic check, not a correction. Nutrients (no3, po4, o2) broadly track the reference’s seasonal shape with model-dependent offsets (MPI-ESM1-2-HR consistently low, especially in summer); si shows the same shape but a wider spread. pH is the clear outlier: GFDL-ESM4 sits ~0.5 units below the reference essentially year-round (large enough to suggest a scale/convention mismatch worth checking on its own, not just model bias), and none of the three models reproduce the reference’s spring/summer pH rise at all.

Nitrate (no3)

no3 CMEMS vs CMIP6 historical

Phosphate (po4)

po4 CMEMS vs CMIP6 historical

Oxygen (o2)

o2 CMEMS vs CMIP6 historical

pH

ph CMEMS vs CMIP6 historical

Silicate (si)

si CMEMS vs CMIP6 historical

Alkalinity, DIC, and ammonium — coverage investigation

Extending delta-change to alkalinity (talk), dissolved inorganic carbon (dissic), and ammonium (nh4) hit a harder constraint than the five variables above: none of the three have a CMEMS reanalysis (multi-year, MY) product anywhere in the region.

variableNWS MYBaltic MY/ANFCGlobal MYNWS ANFC
talk✗ (never produced)✗ (no source at all)✗✓ from 2024-07-29
dissic✗ (never produced)✓ (ANFC only)✗✓ from 2024-07-29
nh4✗✓ (MY and ANFC)✗✓ from 2024-07-29

For talk/dissic, the earliest real CMEMS data of any kind is the NWS analysis-and-forecast (ANFC) product, confirmed via a live dry-run against the real catalogue to start 2024-07-29 (an older config comment, written for AMM7, had claimed 2024-02-28) – a ~2-year window, not a multi-decade reanalysis. This doesn’t block delta-change the way it first appeared to: the CMIP6-vs-CMIP6 trend calculation (the actual future signal) only ever depends on CMIP6’s own historical run (1985-2014) and future run, never on CMEMS. CMEMS’s only role is supplying realistic day-to-day/seasonal cycling values via analog-date repetition (t_analog) – which works with a short window, just with less sampled interannual variability than the 16-year window used for temperature/salinity. Decision: proceed using the real ANFC-only window as that reference for talk/dissic.

nh4 has real CMEMS data (Baltic MY and ANFC both have it, NWS ANFC has it too) but is blocked by a different, genuine gap: some boundary segments have a source for a given period and others don’t, and the CMEMS extraction tool (cmems_processor.py) has no NaN-padding support for that mixed-coverage case yet – a tooling gap, not a data-availability one. Deferred for now.

Two ways to get a future signal. Delta-change (used above for thetao/so, and now talk/dissic) keeps CMEMS’s own real regional variability as the base and adds only the CMIP6-derived mean climate-change signal on top – appropriate because CMIP6’s own coarse ocean resolution has essentially no realistic regional structure worth preserving even after bias-correcting it directly. That reasoning is much weaker for talk/dissic specifically, given CMEMS itself has almost no real regional variability on record for either – direct CMIP6 bias-correction (the same method already used for atmospheric meteo forcing) was built and run alongside delta-change for exactly this reason (see the per-segment comparison below): both land close to the real CMEMS reference, so no further decision was forced between them – the raw (uncorrected) CMIP6 signal is what actually needed correcting, and both methods do that.

Naive vs. honest CMEMS request, tested directly: two variants of the extraction were run side by side – one requesting the same full 2010-2026 window as every other boundary variable with no awareness of the ANFC-only limitation, one explicitly bounded to the real ANFC coverage. Result: byte-for-byte identical output (same 750-timestep time axis, same values, same file size) – the extraction tool already clips silently to whatever the product actually has, with no NaN-padding for the unavailable 2010-2024 span either way. Kept both entries in the source config regardless, since documenting why the window is short is clearer written down explicitly than left implicit.

Bio boundary generation: per-segment method comparison

Period-mean per boundary segment (9 segments). The main plot under each variable overlays all three CMIP6 models x both scenarios (delta-change, the actual production method) so the models can be compared against each other directly; expand “per-model/scenario method breakdown” below it for the raw-CMIP6-vs-corrected comparison for one combo at a time. The raw CMIP6 line there is converted from its native mol m-3 to mmol m-3 here for comparison only (the real raw-CMIP6 files themselves stay unconverted and correctly labeled). **Segment 7 (lon ~12.98°E, lat 54.5-55.25°N – the Baltic-entrance corner, the Fehmarnbelt/Great Belt) was entirely NaN for dissic/talk in both delta-change and direct bias-correction until 2026-09-22 – now fixed, two different ways. no3/po4/si/o2 were already real CMEMS data here (a pre-existing per-segment override to a Baltic BGC product, since the long-standing NWS bio_daily product simply doesn’t reach this segment – same mechanism already used for temperature/salinity/currents at this exact segment, a Baltic ocean model, since NWS is masked across the whole Kattegat there). dissic got the same Baltic-product override, newly added. talk has no CMEMS source anywhere, Baltic included (confirmed against the live catalogue) – instead derived from real Baltic pH + DIC + temperature/salinity through actual seawater carbonate-system equilibrium (PyCO2SYS), not a regression or a raw-CMIP6 fallback. See cli/derive_baltic_talk.py. Every other segment was already real, direct CMEMS data and is untouched.

Nitrate (no3)

no3 all models/scenarios comparison

Per-model/scenario method breakdown (raw CMIP6 vs. delta-change vs. direct bias-correction)

GFDL-ESM4 / ssp126

Methods compared: raw CMIP6 (uncorrected), delta-change (plus the real CMEMS reference where its coverage overlaps)

no3 GFDL-ESM4 ssp126 per-segment method comparison

GFDL-ESM4 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

no3 GFDL-ESM4 ssp370 per-segment method comparison

CNRM-ESM2-1 / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

no3 CNRM-ESM2-1 ssp126 per-segment method comparison

CNRM-ESM2-1 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

no3 CNRM-ESM2-1 ssp370 per-segment method comparison

MPI-ESM1-2-HR / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

no3 MPI-ESM1-2-HR ssp126 per-segment method comparison

MPI-ESM1-2-HR / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

no3 MPI-ESM1-2-HR ssp370 per-segment method comparison

Phosphate (po4)

po4 all models/scenarios comparison

Per-model/scenario method breakdown (raw CMIP6 vs. delta-change vs. direct bias-correction)

GFDL-ESM4 / ssp126

Methods compared: raw CMIP6 (uncorrected), delta-change (plus the real CMEMS reference where its coverage overlaps)

po4 GFDL-ESM4 ssp126 per-segment method comparison

GFDL-ESM4 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

po4 GFDL-ESM4 ssp370 per-segment method comparison

CNRM-ESM2-1 / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

po4 CNRM-ESM2-1 ssp126 per-segment method comparison

CNRM-ESM2-1 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

po4 CNRM-ESM2-1 ssp370 per-segment method comparison

MPI-ESM1-2-HR / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

po4 MPI-ESM1-2-HR ssp126 per-segment method comparison

MPI-ESM1-2-HR / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

po4 MPI-ESM1-2-HR ssp370 per-segment method comparison

Silicate (si)

si all models/scenarios comparison

Per-model/scenario method breakdown (raw CMIP6 vs. delta-change vs. direct bias-correction)

GFDL-ESM4 / ssp126

Methods compared: raw CMIP6 (uncorrected), delta-change (plus the real CMEMS reference where its coverage overlaps)

si GFDL-ESM4 ssp126 per-segment method comparison

GFDL-ESM4 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

si GFDL-ESM4 ssp370 per-segment method comparison

CNRM-ESM2-1 / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

si CNRM-ESM2-1 ssp126 per-segment method comparison

CNRM-ESM2-1 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

si CNRM-ESM2-1 ssp370 per-segment method comparison

MPI-ESM1-2-HR / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

si MPI-ESM1-2-HR ssp126 per-segment method comparison

MPI-ESM1-2-HR / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

si MPI-ESM1-2-HR ssp370 per-segment method comparison

Oxygen (o2)

o2 all models/scenarios comparison

Per-model/scenario method breakdown (raw CMIP6 vs. delta-change vs. direct bias-correction)

GFDL-ESM4 / ssp126

Methods compared: raw CMIP6 (uncorrected), delta-change (plus the real CMEMS reference where its coverage overlaps)

o2 GFDL-ESM4 ssp126 per-segment method comparison

GFDL-ESM4 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

o2 GFDL-ESM4 ssp370 per-segment method comparison

CNRM-ESM2-1 / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

o2 CNRM-ESM2-1 ssp126 per-segment method comparison

CNRM-ESM2-1 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

o2 CNRM-ESM2-1 ssp370 per-segment method comparison

MPI-ESM1-2-HR / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

o2 MPI-ESM1-2-HR ssp126 per-segment method comparison

MPI-ESM1-2-HR / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

o2 MPI-ESM1-2-HR ssp370 per-segment method comparison

Dissolved Inorganic Carbon (dissic)

dissic all models/scenarios comparison

Per-model/scenario method breakdown (raw CMIP6 vs. delta-change vs. direct bias-correction)

GFDL-ESM4 / ssp126

Methods compared: raw CMIP6 (uncorrected), delta-change, direct bias-correction (plus the real CMEMS reference where its coverage overlaps)

dissic GFDL-ESM4 ssp126 per-segment method comparison

GFDL-ESM4 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

dissic GFDL-ESM4 ssp370 per-segment method comparison

CNRM-ESM2-1 / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

dissic CNRM-ESM2-1 ssp126 per-segment method comparison

CNRM-ESM2-1 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

dissic CNRM-ESM2-1 ssp370 per-segment method comparison

MPI-ESM1-2-HR / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

dissic MPI-ESM1-2-HR ssp126 per-segment method comparison

MPI-ESM1-2-HR / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

dissic MPI-ESM1-2-HR ssp370 per-segment method comparison

Alkalinity (talk)

talk all models/scenarios comparison

Per-model/scenario method breakdown (raw CMIP6 vs. delta-change vs. direct bias-correction)

GFDL-ESM4 / ssp126

Methods compared: raw CMIP6 (uncorrected), delta-change, direct bias-correction (plus the real CMEMS reference where its coverage overlaps)

talk GFDL-ESM4 ssp126 per-segment method comparison

GFDL-ESM4 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

talk GFDL-ESM4 ssp370 per-segment method comparison

CNRM-ESM2-1 / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

talk CNRM-ESM2-1 ssp126 per-segment method comparison

CNRM-ESM2-1 / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

talk CNRM-ESM2-1 ssp370 per-segment method comparison

MPI-ESM1-2-HR / ssp126

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

talk MPI-ESM1-2-HR ssp126 per-segment method comparison

MPI-ESM1-2-HR / ssp370

Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

talk MPI-ESM1-2-HR ssp370 per-segment method comparison

Bio boundary generation: 2026-09-22 bug-fix campaign

A user question about an odd-looking plot (“why does the future signal look 3x the historical one?”) led to a single day’s debugging that found and fixed five independent, real bugs in the bio boundary pipeline – every one would otherwise have silently shipped wrong production forcing:

  1. Diagnostics-only depth bug. The per-segment averaging used to make the comparison plots above checked for a depth dimension by that exact name; raw CMIP6 files use lev instead, so the averaging silently fell back to the whole water column instead of the surface for the raw-CMIP6 line only – this alone produced the apparent “factor of 3” jump between the historical and future no3 plots that first prompted this investigation. Diagnostics-only: it never touched any generated boundary file.
  2. A real ~1000x-too-small climate-change signal. CMIP6’s native units for all six bio variables are mol m-3; the CMEMS reference is mmol m-3. The delta-change generator’s shared CMIP6 loader had no unit conversion, so the imported climate-change signal was ~1000x too small for every bio variable, every model, every file this method had ever produced. Fixed at the point CMIP6 data is loaded.
  3. A longitude-wraparound bug, regular-grid models only. NSe’s boundary mixes mostly-negative longitudes (segments 0-6/8, -7.5° to +4.74°) with segment 7 alone at +12.98°E. For GFDL-ESM4 (the one CMIP6 model here on a regular lon/lat grid), a bounding-box helper in the shared spatial-interpolation library mis-measured that point set’s span as ~359° instead of the true ~13°, corrupting the interpolated delta for the whole 310-point set. Worked around per-segment in the boundary generator itself, given the size of what else depends on the shared library. CNRM-ESM2-1/MPI-ESM1-2-HR use a curvilinear grid and a different code path, unaffected.
  4. A stale disk-cached climatology. The CMIP6 historical-baseline climatology is cached to disk (recomputing it on every run had turned out to be the real bottleneck, not the boundary-point interpolation). Two models’ talk/dissic caches had been written minutes before fix #2 landed, so they kept the old, unconverted values – against the now-correct future values, that made the climate-change delta come out close to the full future absolute value, roughly doubling the final output. Fixed by clearing the affected cache entries and regenerating.
  5. Additive delta-change has no positivity floor. no3 came back with a small but real fraction of negative values per segment (up to ~9%), concentrated where the CMEMS reference is already near its seasonal bloom-depletion minimum – a structural limitation of an additive delta (reference + climate-change shift), not a numerical bug. Switched no3/po4/si/o2/dissic/talk to a multiplicative delta (reference × ratio) instead, which is non-negative by construction given a non-negative reference – reusing the same additive-vs-multiplicative variable table the direct bias-correction method already used, for the same reason. This also fixed an unrelated-looking anomaly as a side effect: CNRM-ESM2-1’s dissic at segment 7 had come out physically implausible for the ssp126 scenario only (a smooth but wrong ~42→110 mmol/m³ trend against a real ~1649 mmol/m³ reference), traced to the additive method’s sensitivity to one CMIP6 grid cell’s absolute value; the multiplicative form isn’t sensitive to that and produces a smooth, plausible ~1680→1739 mmol/m³ instead.

The multiplicative switch introduced a smaller failure mode of its own: dividing by a small-but-nonzero historical baseline at one month/point can inflate the ratio past what’s physically sensible. Confirmed for no3, at segment 7 (the Baltic-entrance/Fehmarnbelt corner), winter months only, across all three models. The evaluated ratio is now capped to [1/5, 5] before being applied, which removed every case actually caused by ratio instability. A separate, larger set of high values at that same segment turned out to be real, not an artefact: the CMEMS reference itself reaches similarly large nitrate values there on record (a real, river-influenced, short-duration signal – up to ~94 mmol/m³ in the pure, uncorrected reference) – kept as-is rather than clipped further, since a hard ceiling there would suppress genuine reference-data behaviour, not a bug.

Final verified state, all three CMIP6 models × both scenarios × all six variables (delta-change method; mean / max in mmol m⁻³ across the whole 2015-2100 boundary, dissic/talk given as their boundary-mean at segment 0 for brevity; zero unexplained negative values anywhere – the handful that do occur are ~-0.01 mmol/m³ o2 values at segment 7’s deep layers, real reanalysis noise at the edge of the Baltic’s genuinely near-anoxic bottom water, correctly floored to 0):

GFDL-ESM4
ssp126
GFDL-ESM4
ssp370
CNRM-ESM2-1
ssp126
CNRM-ESM2-1
ssp370
MPI-ESM1-2-HR
ssp126
MPI-ESM1-2-HR
ssp370
no35.4 / 71.55.8 / 79.35.8 / 90.55.8 / 105.04.7 / 97.94.5 / 97.7
po40.35 / 11.40.35 / 11.80.39 / 14.00.40 / 16.30.31 / 11.40.30 / 11.1
si4.1 / 82.44.3 / 76.74.8 / 102.24.9 / 99.93.6 / 89.43.6 / 96.3
o2256.5 / 573.0253.2 / 570.5258.8 / 578.3254.9 / 571.8256.1 / 569.8254.9 / 568.4
dissic (seg 0)224423322216230321882255
talk (seg 0)240324022367236023382312

CNRM-ESM2-1’s no3 shows a noticeably stronger, monotonically-growing correction toward the end of the century – the ratio cap engages on a growing fraction of month/points, from ~3% by 2075 to ~8.5% by 2100 – than GFDL-ESM4 or MPI-ESM1-2-HR, whose correction stays small and roughly constant across the run. Likely a real difference in projected nutrient-cycling trend strength between the three CMIP6 Earth-system models, though with a single ensemble member each this can’t be fully separated from internal variability.

A dedicated automated check (final-boundary-check) confirmed every one of the 36 delta-change files above passes cleanly. It also found that the raw, uncorrected CMIP6 boundary files (kept only as a diagnostic reference here, never used as real production forcing) still carry the unconverted-units bug described above, unfixed – since nothing downstream actually reads that method’s output as a boundary condition, this is tracked as a known limitation of that reference-only path rather than fixed immediately.