seamount
The seamount task group is comprised of a default task and a short task
in each of eight task trees,
planar/seamount/{eos}/{stratification}/{coord}, or in full
planar/seamount/{linear,nonlinear}/{exponential,linear_pressure}/{sigma,zstar}.
This extends the planar/overflow/{eos}/{coord} pattern with a
stratification level, because the stratification is what decides whether the
configuration is one a finite-volume pressure gradient resolves exactly, and
that is not something a user config option should have to be remembered for.
framework
The shared config options for seamount tests are described in
seamount in the User’s Guide. Each tree level contributes one
config override layered on top of the shared seamount.cfg:
level |
files |
what they set |
|---|---|---|
equation of state |
|
|
stratification |
|
|
coordinate |
|
|
The parameters of every stratification stay in the shared seamount.cfg;
only the option that selects one belongs to the tree level, the same split
coord_type already uses.
seamount_linear_eos.cfg is named for the equation of state, not for a
stratification. It was seamount_linear.cfg until the linear_pressure
stratification arrived and made the two too easy to confuse.
Neither equation of state needs new plumbing. update_namelist_eos()
already maps teos-10 to jm for MPAS-Ocean and passes it through to
Omega, and the geometric-to-pseudo-height conversion described below
dispatches on eos_type through
polaris.ocean.eos.compute_specvol().
Additionally, the tests share a forward.yaml file with a few common model
config options related to time management, time integration, Laplacian
viscosity, bottom drag and vertical mixing, as well as defining mesh,
input, restart, and output streams. It is split into a shared ocean:
section and model-specific mpas-ocean: and Omega: sections.
support for both ocean models
Both coordinates are geometric, so one init step serves either model. When
model = omega, Ocean.write_vert_coord_dataset() and
Ocean.write_model_dataset() convert restingThickness and layerThickness
into RefPseudoThickness and PseudoThickness by integrating gauge pressure
down each column through the configured equation of state. No p-star-specific
initialization is involved and no reference pseudo-grid is needed, so the
seamount does not use
polaris.ocean.vertical.pstar_init.PStarInitStep.
tests/ocean/seamount/test_sigma_to_pseudo.py covers that conversion: the
geometric round trip, that sigma layers stay proportional to column depth, and
that interior interfaces sit at genuinely different pressures from column to
column, with a flat-bottom negative control for the last of these.
tests/ocean/seamount/test_tracers.py covers the initial condition: that
both equation-of-state branches reproduce the same Beckmann and Haidvogel
profile, and the negative control that in-situ density under TEOS-10 really
does depart from it with depth.
tests/ocean/seamount/test_linear_in_pressure.py covers the
linear_pressure stratification: that the layer values are exact means of
the continuous profile over each layer’s pressure range, and that the profile
is straight in the pressure reconstructed from the pseudo-thickness rather
than only in the pressure it was built from. Both Beckmann and Haidvogel
profiles are carried through the same measurement as negative controls, since
“density linear in depth” is close enough to “temperature linear in pressure”
that the difference has to be demonstrated rather than asserted.
init
The class polaris.tasks.ocean.seamount.init.Init
defines a step for setting up the initial state for each test case.
First, a mesh appropriate for the resolution is generated using
mpas_tools.planar_hex.make_planar_hex_mesh(). The bottom topography
is defined along with a vertical grid with 32 layers by default. Next, the
ocean state is generated with a vertical temperature stratification,
back-computed from the target Beckmann and Haidvogel density so that the
density the model sees matches the profile the test intends.
The profile and the back-solve live in
polaris.tasks.ocean.seamount.init_utils, a leaf module free of
mpas_tools and of the step framework so the unit tests can import it.
polaris.tasks.ocean.seamount.init_utils.compute_tracers() branches
first on seamount_stratification_type and then on eos_type: for the two
Beckmann and Haidvogel profiles the linear equation of state is inverted
algebraically with the salinity term included, and TEOS-10 is inverted with
polaris.ocean.eos.ct_from_potential_density(), which reads the
profile as a potential density referenced to the surface. See
initial conditions in the User’s Guide for why the surface reference
is the only workable one and what it buys.
The linear_pressure stratification takes a different route through
polaris.tasks.ocean.seamount.init_utils.compute_tracers_linear_in_pressure(),
which needs layerThickness rather than zMid: it prescribes temperature as
a function of pressure and integrates the hydrostatic balance to find it.
Temperature depends on pressure, pressure on the specific volume and the
specific volume on temperature, so the profile is a fixed point and the
function iterates to PRESSURE_ITERATION_TOLERANCE rather than evaluating a
formula. The iteration contracts by roughly the fractional density range of
the column per pass and converges in well under ten;
MAX_PRESSURE_ITERATIONS is a guard rather than a working limit, and failing
to converge raises rather than returning a profile that is nearly straight.
See a stratification linear in pressure in the User’s Guide for what the
profile is for.
The step leaves its tracers in the convention implied by eos_type and does
nothing model-specific: in the nonlinear trees, the framework converts them
to potential temperature and practical salinity at the nominal lon/lat when
the model is MPAS-Ocean (see Initial state), computing
the pressure that conversion needs from the layer thicknesses since a
geometric vertical coordinate carries none.
Note that the tracers carry the Time dimension that layerThickness and
the rest of the state have. TEOS-10 requires its inputs to be aligned; the
linear equation of state did not, which is why the seamount got away without
it for a while.
forward
The class polaris.tasks.ocean.seamount.forward.Forward
defines a step for running the ocean from the initial condition produced in
the init step. Namelist and streams files are updated in
polaris.tasks.ocean.seamount.forward.Forward.dynamic_model_config()
with time steps determined algorithmically based on config options. Both
models take the same step from dt_per_km and the same integrator from
time_integrator; the only model-dependent part is translating the
integrator name to Omega’s (RK4 becomes RungeKutta4 and split_explicit
becomes SplitExplicitRK2). The
number of cells is approximated from config options in
polaris.tasks.ocean.seamount.forward.Forward.compute_cell_count()
so that this can be used to constrain the number of MPI tasks that Polaris
tasks have as their target and minimum (if the resources are not explicitly
prescribed). For MPAS-Ocean, PIO namelist options are modified and a
graph partition is generated as part of runtime_setup(). Next, the ocean
model is run. The duration is set by run_duration, in hours, in the config
section corresponding to the task (seamount_default or seamount_short,
selected by the task_name passed to the step). Finally,
the variables kineticEnergyCell and normalVelocity in the
output.nc file are visualized in the matching viz_<scheme> directory.
The step takes a scheme argument, a key of
polaris.tasks.ocean.seamount.forward.SCHEMES, which maps the
Polaris spelling to Omega’s PressureGradType and is filled into the
PressureGrad block of the Omega section of forward.yaml. There is no
“centered limit” of the finite-volume scheme – the two are separate
implementations and no setting reduces one to the other – so this is a
choice between them rather than a parameter of one. MPAS-Ocean has only the
centered scheme, and dynamic_model_config() raises if any other is asked
for under it.
Every forward step is named forward_<scheme>, from
polaris.tasks.ocean.seamount.forward.forward_step_name(), including
in the short task which runs only the centered one. The alternative was to
leave the shared scheme in a plain forward and suffix only the others, which
reads as though forward were a third thing rather than one of the two.
viz
The polaris.tasks.ocean.seamount.viz.Viz plots the maximum velocity
as a function of time; a horizontal cross-section of the normal velocity;
and a vertical cross-section of the kinetic energy. The vertical cross-section
is also convenient to see the vertical coordinate (sigma versus z-star) and
the bottom topography.
The transect needs a geometric layer thickness, and layerThickness is the
right field for both models. Omega writes PseudoThickness rather than a
geometric thickness, but Ocean.open_model_dataset() converts it on read,
as RhoSw * SpecVol * PseudoThickness. That is why SpecVol has to be in
the Omega History stream alongside State.
The step takes the scheme, from which both its own name, viz_<scheme>, and
the forward_<scheme> step it reads follow. There is one viz per forward
step.
analysis
The polaris.tasks.ocean.seamount.analysis.Analysis step measures
the spurious circulation in every forward step of the task and compares the
schemes against each other, writing metrics.csv,
spurious_velocity_t.png and one interface_tilt_<scheme>.png per scheme.
See spurious-circulation metrics in the User’s Guide for what the metrics
are and how to read them.
The static-stability guard compares each adjacent layer pair at the pressure of the interface between them, so it is potential density at a local reference pressure rather than at the surface. The tracers are used in whatever convention the model wrote them, which is exact for the linear equation of state and for Omega under TEOS-10 and an approximation for MPAS-Ocean under a nonlinear one – adequate to detect an inversion, not to size a marginal one.
Two details of the masking are worth knowing. An edge has water only where
both of its cells do, so its deepest valid level is the shallower of the two
maxLevelCell values; that is what makes the bottom-layer metric follow the
bathymetry on the seamount flanks rather than sitting at a fixed level index.
And the layer interfaces are built up from the sea floor rather than down
from the sea surface, so the tilt diagnostic needs only layerThickness and
bottomDepth and works for either model without the sea-surface height being
in the output stream.
Viz plots a maximum-velocity time series of its own. That is a
single-run quick look which also works in the short task, which has no
analysis step; the analysis step’s version is the measurement, and it
puts every scheme on shared axes.
default
The polaris.tasks.ocean.seamount.default.Default
test runs the init step, a 6 day forward_centered step, the matching
viz_centered step and the analysis step. It is added once per tree, so
eight times. Unlike most tasks, viz runs by default here: the forward run
is long enough that it is not worth making the user re-run the task just to
get the plots.
Under Omega it also runs a forward_finite_volume step and its own
viz_finite_volume, a second 6 day run over the same init step with the
finite-volume horizontal pressure gradient. Those steps are added in
polaris.tasks.ocean.seamount.default.Default.configure() rather
than in __init__(), because configure() is the first point at which the
user’s and machine’s config options have been merged in and ocean:model is
known. Adding them unconditionally would break the task outright for
MPAS-Ocean, which has no such scheme. The analysis step is added there too
and given whichever schemes were actually configured, so that it stays last
in steps_to_run and depends only on forward steps that exist.
short
The polaris.tasks.ocean.seamount.short.Short test is the same
task with a 1 hour forward_centered step, and it too is added once per
tree. It exists as a regression test rather than a measurement: an hour is
far too short for the spurious circulation to develop. viz_centered is
opt-in here, as usual, because re-running the task to get the plots is cheap,
and there is no analysis step.
The two tasks differ only in their config section, seamount_default versus
seamount_short, which is what Forward looks up from its
task_name.
Three of the eight short tasks are in the mpaso_pr and omega_pr suites:
task |
what it catches |
|---|---|
|
the linear equation of state and the z-star coordinate |
|
TEOS-10 and the sigma coordinate, so the pair covers both of each in two runs |
|
the linear-in-pressure initial condition, whose fixed-point iteration is the only part of the initial condition that has to converge rather than evaluate. TEOS-10 is what makes it have to: the specific volume depends on pressure, so the iteration actually turns over. Under the linear equation of state it would converge in a couple of passes and catch only a gross regression |
The third is a third run, not a replacement: the exponential profile is the
realistic one and remains what the other two cover. Nothing from the seamount
is in the nightly suites yet; the default tasks are 6 day runs, and under
Omega they are two of them.