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:
| Layer | Description | Typical use |
|---|---|---|
surface | 2-D field or top of 3-D output | SST, SSS, SSH |
bottom | Deepest wet cell of 3-D output | Bottom 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 theocean-postpackage
Recommended observation sources
| Variable | Source | CMEMS product ID |
|---|---|---|
| SST | OSTIA (CMEMS) | METOFFICE-GLO-SST-L4-REP-OBS-SST (analysed_sst) |
| Bottom temperature | NW Shelf reanalysis | cmems_mod_nws_phy-bottomt_my_7km-2D_P1M-m |
| Bottom salinity | NW Shelf reanalysis | cmems_mod_nws_phy-so_my_7km-3D_P1M-m |
| Depth-level T/S | WOA23 | local 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.