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 theocean-postpackage
Recommended observation sources
| Variable | Source | Notes |
|---|---|---|
| Temperature, salinity | WOA23 (NOAA/NCEI) | climatology, WOA_FOLDER |
| Temperature, salinity | GLORYS12 (CMEMS) | cmems_mod_glo_phy_my_0.083deg_P1M-m |
| Temperature, salinity | CMIP6 ensemble | Via 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_levelstarget 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.