analysis
The analysis task group analyzes a simulation that has already been run,
rather than running one. Its tasks are pointed at a completed Omega
simulation through a config file and produce plots and the netCDF data behind
them.
This makes it unlike the other pages in this section. There is no mesh, no vertical grid, no initial condition, no forcing and no time step to describe, because no model is run: everything the analysis needs it reads from the simulation’s own output.
Analysis products are added over time; products lists what is available so far.
supported models
Omega only. The analysis locates a simulation’s output by reading the
simulation’s own Omega configuration file, which is also how it learns the
mesh, the vertical coordinate and the output streams. MPAS-Ocean describes
its output with namelists and streams files instead, and reading those would
require a translator into the same form, which does not exist. A setup with
[ocean] model set to mpas-ocean reports that rather than failing later.
Neither an Omega nor an MPAS-Ocean build is needed, since no model is run.
pointing the analysis at a simulation
The user supplies a config file describing the simulation to analyze. In the common case it needs three things: where the simulation is, and the two ranges of simulation years to analyze.
[ocean_analysis]
# the simulation's own Omega configuration file
omega_config_filename = /path/to/run/omega.yml
# a short name used in plot titles and file names
simulation_name = my_run
[ocean_analysis_climatology]
# the first and last year of the climatology, inclusive
start_year = 21
end_year = 40
[ocean_analysis_time_series]
# the first and last year of the time series, inclusive
start_year = 1
end_year = 60
omega_config_filename is required and is the only description of where
the simulation’s output lives. Polaris reads the mesh, the vertical
coordinate and the output streams from it, so none of them have to be restated
by hand. An Omega run always writes this file; it is in the run directory.
The two year ranges are independent. The climatology range governs the map-view products and the MOC; the time-series range governs the global statistics and ocean heat content time series. Changing one does not disturb the other.
A few further options in [ocean_analysis] are worth knowing about, though
most simulations need none of them:
simulation_pathThe directory that relative file names in the Omega configuration are resolved against. Defaults to the directory containing that file, which is the run directory. Set it if the output has moved since the run.
mesh_filename,vert_coord_filenameThe horizontal mesh and vertical-coordinate files, absolute or relative to
simulation_path. Each defaults to the file the Omega configuration names. Set one if it has moved.output_pathWhere plots are published for browsing. Defaults to
<work_dir>/analysis_output. Point it somewhere web-servable to share the results.copy_netcdfWhether to publish the netCDF file behind each plot alongside it. Defaults to
False, since on a large mesh these files can be most of the size of the analysis.
The remaining sections — [ocean_analysis_climatology],
[ocean_analysis_ohc], [ocean_analysis_time_series] and
[ocean_analysis_moc] — describe what each product computes and plots. See
the comments in polaris/tasks/ocean/analysis/analysis.cfg for the full list
with its defaults.
running the analysis
polaris suite -c ocean -t omega_analysis -w <work_dir> -f analysis.cfg \
--model omega
polaris serial
Two things about that command differ from most Polaris suites:
--model omegais required (ormodel = omegain an[ocean]section of the config file). Polaris normally detects the model by finding a build, and there is no build here.No
-p/--component_path, for the same reason.
The suite can be run on the machine where the simulation output lives, without copying that output.
To run a single product rather than the whole suite, set up that task on its own:
polaris setup -t ocean/analysis/global_stats -w <work_dir> -f analysis.cfg \
--model omega
where the results go
Two directory trees, with two audiences.
The work directory is built for predictability and for finding a step’s
log, not for browsing. It follows one rule, <product>/<period>, with a
third level only for the one product that is chunked by field group:
ocean/analysis/
├── climatology/0021-0040/ (shared: ncclimo)
├── climatology_maps/0021-0040/
│ ├── temperature/ salinity/ velocity/
│ ├── ssh/ mixed_layer_depth/
│ └── heat_content/
├── heat_content_series/0001-0060/
├── global_stats/0001-0060/
└── moc/0021-0040/
The range in each path is the zero-padded first and last year, matching the
convention ncclimo uses in its own file names.
The staging tree under output_path is where results are published for
browsing. It is shallow, with descriptive file names that carry the product,
the field, the season, the vertical reduction and the range, so that it is
easy to archive, to serve and to search:
<output_path>/
├── index.html (the gallery groups)
├── manifest.json (what was published)
├── galleries/
│ └── climatology_maps_temperature_0021-0040.html
├── plots/
│ ├── climatology_maps_temperature_ANN_-100m_0021-0040.png
│ └── climatology_maps_temperature_ANN_-100m_0021-0040.nc (if copy_netcdf)
└── thumbnails/
└── climatology_maps_temperature_ANN_-100m_0021-0040.jpg
output_path defaults to analysis_output at the root of the work
directory. Point it somewhere a web server can reach if the results are to be
shared.
The plots, and the netCDF files if copy_netcdf is set, are copies of
the files in the step that made them, so a web server can serve the staging
tree without being able to read the work directory. Running publish again
copies only what has changed. The netCDF files stay in the work directory
either way.
the published gallery
The suite’s last step, publish, collects what every other step made,
publishes it into the staging tree, renders a thumbnail for each plot and
generates a static gallery over the result. Open index.html in a browser,
from the filesystem or over a web server; there is nothing to build and no
JavaScript, and the pages look the same either way.
The landing page shows each gallery group — a product for one range of
years, such as “Climatology maps, years 0021-0040” — with one thumbnail per
gallery within it. A gallery page shows every plot in that gallery, in the
order the step made them, and clicking a thumbnail opens the full image, with
its netCDF file linked beside it if copy_netcdf is set. Every page carries
the simulation name, the ranges and the Polaris provenance, so a page found
later can be traced back to what produced it.
Four config options control what a page costs to load, which matters on a portal that throttles:
[ocean_analysis]
# the box, in displayed pixels, each thumbnail is scaled to fit inside
thumbnail_size = 320, 240
# image pixels per displayed pixel, so a thumbnail is sharp on a
# high-resolution display; need not be whole, and 2 is about three times
# the bytes of 1
thumbnail_scale = 2.0
# jpeg or webp; webp is a third to a half smaller at the same quality
thumbnail_format = jpeg
# 1 to 100; above about 80 the bytes grow quickly and it looks no better
thumbnail_quality = 75
Reducing thumbnail_size, then thumbnail_scale, is the first thing to try
if gallery pages are slow to appear. Thumbnails are the only images a page
fetches, they are tens to a hundred times smaller than the plots they stand
for, and a browser fetches only the ones that have been scrolled to.
Publishing again is cheap and safe. A thumbnail is regenerated only when it is missing or older than its plot, so adding one product to an analysis costs one thumbnail rather than all of them, and results from different ranges coexist because the range is in every published name.
A step that ran but made no products publishes nothing, and that is not an
error: every step that publishes leaves a manifest, with nothing in it when it
made nothing, and the gallery simply has nothing from that step in it. The
publish step’s log names those steps, since the gallery cannot. A step that has not run at all is
different. publish declares every step’s manifest as an input and every
such step as a dependency, so Polaris refuses to run it and names what is
missing.
analyzing the same simulation more than once
Every product lives at a directory named for the range it covers, and that is what makes re-analysis behave sensibly:
A new range recomputes. It creates steps in directories that have never run, so they run. Nothing has to be deleted and no flag has to be passed.
An unchanged range recomputes nothing. Those directories are already complete.
An earlier range’s results survive. Two ranges never share a directory, so analyzing years 21–40 leaves the results for years 1–20 in place. This also makes it impossible to get a plot labelled with one range whose contents came from another.
An overlapping range inherits rather than recomputes. The products that reduce the simulation month by month – ocean heat content so far – cache each month they reduce, and a later range reads the months an earlier one already did instead of reading those monthly means again. Extending a twenty-year series to forty therefore costs twenty years, and re-running a sub-range of one already analyzed costs nothing.
what may be inherited, and what may not
Inheriting numbers that were computed from something else is the one way this could go quietly wrong, so what a cache has to agree with before it is used is deliberately narrow:
Only directories of the same product in the same work directory are looked at – sibling ranges of that product, and nothing else.
Only steps that finished are, so a cache left behind by an interrupted run is not picked up. The exception is the step’s own directory: a run that was interrupted picks up where it left off when it is re-run.
Only caches carrying the same provenance stamp are: the simulation’s Omega configuration and output directory, the mesh, the config options and constants that decide the numbers – the elevation ranges and the specific heat capacity for heat content – and a version number for the code that computed them. The path says nothing about which simulation was analyzed, so the stamp is what keeps a work directory pointed at a second simulation from mixing the two.
Every run says in its log how many months it inherited and from where, and
names any candidate it turned down together with the entry of the stamp that
differs. The stamp is also written into the cache and into the product beside
it, so ncdump -h on a result says what it came from.
Setting reuse_previous = False in [ocean_analysis] turns inheritance off
and recomputes everything from the monthly means, for anyone who wants
determinism rather than discovery.
Re-running polaris setup rewrites the suite pickle at the root of the work
directory, so the range most recently set up is the one polaris serial will
run. Step directories from earlier ranges are untouched, so returning to an
earlier range means re-running setup with that range’s config.
Analyzing many ranges accumulates step directories and intermediate files, which is what makes re-analyzing an earlier range nearly free. It is worth knowing about on a filesystem with a quota; the climatology files dominate.
products
The suite is being built up product by product. What exists so far:
task |
product |
status |
|---|---|---|
|
map-view climatologies and maps of the change in ocean heat content, one step per field group |
|
|
time series of the simulation’s global statistics |
|
|
time series of globally integrated ocean heat content |
|
|
latitude-elevation plot of the meridional overturning circulation |
|
|
the staging tree and the gallery over it |
implemented |
In the case of a simulation without MOC output, the step reports that no MOC output was written and produces nothing, rather than failing the suite.
climatology maps
The climatology_maps task computes a climatology of the simulation’s
monthly-mean output and plots global maps from it, on the native MPAS mesh.
Nothing is remapped anywhere in the analysis.
It has a shared climatology step and then one step per field group.
The climatology runs once for a range of years no matter how many field
groups read it, and the groups are temperature, salinity, velocity,
ssh, mixed_layer_depth and heat_content. A group is the unit of work
so that adding a field costs that field and not the others.
the climatology
The climatology is computed with ncclimo from the NCO package, which is what
the rest of the E3SM post-processing workflow uses, so that these
climatologies are comparable with the ones zppy produces. It writes the
twelve monthly climatologies and one file per season in seasons, into
ocean/analysis/climatology/<range>/.
Two conventions are worth knowing:
Seasons are weighted by the length of each month in the simulation’s own calendar, and the annual mean is the same weighting over all twelve months.
DJFtakes its December from the same calendar year as its January and February, so every year in the range contributes exactly one December and no data from outside the range are needed. This is what MPAS-Analysis does.
The monthly means are the output of Omega’s MonthlyAverages analysis
group, which has to be enabled in the simulation with the fields the analysis
reads in its Fields list. Omega names each field of a monthly mean for the
field and the period, Temperature_TimeMean1Month, and names each file for
the month after the one it averages; the climatology strips the suffix and
takes the file named 0001-02 as January of year 1. Each file’s time bounds
are checked against the month it is taken to cover, and one that does not
match stops the step with an error.
Only the variables the analysis needs are averaged, so the cost scales with what is being plotted rather than with the size of the monthly means. A variable the simulation did not write is reported in the step’s log and left out.
the maps
Each field group’s step writes, for every combination of season, field and vertical reduction it was asked for, a PNG and a netCDF file with the same base name holding exactly what was plotted:
temperature_ANN_top.png temperature_ANN_top.nc
temperature_DJF_bottom.png temperature_DJF_bottom.nc
ssh_ANN.png ssh_ANN.nc
A field with no vertical dimension, such as ssh, is already a map, so its
files carry no reduction label. The netCDF files carry the simulation name,
the field, the season, the vertical reduction and the range of years as
global attributes, so a plot cannot be mistaken for a different one.
the config options that govern it
All of these are in [ocean_analysis_climatology]:
start_year,end_yearThe range of years the climatology covers. It is also the directory the results land in, so a different range recomputes rather than overwriting.
seasonsThe seasons to compute, beyond the twelve monthly climatologies, which are always computed.
plot_seasonsThe seasons to plot, which may include the monthly climatologies as
JANthroughDEC. Every season here has to be one the climatology computed.fieldsThe fields to map, using MPAS-Ocean names whatever model produced the output. This is what decides which field group steps exist.
elevationsHow to reduce a field with a vertical dimension to a map.
topandbottomare the topmost and bottommost valid layer of each column, so they respect ice-shelf cavities and partial bottom cells;k<index>is a fixed, zero-based vertical index, masked in columns where it falls outside the valid range; and a number is an elevation in m, positive up, so-100.0is 100 m below the sea surface.
An elevation is interpolated linearly between the midpoints of the two layers
it falls between, using the layer elevations the simulation wrote. Because
the input is a climatology, a -100.0 map is a map on the climatological-mean
position of the -100 m surface rather than the time mean of maps on its
instantaneous position; the two differ only where the seasonal cycle in layer
thickness is large.
Two ends of a column are worth knowing about. Above the midpoint of its
topmost layer the topmost value is used rather than the map being masked,
which is what makes 0.0 and -5.0 mean what a near-surface map is asked
for; likewise below the midpoint of the bottommost layer, down to the
seafloor. Below the seafloor the map is masked, so a column with partial
bottom cells ending at -97 m is blank at -100.0 while k9 still has a value
in it.
The colors come from one config section per field, so the color map and its
range can be set for each field independently. A section is named for its
field with the field name in lower case with underscores, so velocityZonal
is configured by [ocean_analysis_map_velocity_zonal].
Omega does not write velocityZonal and velocityMeridional yet, so Polaris
reconstructs them at cell centers from the climatology of normalVelocity,
using the least-squares weights in the simulation’s mesh file (see
Vector Reconstruction). Reconstruction is linear, so this is exactly
the climatology of the reconstructed velocity. It needs NormalVelocity in
the simulation’s MonthlyAverages group; a simulation that writes the
components themselves has those plotted instead.
ocean heat content
The heat_content field group is the one group that derives its field rather
than reading it. It is always present, whatever fields asks for, since it
is a diagnostic Polaris computes rather than a variable a simulation writes.
What it maps is the change in heat content from the first monthly mean of
the range, heat_content_anomaly, since a map of heat content itself mostly
shows how deep each column is. Each season’s heat content comes from the
climatology and the first month’s from that monthly mean, computed the same
way, and the map is the difference. The first monthly mean stands in for the
initial condition, so a seasonal map carries the difference between that
season and the first month as well as any drift.
Heat content per unit area is a mass-weighted integral of conservative
temperature: the specific heat capacity times the sum over layers of
temperature weighted by each layer’s mass per unit area. That mass comes
from the model’s own mass-like thickness – PseudoThickness for Omega,
layerThickness for MPAS-Ocean – times the reference density that defines
Omega’s pseudo-height. Weighting by mass rather than by a geometric
thickness is what removes the in-situ-versus-reference density error of a few
tenths of a percent that MPAS-Analysis’s formulation carries.
The ranges to integrate over are given by elevation_ranges in
[ocean_analysis_ohc], written <top>:<bottom> in m, positive up:
[ocean_analysis_ohc]
elevation_ranges = top:-700.0, -700.0:-2000.0, -2000.0:bottom, top:bottom
top is the free surface of each column and bottom is its seafloor, so
top:bottom is the whole column. These are geometric elevations, matching
the convention MPAS-Analysis and the observational products use; the integral
itself is still mass-weighted. Writing the upper bound as top rather than
as 0.0 matters: 0.0 would exclude the water between the resting sea
surface and the free surface, and a different amount of it in every column and
every season.
A range boundary usually falls inside a layer rather than on an interface,
and the geometry enters there and nowhere else: the layer is weighted by the
fraction of its thickness that the range covers, and that fraction is applied
to its mass. A layer lying wholly within the range therefore contributes its
whole mass whatever the geometry says, and a range that reaches below the
seafloor is truncated there. A column with no water in the range at all –
one shallower than 2000 m, for the -2000.0:bottom band – is masked rather
than plotted as zero.
The specific heat capacity is the TEOS-10 one that Omega uses with
conservative temperature, seawater_specific_heat_capacity_teos10 in Polaris’s
Physical Constants Dictionary. Like the reference density, it is not a config
option.
Output names follow the rest of the maps, with the range in place of the
elevation, for example heat_content_anomaly_ANN_top_to_bottom.png. The
netCDF is written in J m-2, which is the unit the quantity means, and carries
the range and the month it is a change from as attributes, so a plot cannot be
mistaken for a different one. The plot is drawn in GJ m-2 with a diverging
colormap, set in [ocean_analysis_map_heat_content_anomaly].
what is not there yet
mixedLayerDepth is in the config file’s field list but is a diagnostic
Omega does not compute yet, so its maps are reported as skipped in the step’s
log rather than silently dropped.
global statistics time series
Omega’s GlobalStats analysis group reduces each field it is given to a
handful of numbers per output time — the global mean, minimum, maximum and
standard deviation. The global_stats task plots those against time, which
is the cheapest look at whether a simulation is drifting.
For each field it produces two files in
ocean/analysis/global_stats/<range>/:
<field>.pngTwo panels sharing a time axis in simulation years. The upper one shows the statistics themselves, with the standard deviation as a shaded envelope around the mean. The lower one shows the change in each statistic since the beginning of the series, since drift is usually what the reader is looking for and it is easy to miss at the scale of the absolute values.
<field>.ncExactly the data that were plotted — one variable per statistic, the time axis in simulation years, and the field, the statistics, the simulation name and the year range as global attributes. It is there so the numbers can be inspected, compared against another tool, or re-plotted without re-reading the simulation.
Two options in [ocean_analysis_time_series] govern it, alongside the
start_year and end_year that every time series shares:
fieldsThe fields to plot, using MPAS-Ocean (Polaris standard) names. Leave it empty to plot every field the simulation wrote statistics for.
statsThe statistics to plot for each field: any of
mean,min,maxandstd.
A field or statistic the simulation did not write is skipped with a message in the step’s log, not treated as an error. The defaults describe what we would like to see, and any given simulation will have written some subset of it — which is no more under the user’s control than it is under ours. A field with no surviving statistics is dropped entirely. Only a file with none of the requested variables stops the step, since that usually means the year range does not overlap the simulation rather than that a variable is missing.
Omega can write these statistics either as snapshots or as time means over a period it is configured with, and it names the variables differently in the two cases. Polaris reads the simulation’s Omega configuration to find out which it wrote, so this needs no option of its own.
ocean heat content time series
The heat_content_series task reduces every month of the simulation to the
globally integrated ocean heat content of each elevation range and plots the
series. Heat content drift is what a long coupled simulation is judged on.
The integral is the same mass-weighted one the maps take, over the same ranges
from [ocean_analysis_ohc], described under
ocean heat content; the difference is that it is taken
from the monthly means themselves rather than from a climatology. Averaging
over the range is exactly what a drift curve must not do, so the climatology
is no use here.
The range of years is start_year and end_year in
[ocean_analysis_time_series], the same range the global statistics use:
[ocean_analysis_time_series]
start_year = 1
end_year = 60
Three files land in the step’s directory:
heat_content.pngtwo panels sharing a time axis – the heat content of each range in \(10^{22}\) J, which the deep ocean dominates, and its anomaly from the first month of the range, which is where a drift of a few tenths of a percent is visible at all.
heat_content.ncthe same series in J, with the year and the month of each value, and the range and the options it was computed under as attributes.
ocean_heat_content_cache.ncthe reduced months, which is what a later range inherits; see what may be inherited, and what may not.
Only temperature, the model’s mass-like thickness and, when a range has a
bound inside the column, GeomZInterface are read from each month, and only
one month is held at a time, so the memory this step needs does not grow with
the length of the record. A first run over several decades at high
resolution is the expensive one, and every run after it is nearly free.
meridional overturning circulation
The moc task plots the annual climatology of the global meridional
overturning streamfunction against latitude and elevation over the climatology
years.
Polaris does not compute the MOC. Getting the streamfunction right needs the full three-dimensional velocity at every time step, not a monthly mean of it, which is why Omega computes it in situ; Polaris computes the climatology of what Omega wrote and plots it. The vertical axis is Omega’s too: the streamfunction lives on layer interfaces, whose elevation moves, and Omega reports the mean elevation of each interface over the same period alongside the streamfunction. Polaris does not reconstruct it, since an axis averaged over a different period would quietly disagree with the diagnostic it labels. How Omega computes both is described in the MOC section of Omega’s User’s Guide.
Only the global streamfunction is plotted. Omega’s MOC group can compute the streamfunction for named regions, and regional overturning — the Atlantic MOC in particular — comes with the rest of the regional analysis later.
The simulation has to have run with the MOC analysis group turned on:
Omega:
Analysis:
MOC:
Enable: true
NumBins: 180
Regions: [Global]
ReductionPeriod: [1Month]
Filename: moc.$Y-$M.nc
Polaris reads the group from the simulation’s Omega configuration, so nothing
about it is restated in the Polaris config file. The group has to write
monthly means to one file per month, as above: the climatology is computed
from them with ncclimo, the same way and with the same weighting by the
length of each month as the climatology the
maps are made from. MOC output of any other kind is reported, with what
the group would need to write instead, and nothing is plotted.
Two files land in ocean/analysis/moc/<range>/:
global.pngThe streamfunction in Sv as filled contours with contour lines over them, on a diverging color map centered on zero. The color bar marks the value of each contour line. Elevation is on the vertical axis, positive up, so the sea surface is at the top without the axis being inverted.
Polaris preserves Omega’s streamfunction sign convention. Omega computes the global streamfunction by accumulating upward-positive vertical volume flux from south to north, taking zero at the southern boundary. With latitude increasing to the right and elevation increasing upward, positive streamfunction corresponds to clockwise overturning and negative streamfunction to counterclockwise overturning. For an isolated positive cell, the upper branch flows northward and the lower branch southward.
global.ncThe streamfunction that was plotted, with the latitude of each bin center and the elevation of each interface, and the year range, the reduction period and the files it came from as attributes.
Two options in [ocean_analysis_moc] govern the plot, alongside the color map
options every plot has:
contour_intervalThe spacing of the contour lines, in Sv.
max_streamfunctionThe maximum absolute value of the color map, in Sv, which is symmetric about zero. The default is the largest value the streamfunction reaches. Setting it brings out a weak circulation that a strong one would otherwise flatten, and costs nothing: the contour lines are drawn over the whole range whatever the color map is clipped to.
The range of years is start_year and end_year in
[ocean_analysis_climatology] — the same range the maps use, since both are
averages over the climatology period rather than time series.
A simulation that wrote no MOC output, or wrote it before the axes were attached to the stream, is reported rather than failed. Omega attaches the latitude bin boundaries and the interface elevations to every MOC output stream, but that came later than the streamfunction itself, so a file with one and not the other will be met. The step names the field that is missing and what would have written it, and produces nothing.
troubleshooting
The data set has no zMid, GeomZInterface. The simulation did not write
the elevation of its layers, which every map at an elevation is a position
in. Polaris cannot reconstruct it: geometric thickness is derived from
pseudo-thickness through specific volume, and the monthly mean of that
product is not the product of the monthly means. A run without it is out of
spec rather than merely configured without a field.
The mesh has no ReconStencilCell, ReconWeightsCell. The velocity
components are reconstructed with least-squares weights that every Omega mesh
Polaris builds carries. A mesh made elsewhere needs them added with
add_reconstruction_weights.
invalid interpolation syntax while reading the config file. Polaris
config files use extended interpolation, so a bare $ in a value is an error.
File-name templates containing $Y and $M belong in the simulation’s Omega
configuration, not in a Polaris config file, and there is no option that takes
one.
Missing input files at setup. The analysis checks that every file it will read exists before anything runs, and reports the years and months that are absent together with the Omega stream that named them. The usual cause is a year range that reaches beyond the simulation.
Could not detect ocean model. --model omega was not passed and there
is no [ocean] model option in the config file.