Global Ocean Forward Runs
Creation date: 2026/07/03
Contributors: Xylar Asay-Davis, Claude
Summary
This design document describes a new Polaris capability: a reusable framework for
realistic, global-ocean forward runs with either MPAS-Ocean or Omega, selected by
the [ocean] model config option. The framework belongs to the
ocean/spherical/realistic_global family, alongside the initial-condition
workflow described in global_ocean_init.md. Where that
workflow produces a model initial condition, this framework runs the configured
model forward from such a condition.
The intent is to replace and generalize the forward-run machinery of the legacy
Compass global_ocean test family (compass/ocean/tests/global_ocean/forward.py
and its test cases) using Polaris’ shared-step design and its existing ocean
model abstraction. The single deliverable of the first phase is a reusable
forward step plus one runnable “simple forward” task (a run whose duration and
cadence come from config). The framework is deliberately shaped so that the two
other forward run types from Compass — a restart test and the staged dynamic
adjustment described in
global_ocean_dynamic_adjustment.md — are
straightforward to build on top of it later.
That first phase has landed, and so has more than it promised. Two runnable
task families exist rather than one — forward on a mesh the init workflow
builds, and cached_forward on an initial condition downloaded from the
Polaris input-file database — and both run the same physics, with comparing
MPAS-Ocean against Omega left as an opt-in config. Restart chaining
landed as well and dynamic adjustment is built on it without modifying the
forward step, exactly as this document called for, as are a restart task and a
one-year Omega run that writes what the ocean analysis reads. Now that Omega has
a split-explicit time stepper, it is the default for both models, and every
short run also comes in an RK4 variant.
A forward run must be able to obtain its model inputs (horizontal mesh, vertical
coordinate, initial state, surface forcing, and graph) from either of two
sources: a file staged in the Polaris input-file database and referenced by
name, or the outputs of the upstream realistic_global/init steps. It must
express the per-run controls — run duration, output and restart cadence, time
step, damping, and the mixing and parameterization settings that vary with the
mesh — in a model-agnostic form and translate them, in a single place, to the
configuration options of the configured model. It must also write restart
files, so that a sequence of forward runs can be chained.
The primary software challenge is not the forward integration itself, which
Polaris’ OceanModelStep already supports, but the workflow abstractions around
it: a clean separation between where inputs come from, what a run should do,
and how that maps onto MPAS-Ocean versus Omega. This design is successful if
Polaris can run a realistic global forward simulation for either model from
either initial-condition source, if the per-run settings are described once and
mapped to the model in one isolated location, and if restart and staged
dynamic-adjustment workflows can reuse the same forward step without modifying
it.
Requirements
Requirement: A reusable forward-run capability runs a realistic global ocean simulation
Date last modified: 2026/07/03
Contributors: Xylar Asay-Davis, Claude
Polaris shall provide a reusable capability to run a realistic, global-ocean
simulation forward in time for the ocean model selected by the [ocean] model
config option (resolved to omega or mpas-ocean during component setup). A
given task targets exactly one model.
The capability shall include at least one runnable task: a simple forward run whose duration and output cadence are set through config options rather than Python source code. The task shall run to completion on at least one coarse global mesh and produce a model output file.
The capability shall size its own parallel resources from the mesh so that the same task can run on meshes of very different sizes without per-mesh code changes.
Requirement: Forward runs can start from a database initial condition or from the init workflow
Date last modified: 2026/10/21
Contributors: Xylar Asay-Davis, Claude
A forward run shall be able to obtain the model inputs it consumes (horizontal mesh, vertical coordinate for Omega, initial state, surface forcing, and, for MPAS-Ocean, a graph partition file) from either of two sources:
A file staged in the Polaris input-file database and referenced by name.
The outputs of the upstream
realistic_global/initsteps for the same mesh and configured model.
The choice of source shall be a property of how the task is assembled, not a branch inside the forward step.
Requirement: Comparing MPAS-Ocean with Omega is opt-in
Date last modified: 2026/10/21
Contributors: Xylar Asay-Davis, Claude
Every forward run shall be as close to production as each model allows, whatever its initial condition. MPAS-Ocean therefore runs physics Omega lacks, and a user who wants to compare the two models shall be able to turn that physics off through config, without any code or task knowing that a comparison is being made.
The cached initial conditions are E3SM’s, for E3SM meshes, converted for Omega. They let Omega run realistic global configurations, and be compared with MPAS-Ocean, before Polaris could build its own initial conditions. The tasks that use them are temporary.
Requirement: Per-run settings are described in a common, model-agnostic form
Date last modified: 2026/10/15
Contributors: Xylar Asay-Davis, Claude
The controls that define a single forward run — run duration, output interval, restart interval, time-step selection, and damping strength — shall be described in a single, model-agnostic representation. Polaris shall translate that representation into the configuration options appropriate for the configured model in exactly one place, so that MPAS-Ocean and Omega differences are isolated rather than scattered through the workflow.
Run, output, and restart durations shall be expressible as duration strings compatible with the staged-adjustment schedule format so that a simple forward run and a dynamic-adjustment stage can share the same representation. The time step shall be derivable from mesh resolution (a value per kilometer scaled by the mesh’s minimum resolution) while still allowing an explicit override.
Split-explicit time stepping shall be the default for both models, since RK4 is too expensive for long runs. Every short forward task shall also be available with RK4 in both models, for occasional testing.
Requirement: The framework composes into restart and dynamic-adjustment workflows
Date last modified: 2026/07/03
Contributors: Xylar Asay-Davis, Claude
The forward step shall be general enough that a restart test and the staged dynamic-adjustment workflow can be built from it without changing the step itself. In particular, the step shall be able to write a restart file at a configured cadence, and it shall accept the settings needed to continue from an existing restart (a restart flag, a start time, and a restart input file).
The framework itself is not required to implement the restart test or dynamic
adjustment in this phase; it is required only to leave clean seams for them,
consistent with
global_ocean_dynamic_adjustment.md, which
decomposes dynamic adjustment into one restart-chained forward step per stage.
Requirement: Forward steps produce inspectable outputs and support basic validation
Date last modified: 2026/07/03
Contributors: Xylar Asay-Davis, Claude
A forward step shall produce clearly named, inspectable outputs — at least a model output file and, when a restart cadence is configured, restart files — that users and developers can examine for sanity checking and debugging.
The step shall support basic validation of a small set of output variables against a baseline and, where applicable, conservation-property checks, so that regressions such as numerical blow-up or broken restart chaining can be detected.
Requirement: Physics and mixing options vary with the mesh
Date last modified: 2026/10/17
Contributors: Xylar Asay-Davis, Claude
The horizontal mixing coefficients, the eddy parameterizations, and the way mixing is scaled across a variable-resolution mesh all depend on the mesh a forward run uses. A single, mesh-independent set of model config options is not sufficient: a 240 km mesh needs a Leith closure and a reference cell width, while a 6-to-18 km mesh needs a much smaller biharmonic viscosity, mesh-density scaling, and no eddy parameterization at all.
These settings shall therefore be Polaris config options rather than fixed values
in the model-config template, with defaults in the task’s own config file and
per-mesh overrides in mesh_configs/<mesh_name>.cfg, using the mechanism that
already carries the vertical grid and the ocean-culled cell count. Each mesh’s
values shall follow how that mesh is configured in E3SM, rather than being chosen
to make MPAS-Ocean and Omega agree; the two models are expected to diverge where
one lacks a parameterization the other has.
Options that both models support shall be expressed once, in model-neutral form, and translated automatically. Options with no counterpart in the other model shall be emitted only for the model that has them, which leaves the other model at its own default. An option shall therefore move to the model-neutral form as soon as both models have it.
Algorithm Design
Algorithm Design: A reusable forward-run capability runs a realistic global ocean simulation
Date last modified: 2026/07/03
Contributors: Xylar Asay-Davis, Claude
The forward run should be a single reusable step built on Polaris’ existing ocean
model abstraction (OceanModelStep), which already selects between MPAS-Ocean
namelists/streams and an Omega YAML configuration, partitions the graph for
MPAS-Ocean, and computes the number of MPI tasks dynamically from a mesh cell
count. The closest existing precedent is the spherical convergence forward
(polaris.ocean.convergence.forward.ConvergenceForward and its spherical
subclass), which renders a task-level YAML template with run duration, output
cadence, and time step drawn from a config section and overrides a
compute_cell_count method to size resources. The realistic global forward step
follows the same shape, but sources its cell count from the actual global mesh
rather than a convergence resolution heuristic.
Resource sizing should use the exact number of cells from the mesh when that file is available (at run time) and fall back to a configured approximate cell count during setup, before the initial condition has been produced. This mirrors the approach used in the legacy Compass forward step.
Algorithm Design: Forward runs can start from a database initial condition or from the init workflow
Date last modified: 2026/07/03
Contributors: Xylar Asay-Davis, Claude
The two initial-condition sources should be captured by a small abstraction — an “initial condition” object — whose responsibility is to add the required input files to a forward step and to point the step at the correct graph file. The forward step holds one such object and defers all questions of provenance to it.
Two concrete implementations are needed:
A source backed by the upstream
realistic_global/initsteps, which links themesh,vertical coordinate,initial state,forcingandgraphfiles from the initialization steps’ work directories. This is the source used by the first runnable task, since those outputs already exist in the same run.A source backed by the Polaris input-file database, which constructs the input filename from the mesh, the configured model, and the equation of state, and registers it as a database input.
Because the required filenames and the model-specific handling of the vertical coordinate and graph depend on the configured model, the source applies its input files when the model is known (during step setup) rather than at construction time.
Algorithm Design: Comparing MPAS-Ocean with Omega is opt-in
Date last modified: 2026/10/21
Contributors: Xylar Asay-Davis, Claude
The settings that separate a production run from a comparison are already config options (see “Physics and mixing options vary with the mesh”), so a comparison is just a set of values for them, which a user layers on like any other config. The forward package ships those values as a reference file that Polaris never reads on its own, so that the list is written down, documented and tested in one place. It describes what Omega lacks today and should shrink as Omega gains capabilities.
Algorithm Design: Per-run settings are described in a common, model-agnostic form
Date last modified: 2026/10/17
Contributors: Xylar Asay-Davis, Claude
A single, model-agnostic settings structure should describe one forward run: its
name, run duration, output interval, restart interval, time integrator, an
optional explicit time step (barotropic and baroclinic), per-kilometer time-step
scalings, a damping strength, and the fields needed to continue from a restart
(a restart flag, a start time, and a restart input file). A simple forward run
fills this structure from a config section; a future dynamic-adjustment workflow
fills a list of them from a schedule file. This is the “common internal
representation” called for in
global_ocean_dynamic_adjustment.md.
The mapping from this structure onto model configuration should happen in one
place. Durations are formatted as model duration strings; the time integrator
name is translated where the models differ (for example RK4 to
RungeKutta4). Controls that exist only for one model, such as Rayleigh damping
for MPAS-Ocean, are emitted only under that model’s section of the template. Where a model lacks a direct equivalent (for example Omega
damping), the mapping is the natural place to apply the closest available control
as the capability matures.
Algorithm Design: The framework composes into restart and dynamic-adjustment workflows
Date last modified: 2026/07/03
Contributors: Xylar Asay-Davis, Claude
Restart chaining should be expressed through the same settings structure and the
same template. Writing a restart is a matter of the restart interval; continuing
from a restart is a matter of the restart flag, start time, and a restart input
file. A restart test then becomes two forward steps — a full run that writes a
restart partway through and a restart run that resumes from it — compared for
bit-for-bit agreement. A dynamic-adjustment workflow becomes an ordered list of
settings structures, each producing a restart consumed by the next, exactly as
laid out in global_ocean_dynamic_adjustment.md.
The forward step does not need to know which of these workflows it participates in; it only needs to honor the settings it is given. Keeping start and stop times derived from cumulative durations, as in Compass, lets the higher-level workflow compute restart filenames deterministically.
Algorithm Design: Forward steps produce inspectable outputs and support basic validation
Date last modified: 2026/07/03
Contributors: Xylar Asay-Davis, Claude
The output and restart streams should be declared in the task-level YAML template so that the set of written variables is easy to inspect and change. The step should register its output file as a Polaris output with an optional list of variables to validate against a baseline and optional conservation-property checks, reusing the validation and property-check machinery already provided by the ocean model step base class.
Algorithm Design: Physics and mixing options vary with the mesh
Date last modified: 2026/10/17
Contributors: Xylar Asay-Davis, Claude
The per-mesh settings should join the same ForwardStage structure that already
carries the per-run settings, so that a forward run continues to have a single
description of what it should do. They are read from the same
[realistic_global_forward] section, which means a per-mesh config file
overrides them by the ordinary config-precedence rules and a user can override
them again in a user config file.
A coefficient and its enable flag should be a single Polaris option: blank turns the term off, a value turns it on with that coefficient. This follows the convention the section already uses for the explicit time step and the damping coefficient, and it removes the possibility of a config that enables a term without a coefficient or sets a coefficient that is never used.
Where the model offers several independent booleans for what is really one choice — MPAS-Ocean’s reference-cell-width and scale-with-mesh flags for horizontal mixing — Polaris should expose a single option whose value names the scaling, so that the combinations that are not meaningful cannot be expressed.
The stage should sort the resulting model options into buckets: those both models support, emitted in neutral form and translated by the existing MPAS-Ocean-to-Omega map, and those only one model has, emitted for that model alone. “Both models support” means a one-to-one translation exists, not just that each model has something in the same role: two different boundary-layer schemes would each go in their own model’s bucket. Most model-specific options are MPAS-Ocean’s; Omega’s bucket holds its horizontal-mixing scaling switch, which is not a translation of MPAS-Ocean’s. Gent-McWilliams and Redi stay in the MPAS-Ocean bucket for good, since Omega has neither and is not expected to gain them. Other options move to the neutral bucket as Omega gains them, as KPP has.
Implementation
Implementation: A reusable forward-run capability runs a realistic global ocean simulation
Date last modified: 2026/08/11
Contributors: Xylar Asay-Davis, Claude
The framework lives under
polaris/tasks/ocean/realistic_global/forward/. The forward step,
Forward, subclasses polaris.ocean.model.OceanModelStep and is modeled closely
on polaris.ocean.convergence.forward.ConvergenceForward. Its constructor adds
the shared double-precision output YAML (polaris.ocean.config/output.yaml),
stores an initial-condition object and a settings structure, and registers the
output file. It does not read config in its constructor, because the target model
is not known until setup.
Resource sizing overrides compute_cell_count to return the exact nCells read
from the mesh input file when it exists, and otherwise an approx_cell_count;
the base class turns this into ntasks/min_tasks using the existing
goal_cells_per_core/max_cells_per_core options. The approximate count is a
property of the initial condition rather than a config option, since it is the
initial condition that knows which mesh it is for: a step-backed source takes it
from mesh_info.estimate_ocean_cell_count, and a database-backed one from the
count recorded for that cached mesh.
The runnable task is RealisticGlobalForward, added once per supported mesh by a
factory add_realistic_global_forward_tasks, following the structure of the init
factory in polaris/tasks/ocean/realistic_global/init/tasks.py. Its work
directory is mesh-first, spherical/realistic_global/{mesh_name}/{subdir_name},
matching the init layout, with subdir_name distinguishing forward from
cached_forward. The factory is registered from
polaris/tasks/ocean/realistic_global/__init__.py.
The task holds three steps rather than one. The forward run itself is named
short rather than forward, because that is what it is: a brief run that
checks the model is stable on this mesh and initial condition, not a simulation
worth interpreting. Two diagnostic steps follow it, neither run by default:
global_stats plots time series of the run’s global statistics, and viz plots
global maps of its state.
Implementation: Forward runs can start from a database initial condition or from the init workflow
Date last modified: 2026/08/11
Contributors: Xylar Asay-Davis, Claude
The initial-condition abstraction lives in
forward/initial_condition.py as an InitialCondition base class with two
subclasses. StepInitialCondition wraps the initial_state step returned by
get_realistic_init_steps (in
polaris/tasks/ocean/realistic_global/init/steps.py) and, during the forward
step’s setup, calls the mixin helpers add_horiz_mesh_input_file,
add_vert_coord_input_file, and add_init_input_file with work_dir_target
pointing into that step’s work directory, and sets the forward step’s
graph_target to the init step’s exported graph file. The vertical-coordinate
input and the graph are handled correctly per model by the existing
OceanModelFilesMixin and OceanModelStep machinery: for MPAS-Ocean the graph
is linked and partitioned; for Omega the separate vertical-coordinate file is
used and the graph is not partitioned.
DatabaseInitialCondition constructs the input filename from the mesh name, the
configured model, and the equation of state, and registers it with
add_init_input_file(database=..., target=...). It is now wired into the
cached_forward tasks described above. The MPAS-Ocean graph question was
settled in favor of downloading a prebuilt graph partition file alongside the
initial condition rather than building one from the mesh; Omega partitions
internally and needs neither.
Two things the design did not anticipate fell out of this abstraction once
surface forcing existed. The initial condition also answers where the wind
stress lives, through get_forcing_filename(): a source that stages a forcing
file of its own names it, and a source whose stress travels inside the
initial-condition file names that file instead, so the forcing streams are
pointed at the right place without the forward step knowing which case it is
in. And StepInitialCondition links the initial state only when the run
actually reads one, since a stage continuing from a restart does not — a detail
that belongs to the source rather than to the step for the same reason.
Implementation: Comparing MPAS-Ocean with Omega is opt-in
Date last modified: 2026/10/23
Contributors: Xylar Asay-Davis, Claude
compare_with_omega.cfg in the forward package turns off use_GM and
use_Redi and sets pressure_gradient_type = pressure_and_zmid,
the ordinary counterpart to Omega’s Centered. A user passes it, or its lines,
with polaris setup -f, or sets the same options in a step’s forward.cfg
before running; the stage is rebuilt from config at run time, so either works.
The User’s Guide includes the file verbatim. use_KPP and use_submesoscale
were on the list until Omega gained them.
add_realistic_global_cached_forward_tasks registers one task per entry of
forward/tasks.py’s CACHED_MESHES, with a subdir_name of cached_forward.
Each entry gives the MPAS-Ocean and Omega initial-condition IDs on the Polaris
server, the minimum resolution and the cell count. The two meshes are E3SM’s
QU240 and EC30to60E2r2, called e3sm-qu240 and e3sm-ec30to60e2r2 here so that
they are lower case like the meshes Polaris builds and cannot be mistaken for
them; their initial conditions were converted from E3SM’s by
utils/omega/convert_mpaso_ic_to_omega.py and uploaded, and the files on the
server keep E3SM’s names, which each entry gives as database_name.
A cached mesh’s mesh_configs/<mesh_name>.cfg gives its split and RK4 time
steps, its run duration and the momentum viscosities the one-year Omega runs
settled on, since none of these follows from a mesh Polaris did not build. Its
physics is the default, as for every other mesh.
The cached tasks are the only members of this family cheap enough for a PR
suite: both variants of
ocean/spherical/realistic_global/e3sm-qu240/cached_forward are in omega_pr,
and the split_explicit one is in mpaso_pr. Nothing built on the init
chain belongs in a suite, since that chain is hours of preprocessing before the
model starts.
cached_forward is temporary: now that Polaris builds realistic initial
conditions, the cached tasks will be removed.
Implementation: Per-run settings are described in a common, model-agnostic form
Date last modified: 2026/10/17
Contributors: Xylar Asay-Davis, Claude
The settings structure is a small dataclass, ForwardStage, in
forward/stage.py, with a class method that builds one from a
[realistic_global_forward] config section. Its fields cover the run duration,
output interval, restart interval, statistics interval, the two time
integrators, optional explicit dt/btr_dt, dt_per_km/btr_dt_per_km, a
damping (Rayleigh) coefficient, the mixing and parameterization options
described further below, and the restart fields (do_restart, start_time,
restart_in, restart_out). Durations are duration strings of the form
DDDD_HH:MM:SS, matching the schedule format in
global_ocean_dynamic_adjustment.md.
The time integrator turned out to be the one setting that cannot be shared, so
there are two fields rather than one: mpaso_time_integrator, defaulting to
split_explicit_ab2, and omega_time_integrator, defaulting to
split_explicit (Omega’s SplitExplicitRK2). Omega’s default was RK4 until
it had a split time stepper. The time step follows from that choice rather than
being chosen separately: a split integrator advances on the long baroclinic
step and subcycles the barotropic mode, and a non-split integrator has to
advance on the short barotropic step.
Every short task is registered twice, as
<mesh>/<forward|cached_forward>/split_explicit and
<mesh>/<forward|cached_forward>/rk4, each holding its own steps. The rk4 variant adds rk4.cfg,
which sets both integrators to RK4. A mesh config can need a split step and
an RK4 step at once, so the RK4 step has an explicit option of its own,
rk4_dt, which falls back to btr_dt_per_km like the barotropic step.
The mapping onto model configuration happens only in the forward step’s
dynamic_model_config, which renders forward/forward.yaml with template
replacements derived from the stage. Durations are formatted with
polaris.ocean.model.get_time_interval_string; where the time step is not given
explicitly it is computed as the per-kilometer value times the mesh minimum
resolution. The time-integrator name is mapped to Omega where needed, exactly as
ConvergenceForward does. The template’s model-neutral ocean: block is
auto-translated to Omega through the existing mpaso_to_omega.yaml map, while
Rayleigh damping appears under the mpas-ocean: section. The barotropic time
step moved to the neutral block once Omega had a split time stepper. The restart controls did gain an Omega counterpart, in
restart_streams.yaml rather than in the main template; see below.
Where a model cannot honor a setting, the mapping raises rather than dropping
it: an omega_time_integrator Omega does not support, and a non-blank damping
under Omega, which has no Rayleigh damping at all
(Omega#495). Both raise at
run time rather than at setup, so that a task can be set up and its config
changed before it is run. Reporting success on a run that quietly dropped the
damping it was asked for is the failure mode this avoids.
def dynamic_model_config(self, at_setup):
super().dynamic_model_config(at_setup=at_setup)
replacements = self.stage.model_replacements(self.config, self.min_res)
self.add_yaml_file(self.package, self.yaml_filename,
template_replacements=replacements)
Implementation: The framework composes into restart and dynamic-adjustment workflows
Date last modified: 2026/10/21
Contributors: Xylar Asay-Davis, Claude
The restart stream is declared in restart_streams.yaml, added only for a stage
that is part of a restart chain, and the model-neutral time block carries the
start time and restart flag. The simple forward task leaves the restart fields
unset, so it starts from the initial condition and writes a single restart at
the end of the run.
Dynamic adjustment is built on exactly this and needed no change to the forward
step: it builds a list of ForwardStages from a per-mesh YAML schedule and sets
each one’s restart_in and restart_out so that consecutive stages share a
restarts directory. For MPAS-Ocean the restart stream is both an input and an
output stream, so one filename_template serves both directions and the read
side is just config_do_restart and config_start_time; the start time is
stated explicitly rather than using a restart-pointer file. Omega decides
whether to read a restart from its StartType and finds it through a pointer
file. The Omega block of restart_streams.yaml
requests the RestartRead stream, and
ForwardStage.restart_stream_replacements fills in the start type and the
directories: each stage writes its restart and pointer into a directory under
restarts named for its restart time, and a restarting stage reads its
predecessor’s. A shared directory would let a stage’s own restart replace the
pointer it had just read. ForwardStage.restart_files gives the restart each
model reads and writes, MPAS-Ocean’s in the shared directory and Omega’s in the
stage’s own, and the forward step declares them as inputs and outputs.
Two more tasks are built on the forward step without changing it. The
restart task takes the shape of CIME’s ERS test, so that Omega’s restarts
are tested here the way E3SM will test them: a full_run writes a restart
partway through, a restart_run continues from it to the end, and the two
histories are compared. It reads that restart by name rather than through
restart_in, since the full run writes another at its end. The restart run
appends to a copy of the full run’s history up to the restart, which the full
run writes as a second history stream with an end time, so the task is also a
regression test for Omega #482, where appending to such a file overwrote the
earlier frames. The analysis_output/1_year task runs Omega for a year on
every mesh and writes the monthly means, overturning streamfunction and
daily global statistics the ocean analysis reads. It adds a second yaml file
for the monthly streams and declares every monthly file as an output, so a
year whose analysis groups did not write fails in Polaris rather than in the
analysis.
Implementation: Forward steps produce inspectable outputs and support basic validation
Date last modified: 2026/09/24
Contributors: Xylar Asay-Davis, Claude
The 3-D output stream is declared in forward/forward.yaml — an MPAS-Ocean
output stream and the corresponding Omega History IOStream — and the
restart streams in restart_streams.yaml. The Forward step registers its
output file through add_output_file with an optional validate_vars list
(temperature, salinity, layerThickness, normalVelocity) and optional
check_properties, reusing the baseline-comparison and conservation checks
already implemented in OceanModelStep.
The output stream carries more than the prognostic state: kinetic energy and
relative vorticity for MPAS-Ocean, and for Omega the AuxiliaryState, SshCell
and Eos groups, which add the vorticity, divergence and del2 terms, the free
surface and the specific volume and buoyancy frequency. The extra fields cost
file size and are worth it, because a run that goes wrong can otherwise be seen
to have gone wrong but not diagnosed, and these runs exist partly to be compared
against each other. Density is the one field in that stream with a switch of its
own (output_density), because it is 3-D and roughly 13% of the output volume,
which starts to matter once a workflow writes the stream many times over.
Global statistics are the other output, and the more useful one. Both models
write a time series of the global minimum, maximum, mean and RMS of the state
variables — MPAS-Ocean through its globalStats analysis member and Omega
through its GlobalStats analysis group, plus the global CFL number for
MPAS-Ocean — on a stats_interval cadence deliberately separate from the 3-D
output_interval. They are a handful of scalars, so they can be written far
more often than 3-D fields, which is what makes an excursion within a run
visible rather than only at its end. Two model differences are handled once, in
ForwardStage.stats_filename, so that everything reading a stage’s statistics
asks the same question the same way: Omega treats the configured name as a
prefix and appends its analysis period and the kind of output with no .nc
extension, and Omega’s statistics are taken as instantaneous snapshots rather
than temporal reductions, both because a snapshot’s name carries no averaging
period and because an averaging period would constrain the restart interval.
Each model’s statistics names are built from the field and the statistic by
polaris.ocean.global_stats_names rather than mapped pair by pair in
mpaso_to_omega.yaml, which no longer lists them.
Surface forcing is no longer out of scope, and it is not optional either. Every
realistic global forward run is forced by the time-invariant JRA55-do wind
stress the init workflow produces: forcing.yaml is always added, and there
is no config option to run without it. forcing_streams.yaml points each model’s
input stream at whichever file the initial condition names. There is still no
surface restoring and there are no thermal or freshwater fluxes, so the tracers
spin down from the initial condition while the momentum input holds up a
circulation.
Two diagnostic steps, neither run by default, read these outputs: global_stats
(StatsAnalysis) plots the statistics as time series with a
standard-deviation envelope and an anomaly panel, and viz plots global maps of
each state variable at the start and end of the run along with the wind stress
that forced it. global_stats plots the fields named by
[realistic_global_forward_stats] fields, or every field the run wrote
statistics for when that is left empty; the option came with the step from the
analysis_members task this framework replaced, where it was
[analysis_members] fields.
Implementation: Physics and mixing options vary with the mesh
Date last modified: 2026/10/23
Contributors: Xylar Asay-Davis, Claude
forward.cfg gains the mixing and parameterization options:
mom_del2, mom_del4, tracer_del2 and tracer_del4 (blank meaning off),
mom_del4_div_factor, use_Leith_del2, hmix_scaling with
hmix_ref_cell_width, use_GM with GM_closure and GM_constant_kappa,
use_Redi, use_KPP, use_submesoscale, pressure_gradient_type,
use_frazil_ice_formation and output_density. The list grew past what this
section first named once the forward tasks had to reproduce E3SM’s
configuration rather than a Compass baseline: the defaults are now E3SM’s,
and comparing with Omega turns some of them off (see “Comparing MPAS-Ocean
with Omega is opt-in” above). Several are off in MPAS-Ocean itself, so leaving them alone would
not have given E3SM’s configuration either.
hmix_scaling has two values, none and ref_cell_width, not the three this
section anticipated. MPAS-Ocean’s third combination — scaleWithMesh with
use_ref_cell_width false — scales by the legacy meshDensity field, which
every E3SM v4 mesh writes as uniformly 1.0, so that branch silently applies no
scaling at all. Offering it would only let a user ask for scaling and get none.
The two flags MPAS-Ocean does need are set together by ref_cell_width, since
config_hmix_use_ref_cell_width is read only inside
if (config_hmix_scaleWithMesh) and setting the first alone has the same silent
failure.
Omega scales its coefficients too, but from the global median cell width
(2*sqrt(areaCell/pi) over every cell) rather than a reference width, with the
same per-edge width and powers as MPAS-Ocean. Scaling is the default in both,
and so is Omega’s reference: a blank hmix_ref_cell_width is the median, which
Forward.dynamic_model_config computes from the mesh and gives MPAS-Ocean as
config_hmix_ref_cell_width. A mesh whose coefficients come from E3SM states
its finest resolution, which E3SM’s per-grid values are referenced to, and for
Omega horiz_mixing_options then multiplies the coefficients by
W_med/hmix_ref_cell_width for del2 and by its cube for del4. So
hmix_scaling is not a one-to-one translation: MPAS-Ocean’s two flags stay
MPAS-Ocean-only, and Omega gets MeshScalingEnable from a new
omega_physics_options bucket, always stated. W_med depends on the culled
mesh, which an init-chain task does not have at setup, so it is computed at run
time and logged with the coefficients and the range of scaling factors Omega
should report.
Settings that should not vary are pinned in forward.yaml rather than made
options, purely so that the two models agree: horizontal tracer advection order
3, implicit constant bottom drag at 1e-3, the CVMix convection, shear-mixing and
KPP parameters, and config_Redi_min_layers_diag_terms = 0, which computes the
Redi diagnostic terms in the top layers the Registry default skips. Where the two
models’ defaults differ, the MPAS-Ocean default wins, which turns KPP’s Langmuir
turbulence off for Omega. Omega’s advection order and bottom drag defaults have
since come to match MPAS-Ocean’s; stating them keeps the two from drifting apart
again.
ForwardStage gains the corresponding fields and four methods beside the
existing bottom_drag_options: horiz_mixing_options returns the neutral
del2/del4 options for momentum and tracers and the biharmonic divergence factor,
vert_mixing_options the neutral KPP switch, submesoscale_options the neutral
submesoscale switch, and mpaso_physics_options the MPAS-Ocean-only options —
the Leith closure, horizontal-mixing scaling, Gent-McWilliams, Redi, the
pressure-gradient formulation and frazil ice. Forward.dynamic_model_config
adds the first three with config_model='ocean', so
OceanModelStep.map_yaml_options translates them for Omega, and the fourth
with config_model='mpas-ocean'. KPP and the submesoscale parameterization
began in the fourth bucket and moved once Omega gained them. Left there, KPP
never reached Omega, which ran KPP, its default, in tasks that had turned it
off.
Per-mesh overrides live in mesh_configs/<mesh_name>.cfg under the same
[realistic_global_forward] section, applied by add_realistic_global_mesh_config
after the task config so that they take precedence. The values are ported from
the per-mesh namelist.split_explicit_ab2 files of the corresponding Compass
meshes: qu for u-oi30-lr10, rrs6to18 for u-oi6to18-lr6to10, and
so12to30 for u-oi-so12to30-lr10. The three 240 km meshes (u-oi240-lr240,
qu240km and icos240km, which are qualitatively the same mesh and share a
block) follow E3SM’s oQU240 instead: a biharmonic momentum viscosity of
2.0e14 m^4/s alone, referenced to 240 km, and a 1 h / 3 min split time step.
Compass’s qu240 coefficients were never scaled there, and at 240 km they are
almost no viscosity; scaled from the shared 30 km reference, they would be about
500 times too large.
Two exceptions to that port are deliberate. The Compass so12to30 time step is
not carried over: it is far more conservative than its resolution alone justifies,
almost certainly because the Compass mesh has ice-shelf cavities, and
u-oi-so12to30-lr10 is for now a proof of concept for regional refinement rather
than a mesh run in earnest. And Compass’s run durations and debug-tracer settings
are test-harness details that Polaris expresses through its own config options.
Because the neutral bucket can carry tracer diffusivities, the MPAS-Ocean-to-Omega
map also gains the two entries it was missing, config_tracer_del2 to
EddyDiff2 and config_tracer_del4 to EddyDiff4; the corresponding enable
flags were already mapped, so without these a tracer diffusivity set from Polaris
would have been dropped for Omega with only a warning. It gains
config_mom_del4_div_factor to DivFactor as well, which is what lets the
divergence factor be neutral.
Testing
Testing and Validation: A reusable forward-run capability runs a realistic global ocean simulation
Date last modified: 2026/10/21
Contributors: Xylar Asay-Davis, Claude
A polaris setup smoke test on a coarse global mesh, run for both
[ocean] model = mpas-ocean and omega, should confirm that the forward work
directory contains the expected input symlinks and that the generated namelist
and streams (MPAS-Ocean) or omega.yml (Omega) contain the expected run
duration, cadence, time step, and time integrator, without running the model.
Where a model build is available, a short end-to-end run on a coarse mesh should
confirm that the output file and a restart file are written.
What landed is tests/ocean/realistic_global/test_forward.py, which renders the
model config for both models and checks it rather than requiring a setup, plus
test_tasks.py, which checks that a task is registered per mesh with the step
list this design calls for. Resource sizing is covered by tests that
compute_cell_count falls back to the initial condition’s estimate before the
mesh exists, and raises rather than guessing when there is no estimate. The
end-to-end run is the cached e3sm-qu240 task in the two PR suites.
Testing and Validation: Forward runs can start from a database initial condition or from the init workflow
Date last modified: 2026/08/11
Contributors: Xylar Asay-Davis, Claude
Unit tests confirm that StepInitialCondition registers its inputs and sets the
graph target from the init step’s work directory, that it skips the initial
state on a stage continuing from a restart, and that it requires a forcing step
and wires the forcing file; and that DatabaseInitialCondition constructs the
expected database filename and target for each configured model, raising when an
Omega run has no Omega initial-condition ID. A further pair of tests checks the
seam between the two: that the forcing streams point at the staged file for one
source and at the initial condition itself for the other.
Testing and Validation: Comparing MPAS-Ocean with Omega is opt-in
Date last modified: 2026/10/21
Contributors: Xylar Asay-Davis, Claude
Unit tests confirm that every cached mesh has a per-mesh config, that the cached
tasks run the same physics as every other forward run, that the model config a
cached task renders is what those options imply, that compare_with_omega.cfg
layered on as a user config turns off what Omega lacks and names only real
options, and that the task’s config carries what the database source needs —
the equation of state above all, since a cached run never sets the init
workflow’s config up at all and still has to name the equation of state to
find the right file.
The end-to-end check is the PR suites, which run the cached e3sm-qu240 task on
every pull request: split-explicit in both models, and RK4 in Omega.
Testing and Validation: Per-run settings are described in a common, model-agnostic form
Date last modified: 2026/10/17
Contributors: Xylar Asay-Davis, Claude
Unit tests should cover building a ForwardStage from config and its mapping onto
template replacements: correct duration strings, the time-integrator translation
for Omega, the derivation of the time step from dt_per_km and the mesh minimum
resolution versus an explicit override (rk4_dt for RK4), and the presence of
Rayleigh damping only for MPAS-Ocean.
Testing and Validation: The framework composes into restart and dynamic-adjustment workflows
Date last modified: 2026/10/04
Contributors: Xylar Asay-Davis, Claude
Tests confirm that the restart-in settings render the expected restart flag and
start time, that restart_stream_replacements gives a first stage Omega’s
StartUp and a restarting one Continue with its predecessor’s restart
directory, that restart_streams.yaml requests Omega’s RestartRead stream
through a pointer file, and that restart_files names each model’s restarts.
Which files a step declares is checked from the dynamic-adjustment side, where
the chain is built: that a stage in a chain declares both ends of it and that
a lone stage declares neither.
Testing and Validation: Forward steps produce inspectable outputs and support basic validation
Date last modified: 2026/08/11
Contributors: Xylar Asay-Davis, Claude
Tests should confirm that the output file is registered with the expected validation variables and that, when property checks are requested, they are wired to the existing conservation checks. Baseline comparison of the validation variables should be exercised through the standard Polaris validation path once a baseline is available.
Two classes of test landed that this section did not anticipate, both guarding
against a failure that is silent by construction. An option in the neutral
ocean: block with no entry in mpaso_to_omega.yaml only warns, so a check
that every neutral option in forward.yaml, in forcing.yaml and in the
horizontal-mixing bucket has an Omega counterpart is what keeps the two models
from quietly running different physics. And the statistics filename moves for
Omega but not for MPAS-Ocean, and a name that is not found reads as a stage that
wrote no statistics, so tests pin the filename each model actually writes, that
it follows the statistics interval for Omega, and that Omega’s statistics are
snapshots rather than reductions.
Testing and Validation: Physics and mixing options vary with the mesh
Date last modified: 2026/10/17
Contributors: Xylar Asay-Davis, Claude
Unit tests should confirm that ForwardStage.from_config reads the new options,
that a blank coefficient turns its term off while a value turns it on, and that
the neutral and MPAS-Ocean-only buckets contain the options they are supposed to.
Because the coefficients are what keep a run stable, a test should also confirm
that each per-mesh config produces the expected values, and that the three
qualitatively identical 240 km meshes agree with one another so they cannot drift
apart unnoticed.
Two further tests guard against failures that are silent by construction. One fails if an option in the MPAS-Ocean-only bucket has an Omega counterpart in the map, which is how KPP was left behind. It cannot see an Omega option that is on by default and not yet mapped. The other requires every mesh that scales its coefficients to reference its own finest resolution, which the 240 km meshes did not.
The end-to-end check is that a short forward run on each mesh completes without producing NaNs, which is the failure mode these options exist to prevent.