Skip to content

Seamount testing of the Omega finite-volume pressure-gradient - #681

Draft
xylar wants to merge 24 commits into
E3SM-Project:mainfrom
xylar:seamount-for-hpg-testing
Draft

Seamount testing of the Omega finite-volume pressure-gradient#681
xylar wants to merge 24 commits into
E3SM-Project:mainfrom
xylar:seamount-for-hpg-testing

Conversation

@xylar

@xylar xylar commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Coming soon...

Checklist

  • User's Guide has been updated
  • Developer's Guide has been updated
  • API documentation in the Developer's Guide (api.md) has any new or modified class, method and/or functions listed
  • Documentation has been built locally and changes look as expected
  • Testing comment in the PR documents testing used to verify the changes
  • New tests have been added to a test suite

xylar and others added 24 commits August 1, 2026 05:28
The seamount initial condition back-solved temperature from the target
Beckmann and Haidvogel density using only

    rho = rho_ref - alpha * (T - T_ref)

dropping the beta * (S - S_ref) term that compute_linear_density() and
both ocean models actually apply.  With beta = 0.8 and S = 35 PSU the
density the model saw was 28 kg m-3 above the profile the test intended
to impose.  Under MPAS-Ocean that is a constant offset and harmless, but
Omega's p-star coordinate maps geometric to pseudo thickness using the
absolute density, so it is not harmless there.

Separately, seamount.cfg set eos_linear_Tref = 5, which Omega rejects:
its linear EOS has no reference temperature or salinity, and
OceanModelStep._get_linear_eos_replacements() raises for a nonzero Tref
or Sref.  This blocked Omega support outright.

Fold the reference state into rhoref at Tref = Sref = 0
(1028.0 + 0.2 * 5.0 - 0.8 * 35.0 = 1001.0) and back-solve through the
full linear EOS.  The two changes cancel exactly in the temperature
field, so temperature, salinity and velocity are unchanged to machine
precision; only the model's density diagnostic moves, by -28 kg m-3,
onto the intended Beckmann and Haidvogel profile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow the overflow pattern of a subdirectory per vertical coordinate,
giving planar/seamount/sigma and planar/seamount/zstar.  Sigma remains
the main target; z-star is the control, since the flatter its layers the
smaller the spurious pressure-gradient velocity should be.  Both are
geometric coordinates, so both will serve Omega once the forward step
supports it.

coord_type moves out of the shared seamount.cfg into per-coordinate
seamount_sigma.cfg and seamount_zstar.cfg.

The z-star tree uses partial bottom cells.  Sigma ignores partial cells,
but z-star cuts the reference grid at the seafloor, and on this
bathymetry that needs snapping to be usable: sampling the seamount
profile densely at 32 levels gives a minimum bottom-cell thickness of
7 cm with partial_cell_type = None, against 15.6 m with "partial" and
min_pc_fraction = 0.1.  min_pc_fraction is added to the shared config,
following the convention of the other ocean tasks.

Also adopt the usual os.path.join(component.name, ...) form for the
shared config filepath, which the seamount had been omitting.

No test suite references the seamount, so the task path change is safe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Split forward.yaml into shared "ocean:", "mpas-ocean:" and "Omega:"
sections following the overflow task, and teach forward.py to configure
either model.

Omega has no split-explicit integrator, so it is limited by the
barotropic gravity wave: sqrt(g * 5000 m) = 221 m/s against dx = 6.7 km
gives a CFL-1 limit near 4.5 s/km, where MPAS-Ocean's split-explicit
step runs at 10.  The models therefore need separate time steps and
integrators, so dt_per_km/time_integrator gain omega_ counterparts.
The Omega step starts at 2.5 s/km, matching MPAS-Ocean's own barotropic
step; this is a first guess pending a stability test.

Vertical mixing is switched off.  The exact solution is a resting ocean,
so the only thing that would ever trigger convection here is a spurious
pressure gradient -- the quantity being measured -- and convection is a
known source of MPAS-Ocean/Omega divergence, since Omega uses a single
coefficient for both convective diffusivity and viscosity.  cvmix stays
enabled with its coefficients zeroed rather than disabled, because
config_use_cvmix = false falls back on MPAS-Ocean's constant vertical
viscosity and diffusivity, which are not zero.  This changes MPAS-Ocean
answers.

Bottom drag stays implicit and constant in both models, which the
recently added Omega implicit bottom drag support makes possible, and
moves to the shared section.  Horizontal tracer advection order is
pinned so the two models do not use their differing defaults.

Verified with polaris setup for Omega: the generated omega.yml has
TimeStepper RungeKutta4, TimeStep 16.750 s, BottomDragTendency implicit
and constant, VertMix background zeroed with convection and shear off,
and Eos linear with RhoT0S0 1001.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both changes re-baseline MPAS-Ocean.

Cd 1e-2 -> 1e-3.  The implicit drag damping timescale is
h_bot / (Cd * |u|), and with sigma at 32 levels the bottom layer over the
seamount summit -- where the coordinate is most tilted and the signal
lives -- is only ~16 m thick.  At 1e-2 a 5 cm/s spurious velocity damps
in 0.4 days against a 6 day run, while a 1 cm/s one damps in 1.8 days;
the metric saturates, capping a bad pressure gradient while leaving a
good one untouched.  At 1e-3 drag remains a backstop against unbounded
spin-up without flattening the signal.  Comparable idealized planar
cases in Compass use 1e-2, but none of them is measuring a quantity that
drag suppresses.

vert_levels 10 -> 32.  A multiple of 16, which Omega prefers, dividing
the 5000 m bottom depth into exact 156.25 m layers, and in the range the
literature uses for this case (20 in Beckmann and Haidvogel, 20-30 in
Shchepetkin and McWilliams) rather than below it.  It also rescues the
z-star variant: at 10 uniform levels the 500 m summit column has a
single level, against three full levels plus a partial at 32.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The seamount hands Omega a geometric coordinate and relies on Polaris
converting it to pseudo-height, rather than on any p-star-specific
initialization.  These tests exercise that conversion directly, on a
seamount-like set of columns, with no file I/O or step framework.

They cover:

- the temperature back-solve reproduces the Beckmann and Haidvogel
  density through compute_linear_density();
- geometric -> pseudo -> geometric recovers the column thickness, for
  sigma and for z-star with partial cells;
- sigma layers are exactly proportional to column depth;
- interior interfaces sit at genuinely different pressures from column
  to column, which is the property a clipped reference grid would
  destroy by leaving the whole pressure-gradient signal in the bottom
  cell.

The last of these gets an explicit negative control on a flat bottom,
where the same measurement must come back at round-off.  Without it the
tilt assertion could not fail, and a guard that cannot fire is worse
than no guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace "These tasks support only MPAS-Ocean", describe the two
coordinate trees, and explain the geometric-to-pseudo-height route that
lets one init step serve both models.

Record the decisions that are not obvious from the code: why
eos_linear_Tref must be zero and the reference state is folded into
eos_linear_rhoref; why the temperature back-solve has to include the
salinity term; why all vertical mixing is off but cvmix stays enabled;
why the bottom drag coefficient sits an order of magnitude below the
value comparable idealized planar cases use; why z-star needs partial
cells; and why Omega takes a shorter time step.

Also drop config options from the User's Guide that the task does not
have: seamount_density_ref, seamount_density_Tref and
seamount_density_alpha, replaced by the eos_linear_* options in the
ocean section, and coriolis_parameter, now type and constant_f in the
coriolis section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add `ct_from_potential_density()` to `polaris/ocean/eos/teos10.py`, the
TEOS-10 counterpart of back-solving a linear equation of state for
temperature.  It lets a test case specify its stratification as a density
profile rather than a temperature profile, which several idealized cases
do -- the seamount's Beckmann and Haidvogel profile among them.

Referencing the target density to the surface makes the inversion exact
and non-iterative: `gsw.CT_from_rho` is called at `p = 0`, so the result
does not have to be folded into the pressure/specific-volume iteration
that the geometric-to-pseudo-height conversion already performs.

Only the cold branch of `CT_from_rho` is returned.  The warm branch
exists only below the density maximum, which for seawater lies below the
freezing point at oceanographic salinities; `gsw` reports it as NaN
throughout the range any of these cases use.  An unattainable target
density raises rather than returning NaN, since a NaN would otherwise
propagate silently into an initial condition.  NaN input, which marks
below-bottom cells, still passes through untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Split the seamount into `planar/seamount/{linear,nonlinear}/{sigma,zstar}`,
following the `planar/overflow/{eos}/{coord}` pattern.  The nonlinear trees
use TEOS-10 for Omega and Jackett-McDougall for MPAS-Ocean, selected by the
shared `polaris.ocean.eos` `teos10.cfg`; no new EOS or vertical-coordinate
plumbing is needed, because the geometric-to-pseudo-height conversion that
Omega's coordinate goes through already dispatches on `eos_type`.

The Beckmann and Haidvogel initial condition is specified as a density
profile, and the linear EOS is inverted for the temperature that reproduces
it.  That inverse does not carry over: TEOS-10 in-situ density at 5000 m is
near 1050 kg m^-3 from compression alone, so the 1025-1028 kg m^-3 profile
cannot be read as in-situ density at any temperature.  It is therefore read
as a potential density referenced to the surface and inverted with
`ct_from_potential_density()`.  Referencing to the surface is not just a
convenience: it makes the linear and nonlinear trees share a buoyancy
stratification exactly, so a difference in spurious velocity between them is
attributable to the equation of state rather than to a different N^2.
Verified on the 32-level grid: sigma_0 agrees between the two trees to
2.3e-13 kg m^-3 while in-situ density spans 1025.1-1050.6, and the geometric
layer positions are identical to the last bit.

The profile and tracer construction move to a new `init_utils.py`, a leaf
module free of `mpas_tools` and of the step framework so the unit tests can
import it, named after overflow's module of the same name.  The linear EOS
coefficients move from the shared `seamount.cfg` into a new
`seamount_linear.cfg`, since they mean nothing to the nonlinear trees.

For MPAS-Ocean the CT/SA tracers have to become potential temperature and
practical salinity.  The step does not do that itself: it leaves the tracers
in the convention `eos_type` implies and `write_initial_state_dataset()`
converts them at the `[ocean]` nominal lon/lat, computing the pressure the
salinity correction needs from the layer thicknesses, since a geometric
vertical coordinate carries none.

The tracers now carry the `Time` dimension that `zMid`, `layerThickness` and
the rest of the state already had, matching overflow.  TEOS-10 requires its
inputs to be aligned and the linear EOS did not, which is why this went
unnoticed.  Values are unchanged: `init.nc` and `vert_coord.nc` for both
linear trees match the test_20260730 baseline exactly across all 23 and 5
variables respectively, with only the added dimension differing.

Paths change for the existing trees, from `planar/seamount/{sigma,zstar}` to
`planar/seamount/linear/{sigma,zstar}`.  No suite references the seamount.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cover both equation-of-state branches of the seamount tracer construction,
in the self-contained style of the existing coordinate tests: a synthetic
two-column set spanning the seamount's depth range, no file I/O and no step
framework.

The tests that matter are the two that state the design:

- both branches reproduce the same Beckmann and Haidvogel profile, the
  linear one as density and the nonlinear one as potential density at zero
  reference pressure, agreeing to 1e-11 kg m^-3.  This is the property that
  makes a difference in spurious velocity between the trees attributable to
  the equation of state rather than to a different N^2.
- the negative control: in-situ density under TEOS-10 departs from the
  surface-referenced profile by more than 20 kg m^-3 at 5000 m, monotonically
  with depth, against the 3 kg m^-3 the whole profile spans.  Without that
  departure the nonlinear tree would measure nothing the linear one does
  not.

Also covers the linear back-solve against `compute_linear_density()`, which
the coordinate tests did not reach, the linear stratification branch, and
both unsupported-option errors.  The conversion of the nonlinear tracers to
the MPAS-Ocean convention is the framework's and is covered by the
framework's own tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the two-tree coordinate description with the four-tree
`{linear,nonlinear}/{sigma,zstar}` layout, and split the "variants" section
into what the coordinate controls (how much the layers tilt) and what the
equation of state controls (whether density depends on pressure).

The initial-conditions section now explains why the Beckmann and Haidvogel
profile is read as a potential density referenced to the surface under a
nonlinear equation of state -- in-situ density at 5000 m is near
1050 kg m^-3, so no temperature reproduces the 1025-1028 profile below about
1000 m -- and what the surface reference buys: an identical buoyancy
stratification between the linear and nonlinear trees.

Also records the caveat that TEOS-10 and Jackett-McDougall are different
functions rather than two implementations of one, so the two models are
only expected to agree qualitatively in the nonlinear trees.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Omega has no split time stepper, so MPAS-Ocean gives up its
split-explicit one and both models now run RK4 at the same
`dt_per_km = 4.0`.  The per-model `omega_dt_per_km` and
`omega_time_integrator` config options are removed; only the integrator
name is still translated for Omega (`RK4` -> `RungeKutta4`).
`btr_dt_per_km` is kept but is inert unless `time_integrator` is set back
to a split-explicit scheme.

Vertical mixing is also switched off for MPAS-Ocean with
`config_use_cvmix = false`, matching Omega, whose coefficients were
already zeroed.  The comment claiming that disabling cvmix falls back on
a nonzero constant vertical viscosity and diffusivity is removed: in the
current MPAS-Ocean, `ocn_vmix_coefs` zeroes the coefficients and
`ocn_vmix_coefs_cvmix_build` returns immediately when cvmix is off, so
nothing writes into them.

The implicit vertical mixing solve itself has to stay enabled in both
models, since that is what applies the implicit bottom drag, which is
now noted where it would otherwise be tempting to disable it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The default seamount task integrates for 6 days, which is long enough
that it is not worth making the user re-run the task just to get the
plots.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The default seamount task runs for 6 days, which is far too long for a
pull request suite.  Add a `short` task alongside it in each of the four
trees: the same mesh, initial condition, time step and physics, run for
1 hour.  An hour is too short for the spurious circulation to develop,
so it measures nothing about the pressure gradient error, but it is
enough to catch a change in the answer cheaply.

`viz` does not run by default in the short task, unlike the default
task, since re-running an hour-long forward step to get the plots costs
almost nothing.

Two of the four are added to `mpaso_pr` and `omega_pr`, `linear/zstar`
and `nonlinear/sigma`, which covers both equations of state and both
vertical coordinates in two runs rather than four.

`run_duration` is now given in hours rather than days so that both task
sections can use the same units as `output_interval`.  The default task
is unchanged at 144 hours.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.7 km was never prescribed by Beckmann and Haidvogel; it was carried
over from the legacy Compass version of this test, where it appears to
have been chosen as roughly 320 / 48.  It does not divide the domain,
and since the mesh is built from `nx * resolution` rather than from `lx`,
the domain was really 321.6 km by 324.9 km rather than the 320 km by
320 km the config and docs advertise.

320 km divides into exactly 50 cells at 6.4 km, so `lx` now means what it
says (`ly` is still approximate at 321.5 km, since the hexagonal mesh
spacing in y is sqrt(3)/2 times the spacing in x).

The test itself is unchanged in any way that matters.  What the pressure
gradient error responds to is the discrete steepness of the seamount,
max |dh| / (h1 + h2) between adjacent cells, which for a Gaussian
seamount is about 1.5 * resolution / seamount_width: 0.22 at 6.7 km and
0.21 at 6.4 km.  Both bracket the value quoted for the configuration in
the literature, so the case is neither easier nor harder in any
meaningful sense, and the bottom drag and time step reasoning carries
over untouched.  `dt_per_km` in particular needs no retuning: the
gravity wave frequency goes like 1 / dcEdge, so the stability cap of
5.2 s/km is the same at any resolution.

The cost is 8% more cells (2900 rather than 2688) and a 4% shorter time
step.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A finite-volume horizontal pressure gradient reconstructs the tracers as
polynomials in pressure and is exact when the continuous profile is linear
in pressure.  Neither Beckmann and Haidvogel profile has that property.
The exponential one obviously does not; the linear one is a density linear
in geometric *depth*, and since specific volume varies down the column,
pressure is quadratic in depth and the profile is only approximately
straight in pressure.

Measured on this configuration, temperature departs from a least-squares
straight line in pressure by 8.5e-6 of its range under the linear equation
of state and 6.9e-4 under TEOS-10 -- small, but ten orders of magnitude
above round-off, and a residual at that level leaves a spurious velocity
that is neither the error one wants excluded nor evidence of anything else.

So add seamount_stratification_type = linear_pressure, prescribing
temperature as a function of pressure with salinity constant.  Two
properties make the exactness usable, and both are what the new tests
measure:

- The layer values are exact layer means, not point samples.  Both models
  carry a layer-mean tracer, and the mean is over the mass of the layer,
  which is the same as over its pressure range; for a straight line that
  is the value at the layer's mid-pressure.
- The profile is a fixed point, not a formula.  Temperature depends on
  pressure, pressure on the specific volume, and the specific volume on
  temperature.  The iteration runs to round-off, so the profile is linear
  in the pressure the model itself carries; failing to converge raises
  rather than returning a profile that is merely nearly straight.

The profile reaches 7e-15 on the same measure that gives the Beckmann and
Haidvogel profiles 8.5e-6, 6.9e-4 and 6.1e-1, so the guard can fire.  Both
of those profiles are carried through the tests as negative controls.

The default range is 15 degC over 5000 dbar, which under the linear
equation of state spans 3.0 kg m^-3 -- the same density range as the
exponential profile, so the two runs have the same total buoyancy range.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two schemes have to be compared at an identical state, so add a second
forward step over the shared init rather than a second task with its own
initial condition.  Forward gains a `scheme` argument that maps through
SCHEMES to Omega's PressureGradType, filled into a new PressureGrad block
in the Omega section of forward.yaml.

The scheme is written explicitly in both steps rather than left to Omega's
default, so which scheme a run used is recorded with the run instead of
inferred from the model version.

Only Omega has a finite-volume scheme, so the step is added in
Default.configure() -- the first point at which ocean:model is known --
and not at all under MPAS-Ocean, where the task stays exactly what it was.
dynamic_model_config() raises if a non-centered scheme is asked for under
MPAS-Ocean anyway, in case the step is reached some other way.

The centered step keeps the plain name `forward` rather than becoming
`forward_centered`: it is the run both models share and the one existing
baselines hold, so renaming it would silently drop the baseline comparison
for the scheme that has not changed.

Verified by setting up ocean/planar/seamount/linear/sigma/default under
both models: Omega gets `init forward forward_finite_volume viz` with
PressureGradType Centered and FiniteVolume respectively, MPAS-Ocean gets
`init forward viz`, and `short` is `init forward` under both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every velocity in this task is error, so the question the metrics have to
answer is how much of it there is, where in the column it sits, and how
the schemes compare on a shared initial condition.  Add an analysis step
that writes, per scheme and output time: max |u|, the level index where
that maximum sits, max |u| restricted to each edge's bottom valid level,
the volume-weighted mean kinetic energy, and the balanced acceleration the
spurious velocity implies as a fraction of a reference bottom-layer
pressure gradient.

An absolute spurious velocity says little on its own; the ratio to a
gradient a realistic configuration actually carries is what gives it
meaning, so reference_bottom_pressure_grad is a config option with its
provenance written down.  |f| max|u| is a balanced-state estimate and
overstates the acceleration while the flow is still spinning up, which is
said in the config comment, the docs and the plot label rather than left
for a reader to infer.

No thresholds.  They are to be set from what this measures; a threshold
guessed before the first measurement is a guard that either cannot fire or
fires for the wrong reason.

The bottom-layer metric follows the bathymetry rather than a fixed level:
an edge has water only where both its cells do, so its deepest valid level
is the shallower of the two maxLevelCell values.

Also plot how each interface's tilt evolves, since a spurious velocity that
peaks at the surface has a mundane candidate explanation -- sigma
interfaces start level at the surface, but the free surface moves the
layers and the coordinate movement weights are uniform, so the top
interface can acquire a tilt it did not start with.  Interfaces are built
up from the sea floor, so this needs only layerThickness and bottomDepth
and works for either model.

Viz gains the forward step it plots and the scheme it ran, so each forward
step has its own.

Checked against the existing 6 day Omega baseline for linear/sigma.  Two
things fall out of it already: the maximum starts in the bottom layer and
only moves to the surface after about a fifth of a day, and the free
surface tilt stays near 2e-4 m/km against 93 m/km for the deepest
interfaces, so the tilt-evolution candidate does not look like the
explanation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An Omega that does not recognize the requested PressureGradType logs a
message and carries on with the centered scheme instead of aborting.  The
finite-volume scheme does not exist yet in the Omega this branch pins, so
forward_finite_volume will run, succeed, and produce centered answers --
which a comparison of the two steps would read as the schemes agreeing.

Nothing can be done about it from the Polaris side, so say it where
someone about to believe such a result will see it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two new subsections landed between the description paragraph and the
figures it refers to, leaving "these are the images produced in the viz
folder" pointing past two pages of unrelated prose.  Move the figures back
up against that sentence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A configuration only a user config file reaches is one nobody runs.  The
linear-in-pressure stratification is the configuration on which a
finite-volume pressure gradient is exact, which is the whole reason it
exists, and until now it needed `seamount_stratification_type` set by hand.

So add a stratification level to the task tree:
`planar/seamount/{eos}/{stratification}/{coord}`, eight trees rather than
four.  The option that selects the profile moves into
seamount_exponential.cfg / seamount_linear_pressure.cfg, the same split
`coord_type` already uses; the parameters of every profile stay shared.

Every existing path changes, which is the cost of the symmetric layout over
bolting a third task onto each tree.  Taken now because the branch is not
merged and the alternative leaves two tasks named by duration next to two
named by stratification.

`seamount_linear.cfg` becomes `seamount_linear_eos.cfg`.  It is named for
the equation of state, and next to `seamount_linear_pressure.cfg` the old
name was a trap.

Since paths are moving anyway, drop the asymmetry that had the centered
scheme in a plain `forward` while the other was `forward_finite_volume`.
Both are now `forward_<scheme>` with a matching `viz_<scheme>`, including
in `short`, which runs only the centered one -- so an output directory says
which scheme produced it without anyone opening the model config.  The
argument for the old name was that it preserved baseline comparability, and
the tree rename ends that either way.

`nonlinear/linear_pressure/sigma/short` joins mpaso_pr and omega_pr, a
third seamount run of about seven seconds.  It is the only suite coverage
of the fixed-point iteration in the initial condition, and TEOS-10 is what
makes that iteration have to converge rather than evaluate: under the
linear equation of state the specific volume does not depend on pressure
and it would settle in a couple of passes.

Checked by setting up nonlinear/linear_pressure/sigma under both models:
Omega gets `init forward_centered viz_centered forward_finite_volume
viz_finite_volume analysis` with Centered and FiniteVolume respectively,
MPAS-Ocean gets `init forward_centered viz_centered analysis`, `short` is
`init forward_centered` under both, and all 16 tasks list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Under the linear equation of state, specific volume depends only on
temperature and salinity.  Prescribe a temperature linear in pressure and
specific volume becomes linear in pressure too -- and the same function of
pressure in every column, which leaves the ocean horizontally homogeneous in
pressure coordinates and nulls the centered scheme along with the
finite-volume one.

Measured on this case, specific volume departs from a straight line in
pressure by 4.6e-4 of its range under the linear equation of state against
8.6e-3 under TEOS-10, whose compressibility keeps the dependence nonlinear
whatever the tracers do.  The first 6 day pair was run in the linear trees
and the two schemes tracked each other to sub-percent in mean kinetic energy
for four days, which reads as a finding about a non-pressure-gradient source
and is nothing of the kind.

Same trap one level down from the one the profile was added to avoid: a null
configuration is null only for the scheme it was built for, and an equation
of state can quietly extend it to both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All vertical mixing including convection is off in this task, so nothing
restores a column that overturns.  Once one does, the run stops measuring a
spurious circulation against a resting exact solution and starts measuring an
unbounded convective response with the response removed -- and the velocity
grows without any physical bound, which reads as a large spurious circulation
if nobody checks.

This is not hypothetical.  On the 6 day linear/exponential/sigma run the
finite-volume integration first overturned at day 4 on the seamount flank at
r = 14 km, level 22, reached 232 unstable layer pairs and a 0.15 kg m^-3
inversion by day 6, and grew exponentially with an e-folding time of 1.2 days
over the last day and a half.  The centered integration on the identical
configuration stayed stable throughout.  Every day-6 number reported from
those runs was measuring the overturning.

So count the statically unstable layer pairs at each output time, alongside
the largest density inversion, compare each pair at the pressure of the
interface between them, plot the count as the last panel and say in the log
which day a run first overturned.  A run that overturns now says so instead
of quietly producing numbers.

The guard was checked against the counts computed by hand from the same
output: 0, 2, 100 and 232 at days 3, 4, 5 and 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
forward.yaml set only config_use_mom_del2 and config_mom_del2, so each model
fell back to its own defaults for the rest.  Omega ran with hyperviscosity at
1.2e11 m^4 s^-1 and tracer diffusion at 10 m^2 s^-1; MPAS-Ocean ran with
neither.  Neither was asked for, and the docs claimed the case had only
Laplacian momentum viscosity.

Tracer diffusion is the one that matters.  It acts along the coordinate
surfaces, and on a sigma coordinate over a seamount those are tilted, so at a
slope of 0.1 it is an effective diapycnal diffusivity of order 0.1 m^2 s^-1
-- in a case whose exact solution is a resting ocean and whose whole subject
is what a tilted coordinate does to the pressure gradient.

This is the trap the advection order two lines below is already pinned
against, and the same one behind the model-default hyperviscosity: an option
left unset is not an option left off.

Changes Omega's answer, not MPAS-Ocean's.  The time step is unaffected but its
justification is: with hyperviscosity off, the 1100 s it would have allowed no
longer applies, so the comment in seamount.cfg drops it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@xylar xylar self-assigned this Aug 3, 2026
@xylar xylar added enhancement New feature or request ocean Related to the ocean component Omega PR required The polaris changes won't work with the current Omega submodule and require an update labels Aug 3, 2026
@xylar

xylar commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Build off of #678

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request ocean Related to the ocean component Omega PR required The polaris changes won't work with the current Omega submodule and require an update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant