Gridded 2D Validation Guide

Overview

gridded-2d-validation compares any horizontal 2-D model field against a gridded observation product. Three layer modes are supported:

LayerDescriptionTypical use
surface2-D field or top of 3-D outputSST, SSS, SSH
bottomDeepest wet cell of 3-D outputBottom temperature / salinity
<depth_m>Horizontal slice at a fixed depth (m)Depth-layer climatology

Multiple layers can be run in a single invocation.

There are four shipped configs: gridded_2d_validation.yaml is the generic one, with any combination of layers via --layers. gridded_2d_validation_surface.yaml, _bottom.yaml and _depths.yaml are the dedicated single-purpose variants used for NSe.

Prerequisites

  • Model NetCDF output accessible via config/sources.yaml (see below), or directly via model.base_path / model.filename_pattern
  • A gridded observation file (local or Copernicus Marine / WOA)
  • pip install -e . with the ocean-post package
VariableSourceCMEMS product ID
SSTOSTIA (CMEMS)METOFFICE-GLO-SST-L4-REP-OBS-SST (analysed_sst)
Bottom temperatureNW Shelf reanalysiscmems_mod_nws_phy-bottomt_my_7km-2D_P1M-m
Bottom salinityNW Shelf reanalysiscmems_mod_nws_phy-so_my_7km-3D_P1M-m
Depth-level T/SWOA23local file, NOAA/NCEI

OSTIA SST is delivered in Kelvin — add offset: -273.15 in the obs config block.

Model source: --source / --experiment / --model / --scenario

Model input is NSe’s surf_bott_daily.nc (surface: temp_0/salt_0, bottom and depth slices: temp/salt), located via config/sources.yaml:

# Surface, CMEMS run
gridded-2d-validation --config config/gridded_2d_validation_surface.yaml --source CMEMS --experiment run01 --years 2010 2011

# Bottom, WOA run
gridded-2d-validation --config config/gridded_2d_validation_bottom.yaml --source WOA --experiment run01 --years 2010 2011

# Surface, CMIP6_raw run — forcing WITHOUT bias correction (e.g. ssp126)
gridded-2d-validation --config config/gridded_2d_validation_surface.yaml --source CMIP6_raw --model GFDL-ESM4 --scenario ssp126 --experiment run01 --years 2010 2011

CMIP6 is bias-corrected forcing; CMIP6_raw is not — use CMIP6_raw for raw-forcing runs such as ssp126; --source CMIP6 points at a folder that does not exist for that model/scenario.

--experiment is the run folder name, and the output label becomes <SOURCE>/<experiment>, or <SOURCE>/<model>-<scenario>/<experiment> for CMIP6/CMIP6_raw.

Config file (generic, multi-layer)

# config/gridded_2d_validation.yaml
area: NS
experiment: Baseline   # overridden by --experiment when --source is used
years: [2010, 2011, 2012]

model:
  # Location, layout and variable names come from config/sources.yaml
  # (use --source). See the section above.
  name: "pyGETM"
  filename_pattern: "surf_bott_daily.nc"
  layout: explicit_file
  subdir:
    CMEMS: "{experiment}"
    WOA: "{experiment}"
    CMIP6: "{model}/{scenario}/{experiment}"
    CMIP6_raw: "{model}/{scenario}/{experiment}"
  run_model: GFDL-ESM4     # CMIP6 / CMIP6_raw run: overridden by --model
  run_scenario: ssp126     # CMIP6 / CMIP6_raw run: overridden by --scenario
  time_offset_days: -1
  variable_map:
    temp: sst        # rename model variable if needed

variables: [temp]

observations:
  temp:
    - name: OSTIA
      path:
        source: copernicus
        dataset_id: METOFFICE-GLO-SST-L4-REP-OBS-SST
        variable: analysed_sst
      offset: -273.15

processing:
  layer: surface              # single layer (default)
  # layers: [surface, bottom, 50]   # or multiple — CLI: --layers surface bottom 50
  chunking:
    time_chunk: 30              # days per processing chunk

output:
  analyses_dir: "${OCEANICU_ANALYSES_FOLDER}"   # no default — see below

output.analyses_dir has no default

There is no ./analyses fallback. The value is expanded from OCEANICU_ANALYSES_FOLDER in this machine’s data-roots file (<hostname>_ocean-post_data_roots.yaml), or set directly with --analyses-dir. If neither is set, the run stops with an error naming the missing variable.

Usage

# Surface only (config default)
gridded-2d-validation --config config/gridded_2d_validation_surface.yaml --source CMEMS --experiment run01 --years 2010 2011

# Surface + bottom in one run (generic config)
gridded-2d-validation --config config/gridded_2d_validation.yaml --source CMEMS --experiment run01 \
    --layers surface bottom

# Surface + 50 m depth slice + bottom
gridded-2d-validation --config config/gridded_2d_validation.yaml --source CMEMS --experiment run01 \
    --layers surface 50 bottom

The --layers flag overrides processing.layer/processing.layers in the config.

Output

Results are written to <OCEANICU_ANALYSES_FOLDER>/areas/<area>/validations/<SOURCE>/<experiment>/ (or .../<SOURCE>/<model>-<scenario>/<experiment>/ for CMIP6/CMIP6_raw):

plots/physics/pyGETM/<period>/
    surface/        ← time-mean maps, monthly bias maps, scatter plots
    bottom/
    50m/
tables/physics/pyGETM/
    <variable>_validation_statistics.txt

Statistics text files contain overall + monthly RMSE, bias, MAE, and correlation.

Generating residuals for MLE comparison

gridded-2d-validation can write matched obs/model pairs as a parquet file for use with mle-comparison. One file is written per layer:

<OCEANICU_ANALYSES_FOLDER>/areas/<area>/validations/<SOURCE>/<experiment>/surface_residuals.parquet
<OCEANICU_ANALYSES_FOLDER>/areas/<area>/validations/<SOURCE>/<experiment>/bottom_residuals.parquet
<OCEANICU_ANALYSES_FOLDER>/areas/<area>/validations/<SOURCE>/<experiment>/0050m_residuals.parquet

Enable in the config (disabled by default to avoid large files):

output:
  save_residuals: true
  spatial_thinning:
    stride: 5        # keep every 5th grid point in both lat and lon
    time_stride: 1   # keep every N-th time step (1 = all)
    max_rows: 100000 # hard cap per parquet; excess rows are randomly sampled

The parquet schema is identical to profile residuals (time, lat, lon, depth, value, model_value, variable) so mle-comparison accepts it directly:

# Compare experiments using gridded surface obs
mle-comparison --analyses-dir $OCEANICU_ANALYSES_FOLDER --area NS \
    --obs-types surface --variables TEMP PSAL

# Combine gridded surface obs with Argo profiles
mle-comparison --analyses-dir $OCEANICU_ANALYSES_FOLDER --area NS \
    --obs-types surface argo --variables TEMP PSAL

Thinning is important. A 0.1° North Sea grid with daily SST obs has millions of rows, almost all spatially correlated. Without thinning the MLE independence assumption is badly violated. Start with stride: 5 and max_rows: 100000; tune until Δlog L values are stable across stride choices. See the Scope section of guides/mle-comparison.md for a full discussion.

Notes

  • Observations are regridded onto the model grid using bilinear interpolation (xESMF); weight files are cached by MD5 hash under regrid_weights/.
  • For bottom layer: the deepest non-NaN cell is selected per column using sigma-coordinate-aware extraction.
  • For depth slices: the model field is interpolated vertically to the requested depth before regridding.