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:

  • PASS means every compared numeric variable has status PASS.

  • FAIL means at least one compared variable is MARGINAL, DISCREPANT, or BROKEN.

  • ERROR means 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

couette

PASS

100

0

0

0

Simple Couette flow.

blacksea

PASS

121

0

0

0

Black Sea seasonal cycle.

channel

PASS

100

0

0

0

Open-channel flow.

entrainment

PASS

100

0

0

0

Convective entrainment.

estuary

PASS

100

0

0

0

Estuarine circulation.

flex

PASS

104

0

0

0

FLEX experiment.

gotland

FAIL

74

14

16

0

Baltic Sea Gotland Deep.

lago_maggiore

PASS

105

0

0

0

Alpine lake.

langmuir

FAIL

111

1

1

0

Langmuir turbulence with Stokes drift.

liverpool_bay

PASS

105

0

0

0

Tidal mixing in Liverpool Bay.

medsea_east

FAIL

124

16

0

0

Eastern Mediterranean.

medsea_west

FAIL

128

11

1

0

Western Mediterranean.

nns_annual

FAIL

102

7

6

0

North Sea annual cycle.

nns_seasonal

PASS

105

0

0

0

North Sea seasonal cycle.

ows_papa

FAIL

111

1

1

0

Ocean Weather Station Papa.

plume

PASS

113

0

0

0

Freshwater plume.

resolute

FAIL

92

17

6

0

Arctic mixing.

reynolds

PASS

105

0

0

0

Reynolds number scaling.

rouse

PASS

108

0

0

0

Rouse sediment profile.

seagrass

PASS

104

0

0

0

Seagrass canopy dynamics. See Fortran Parity Deviations.

wave_breaking

PASS

100

0

0

0

Wave-breaking enhanced mixing.

asics_med

PASS

104

0

0

0

Mediterranean deep convection.

Indicator Summary

The current validation suite uses Frechet-distance indicators from src/pygotm/validation:

d_raw

Discrete Frechet distance on aligned original values.

d_norm

Discrete 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_rel

d_raw / signal_scale for variables whose signal magnitude is below the configured variable floor.

score

The status-driving value. This is normally d_norm and switches to d_rel for below-floor signals. The selected indicator is recorded in metric_mode.

peak_d_norm

Non-classifying diagnostic d_norm computed with full-range normalization and frechet_k = 400 to retain a peak-sensitive debugging signal.

Variable status bands are:

  • PASS - score < 0.01

  • MARGINAL - 0.01 <= score < 0.05

  • DISCREPANT - 0.05 <= score < 0.20

  • BROKEN - score >= 0.20 or 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.pyinit_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.pytime_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.pystep_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.