Test Cases¶
pyGOTM is validated against the 22 official GOTM 6.0.7 test cases. The table below summarizes the latest generated validation/report/report.html snapshot,
generated at 2026-06-01T23:33:58Z.
Case status is aggregated from Frechet variable statuses:
PASSmeans every compared numeric variable has statusPASS.FAILmeans at least one compared variable isMARGINAL,DISCREPANT, orBROKEN.ERRORmeans the case failed during setup, execution, or comparison before a complete variable table could be produced.
The snapshot verdict is PARTIAL PARITY: 15 cases pass, 7 cases fail, and 0 cases error.
Across all cases, the variable totals are 2316 PASS, 67 MARGINAL, 31 DISCREPANT, and 0 BROKEN.
Each case name in the table below links to the committed full
per-case report snapshot. If a link 404s, regenerate the reports with
conda run -n pygotm python -m pygotm.validation.run_validation
and rebuild the documentation.
Case |
Case status |
PASS |
MARGINAL |
DISCREPANT |
BROKEN |
Notes |
|---|---|---|---|---|---|---|
PASS |
100 |
0 |
0 |
0 |
Simple Couette flow. |
|
PASS |
121 |
0 |
0 |
0 |
Black Sea seasonal cycle. |
|
PASS |
100 |
0 |
0 |
0 |
Open-channel flow. |
|
PASS |
100 |
0 |
0 |
0 |
Convective entrainment. |
|
PASS |
100 |
0 |
0 |
0 |
Estuarine circulation. |
|
PASS |
104 |
0 |
0 |
0 |
FLEX experiment. |
|
FAIL |
74 |
14 |
16 |
0 |
Baltic Sea Gotland Deep. |
|
PASS |
105 |
0 |
0 |
0 |
Alpine lake. |
|
FAIL |
111 |
1 |
1 |
0 |
Langmuir turbulence with Stokes drift. |
|
PASS |
105 |
0 |
0 |
0 |
Tidal mixing in Liverpool Bay. |
|
FAIL |
124 |
16 |
0 |
0 |
Eastern Mediterranean. |
|
FAIL |
128 |
11 |
1 |
0 |
Western Mediterranean. |
|
FAIL |
102 |
7 |
6 |
0 |
North Sea annual cycle. |
|
PASS |
105 |
0 |
0 |
0 |
North Sea seasonal cycle. |
|
FAIL |
111 |
1 |
1 |
0 |
Ocean Weather Station Papa. |
|
PASS |
113 |
0 |
0 |
0 |
Freshwater plume. |
|
FAIL |
92 |
17 |
6 |
0 |
Arctic mixing. |
|
PASS |
105 |
0 |
0 |
0 |
Reynolds number scaling. |
|
PASS |
108 |
0 |
0 |
0 |
Rouse sediment profile. |
|
PASS |
104 |
0 |
0 |
0 |
Seagrass canopy dynamics. See Fortran Parity Deviations. |
|
PASS |
100 |
0 |
0 |
0 |
Wave-breaking enhanced mixing. |
|
PASS |
104 |
0 |
0 |
0 |
Mediterranean deep convection. |
Indicator Summary¶
The current validation suite uses Frechet-distance indicators from
src/pygotm/validation:
d_rawDiscrete Frechet distance on aligned original values.
d_normDiscrete Frechet distance after section-aware dynamic linear/log normalization. Core PyGOTM variables use full finite ranges by default; non-PyGOTM variables use a wide robust range.
d_reld_raw / signal_scalefor variables whose signal magnitude is below the configured variable floor.scoreThe status-driving value. This is normally
d_normand switches tod_relfor below-floor signals. The selected indicator is recorded inmetric_mode.peak_d_normNon-classifying diagnostic
d_normcomputed with full-range normalization andfrechet_k = 400to retain a peak-sensitive debugging signal.
Variable status bands are:
PASS-score < 0.01MARGINAL-0.01 <= score < 0.05DISCREPANT-0.05 <= score < 0.20BROKEN-score >= 0.20or a structural comparison failure
Variables listed in PYGOTM_VARIABLES are reported in the PyGOTM section.
Other numeric variables are treated as FABM biogeochemical variables.
Fortran Parity Deviations¶
Some pyGOTM behaviours intentionally preserve quirks in the GOTM 6.0.7 reference path. These are validation-contract choices for the current reference NetCDF set; changing them changes the validation target and requires new reference outputs.
seagrass: init_seagrass activation bug¶
Affected case: seagrass
Source file: src/pygotm/extras/seagrass/seagrass.py —
init_seagrass()
Fortran seagrass.F90 declares module variable method and a separate
local variable i inside init_seagrass. The YAML value is read into
method, but the activation check uses local i. There is no assignment to
i in the subroutine:
call branch%get(method, 'method', ..., default=0)
...
if (i .ne. 0) seagrass_calc = .true.
pyGOTM mirrors the current reference behaviour by storing method but leaving
state.seagrass_calc at its default False value in
init_seagrass(). Runtime construction
then emits seagrass_active = 0 and the timestep loop does not run the
seagrass drag path. In the current validation report, seagrass is PASS
with 104 passing variables and no MARGINAL, DISCREPANT, or BROKEN
variables.
first_order turbulence: step-0 cmue1/cmue2 initialisation¶
Affected reference cases: current turb_method = first_order cases:
seagrass and wave_breaking.
Source files: src/pygotm/gotm/time_loop.py —
time_loop_compiled(); and
src/pygotm/turbulence/compute_cpsi3.py.
Fortran turbulence.F90 allocates cmue1 and cmue2 as zero-filled
arrays. During model-parameter setup, compute_cpsi3 can write
stability-function probe values before the first output. In pyGOTM this
initialisation side effect lives in compute_cpsi3.py: the Constant
first-order stability path fills the full arrays, while the other first-order
stability paths update only the probe level. time_loop_compiled therefore
does not run the regular first-order stability-function update before the
step-0 output.
first_order turbulence: kb forwarded to alpha_mnb¶
Affected reference cases: current turb_method = first_order cases:
seagrass and wave_breaking.
Source file: src/pygotm/gotm/time_loop.py —
step_turbulence_first_order_single()
Fortran alpha_mnb.F90 computes at from tke, eps, and kb:
at(i) = tke(i) / eps(i) * kb(i) / eps(i). pyGOTM implements the same
calculation in src/pygotm/turbulence/alpha_mnb.py. In the first-order
compiled path, kb is initialised to kb_min and is not advanced by
step_turbulence_first_order_single, but it is still passed to
step_alpha_mnb_single so at is computed from the real kb array, not
from a placeholder.