Gridded 3D Validation Guide

Overview

gridded-3d-validation validates model temperature and salinity (or any 3-D variable) against a gridded observation product — WOA23 climatology, CMIP6 ensembles, Copernicus reanalysis, or any dataset that provides values at every horizontal grid point and a set of depth levels.

The observations are regridded onto the model grid and compared cell-by-cell, enabling:

  • Spatial statistics maps (RMSE, bias, MAE, correlation per grid point)
  • Time-mean comparison maps at selected depth levels
  • Monthly statistics bar charts
  • Statistics text file (overall + monthly breakdown)

When to use this tool vs profile validation: Use gridded-3d-validation for climatologies or reanalyses that cover the full model domain at all depths. Use argo-profiles, cruise-ctd-profiles, or fixed-platform for sparse in-situ observations scattered in space and time.

Prerequisites

  • Model NetCDF output accessible via config/sources.yaml (see below), or directly via model.base_path / model.filename_pattern
  • A gridded 3-D observation product (WOA23, CMEMS reanalysis, CMIP6)
  • pip install -e . with the ocean-post package
VariableSourceNotes
Temperature, salinityWOA23 (NOAA/NCEI)climatology, WOA_FOLDER
Temperature, salinityGLORYS12 (CMEMS)cmems_mod_glo_phy_my_0.083deg_P1M-m
Temperature, salinityCMIP6 ensembleVia PANGEO catalog (cmip6://)

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

Model input comes from config/sources.yaml, selected with --source. Each run writes one file, nse_3d.nc, in its own run folder, so model.layout is explicit_file and model.subdir maps each source name to that folder:

# CMEMS run
gridded-3d-validation --config config/gridded_3d_validation.yaml --source CMEMS --experiment run01 --years 2010 2011

# WOA run
gridded-3d-validation --config config/gridded_3d_validation.yaml --source WOA --experiment run01 --years 2010 2011

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

CMIP6 is bias-corrected forcing; CMIP6_raw is not. Use whichever actually matches the run — do not use --source CMIP6 for a run that was forced with the raw (non-bias-corrected) data, since the input folder for that model/scenario combination will not exist under CMIP6.

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

Config file

# config/gridded_3d_validation.yaml
area: "NS"
experiment: "Baseline"   # overridden by --experiment when --source is used
year: 2021
# years: [2020, 2022]

variables: [temp, salt]

observations:
  temp:
    WOA: "${WOA_FOLDER}/woa_t.nc"
  salt:
    WOA: "${WOA_FOLDER}/woa_s.nc"

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

processing:
  depth_levels: [5, 50, 100, 200, 500]   # depth slices to compare
  compute_spatial_stats: true

subsetting:
  type: none   # none | bbox | polygon — restrict to a sub-region (optional)

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

Config file on GitHub: config/gridded_3d_validation.yaml

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

# Run with config file and --source
gridded-3d-validation --config config/gridded_3d_validation.yaml --source CMEMS --experiment run01 --years 2010 2011 --workers 16

# Override depth levels on the command line
gridded-3d-validation --config config/gridded_3d_validation.yaml --source CMEMS --experiment run01 \
    --depths 5 50 100 200

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>/
    3d/
        <variable>_bias_<depth>m.png
        <variable>_rmse_<depth>m.png
        <variable>_monthly_stats.png
tables/physics/pyGETM/
    <variable>_validation_statistics.txt

Statistics text files contain overall RMSE, bias, MAE, and correlation plus a monthly breakdown.

Generating residuals for MLE comparison

gridded-3d-validation can write matched obs/model pairs as a parquet file for use with mle-comparison. All variables are combined into a single file:

<OCEANICU_ANALYSES_FOLDER>/areas/<area>/validations/<SOURCE>/<experiment>/gridded3d_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
    z_stride: 2      # keep every N-th depth level
    time_stride: 1   # keep every N-th time step (1 = all; 12 for monthly clim)
    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), with depth holding the actual depth coordinate value for each level.

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

# Combine gridded 3D obs with Argo profiles for fuller vertical coverage
mle-comparison --analyses-dir $OCEANICU_ANALYSES_FOLDER --area NS \
    --obs-types gridded3d argo --variables TEMP PSAL

Thinning is important. A 1° WOA grid with 20 depth levels gives ~500,000 cells for a typical regional domain; neighbouring levels and grid points are strongly correlated. Use stride and z_stride to reduce this. See the Scope section of guides/mle-comparison.md for a full discussion.

Notes

  • Observations are interpolated horizontally onto the model grid (xESMF bilinear); weight files are cached by MD5 hash under regrid_weights/.
  • Observations are then interpolated vertically onto each depth_levels target using linear interpolation.
  • If the obs product is a 12-month climatology (no explicit year axis), each model time step is matched to the corresponding calendar month.