realistic_global
The realistic_global tasks in polaris.tasks.ocean.realistic_global use
realistic global ocean meshes, bathymetry and forcing. They fall into seven
groups:
hydrography/woa23, a mesh-independent preprocessing task that builds a reusable hydrography product from the World Ocean Atlas 2023 on its native 0.25-degree latitude-longitude grid.forcing/jra55, a mesh-independent preprocessing task that builds a reusable wind-stress product from JRA55-do 10-m winds.init, which creates mesh-specific ocean initial conditions using that hydrography and forcing together with the culled mesh frome3sm/init.forward, short forward runs on the meshesinitbuilds.cached_forward, the same forward runs on E3SM initial conditions cached in the Polaris input database instead, a temporary task.analysis_output, one-year Omega runs on either source of initial condition that write what the ocean analysis reads.restart, a short Omega run continued from a restart, checking that it matches the uninterrupted run, history output included.
Tasks are added to the ocean component by
polaris.tasks.ocean.realistic_global.add_realistic_global_tasks(),
which registers the woa23 and jra55 tasks, one init task per MPAS mesh,
a forward task per mesh for each of
polaris.tasks.ocean.realistic_global.forward.TIME_INTEGRATORS, the
same cached_forward variants for each mesh in
polaris.tasks.ocean.realistic_global.forward.tasks.CACHED_MESHES,
an analysis_output/1_year task for every mesh of either kind, and a single
restart task on e3sm-qu240. Adding a cached mesh requires an entry in that
dictionary, keyed by the E3SM mesh’s name in lower case with an e3sm- prefix,
giving the name the database files carry, the MPAS-Ocean and Omega
initial-condition IDs, the minimum resolution and the cell count, and a
<mesh_name>.cfg in mesh_configs giving the split and RK4 time steps and the
run duration. A mesh coarser than
about a degree should also set fewer MOC latitude bins for its 1_year run
(moc_num_bins in [analysis_output]).
forward and cached_forward run the same physics. Comparing MPAS-Ocean
with Omega is opt-in, through the options in forward/compare_with_omega.cfg,
which Polaris never reads on its own and a unit test keeps honest; see
physics, and comparing with Omega in the User’s Guide.
framework
The config options for these tasks are described in
realistic_global in the User’s Guide. The shared colormap
options for the viz step live in realistic_global.cfg.
forward
The realistic_global.forward package holds one forward-run capability that
both the forward and cached_forward tasks are built from. They differ in
one argument: the
InitialCondition
they are given. Everything else — the step, the model config, the diagnostics
— is shared, including the physics, so a cached mesh’s config gives only
its time steps, viscosities and run duration.
The package is deliberately split three ways, so that adding a new source of initial condition, a new run setting, or a new consumer each touches one place:
module |
answers |
|---|---|
|
where do the model input files come from? |
|
what settings does this run use? |
|
run the model |
RealisticGlobalForward
takes a time_integrator, split_explicit or rk4, which names the task’s
directory under forward or cached_forward. The rk4 variant adds
rk4.cfg after the per-mesh config file, so that it sets both models’
integrators whatever the mesh config says.
Forward
Forward is an
OceanModelStep that defers both of those
questions rather than answering them itself.
setup() asks the initial condition to add its input files before calling
the base class, so that OceanModelStep sees a finalized graph_target and
input list. A step whose graph comes from the input-file database rather than
an upstream work directory leaves graph_target unset and adds graph.info
itself; see setup().
compute_cell_count() reads nCells from the mesh once it exists, and before
that falls back to the initial condition’s approx_cell_count. Both are
needed: resources are sized during setup, when an initial condition built by
the init workflow has not been produced yet.
dynamic_model_config() is the single place where model-agnostic settings
become model-facing ones. It renders forward.yaml from the stage, always
adds forcing.yaml (every realistic global forward run is wind-forced, and
there is no option to turn that off), adds forcing_streams.yaml when the
initial condition stages a file for it, and applies the horizontal-mixing
options in MPAS-Ocean naming so that mpaso_to_omega.yaml translates them.
The files the run writes are given as output_filenames, each declared as an
output; the default is the single output.nc. stream_dirs lists the
directories under the step’s work directory that Omega’s streams write into,
which the step creates in dynamic_model_config() because Omega does not.
MPAS-Ocean needs no equivalent, since the MPAS framework creates stream
directories itself. Both are for a subclass like the one-year
analysis_output step, which writes one file per month into output/.
ForwardStage
ForwardStage is
a dataclass of model-agnostic run settings — durations, cadences, time steps,
damping, mixing and the physics switches — built from a
[realistic_global_forward] config section by
from_config().
A simple forward run uses one. It is a stage rather than a config because a multi-stage workflow such as dynamic adjustment builds a sequence of them and sets the restart fields to chain one into the next; the step supports that already even though nothing here uses it yet.
model_replacements()
maps a stage onto template replacements, and is where the two models diverge:
the time integrator is chosen per model, the time step follows from that
choice, and a non-split integrator has to advance on the short barotropic step.
An explicit dt and btr_dt are the steps of a split integrator, and rk4_dt
the step of a non-split one, so that one mesh config can give both.
The MPAS-Ocean-only physics is separated into
mpaso_physics_options()
from the options both models share in
horiz_mixing_options(),
vert_mixing_options()
and
submesoscale_options().
Frazil ice is set in the neutral forward.yaml section from
use_frazil_ice_formation, so both models honor it.
An option belongs in the MPAS-Ocean-only bucket only while
mpaso_to_omega.yaml has no Omega counterpart for it. Once it has one, it
has to move to a shared bucket: otherwise Omega never sees it and runs its own
default, which is how Omega came to run KPP in tasks that had turned it off. A
unit test fails if a mapped option is left behind, but it cannot see an Omega
option that is on by default and not yet mapped, so check Default.yml when
updating the Omega submodule.
Horizontal-mixing scaling is the one setting split across buckets on purpose.
MPAS-Ocean scales from hmix_ref_cell_width and Omega from the mesh’s median
cell width, so Omega’s MeshScalingEnable is not a translation of MPAS-Ocean’s
flags. It goes in
omega_physics_options().
A blank reference is the median, from
omega_median_cell_width(),
which Forward computes once the mesh exists and fills in with
with_median_reference().
When a mesh states a reference, horiz_mixing_options converts the
coefficients Omega gets by the ratio of the two widths (cubed for del4). At
setup, before an init-chain mesh has been made, neither happens until run time
rewrites the config. Do not map config_hmix_scaleWithMesh to
MeshScalingEnable in mpaso_to_omega.yaml: a stated reference would then
reach Omega unconverted.
An unsupported omega_time_integrator is an error at run time rather than at
setup, so that a task can be set up and the config changed before it is run.
Damping reaches Omega as its RayleighDamping group, which is not mapped in
mpaso_to_omega.yaml because MPAS-Ocean turns damping on through
config_implicit_bottom_drag_type rather than a switch of its own. The forward
step adds omega_damping.yaml to a damped stage only, so that an undamped run
does not need an Omega build that has the group.
InitialCondition
InitialCondition
is the abstraction the whole package is arranged around. It knows where the
model input files come from, and the forward step asks rather than deciding.
StepInitialConditionlinks the mesh, vertical coordinate, initial state and forcing from theinitworkflow’s step directories, and reuses the graph it already built. It links the initial state only when the run reads one, since a stage continuing from a restart does not.DatabaseInitialConditiondownloads a single cached file that supplies everything at once, plus a prebuilt graph for MPAS-Ocean.
get_forcing_filename() is what lets both work through one mechanism: a source
that stages a forcing file names it, and a source whose wind stress travels
inside the initial condition names that file instead, so the forcing streams
are pointed at the right place without the step knowing which case it is in.
the model config files
file |
what it does |
|---|---|
|
a Jinja2 template: time management, time step, output/restart/statistics streams |
|
turns wind forcing on; always added |
|
points each model’s forcing stream at the staged file; added only when one is staged |
|
the restart read/write side; added only for a stage in a restart chain |
forward.yaml’s ocean: section is model-neutral and reaches Omega through
mpaso_to_omega.yaml; a unit test checks that every option in it has an Omega
counterpart, since an unmapped option only warns and would silently give the
two models different physics.
viz
The class polaris.tasks.ocean.realistic_global.forward.viz.Viz
plots global maps of each state variable at the start and end of the run, plus
the zonal and meridional wind stress that forced it. Which file the stress
comes from is the forward run’s initial condition’s business: a file of its
own when the init workflow produced one, or the initial condition itself
when the stress travels inside it. The list of
variables comes from the ocean component’s state_vars, with
normalVelocity replaced by kineticEnergyCell because the normal velocity
lives on edges and is not directly plottable as a cell field. Variables
missing from a given file are logged and skipped, so the step does not fail
when a model writes a different subset of fields.
global_stats
The class
polaris.tasks.ocean.realistic_global.forward.stats_analysis.StatsAnalysis
plots, for each field, the minimum, maximum and mean over time along with a
shaded standard-deviation envelope, and a companion panel showing the same
quantities as anomalies relative to their initial values. The fields are the
ones named by [realistic_global_forward_stats] fields, or, when that is left
empty, every field the run wrote statistics for. The variable names are built
from the field and the statistic by polaris.ocean.global_stats_names rather
than mapped one by one, so a field that one model writes and the other does not
needs no special handling.
This step normalizes two differences between the models:
Output location. MPAS-Ocean writes
global_stats.ncas named, while Omega treats the name as a prefix and builds the real one from the analysis period and the kind of output:global_stats_1DayInstantsfor daily instantaneous samples, or..._1DayTimeStatshad a temporal reduction been asked for. Both names come frompolaris.tasks.ocean.realistic_global.forward.stage.ForwardStage.stats_filename(), which lives on the stage because the period does; this step only asks it, insetup(), once the model is known. Anything else that reads a stage’s statistics asks the same question the same way, so the two cannot drift apart.Standard deviation. Omega writes a standard deviation (
<Field>_SpatialStdDev), while MPAS-Ocean writes a true root-mean-square (<field>Rms), so for MPAS-Ocean the standard deviation is recovered as \(\sigma = \sqrt{\mathrm{rms}^2 - \mathrm{mean}^2}\).
analysis_output
The polaris.tasks.ocean.realistic_global.analysis_output.AnalysisOutput
task runs a year of Omega, writing what the ocean analysis reads. It takes an
initial condition and optional init_steps as RealisticGlobalForward does,
so it is registered on the cached meshes and on every mesh the init workflow
builds. Its config is built in layers, each overriding the ones before it:
forward.cfg, the forward tasks’ config;analysis_output.cfg, with the defaultmoc_num_bins;the mesh’s file in
mesh_configs, which sets fewer bins on a coarse mesh;one_year.cfg, which sets only the run duration.
So the run is a split_explicit forward or cached_forward run a year long.
Its step,
AnalysisOutputForward,
is a forward-package Forward whose dynamic_model_config() adds
monthly_output.yaml after the forward step’s own yaml files, so that it
overrides them. The fragment replaces the History stream with monthly
snapshots in output/, turns on the MonthlyAverages and MOC analysis
groups with moc_num_bins latitude bins, adds sea surface height to
GlobalStats, and makes restarts monthly. A stream’s
options merge across yaml files, so the fragment has to set the History
stream’s FileFreq explicitly: the forward step’s value of 9999 years would
put every snapshot into one file. The step appends output to stream_dirs
and raises at setup unless the model is Omega.
Every monthly file is declared as an output, so that a run whose analysis
groups did not write is reported as a missing output rather than found later
by the analysis. Omega names a time-mean file for the instant it was
finalized (E3SM-Project/Omega#554), so a run that starts on 0001-01-01
writes the files covering year 1 as 0001-02 through 0002-01;
get_monthly_filenames() builds the names that way, and the snapshots, taken
at those instants, are named the same way.
restart
The polaris.tasks.ocean.realistic_global.restart.Restart task is
shaped like CIME’s ERS (exact restart) test, since Omega’s restarts will be
tested that way once Omega runs in E3SM. A full_run covers the whole period
and writes a restart partway through; a restart_run continues from that
restart to the end; and validate checks that the two histories agree. It is
also a regression test for
Omega #482, which ERS
alone would not catch: CIME’s restart run writes history files of its own,
whereas #482 broke appending to a history file that already held the frames
from before the restart. So the full run also writes its history up to the
restart to a file of its own, and the restart run appends to a copy of it.
The run lengths live as module-level constants in the task rather than in the config file, since they are chosen to make the failure visible – two history frames on each side of the restart, so that a restart run that clobbered rather than appended would be obvious – rather than to be tuned by a user.
Everything else comes from the [realistic_global_forward] config options,
as for any other forward run, including the time steps and viscosity in
e3sm-qu240’s per-mesh config file; the task’s restart.cfg sets only the
equation of state. Both forward steps are RestartSteps, so that the two run
the same model.
restart_step
The class
polaris.tasks.ocean.realistic_global.restart.restart_step.RestartStep
extends the
Forward step
with what the full run and the restart run need. dynamic_model_config()
builds the step’s
ForwardStage
from config with the step’s run duration and the task’s history interval, so
that config changes after setup still take effect, and then layers the task’s
full_run.yaml or restart_run.yaml on top of the shared forward.yaml;
because model config data is processed in the order it was added, the task’s
overrides win. setup() rejects any model other than Omega, since the
failure being tested cannot arise in MPAS-Ocean.
full_run.yaml writes a restart every restart_freq seconds, so once at the
restart time and again at the end, with no pointer file. It also adds a
HistoryBeforeRestart stream: a copy of the base History stream, writing
output_before_restart.nc, that stops with an end time between the frame at
the restart time and the next one, since Omega writes nothing once a stream’s
end time has passed. A unit test checks that the copy matches History in
everything else, since the restart run appends to that file with the
History stream.
restart_run.yaml continues from the full run’s restart at the restart time,
read by name rather than through a pointer file, since the full run writes
another restart at its end. The restart’s file name is computed in
restart_file() the way Omega stamps it. Both steps declare that restart to
Polaris, the full run as an output and the restart run as an input, so a
missing restart is caught before the model launches. It does not use the
restart_in/restart_out chaining of ForwardStage, which is for chaining
whole runs one after another.
runtime_setup() creates the restart directory the step writes into, which
Omega does not create itself, and, for the restart run, copies the full run’s
output_before_restart.nc into place as output.nc. Each step gets a
directory of its own under the task’s restarts, named for the step, so the
restart run’s restart at its end cannot replace one the full run wrote. The
copy is what makes the #482 check meaningful: it reproduces what a continuing
run finds on disk, a history file that already holds the earlier frames. It
is a copy rather than a symlink so that appending to it cannot reach back into
the full run’s work directory. The full run’s history up to the restart is
also added as an input file under the name previous_output.nc, which both
establishes the dependency between the two steps and keeps the symlink out of
the way of the copy.
the start time
The one subtlety in restart_run.yaml is that the restart run is given the
same StartTime as the full run, the start of the simulation, rather than the
restart time. Omega measures the history time axis from the clock’s start
time, so a run that moves the start time restarts that axis at zero, and its
first frame then collides with the first frame already in the file. That
collision is what silently overwrote the earlier frames in #482. On a
Continue the clock’s current time comes from the restart file instead, so
the start time does not need to move.
validate
The class
polaris.tasks.ocean.realistic_global.restart.validate.Validate
compares the history of the restart run with that of the full run. It reads
the elapsed-time coordinate with xarray directly, rather than through the
Omega-to-MPAS-Ocean renaming in open_model_dataset(), because that coordinate
is the variable under test; it then compares the state variables with
polaris.validate.compare_variables() as any other exact-restart test
would. A restart run whose history has fewer frames than the full run’s is
reported as the #482 failure specifically, since that is the shape the bug
takes. It also checks that the history up to the restart holds fewer frames
than the full run’s and matches its first ones; otherwise the restart run
would have had nothing to append to, and the time-axis check would pass
without testing anything.
dynamic_adjustment
The dynamic_adjustment task family (steps under
spherical/realistic_global/{mesh_name}/dynamic_adjustment) runs a
restart-chained sequence of forward stages from a mesh’s realistic initial
condition. One
polaris.tasks.ocean.realistic_global.dynamic_adjustment.task.RealisticGlobalDynamicAdjustment
task is registered per MPAS mesh.
schedule parsing
polaris.tasks.ocean.realistic_global.dynamic_adjustment.schedule.load_schedule_stages()
reads a schedule YAML (the per-mesh <mesh_name>.yaml or default.yaml, or a
file named by the schedule config option) and returns a list of
polaris.tasks.ocean.realistic_global.forward.stage.ForwardStage
objects.
A stage is a forward run, so the base stage comes from
ForwardStage.from_config on the [realistic_global_forward] section and the
schedule is applied on top with dataclasses.replace. The task’s config
carries that section, this task’s own, and the per-mesh overrides from
polaris.tasks.ocean.realistic_global.mesh_configs, so the physics
options and per-mesh tuning reach the stages without the schedule mentioning
them. Keeping the schedule a delta is what stops it from becoming a second
configuration system that has to track ForwardStage field by field.
Schedule keys are validated against SCHEDULE_FIELDS — the ForwardStage
fields minus the ones the restart chain owns — and coerced to each field’s
declared type, so a renamed or misspelled option raises at setup instead of
falling back to the config value. The parser also computes the chain itself:
the cumulative start_time, do_restart, and the shared restart_in /
restart_out filenames (restarts/rst.<stop-time>.nc).
restart chaining
Each stage is a
polaris.tasks.ocean.realistic_global.forward.forward.Forward step
reused from the forward workflow. When a stage’s ForwardStage sets
restart_out, the step writes its restart to the shared ../restarts
directory (via restart_streams.yaml) and declares it as an output; a stage
that sets restart_in declares its predecessor’s restart as an input, so the
chain is explicit to Polaris and a missing link fails before the model
launches. Consecutive stages are also linked with add_dependency.
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 / config_start_time from forward.yaml. The start time
is explicit rather than 'file' with a restart_timestamp, matching
cosine_bell/restart and ice_shelf_2d.
Omega needs a separate RestartRead stream, which the Omega block of
restart_streams.yaml supplies, switched on per stage by
ForwardStage.restart_stream_replacements. Each stage writes its restart and
pointer file into a directory of its own under restarts, and
ForwardStage.restart_files gives the files Forward.setup() declares for
either model.
setup-time rebuild
The stages are built in __init__ from the built-in schedule (so polaris list shows a representative set) and rebuilt in configure(), which runs after
the user config is merged, following the
polaris.tasks.ocean.cosine_bell.CosineBell pattern. This lets a
user’s setup-time schedule override or edited stage options take effect. The
step graph is therefore fixed at setup, not run time.
diagnostics and validation
The
polaris.tasks.ocean.realistic_global.dynamic_adjustment.summary.Summary
step builds one row of diagnostics per stage with
polaris.tasks.ocean.realistic_global.dynamic_adjustment.diagnostics.collect_stage_diagnostics()
and writes dynamic_adjustment_stats.csv. It makes no checks of its own; the
per-stage checks below read the same statistics with the same windows, so the
summary a user reads and the checks that passed or failed cannot disagree.
diagnostics.METRICS is the column list. Each metric names the global-statistics
variable it reads (in MPAS-Ocean naming, which diagnostics.open_stage_stats
renames Omega’s statistics to), how to reduce that variable’s time series over
the stage, and the output.nc field to fall back to when the model does not
report the statistic.
A metric with no fallback is left blank instead: output_var is set only where
computing the metric from the 3-D field gives the same quantity, which is true of
a maximum and not of a volume-weighted mean. The mean_*_change_per_day
columns are derived rather than read, from the change in a volume-weighted mean
across the stage divided by the stage’s run duration, which is why Summary
takes the ForwardStage list rather than just the stage names. They are a
conservation check, not a settling one: these runs are forced by wind alone,
with no surface fluxes, so the volume-weighted mean tracers are conserved and
both columns should be zero to within round-off.
The statistics cadence is ForwardStage.stats_interval, which drives the
globalStatsOutput stream and through it the analysis member’s compute
interval. It is separate from the 3-D output_interval on purpose: tying a few
scalars to the cadence of 3-D fields gave two samples per stage on the 10-day
schedules and, on u-oi6to18-lr12p5, a single startup record for every stage
shorter than the 10-day output interval.
schedule._check_stage_writes_its_records rejects a restart_interval that
misses MPAS-Ocean’s restart alarm (measured from the stream’s fixed
reference_time, not the stage start) and a stats_interval longer than the
stage. Both otherwise fail only after the model has run.
The statistics file is opened by relative path rather than declared as a step
input, so a stage that did not write one degrades to computing what it can from
output.nc. Its name comes from
polaris.tasks.ocean.realistic_global.forward.stage.ForwardStage.stats_filename()
on the stage itself, because for Omega it depends on the stage: Omega treats
its Filename as a prefix and appends its own analysis period with no .nc,
so a stage writing hourly statistics writes them somewhere a daily one does
not. MPAS-Ocean’s name does not move, which is exactly what makes the Omega
one moving easy to miss — and the symptom is silence, since a name that is not
found reads as a stage that wrote no statistics.
The checks live in
polaris.tasks.ocean.realistic_global.dynamic_adjustment.checks,
so that a step decides what to read and a check decides what is acceptable.
The temperature_max, temperature_min, salinity_max and cfl_max
thresholds all belong to
polaris.tasks.ocean.realistic_global.dynamic_adjustment.validate.StageCheck,
one per stage. Each check runs right after its stage and the next stage waits
for it, so the sequence stops at the stage that broke. Each is skipped, with a log line, when the model reports no
such metric. Summary applies none of them; it collects, writes and logs the
diagnostics.
StageCheck is a step of its own rather than work the forward step does after
the model exits. A Polaris step is sized for its MPI call and there is no good
mechanism for post-MPI Python, so folding the check in would hold the model’s
whole allocation open to compare a few scalars.
The thresholds read the extreme reached at any point in the stage, so an
excursion the run recovered from is still caught, and
diagnostics.extreme_and_day returns the day it happened alongside the value.
A NaN or infinite value fails a check outright, since a NaN compares as false
against any bound and would otherwise pass.
Which samples a stage is judged on is decided in one place,
diagnostics.first_judged_index, which both that function and
diagnostics._reduce go through. It always skips the stage’s first sample,
written before any time step: for the first stage it is the initial condition
and for the rest it is the predecessor’s final state, already checked, so the
extremes describe what each stage did. Without this the u-oi30-lr10 run
failed on a WOA23 source artifact in its initial condition. It returns None
when a stage wrote nothing else, which the schedule’s stats_interval check is
what prevents.
Its exclude_days argument is how the sequence’s startup_exclusion_duration
reaches a stage. schedule.excluded_days_in_stage turns that duration into a
number of days into a given stage, measuring from the first stage’s
start_time and returning zero for a stage that begins after the window has
closed. Measuring from the sequence rather than from each stage is deliberate:
what the window skips belongs to the initial condition, and a per-stage window
would stop watching the hours just after every damping change. Expressing the
result relative to the stage is deliberate too — a stage’s statistics count
time from their own first sample, and this way nothing depends on whether the
model restarted its clock.
Only the metrics with Metric.startup_window set — the tracer extremes — are
given it. cfl_max_in_stage is not, because a time step too long for the flow
shows up in exactly the hours the window covers. Summary passes the same
windows when it builds the summary, so the CSV a user reads and the checks that
passed cannot disagree. This is what the u-oi6to18 runs need: their
statistics are hourly, and the Sumatra artifact is still at 35.1 to 36.1 °C an
hour in.
There is deliberately no automated settling criterion. One existed and was
rewritten three times – comparing kinetic-energy levels, then the fractional
change stage over stage, then that change per unit time – and every revision
was forced by a healthy run failing it. The quantity is not comparable across
stages: they differ in length by up to 70x, the last one switches the damping
off, and a stage whose energy dips makes the next ordinary rise read as
acceleration. Three samples cannot separate any of that from a runaway. The
judgement is made from the diagnostics table and the viz figure instead. The
final simulation stage additionally compares the 'state' class of
variables in its output.nc against a baseline, as the forward tasks do.
polaris.tasks.ocean.realistic_global.dynamic_adjustment.viz.VizDynamicAdjustmentStep
plots the same statistics as time series. Its panels are declared in
viz.PANELS, each naming the variables it draws in MPAS-Ocean naming, so a
panel whose variables the configured model does not report is dropped and the
grid closes up around it. Stages are placed on a common axis from their
schedule start times rather than from the time variable in the files, because a
restart may or may not reset what the model counts from.
Omega’s temperature is conservative temperature where MPAS-Ocean’s is potential
temperature, so temperature_max and temperature_min are not literally the
same quantity in the two models — immaterial against these thresholds.
hydrography/woa23
The polaris.tasks.ocean.realistic_global.hydrography.woa23.task.Woa23
task is the Polaris port of the WOA preprocessing part of the legacy Compass
utility/extrap_woa workflow.
The implementation is intentionally organized around reusable Polaris steps
rather than around the legacy multiprocessing workflow. One notable design
choice is that the task reuses the combined topography product from
e3sm/init rather than taking a raw topography filename as a task-specific
input.
cached topography dependency
polaris.tasks.ocean.realistic_global.hydrography.woa23.steps.get_woa23_steps()
internally creates a shared e3sm/init
polaris.tasks.e3sm.init.topo.combine.step.CombineStep
configured for a 0.25-degree lat-lon target grid. The
polaris.tasks.ocean.realistic_global.hydrography.woa23.task.Woa23
task adds this step with a symlink combine_topo.
Because CombineStep sets default_cached = True, the combine_topo step
is automatically treated as cached during setup — no explicit opt-in is
needed.
This keeps the expensive topography blending logic in one place and makes the
ocean hydrography preprocessing task consistent with the broader Polaris
approach to shared, cacheable preprocessing steps. See
Automatic cache use with default_cached and free_running_steps for a full description of the
default_cached / free_running_steps mechanism.
combine
The class
polaris.tasks.ocean.realistic_global.hydrography.woa23.combine.CombineStep
combines January and annual WOA23 temperature and salinity climatologies into
a single dataset. January values are used where they exist, and annual values
fill deeper levels where the monthly product is not available.
WOA23 supplies in-situ temperature and practical salinity, so this step uses
gsw to derive conservative temperature and absolute salinity for the
canonical woa_combined.nc product.
extrapolate
The class
polaris.tasks.ocean.realistic_global.hydrography.woa23.extrapolate.ExtrapolateStep
uses the cached combined-topography product on the WOA grid together with
woa_combined.nc to build a 3D ocean mask and then fill missing WOA values in
two stages:
Horizontal then vertical extrapolation within the ocean mask
Horizontal then vertical extrapolation into land and grounded-ice regions
The final output is woa23_decav_0.25_jan_extrap.nc.
viz
The class
polaris.tasks.ocean.realistic_global.hydrography.woa23.viz.Woa23VizStep
plots horizontal maps of the extrapolated temperature and salinity at the
depths given by the horizontal_plot_depths config option, along with
vertical sections through Filchner Trough and the Ross Ice Shelf cavity. It
is added with run_by_default=False.
forcing/jra55
The polaris.tasks.ocean.realistic_global.forcing.jra55.task.Jra55
task builds the reusable wind-stress product used by the init tasks. Its
shared steps come from
polaris.tasks.ocean.realistic_global.forcing.jra55.steps.get_jra55_steps().
stress
polaris.tasks.ocean.realistic_global.forcing.jra55.stress.Jra55StressStep
downloads the yearly JRA55-do uas/vas files through the
initial_condition_database mechanism, selects the configured month, and
computes the stress at every 3-hourly step before averaging. Averaging the
stress rather than the wind preserves the gust contribution, which is why the
3-hourly data is needed; the drag law is
wind_stress().
The time loop is chunked, since a month of 3-hourly TL319 winds is
248 x 320 x 640 per component.
The step is default_cached = True, with the product in the cache database,
so that the multi-GiB download happens only when the product is deliberately
regenerated. The standalone jra55 task marks the step free-running for
that purpose.
The product is deliberately not padded, in latitude or longitude. Bilinear
remapping is center-based for ESMF but corner-based for mbtempest, and padding
a lat-lon source so that either its corners or its centers reach the pole
aborts mbtempest; duplicating a longitude column makes the grid overlap itself
and breaks both map tools. The output is jra55_stress.nc.
viz
polaris.tasks.ocean.realistic_global.forcing.jra55.viz.Jra55VizStep
plots global maps of the stress components and magnitude plus a zonal-mean
taux curve, which is the diagnostic that confirms the bulk formula and air
density are right.
init
The init task family (whose steps live under
spherical/realistic_global/{mesh_name}/init) creates mesh-specific ocean
initial conditions using WOA23 hydrography and the culled mesh produced by
e3sm/init. One
polaris.tasks.ocean.realistic_global.init.task.RealisticGlobalInit
task is registered per MPAS mesh; the target ocean model is determined by the
[ocean] model config option at run time.
step dependency chain
polaris.tasks.ocean.realistic_global.init.steps.get_realistic_init_steps()
composes the full chain:
cull_topo (
CullTopoStep): reindexes remapped topography from the base mesh to the culled ocean mesh usingocean_map_culled_to_base.nc, producingtopography_culled.nc. The standard topography fields (seeTOPO_VARIABLES) are validated against a baseline when one is provided.woa23_map (
Woa23MapStep): apolaris.remap.MappingFileStepthat builds the bilinear mapping file from the 0.25-degree WOA23 lat-lon grid to the culled MPAS mesh. This is the only MPI step in the WOA23 chain (it runsmbtempestor ESMF). Its task count scales with the approximate culled ocean cell count via theremap_cells_per_taskandremap_min_cells_per_taskoptions in the[realistic_global_init]config section. It and jra55_map are thin subclasses ofLatLonMapStep, which links the source and culled mesh, sizes the task count and builds the weights; each subclass supplies only its source.remap_woa23 (
RemapWoa23Step): a serial step that applies the weights from woa23_map withncremap, remapping WOA23 conservative temperature and absolute salinity to the culled MPAS mesh and producingwoa23_on_mesh.nc. The remapper is retrieved from woa23_map through the step dependency mechanism, so it is resolved only after that step has run.jra55_map (
Jra55MapStep): the bilinear mapping file from the JRA55-do TL319 grid to the culled MPAS mesh, sized the same way as woa23_map. Bilinear rather than conservative, because the ocean responds to wind stress curl and first-order conservative remapping makes that curl grid-scale noise; pyremap’s moab path hard-codes--order 1.remap_jra55 (
RemapJra55Step): applies those weights withncremap, producingjra55_on_mesh.nc. The stress is remapped in Cartesian components (stress_to_cartesian()) and rotated back to zonal and meridional components at the cell centers (cartesian_to_stress()), since the east/north basis rotates with longitude. That also makes the result independent ofmap_tool: ESMF’s default pole handling averages the source’s outermost row, which would cancel a vector given in zonal and meridional components.ncremapwrites zero rather than a missing value to a cell the weights do not reach, socheck_no_missing_cells()reads each cell’s coverage (frac_b) from the mapping file and fails the step if any cell is not fully covered or has a non-finite stress.pstar_init (
RealisticPStarInitStep): subclass ofpolaris.ocean.vertical.pstar_init.PStarInitStepthat runs the fixed-point p-star iteration jointly with the vertical interpolation of the WOA23 tracers, writing a model-neutralpstar_init.ncwith converged geometric layer interfaces and CT/SA tracers. The target bathymetry is first clamped into the range of depths the reference grid can represent, and to no shallower thanmin_bottom_depthormin_vert_levelslayers. The column is anchored at the prescribed sea surface, sosshmatches its prescribed value (0 here) and any residual from partial-cell snapping goes intobottomDepthinstead. Isolated bathymetry holes, cells deeper than every ocean neighbor, are capped at their deepest neighbor’s level and re-solved viapolaris.ocean.vertical.bathymetry_holes.fill_max_level_holes().initial_state (
InitialStateStep): readspstar_init.ncand the model resolved from[ocean] modelto produce model-specific output files (init.ncfor both models;vert_coord.ncadditionally for Omega). Tracer fields are kept as CT/SA for Omega and converted to potential temperature / practical salinity for MPAS-Ocean; the conversion itself is the framework’s (see Initial state), with this step supplying the per-cell longitude and latitude from the culled mesh becausepstar_init.nchas no horizontal mesh fields. It also writesmesh.nc, adding the Coriolis fields viapolaris.coriolis.add_coriolis_to_dataset(). For Omega,write_horiz_mesh_dataset()merges in the cell-centered vector-reconstruction fields fromreconstruction_weights.nc, so the step links cull_mesh’sculled_ocean_reconstruction_weights.ncunder that name. The weights have to be the culled mesh’s, not the base mesh’s, since that is the mesh the initial condition is built on.forcing (
ForcingStep): writes the model-specificforcing.ncfromjra55_on_mesh.ncviapolaris.ocean.model.OceanIOStep.write_forcing_dataset(). Omega reads 1-D fields onNCells; MPAS-Ocean’s Registry declaresdimensions="nCells Time", so aTimedimension of one is added there.viz (
VizInitStep): visualizes and sanity-checks the initial condition, vertical-coordinate and forcing datasets (see below).
viz
The VizInitStep
step is a shared step that is only added to a task’s steps_to_run when
get_realistic_init_steps is called with include_viz=True (as the standalone
RealisticGlobalInit task does). Other consumers that reuse the init outputs
as dependencies leave it out of their run list so the plots are not
regenerated.
The step is model-agnostic. It reads through
open_model_dataset() — which maps
Omega variable names to their MPAS-Ocean equivalents — and
open_vert_coord_dataset(), so one
code path serves both models. The titles and labels, by contrast, use each
model’s own variable names (Temperature, BottomGeomDepth, SshCell, …
for Omega), and temperature and salinity also say which quantity the model
holds: conservative temperature and absolute salinity for Omega, potential
temperature and practical salinity for MPAS-Ocean. Output filenames keep the
MPAS-Ocean names for both models so that the two sets of plots line up.
Omega’s initial state has no SpecVol, which open_model_dataset needs to turn PseudoThickness
into a geometric layerThickness, so the step derives the specific volume from
the equation of state itself. It produces:
initial_state_summary.png: histograms of the initial condition (a port of Compass’plot_initial_statewithout its Haney-number panel). The prognostic layer-thickness panel shows each model’s native variable —layerThicknessfor MPAS-Ocean andPseudoThicknessfor Omega — read from the raw output file and, like the other 3D panels, masked below the seafloor.vertical_coordinate.png: the vertical-coordinate structure derived from the geometricrestingThicknessof the deepest column (there are norefMidDepth/refBottomDepthreference profiles in this workflow).global native-mesh maps (via
polaris.viz.plot_global_mpas_field()) of temperature and salinity at the depths listed in[realistic_global_init_viz] depths, plus surface and seafloor, andbottomDepth,ssh,maxLevelCelland column thickness. For Omega the more nativeSurfacePressureandBottomPressureare also plotted when present.vertical transects (via
mpas_toolscompute_transect/plot_transect) of temperature and salinity along each transect in[realistic_global_init_viz_transects]. Layer interfaces are not drawn, because the thin upper layers pack them into a band that hides the upper few hundred meters;vertical_coordinate.pngshows the layer structure.Omega only: a stratification check using the TEOS-10 in-situ
Density(global surface/seafloor maps and transects). Density is not plotted for MPAS-Ocean, whose equation of state differs and is not evaluated here.xdmf/init/and (Omega)xdmf/vert_coord/: XDMF/HDF5 exports for ParaView, produced withmpas_tools.viz.mpas_to_xdmf.MpasToXdmf. For Omega the native variable names are preserved and only the dimension names are renamed to their MPAS-Ocean equivalents, as required by the converter.
Colormaps come from the shared viz defaults in
polaris.viz.get_viz_defaults(), looked up by variable name, so a
variable gets the same colormap everywhere it is plotted; none are named in
the plotting code. Units come from the same defaults (Omega’s Density
falls back on density), except that MPAS-Ocean’s salinity is labeled PSU,
since it is practical rather than absolute salinity. The limits, by
contrast, are computed per plot from the data range and written into
[realistic_global_init_viz] just before each call. That is deliberate and
differs from the forward viz step, which reads fixed limits from
realistic_global.cfg: fixed limits are what you want to compare runs or
times against each other, and the data range is what you want when the
question is whether a brand-new initial condition is sane. For a diverging
colormap the range is made symmetric about zero.