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

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.

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

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.

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’])

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

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

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)

Phosphate (po4)

Oxygen (o2)

pH

Silicate (si)

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.
| variable | NWS MY | Baltic MY/ANFC | Global MY | NWS 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)

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)

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

CNRM-ESM2-1 / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

CNRM-ESM2-1 / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

Phosphate (po4)

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)

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

CNRM-ESM2-1 / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

CNRM-ESM2-1 / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

Silicate (si)

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)

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

CNRM-ESM2-1 / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

CNRM-ESM2-1 / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

Oxygen (o2)

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)

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

CNRM-ESM2-1 / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

CNRM-ESM2-1 / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

Dissolved Inorganic Carbon (dissic)

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)

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

CNRM-ESM2-1 / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

CNRM-ESM2-1 / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

Alkalinity (talk)

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)

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

CNRM-ESM2-1 / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

CNRM-ESM2-1 / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp126
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

MPI-ESM1-2-HR / ssp370
Methods compared: delta-change (plus the real CMEMS reference where its coverage overlaps)

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:
- Diagnostics-only depth bug. The per-segment averaging used to make the comparison plots above checked for a
depthdimension by that exact name; raw CMIP6 files uselevinstead, 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 futureno3plots that first prompted this investigation. Diagnostics-only: it never touched any generated boundary file. - A real ~1000x-too-small climate-change signal. CMIP6’s native units for all six bio variables are
mol m-3; the CMEMS reference ismmol 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. - 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.
- 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/dissiccaches 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. - Additive delta-change has no positivity floor.
no3came 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. Switchedno3/po4/si/o2/dissic/talkto 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’sdissicat 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 | |
|---|---|---|---|---|---|---|
| no3 | 5.4 / 71.5 | 5.8 / 79.3 | 5.8 / 90.5 | 5.8 / 105.0 | 4.7 / 97.9 | 4.5 / 97.7 |
| po4 | 0.35 / 11.4 | 0.35 / 11.8 | 0.39 / 14.0 | 0.40 / 16.3 | 0.31 / 11.4 | 0.30 / 11.1 |
| si | 4.1 / 82.4 | 4.3 / 76.7 | 4.8 / 102.2 | 4.9 / 99.9 | 3.6 / 89.4 | 3.6 / 96.3 |
| o2 | 256.5 / 573.0 | 253.2 / 570.5 | 258.8 / 578.3 | 254.9 / 571.8 | 256.1 / 569.8 | 254.9 / 568.4 |
| dissic (seg 0) | 2244 | 2332 | 2216 | 2303 | 2188 | 2255 |
| talk (seg 0) | 2403 | 2402 | 2367 | 2360 | 2338 | 2312 |
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.