Skip to content

Add finite-volume pressure gradient (phase 1 -- 2nd order) - #493

Open
xylar wants to merge 24 commits into
E3SM-Project:developfrom
xylar:omega/add-finite-volume-horiz-press-grad-phase1
Open

xylar wants to merge 24 commits into
E3SM-Project:developfrom
xylar:omega/add-finite-volume-horiz-press-grad-phase1

Conversation

@xylar

@xylar xylar commented Aug 3, 2026 •

Copy link
Copy Markdown

Add a FiniteVolume horizontal pressure gradient, phase 1 of the design in #448, and make it the default. It compares the two columns sharing an edge at the same pressure rather than the same layer index, so the gradient is zero to machine precision for a resting ocean with temperature and salinity linear in pressure, whatever the coordinate tilt, provided the two columns' sea floors are at the same pressure. Over a sloping floor a residual remains, largest with terrain-following layers. Centered is unchanged and can still be selected. Builds on #485.


Behaviour changes:

  • PressureGradType defaults to FiniteVolume, so simulations with horizontal density gradients are no longer bit-for-bit with Centered.
  • New options under PressureGrad: HorzOrder, VerticalReconstruction and QuadraturePoints. All are optional; the phase 2 values are rejected with an error.
  • An unrecognized PressureGradType is now an error instead of falling back to Centered.
  • With FiniteVolume selected, the equation of state also returns its derivatives; no extra calls are added.

Known limitation: on the seamount test's sigma coordinate the new scheme develops a grid-scale instability that Centered does not; the cause is not yet understood. z-star, the nearer analogue of Omega's p-star, is clean.

Not in this PR: phase 2, and the Polaris tasks that exercise the scheme (E3SM-Project/polaris#675 and E3SM-Project/polaris#681). The sea-floor anchor error where the two columns' floors are more than a layer apart is fixed in draft #635. If the instability on the seamount's sigma coordinate is still unsolved once this PR and the Polaris PRs merge, open an issue for it (see the 2026-10-25 comments).

Checklist

  • Documentation:

  • Linting

  • Building

    • CMake build does not produce any new warnings from changes in this PR
  • Testing

    aurora, intel, mpich

    • CTests Pass
    • Polaris omega_pr Pass

    aurora, intelgpu, mpich

    • CTests Pass
    • Polaris omega_pr Pass

    chrysalis, intel, openmpi

    • CTests Pass
    • Polaris omega_pr Pass

    frontier, craygnu, mpich

    • CTests Pass
    • Polaris omega_pr Pass

    frontier, craygnu-mphipcc, mpich

    • CTests Pass
    • Polaris omega_pr Pass

    pm-cpu, gnu, mpich

    • CTests Pass
    • Polaris omega_pr Pass

    pm-gpu, gnugpu, mpich

    • CTests Pass
    • Polaris omega_pr Pass
  • Provide relevant details in a comment to the PR titled Testing

  • New tests:

    • CTest unit tests for new features have been added per the approved design.
    • Polaris tests for new features have been added per the approved design (and included in a test suite)

Posted by Claude Code on @xylar's behalf. The wording above is AI-authored; please check it accordingly.

🤖 Generated with Claude Code

@xylar

xylar commented Aug 3, 2026

Copy link
Copy Markdown
Author

This implements the design described in #448

@xylar

xylar commented Aug 3, 2026

Copy link
Copy Markdown
Author

This work is built on #485

@xylar

xylar commented Aug 3, 2026 •

Copy link
Copy Markdown
Author

Where the finite-volume pressure gradient stands

What the centered scheme misses. Over sloping bathymetry it produces spurious currents in the bottom layer, strong enough to drive numerical instability in realistic runs. The cause is that it compares two neighbouring columns layer by layer — but when layers tilt, the same layer sits at different pressures in the two columns, so it is comparing water at different depths.

What this branch does instead. It compares the two columns at the same pressure. For a resting ocean with a simple stratification the answer is then exactly zero rather than merely small, no matter how steeply the layers tilt or how rough the bathymetry.

Evidence it works.

  • Unit tests: the spurious pressure gradient is at machine precision — thirteen orders of magnitude below the centered scheme on the same state.
  • An independent Python implementation, written from the design rather than from the code, agrees with Omega essentially exactly.
  • A six-day seamount simulation: spurious currents about 40× smaller, and the energy in them about 30,000× smaller, than with the centered scheme.
  • Small cost — it adds no equation-of-state evaluations, and the partial derivatives of SpecVol are relatively cheap.

Caveats.

  • With sigma coordinates, where layers follow the bathymetry, the new scheme goes unstable after a couple of days while the centered one stays clean. This is unexplained. It is not an accuracy problem — the scheme is more accurate right up until the instability starts.
  • The coordinate Omega actually runs, p-star, has only been tested in horiz_press_grad (not forward time stepping). The closest one we have tested in seamount behaves well but it would be good to test a p-star initial condition, too.
  • We have shown the pressure-gradient error is fixed. We have not yet shown that fixing it cures the bottom-layer noise in a global simulation.

@xylar
xylar force-pushed the omega/add-finite-volume-horiz-press-grad-phase1 branch from d9cc591 to 17e6406 Compare September 11, 2026 15:37
@xylar

xylar commented Sep 11, 2026 •

Copy link
Copy Markdown
Author

Where things stand on 2026-09-11

The branch is rebased onto develop (16 commits; #485 and the VertCoord single-precision fix have merged). The rebase changed no answers: CTests and the Polaris pressure-gradient and seamount tasks pass, and the sigma seamount runs reproduce the August numbers to three digits.

The sigma instability is unchanged and still unexplained. With explicit mixing now off, Centered on z-star overturns from day 0.6, so the August 44x / 3e4 comparison is superseded: at one hour, before either run has done anything else, FiniteVolume has 92x lower max |u| on the realistic profile and is at round-off on the linear-in-pressure one.

Outstanding: the omega_pr suite, other machines, a wall-time comparison with Centered, and a seamount run initialized on p-star.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@xylar

xylar commented Sep 11, 2026

Copy link
Copy Markdown
Author

Testing

CTests on chrysalis with intel and openmpi: 50/50 pass in two builds of 17e6406, rebased on develop at d086d49. The Polaris horiz_press_grad tasks (8) and seamount/nonlinear tasks (4), from Polaris branches new-horiz-press-grad-tests and seamount-for-hpg-testing, all pass:

/lcrc/group/e3sm/ac.xylar/polaris_1.1/chrysalis/test_20260911/hpg_rebased
/lcrc/group/e3sm/ac.xylar/polaris_1.1/chrysalis/test_20260911/seamount_fv

Omega and the Polaris reference agree to 1.6e-13 m/s2 or better for both schemes. The omega_pr suite has not been run.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@xylar
xylar force-pushed the omega/add-finite-volume-horiz-press-grad-phase1 branch from 17e6406 to c26a9b4 Compare October 4, 2026 06:40
@xylar

xylar commented Oct 4, 2026

Copy link
Copy Markdown
Author

Testing

I ran the Polaris realistic_global/EC30to60E2r2/analysis_members_test forward step for 10 days on Chrysalis (Intel, Release build of c26a9b4), once with PressureGradType: Centered and once with FiniteVolume. All other settings are the task's defaults (bottom drag 1e-3, del4 1.2e11). The finite-volume scheme removes the grid-scale noise in bottom-layer kinetic energy that the centered scheme shows across the open ocean. What remains is mostly on shelves and in marginal seas.

bottom-layer KE, day 5

Area-mean bottom-layer KE (m² s⁻²):

scheme day 1 day 5 day 10
centered 1.30e-2 8.02e-3 7.44e-3
finite volume 4.65e-4 4.88e-4 4.54e-4

Day 1 and 10 plots, daily statistics and the plotting script are on the portal. Both runs took about 3 minutes on 6 nodes. The pressure-gradient tendency cost 8.0 s with finite volume and 1.2 s with centered. The work directory is:

/lcrc/group/e3sm/ac.xylar/polaris_1.1/chrysalis/test_20261004/ec30to60-hpg-ke

The task runs 1 day by default, so I set the 10-day duration and the scheme by editing each forward step's omega.yml after setup.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

xylar and others added 17 commits October 12, 2026 01:59
Rename PressureGradType::HighOrder1 to FiniteVolume and drop the
unimplemented HighOrder2, leaving a commented stub for a future variant.
Rename the functor class PressureGradHighOrder to
PressureGradFiniteVolume and the PressureGrad member HighOrderPGrad to
FiniteVolumePGrad.  Accept 'FiniteVolume' or 'finiteVolume' in place of
'HighOrder1' when parsing PressureGradType, and update the PGrad dev and
user guides accordingly.

The new scheme uses the same two-cell horizontal stencil as the centered
scheme and is second order, so naming the class for an order it does
not have would misdescribe what ships.  FiniteVolume names the
layer-integrated control-volume form instead.

This is answer-neutral: a pure rename plus a config-facing string change.
PressureGradCentered is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add HorzOrder, VerticalReconstruction and QuadraturePoints to the
PressureGrad configuration group, parsed and validated in the
PressureGrad constructor and cached on the PressureGradFiniteVolume
functor.  All three are optional, so a configuration written before they
existed still parses; where a key is absent the default
(HorzOrder 2, VerticalReconstruction 'linear', QuadraturePoints 2) is
used.

Reject values that are not implemented, such as HorzOrder 4 and
VerticalReconstruction 'ppm', with a clear "not implemented" error
rather than falling back silently, which would make a run requesting
them look like a run that used them.
QuadraturePoints is an accuracy knob only: the matched-pressure integrand
is zero pointwise on the exact set, so no quadrature rule can break the
robustness property.  There is no 'constant' reconstruction and no
setting that reduces the scheme to Centered.

Add accessors for the configured type and options, and extend PGradTest
with a parse and dispatch check that creates a FiniteVolume instance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Widen PressureGrad::computePressureGrad and
PressureGradFiniteVolume::operator() to take the layer-mean conservative
temperature and absolute salinity and the Eos specific volume
derivatives.  PressureGradCentered is untouched: its operator() keeps its
existing signature and the Centered dispatch branch passes the same
arguments it always did.  Update the Tendencies call site to supply Theta
and S from the tracer array and the Eos instance it already holds.

Switch AuxiliaryState::computeMomVertAux to call
Eos::computeSpecVolAndDerivs in place of Eos::computeSpecVol when the
FiniteVolume scheme is selected.  computeSpecVolAndDerivs fills SpecVol
as well, so it replaces rather than accompanies computeSpecVol; the
branch keeps a Centered run from paying for the derivative arithmetic.

The FiniteVolume path is still a no-op, so Centered answers are
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assert design section 3.9's identity directly in PGradTest: with the
tidal and self-attraction-and-loading potentials zero,
PressureGradCentered is exactly -g/d_e times the fixed-pressure shift

   S = 1/2 (dZ_k + dZ_k+1) + alphaBar / (2 g) (dq_k + dq_k+1)

with dZ and dq the cross-edge differences of GeomZInterface and
PressureInterface.  The check runs over a sweep of interface tilts and is
scaled by the size of the hydrostatic terms the centered functor forms
and cancels, since agreement is to round-off rather than bit-for-bit.

Factor the two-column state construction out of the refinement loop into
setupTwoColumnState so the sweep can rebuild it at each tilt.  The
refinement loop is unchanged in what it computes.

This is the permanent regression test of design section 5.5, not a
transitional one.  Because the two expressions read the mesh, VertCoord
and Eos state through independently written code, their agreement tests
the shared upstream state -- edge masks, interface indexing, VertCoord
conventions -- and not just the pressure gradient arithmetic.  A guard
asserts that dropping the pressure half of S misses by far more than the
tolerance, so a pass cannot come from both sides being trivially equal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Make an unrecognized PressureGradType a fatal error instead of falling
back to the centered scheme with an informational message.  A silent
fallback turns a typo, or a configuration naming a scheme that no longer
exists such as the former 'HighOrder1', into a run that looks like a
passing centered run.  This is the same hazard unimplemented
sub-option values are already rejected for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add PGradRecon.h, holding the mean-preserving linear reconstruction of
conservative temperature and absolute salinity in pressure that the
FiniteVolume pressure gradient uses.  The slope is a centered difference
of the layer means with respect to mid-layer pressure, one-sided in the
shallowest and deepest valid layer of a column and zero where a column
has a single valid layer.

Differencing against the actual mid-layer pressures rather than against
layer index is what makes the estimator exact for a profile linear in
pressure on Omega's non-uniform vertical grid: the mean of a linear
function over a layer is its value at the layer's midpoint, so the layer
means lie exactly on the line as a function of mid-layer pressure.  A
formula that assumed uniform thickness would pass a uniform-thickness
test and fail on a real grid.

The deviation integrates to zero over the layer because PressureMid is
the exact arithmetic midpoint of the two interface pressures, which is
also what makes VertCoord's midpoint rule the exact layer integral of a
linear reconstruction and so spares VertCoord any change.  There is no
limiter.

The functions take scalars so they can be unit tested without a mesh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Check the mean-preserving linear reconstruction on a single column,
without a mesh.  Layer means are sampled from a profile exactly linear in
pressure, so the recovered slope must match the profile's slope to
round-off in every layer, including the two that use a one-sided
difference.

The layer thicknesses are deliberately non-uniform, which is the case the
test exists for: a formula that assumed uniform thickness would pass the
uniform control that runs alongside and fail here.  Running both means a
failure says which of the two it is.

Also asserted are the two properties the scheme leans on: that the
reconstruction reproduces the layer mean at mid-layer pressure, and that
its deviation integrates to zero over the layer, by two-point Gauss
quadrature which is exact for a linear integrand.  A single-valid-layer
column must give a zero slope, a constant being the only mean-preserving
reconstruction available there.

This is worth testing separately because it is the most likely place for
the exactness gate to fail, and it localizes such a failure immediately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add PGradFiniteVolume.h with the first-order equation-of-state expansion
shared across an edge layer, built by averaging the two adjacent cells'
coefficients and reference states.  One set multiplies both columns,
which is what the robustness property rests on: give each column its own
expansion point and the alpha0 and alphaP terms no longer cancel in the
matched-pressure difference, and the scheme generates spurious flow out
of nothing but its own equation-of-state approximation.  Which set is
used is an ordinary accuracy question and cannot break exactness.

Add findLayerForPress to PGradRecon.h, the lookup that makes the scheme a
fixed-pressure comparison rather than a fixed-layer-index one.  At a
pressure in edge layer K each column supplies its state from whichever of
its own layers contains that pressure, which under tilt is generally not
layer K.  A pressure outside the column clamps to the outermost valid
layer and extrapolates its reconstruction, which is the rule where the
edge control volume extends past a column's own floor; exactness survives
extrapolation because an extrapolated reconstruction still reproduces a
resolved profile.

Pin the lookup with direct property tests, on a fabricated column pair
without a mesh.  These are not optional: no answer-level check anywhere
in the test plan can distinguish this from a layer-index lookup, since on
a profile linear in pressure every layer's reconstruction is the same
line and looking up the wrong layer costs nothing.  The tests assert that
the returned layer brackets the pressure, that it differs from the edge
layer index by up to three layers so the lookup is demonstrably not an
index lookup, that the answer is independent of the starting hint so the
column scan's incremented cursors cannot disagree with a search, and that
a pressure outside the column clamps to the outermost valid layer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add matchedPressSpecVolDiff, the central quantity of the scheme: the
difference in specific volume between an edge's two columns, evaluated at
one pressure with one shared expansion.  Because both columns use the
same expansion, the SpecVol0 and SpecVolDP terms are identical and cancel,
leaving a coefficient times the horizontal contrast in reconstructed
temperature and salinity at matched pressure.  Compressibility drops out
entirely, which is correct physics rather than an approximation: a
horizontally uniform compressibility exerts no horizontal pressure
gradient.

Add the Gauss-Legendre rule for 1 to 4 points, and move
MaxPGradQuadPoints alongside it.  The number of points is an accuracy
setting only, since the integrand is zero at every point on a resolved
profile.

Unit test on a fabricated column pair, without a mesh, exercising the
reconstruction, the pressure lookup and the shared expansion together.
Both columns' layer means are the exact layer averages of one prescribed
continuous profile, so under the offset between the columns those means
genuinely differ.  On a profile linear in pressure the integrand is zero
at every quadrature point, not merely in the integral, measured at
5.8e-21 m3/kg or 0.9 eps of the terms that had to cancel.  A quadratic
profile runs as a control and gives 5.3e-10 m3/kg, eleven orders larger,
so the linear result is not a statement that everything in the test is
zero.  The tolerance is scaled by the cancelling terms rather than being
an absolute constant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Accumulate the fixed-pressure height difference down each edge's column,
into a per-edge array of interface values.  This is a prefix sum with
edge-dependent coefficients, so it is not expressible as an independent
per-vertical-chunk operation and cannot live in the functor; it is the one
structural addition beyond the per-edge, per-chunk pattern the centered
scheme uses.  It runs as a parallelForOuter over edges with a
parallelScanInner down the column, in the same shape as
VertCoord::computeGeomZHeight.

The scan runs in two passes.  The first evaluates the matched-pressure
integrand at each edge layer's quadrature points and forms two integrals
of it over the same points: the increment of the recurrence, and its
first moment about the layer's top interface, which the layer mean will
need.  The second anchors the column and turns the increments into the
height difference at every interface.  The per-cell reconstruction slopes
are computed once and reused across each cell's edges, so the per-edge
work is polynomial arithmetic on cached values.

Anchor at the sea floor, per design section 3.7.4, which leaves the end
open and prefers this one on conditioning grounds.  There turns out to be
a second and stronger reason.  VertCoord builds geometric height by
accumulating a midpoint rule over each column's own layers, and on a
curved profile two columns with different layer partitions give sums that
differ at second order in layer thickness.  Anchored at the surface that
discrepancy enters the height difference directly and the exactness gate
would not be machine-zero; anchored at the sea floor over a flat floor the
height difference is exact input and vanishes identically, and the
discrepancy never enters, because the scheme integrates its own
reconstruction rather than accumulating VertCoord's height.  This is a
prediction to be checked against the measurement.

The anchor is computed, not assumed: each column's height at the deepest
shared interface is shifted to the common pressure by integrating its own
reconstruction over half the cross-edge pressure difference, and both
short integrals vanish where the two columns' interface pressures agree.

Test that the height difference is zero at every interface, not merely in
the layer mean, on a two-column state whose layer means are the exact
layer averages of one profile linear in pressure -- so that under the
offset between the columns those means genuinely differ.  The shape of a
failure is reported as well as its size, since a residual growing with
depth points at the recurrence while one flat with depth points at the
anchor.  The tolerance is provisional pending the first measurement.

Allocate the working arrays only when the FiniteVolume scheme is
selected, so a Centered run pays no memory for them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Turn the column scan's output into the tendency: form the layer mean of
the fixed-pressure height difference from its value at the layer's bottom
interface and the first moment of the integrand over the layer, scale by
-g/d_e, subtract the tidal and self-attraction-and-loading difference, and
accumulate into Tend with EdgeMask exactly as the centered functor does.
The edge control volume's pressure thickness is exactly the edge average
of the two columns' own.

This is the first point at which the FiniteVolume scheme produces a
number.

Narrow the functor's arguments to the two arrays the column scan fills.
Design section 4.1.3's illustrative signature lists the temperature,
salinity and specific volume derivative arrays, but it also omits the
scan's own array while describing the functor as reading it "like any
other input", so it is not self-consistent.  With both integrals formed
in the scan over one set of quadrature points, which section 3.5.1
requires, the functor reads none of the state the scan consumed; passing
it would only invite evaluating the integrand a second time.

Report the tendency on the exact-set state alongside PressureGradCentered's
on the same state, and require the centered value to be nonzero, so that
the FiniteVolume result is a cancellation against a known nonzero
reference rather than a comparison with zero.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gating test of design section 5.2, in three groups.

Exactness.  On a two-column state whose layer means are the exact layer
averages of one profile linear in pressure, with a horizontally uniform
surface pressure, the tendency and the fixed-pressure height difference
must both be zero to machine precision.  Measured at commit 10: tendency
1.6e-18 m/s2 against PressureGradCentered's 1.6e-05 m/s2 on the same
state, and height difference 6.7e-16 m, both 0.027 eps of the terms that
had to cancel.  The gates replace commit 9's provisional value with four
eps, which leaves over a hundredfold margin while still catching drift,
and they track Real's epsilon and the size of the hydrostatic terms
rather than a physical tolerance.  Absolute tendencies are reported
alongside the centered scheme's, so a pass is a cancellation of thirteen
orders against a known nonzero reference.  The maximum is the right norm
here, since the claim is a zero at every edge and layer.

Convergence.  Quadratic and cubic profiles, which the linear
reconstruction does not resolve, over 15, 30 and 60 layers at fixed tilt.
The residual must shrink at second order in layer thickness; a rate below
that means one of the scheme's conditions has been broken somewhere and
is a bug to find, not a tolerance to widen.  The rate is measured on the
RMS rather than the maximum: a maximum is set by whichever layer happens
to be worst, and that layer moves under refinement, which makes the rate
noisy and non-monotone.  RMS is also the norm the design's measured rates
were taken in, so the two are comparable.

Guards.  Guard (a), tilt sensitivity, runs on the kernel and is the only
one that can fire where every other check passes.  Guards (b), (c) and
(d) need the scheme's rules switched off one at a time, so they run
against a host reference assembled from the same helper functions the
kernel uses.  That reference is only trusted after it reproduces the
kernel, and the fidelity check that has content is on a curved profile
where both sides are large -- agreeing at 1e-18 on the exact set would
also be satisfied by a harness that computed nothing.  Note that the
reference shares the helper functions, so the guards establish that the
three rules are load-bearing rather than independently verifying the
arithmetic; that verification comes from the centered identity and from
the Omega-versus-Polaris comparison.

Guard (b), a cell-local expansion point, must fire: two expansion points
mean the SpecVol0 and SpecVolDP terms no longer cancel.  Guard (d), the
anchor taken as the raw height difference, must fire where the two
columns' end pressures differ and must be flat with depth, which is what
distinguishes it from (b); at a sea-floor anchor that means a surface
pressure contrast reaching the bottom interface.

Guard (c), a layer-index lookup in place of the pressure lookup, is
recorded rather than required to fire.  It cannot fire on the exact set,
because the profile is a single line in pressure and every layer's
reconstruction is that same line, so writing it as a guard that must fire
would assert something false.  It is logged either way, and if it ever
does fire an answer-level check on the lookup has become available.  Until
then the lookup is pinned only by the property tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add PGRAD_SINGLE_PRECISION_TEST, which builds PGradTest against the
single-precision library.  Design section 3.7.5 predicts the exactness
gate should pass there: the scheme differences the integrand before
integrating it, so every quantity in the column scan is already a
horizontal contrast and no large quantity is formed anywhere.  Running
PressureGradCentered alongside, which does form and cancel quantities of
order a hundred metres, is what makes the result interpretable.

The gate is loosened from four epsilons to sixteen in single precision.
A build with fewer guard digits accumulates more round-off down the
column, so the same measurement in epsilons is expected to be somewhat
larger; the tolerance moves with that rather than the claim changing.

Skip the convergence rates and guards (b) to (d) in single precision.
Those measure truncation errors, and this build cannot resolve them: the
round-off floor of the tendency is about 3e-8 m/s2, which swamps the
quadratic profile's residual at 60 layers and leaves only a factor of ten
between guard (b)'s signal and the noise.  They are statements about the
discretization rather than about precision, so measuring them once in
double precision is enough.  The exactness gate and guard (a) run in
both, which is what design section 5.2 asks for.

Make the matched-pressure integrand's control a ratio of the quadratic
result to the linear one rather than a count of epsilons.  In single
precision the linear result rises with the round-off floor while the
quadratic one, being a truncation error, does not, so an absolute epsilon
count would fail for a reason that has nothing to do with the property
being tested.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Instrument Eos with a count of specific-volume evaluations, one per cell
per active layer per call, and assert against it in PGradTest.

Requirement 2.2 of the design bounds the number of equation-of-state
evaluations at about one per cell per layer per step, independent of the
reconstruction order, the stencil width and the quadrature.  Nothing else
in the test suite would notice that being violated: an evaluation inside
the quadrature loop would change run time without changing any answer, so
every accuracy gate would still pass.

The check is sharper than the requirement.  The pressure gradient
performs no equation-of-state evaluations at all -- the one per cell per
layer the requirement allows is paid once by AuxiliaryState, before the
tendency is computed, and the scheme works from the specific volume and
derivatives that call leaves behind.  So the count across a call to
computePressureGrad must be exactly zero, and it is asserted at
QuadraturePoints 1 through 4, the quadrature being where a call would
most naturally creep in.

A count of zero would also be produced by broken instrumentation, so the
test additionally confirms that one call to computeSpecVolAndDerivs
registers exactly one evaluation per cell per layer.

This is a counter comparison rather than a timing measurement, so it is
deterministic and suitable for continuous integration.  The wall-time
comparison against PressureGradCentered remains a separate, non-CI
measurement for the pull request description.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bring the PGrad dev and user guides up to what ships.

The user guide gains the three sub-options and their defaults, and says
plainly that QuadraturePoints is an accuracy setting only, since the
integrand is zero at every point on a resolved profile and no quadrature
rule can affect the exactness.  It records that unimplemented values
are rejected rather than falling back, that an unrecognized PressureGradType
aborts, and that no setting reduces the finite-volume scheme to the
centered one.

The dev guide gains four things the code alone does not make obvious: why
the per-edge column scan cannot live in the functor, that it anchors at
the sea floor and why, that each column's state is looked up by pressure
rather than by layer index -- with the warning that no answer-level test
can catch getting that wrong, so the property tests pinning it are not
optional -- and that the equation-of-state expansion is shared across each
edge, with what is and is not load-bearing about that.

Also correct the computePressureGrad call signature in the dev guide,
which described arguments the method has never taken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dispatch check in PGradTest asserted only that the tendency came out
finite, which is weak enough to pass on garbage: a specific volume
derivative array still holding the fill value its field was attached
with, 9.969e36, propagates through the arithmetic without overflowing a
double.

Require instead that the tendency be nonzero and no more than three
orders of magnitude larger than PressureGradCentered's on the same state.
The bound is one-sided on purpose.  The two schemes agree to second order
in the cross-edge pressure difference, which limits how much larger the
finite-volume answer can be but says nothing about how much smaller; on a
state whose profile is close to linear in pressure it is legitimately far
smaller, which is the whole point of the scheme.  A fill value misses the
upper bound by thirty orders, in either precision.

Also check the result of every PressureGrad::create in this test.  create
returns null if an instance of that name already exists, and
dereferencing that gives a segmentation fault with no message rather than
a diagnosable failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@xylar
xylar force-pushed the omega/add-finite-volume-horiz-press-grad-phase1 branch from 8e86c05 to 4d59331 Compare October 12, 2026 07:16
xylar and others added 2 commits October 12, 2026 02:34
Make computeReconSlopes and computeColumnScan public.  nvcc rejects an
extended host-device lambda whose enclosing function is private or
protected in its class, which broke the build on GPUs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Template buildEdgeEos on the array type, as findLayerForPress already
is.  PGradTest's host-side reference scan calls it with host arrays,
which are the same type as device arrays on CPUs but not on GPUs, so
PGradTest would not have compiled with nvcc.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@xylar

xylar commented Oct 12, 2026

Copy link
Copy Markdown
Author

Testing: aurora, intelgpu, mpich

CTests: 59 of 59 passed. omega_pr: 9 of 31 tasks passed, 22 with baseline differences.

Tested the merge of #493 (480626b4f6) into develop (55c3de8c15), with #600 merged in, as 6727c6d08b, against develop (15c92b1996) rather than the Polaris submodule, because Compare against current develop rather than the older commit Polaris pins, with #600 merged in. The baseline ran with Polaris b63579dee0, without the changes in Polaris 781fdcb08f that the PR needs.

Polaris omega_pr suite

  • Baseline workdir: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/baselines/aurora_intelgpu_polaris-d3eb5b0_omega-15c92b1/omega_pr
  • Baseline build: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/baselines/aurora_intelgpu_polaris-d3eb5b0_omega-15c92b1/build
  • PR build: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/pr493-480626b/aurora_intelgpu/build
  • PR workdir: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/pr493-480626b/aurora_intelgpu/omega_pr
  • Baseline Polaris: d3eb5b027e057e5cb6109e457510557795fc86dc (1.0.0-973-gd3eb5b027)
  • PR Polaris: f8af352ccd268d55b64c5a312972e47617c15dfa (1.0.0-1016-gf8af352cc)
  • Baseline Omega: 15c92b19962be3d48a93fd45f8c0f25bd0d0ae03 (bp_maint3.2-3410-g15c92b1996)
  • PR Omega: 6727c6d08b0d59d025fe478d24247f8c74fe6a0a (bp_maint3.2-3430-g6727c6d08b)
  • Machine: aurora
  • Compiler: intelgpu
  • Build type: Release
  • Log: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/pr493-480626b/aurora_intelgpu/omega_pr/polaris_omega_pr.o8922446
  • Result:
    • Diffs (22 of 31):
    - ocean/column/horiz_press_grad/salinity_gradient
      - temperature:        l1=3.268e-02  l2=2.870e-03  linf=5.196e-04
      - salinity:           l1=5.277e-03  l2=8.669e-04  linf=3.665e-04
      - PseudoThickness:    l1=1.437e-05  l2=1.022e-05  linf=7.966e-06
      - RefPseudoThickness: l1=1.437e-05  l2=1.022e-05  linf=7.966e-06
      - bottomDepth:        l1=2.842e-13  l2=2.050e-13  linf=1.705e-13
    - ocean/planar/baroclinic_channel/split_explicit/10km/decomp
      - normalVelocity:     l1=5.483e-03  l2=7.847e-05  linf=5.266e-06
      - temperature:        l1=3.090e-04  l2=6.527e-06  linf=3.520e-07
      - salinity:           l1=1.205e-10  l2=1.205e-12  linf=4.263e-14
    - ocean/planar/baroclinic_channel/split_explicit/10km/restart
      - normalVelocity:     l1=1.760e-03  l2=2.571e-05  linf=1.798e-06
      - temperature:        l1=7.944e-05  l2=1.656e-06  linf=9.526e-08
      - salinity:           l1=8.876e-11  l2=9.392e-13  linf=3.553e-14
    - ocean/planar/baroclinic_channel/split_explicit/10km/threads
      - normalVelocity:     l1=5.483e-03  l2=7.847e-05  linf=5.266e-06
      - temperature:        l1=3.090e-04  l2=6.527e-06  linf=3.520e-07
      - salinity:           l1=1.205e-10  l2=1.205e-12  linf=4.263e-14
    - ocean/planar/baroclinic_channel/rk4/10km/decomp
      - normalVelocity:     l1=1.751e-05  l2=3.396e-07  linf=3.875e-08
      - temperature:        l1=7.773e-08  l2=1.770e-09  linf=2.098e-10
      - salinity:           l1=6.328e-11  l2=9.948e-13  linf=4.974e-14
    - ocean/planar/baroclinic_channel/rk4/10km/restart
      - normalVelocity:     l1=1.301e-05  l2=1.988e-07  linf=1.732e-08
      - temperature:        l1=4.462e-08  l2=9.983e-10  linf=6.245e-11
      - salinity:           l1=4.275e-11  l2=7.301e-13  linf=4.263e-14
    - ocean/planar/baroclinic_channel/rk4/10km/threads
      - normalVelocity:     l1=1.751e-05  l2=3.396e-07  linf=3.875e-08
      - temperature:        l1=7.773e-08  l2=1.770e-09  linf=2.098e-10
      - salinity:           l1=6.328e-11  l2=9.948e-13  linf=4.974e-14
    - ocean/planar/barotropic_channel/short
      - layerThickness:     l1=1.371e-12  l2=1.832e-13  linf=5.684e-14
      - normalVelocity:     l1=1.754e-12  l2=1.439e-13  linf=2.863e-14
      - temperature:        l1=2.159e-13  l2=2.962e-14  linf=8.660e-15
    - ocean/planar/barotropic_gyre/munk/free-slip
      - layerThickness:     l1=9.604e-10  l2=2.956e-11  linf=9.095e-13
      - normalVelocity:     l1=2.581e-11  l2=1.779e-13  linf=5.495e-15
    - ocean/planar/manufactured_solution/convergence_both/default
      - layerThickness:     l1=1.808e-07  l2=5.660e-10  linf=6.025e-12
      - normalVelocity:     l1=3.787e-08  l2=6.849e-11  linf=4.818e-13
    - ocean/planar/manufactured_solution/convergence_both/del2
      - layerThickness:     l1=4.050e-10  l2=1.055e-11  linf=7.958e-13
      - normalVelocity:     l1=9.090e-11  l2=1.324e-12  linf=6.001e-14
    - ocean/planar/manufactured_solution/convergence_both/del4
      - layerThickness:     l1=1.772e-08  l2=1.116e-10  linf=2.615e-12
      - normalVelocity:     l1=1.096e-09  l2=7.928e-12  linf=1.957e-13
    - ocean/planar/overflow/linear/zstar/smoke_test_horiz_adv_order_2_del4
      - normalVelocity:     l1=1.214e-01  l2=3.131e-03  linf=3.502e-04
      - temperature:        l1=2.663e-02  l2=1.815e-03  linf=3.241e-04
      - salinity:           l1=1.930e-09  l2=7.292e-12  linf=9.237e-14
    - ocean/planar/overflow/linear/zstar/smoke_test_horiz_adv_order_3
      - temperature:        l1=1.104e-01  l2=6.676e-03  linf=6.707e-04
      - normalVelocity:     l1=3.594e-01  l2=7.407e-03  linf=3.151e-04
      - salinity:           l1=1.888e-09  l2=7.141e-12  linf=9.948e-14
    - ocean/planar/overflow/linear/zstar/smoke_test_horiz_adv_order_4
      - temperature:        l1=1.095e-01  l2=6.639e-03  linf=6.773e-04
      - normalVelocity:     l1=3.595e-01  l2=7.407e-03  linf=3.152e-04
      - salinity:           l1=1.833e-09  l2=6.976e-12  linf=1.137e-13
    - ocean/planar/overflow/nonlinear/pstar/smoke_test_horiz_adv_order_4_del4
      - normalVelocity:     l1=1.387e+01  l2=2.561e-01  linf=3.954e-02
      - temperature:        l1=4.214e+00  l2=2.491e-01  linf=3.280e-02
      - salinity:           l1=1.711e-09  l2=6.252e-12  linf=9.237e-14
    - ocean/planar/overflow/nonlinear/pstar/smoke_test_horiz_adv_order_4_del4_centered_pgf
      - no norms (missing file or variable)
    - ocean/planar/overflow/nonlinear/pstar/smoke_test_horiz_adv_order_3_nofct
      - temperature:        l1=4.661e+00  l2=2.152e-01  linf=1.770e-02
      - normalVelocity:     l1=1.314e+01  l2=1.463e-01  linf=9.264e-03
      - salinity:           l1=1.734e-09  l2=6.316e-12  linf=8.527e-14
    - ocean/planar/seamount/linear/exponential/zstar/short
      - no norms (missing file or variable)
    - ocean/planar/seamount/nonlinear/exponential/sigma/short
      - no norms (missing file or variable)
    - ocean/planar/seamount/nonlinear/linear_pressure/sigma/short
      - no norms (missing file or variable)
    - ocean/column/inertial
      - salinity:           l1=1.786e-09  l2=5.774e-11  linf=4.896e-12
      - temperature:        l1=7.600e-11  l2=3.656e-12  linf=5.649e-13
      - layerThickness:     l1=3.925e-12  l2=4.092e-13  linf=2.585e-13
      - normalVelocity:     l1=6.564e-11  l2=1.113e-12  linf=4.058e-14
Recent commits

Baseline Polaris:

d3eb5b027 Follow Aurora's compiler rename in Omega PR testing
07eddfe29 Merge #835 (update-to-mache-5.0.0) at 5294fc48e for testing Omega PRs on Aurora
fffaaa6db Follow Omega's intel compiler names in the PR template rows
d6af3fd42 Let init test a merge the requester made
6e84baff0 Leave a test branch alone when its manifest pins the same commits

PR Polaris:

f8af352cc Merge the Omega PR testing utility with #835 for testing Omega#493
781fdcb08 Merge branch 'seamount-for-hpg-testing' into fv-pgf-integration
2226b5159 Merge branch 'new-horiz-press-grad-tests' into fv-pgf-integration
ff7e1384a Document the centered-pressure-gradient overflow test
f34595393 Run the centered-pressure-gradient overflow test in omega_pr

Baseline Omega:

15c92b1996 Test merge of E3SM-Project/Omega#600 (e40578ac7b) for testing
55c3de8c15 Merge pull request #566: e3sm/master into omega/develop
9aba4a8032 Merge pull request #597 from xylar/omega/fix-real-literal-nvcc
ca1387035e Merge pull request #548 from xylar/omega/fix-analysis-units
31393ac215 Merge pull request #591 from brian-oneill/omega/fix-mpi-sumdd-and-tend-test

PR Omega:

6727c6d08b Test merge of E3SM-Project/Omega#600 (e40578ac7b) for testing
411eb56c24 Test merge of E3SM-Project/Omega#493 (480626b4f6) into develop (55c3de8c15)
55c3de8c15 Merge pull request #566: e3sm/master into omega/develop
9aba4a8032 Merge pull request #597 from xylar/omega/fix-real-literal-nvcc
ca1387035e Merge pull request #548 from xylar/omega/fix-analysis-units

CTest unit tests:

  • Machine: aurora
  • Compiler: intelgpu
  • Build type: Release
  • Build: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/pr493-480626b/aurora_intelgpu/build
  • Omega: 6727c6d08b0d59d025fe478d24247f8c74fe6a0a (bp_maint3.2-3430-g6727c6d08b)
  • Result: All tests passed
  • Log: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/pr493-480626b/aurora_intelgpu/build/ctests.log

Build warnings

No new warnings (233 in the PR build, 255 in the baseline build).

  • Baseline build log: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/baselines/aurora_intelgpu_polaris-d3eb5b0_omega-15c92b1/build/build_omega.log
  • PR build log: /lus/flare/projects/E3SM_Dec/xylar/polaris_1.1/aurora/omega_pr_test/pr493-480626b/aurora_intelgpu/build/build_omega.log

Notes

The baseline differences are expected: this PR makes the finite-volume pressure gradient the default, and the baseline is develop. Tasks that switch the pressure gradient off are bit-for-bit, and tasks with no horizontal density gradient (barotropic_*, manufactured_solution, ekman, inertial) differ only at round-off. The three seamount tasks and smoke_test_horiz_adv_order_4_del4_centered_pgf show no norms because the PR's Polaris branch renames or adds them, so the baseline has no task of the same name. polaris#675 also changes horiz_press_grad/salinity_gradient, so its differences are not from this PR alone.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@xylar

xylar commented Oct 12, 2026

Copy link
Copy Markdown
Author

Testing: pm-gpu, gnugpu, mpich

CTests: 59 of 59 passed. omega_pr: 8 of 31 tasks passed, 23 with baseline differences.

Tested the merge of #493 (480626b4f6) into develop (55c3de8c15), with #600 merged in, as 6727c6d08b, against develop (15c92b1996) rather than the Polaris submodule, because Compare against current develop rather than the older commit Polaris pins, with #600 merged in. The baseline ran with Polaris b63579dee0, without the changes in Polaris 781fdcb08f that the PR needs.

Polaris omega_pr suite

  • Baseline workdir: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/baselines/pm-gpu_gnugpu_polaris-fffaaa6_omega-15c92b1/omega_pr
  • Baseline build: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/baselines/pm-gpu_gnugpu_polaris-fffaaa6_omega-15c92b1/build
  • PR build: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/pr493-480626b/pm-gpu_gnugpu/build
  • PR workdir: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/pr493-480626b/pm-gpu_gnugpu/omega_pr
  • Baseline Polaris: fffaaa6db064351fbb71ed8ddc775213d6cc331a (1.0.0-962-gfffaaa6db)
  • PR Polaris: b26ae159759b47cce5200224dfe6cb2ac50b3190 (1.0.0-1005-gb26ae1597)
  • Baseline Omega: 15c92b19962be3d48a93fd45f8c0f25bd0d0ae03 (bp_maint3.2-3410-g15c92b1996)
  • PR Omega: 6727c6d08b0d59d025fe478d24247f8c74fe6a0a (bp_maint3.2-3430-g6727c6d08b)
  • Machine: pm-gpu
  • Compiler: gnugpu
  • Build type: Release
  • Log: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/pr493-480626b/pm-gpu_gnugpu/omega_pr/polaris_omega_pr.o59702806
  • Result:
    • Diffs (23 of 31):
    - ocean/column/horiz_press_grad/salinity_gradient
      - temperature:        l1=3.268e-02  l2=2.870e-03  linf=5.196e-04
      - salinity:           l1=5.277e-03  l2=8.669e-04  linf=3.665e-04
      - PseudoThickness:    l1=1.437e-05  l2=1.022e-05  linf=7.966e-06
      - RefPseudoThickness: l1=1.437e-05  l2=1.022e-05  linf=7.966e-06
      - bottomDepth:        l1=2.842e-13  l2=2.050e-13  linf=1.705e-13
    - ocean/planar/baroclinic_channel/split_explicit/10km/decomp
      - normalVelocity:     l1=5.483e-03  l2=7.847e-05  linf=5.266e-06
      - temperature:        l1=3.090e-04  l2=6.527e-06  linf=3.520e-07
      - salinity:           l1=1.203e-10  l2=1.208e-12  linf=4.263e-14
    - ocean/planar/baroclinic_channel/split_explicit/10km/restart
      - normalVelocity:     l1=1.760e-03  l2=2.571e-05  linf=1.798e-06
      - temperature:        l1=7.944e-05  l2=1.656e-06  linf=9.526e-08
      - salinity:           l1=8.883e-11  l2=9.406e-13  linf=3.553e-14
    - ocean/planar/baroclinic_channel/split_explicit/10km/threads
      - normalVelocity:     l1=5.483e-03  l2=7.847e-05  linf=5.266e-06
      - temperature:        l1=3.090e-04  l2=6.527e-06  linf=3.520e-07
      - salinity:           l1=1.203e-10  l2=1.208e-12  linf=4.263e-14
    - ocean/planar/baroclinic_channel/rk4/10km/decomp
      - normalVelocity:     l1=1.751e-05  l2=3.396e-07  linf=3.875e-08
      - temperature:        l1=7.773e-08  l2=1.770e-09  linf=2.099e-10
      - salinity:           l1=6.352e-11  l2=9.918e-13  linf=4.974e-14
    - ocean/planar/baroclinic_channel/rk4/10km/restart
      - normalVelocity:     l1=1.301e-05  l2=1.988e-07  linf=1.732e-08
      - temperature:        l1=4.462e-08  l2=9.983e-10  linf=6.245e-11
      - salinity:           l1=4.249e-11  l2=7.241e-13  linf=3.553e-14
    - ocean/planar/baroclinic_channel/rk4/10km/threads
      - normalVelocity:     l1=1.751e-05  l2=3.396e-07  linf=3.875e-08
      - temperature:        l1=7.773e-08  l2=1.770e-09  linf=2.099e-10
      - salinity:           l1=6.352e-11  l2=9.918e-13  linf=4.974e-14
    - ocean/planar/barotropic_channel/short
      - layerThickness:     l1=1.457e-12  l2=1.921e-13  linf=5.684e-14
      - normalVelocity:     l1=1.398e-12  l2=1.168e-13  linf=3.214e-14
      - temperature:        l1=2.075e-13  l2=2.777e-14  linf=9.215e-15
    - ocean/planar/barotropic_gyre/munk/free-slip
      - layerThickness:     l1=9.604e-10  l2=2.956e-11  linf=9.095e-13
      - normalVelocity:     l1=2.581e-11  l2=1.779e-13  linf=5.495e-15
    - ocean/planar/manufactured_solution/convergence_both/default
      - layerThickness:     l1=1.805e-07  l2=5.661e-10  linf=6.366e-12
      - normalVelocity:     l1=3.797e-08  l2=6.872e-11  linf=4.544e-13
    - ocean/planar/manufactured_solution/convergence_both/del2
      - layerThickness:     l1=1.210e-09  l2=1.620e-11  linf=7.958e-13
      - normalVelocity:     l1=8.935e-11  l2=1.298e-12  linf=5.995e-14
    - ocean/planar/manufactured_solution/convergence_both/del4
      - layerThickness:     l1=5.144e-09  l2=6.463e-11  linf=2.501e-12
      - normalVelocity:     l1=1.089e-09  l2=7.885e-12  linf=1.983e-13
    - ocean/planar/overflow/linear/zstar/smoke_test_horiz_adv_order_2_del4
      - normalVelocity:     l1=1.211e-01  l2=3.131e-03  linf=3.502e-04
      - temperature:        l1=2.663e-02  l2=1.815e-03  linf=3.241e-04
      - salinity:           l1=1.935e-09  l2=7.334e-12  linf=1.137e-13
    - ocean/planar/overflow/linear/zstar/smoke_test_horiz_adv_order_3
      - temperature:        l1=1.104e-01  l2=6.676e-03  linf=6.707e-04
      - normalVelocity:     l1=3.594e-01  l2=7.407e-03  linf=3.151e-04
      - salinity:           l1=1.887e-09  l2=7.113e-12  linf=9.948e-14
    - ocean/planar/overflow/linear/zstar/smoke_test_horiz_adv_order_4
      - temperature:        l1=1.095e-01  l2=6.639e-03  linf=6.773e-04
      - normalVelocity:     l1=3.595e-01  l2=7.407e-03  linf=3.152e-04
      - salinity:           l1=1.844e-09  l2=6.998e-12  linf=9.948e-14
    - ocean/planar/overflow/nonlinear/pstar/smoke_test_horiz_adv_order_4_del4
      - normalVelocity:     l1=1.379e+01  l2=2.482e-01  linf=4.774e-02
      - temperature:        l1=4.272e+00  l2=2.547e-01  linf=4.022e-02
      - salinity:           l1=1.706e-09  l2=6.230e-12  linf=8.527e-14
    - ocean/planar/overflow/nonlinear/pstar/smoke_test_horiz_adv_order_4_del4_centered_pgf
      - no norms (missing file or variable)
    - ocean/planar/overflow/nonlinear/pstar/smoke_test_horiz_adv_order_3_nofct
      - temperature:        l1=4.661e+00  l2=2.152e-01  linf=1.770e-02
      - normalVelocity:     l1=1.314e+01  l2=1.463e-01  linf=9.264e-03
      - salinity:           l1=1.736e-09  l2=6.323e-12  linf=8.527e-14
    - ocean/planar/seamount/linear/exponential/zstar/short
      - no norms (missing file or variable)
    - ocean/planar/seamount/nonlinear/exponential/sigma/short
      - no norms (missing file or variable)
    - ocean/planar/seamount/nonlinear/linear_pressure/sigma/short
      - no norms (missing file or variable)
    - ocean/column/ekman
      - normalVelocity:     l1=2.700e-13  l2=4.796e-15  linf=1.804e-16
    - ocean/column/inertial
      - salinity:           l1=1.009e-09  l2=3.285e-11  linf=3.858e-12
      - temperature:        l1=5.069e-11  l2=2.238e-12  linf=4.334e-13
      - layerThickness:     l1=3.662e-12  l2=4.834e-13  linf=3.144e-13
      - normalVelocity:     l1=7.330e-11  l2=1.264e-12  linf=5.765e-14
Recent commits

Baseline Polaris:

fffaaa6db Follow Omega's intel compiler names in the PR template rows
d6af3fd42 Let init test a merge the requester made
6e84baff0 Leave a test branch alone when its manifest pins the same commits
c93412d65 Count a repeated warning as new only in files the PR changes
8cb406b55 Make one agent testing over ssh the default for Omega PR testing

PR Polaris:

b26ae1597 Merge the Omega PR testing utility for testing Omega#493
781fdcb08 Merge branch 'seamount-for-hpg-testing' into fv-pgf-integration
2226b5159 Merge branch 'new-horiz-press-grad-tests' into fv-pgf-integration
ff7e1384a Document the centered-pressure-gradient overflow test
f34595393 Run the centered-pressure-gradient overflow test in omega_pr

Baseline Omega:

15c92b1996 Test merge of E3SM-Project/Omega#600 (e40578ac7b) for testing
55c3de8c15 Merge pull request #566: e3sm/master into omega/develop
9aba4a8032 Merge pull request #597 from xylar/omega/fix-real-literal-nvcc
ca1387035e Merge pull request #548 from xylar/omega/fix-analysis-units
31393ac215 Merge pull request #591 from brian-oneill/omega/fix-mpi-sumdd-and-tend-test

PR Omega:

6727c6d08b Test merge of E3SM-Project/Omega#600 (e40578ac7b) for testing
411eb56c24 Test merge of E3SM-Project/Omega#493 (480626b4f6) into develop (55c3de8c15)
55c3de8c15 Merge pull request #566: e3sm/master into omega/develop
9aba4a8032 Merge pull request #597 from xylar/omega/fix-real-literal-nvcc
ca1387035e Merge pull request #548 from xylar/omega/fix-analysis-units

CTest unit tests:

  • Machine: pm-gpu
  • Compiler: gnugpu
  • Build type: Release
  • Build: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/pr493-480626b/pm-gpu_gnugpu/build
  • Omega: 6727c6d08b0d59d025fe478d24247f8c74fe6a0a (bp_maint3.2-3430-g6727c6d08b)
  • Result: All tests passed
  • Log: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/pr493-480626b/pm-gpu_gnugpu/build/ctests.log

Build warnings

No new warnings (842 in the PR build, 842 in the baseline build).

  • Baseline build log: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/baselines/pm-gpu_gnugpu_polaris-fffaaa6_omega-15c92b1/build/build_omega.log
  • PR build log: /pscratch/sd/x/xylar/polaris_1.1/omega_pr_test/pr493-480626b/pm-gpu_gnugpu/build/build_omega.log

Notes

The baseline differences are expected: this PR makes the finite-volume pressure gradient the default, and the baseline is develop. Tasks that switch the pressure gradient off are bit-for-bit, and tasks with no horizontal density gradient (barotropic_*, manufactured_solution, ekman, inertial) differ only at round-off. The three seamount tasks and smoke_test_horiz_adv_order_4_del4_centered_pgf show no norms because the PR's Polaris branch renames or adds them, so the baseline has no task of the same name. polaris#675 also changes horiz_press_grad/salinity_gradient, so its differences are not from this PR alone.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@xylar

xylar commented Oct 12, 2026

Copy link
Copy Markdown
Author

Testing: evidence for making FiniteVolume the default

This PR now makes FiniteVolume the default pressure gradient. In the configuration Omega will run in E3SM (p-star, TEOS-10, no ice shelf), it removes the grid-scale bottom-layer kinetic energy that Centered produces across the open ocean in a realistic global run. In idealized tests, it matches a separate Python implementation to round-off and converges at second order where Centered does not. The weaknesses of the second-order scheme show up on tilted coordinates: on a tilted p-star coordinate, the error at rest is still linear in the tilt, and on sigma the seamount overturns within a few days. Both point to higher order.

These come from Polaris realistic_global, horiz_press_grad, seamount and overflow tasks (polaris#675, polaris#681 and a centered-PGF overflow task, merged as fv-pgf-integration), run on Chrysalis with Intel. None of them has an ice shelf.

Realistic global

I repeated the 10-day realistic_global/EC30to60E2r2/analysis_members_test comparison from my earlier comment with the merge being tested here, which also has KPP on from develop. All other settings are the task's defaults. The result is the same as before.

bottom-layer KE, day 5

Bottom-layer KE (m² s⁻²):

scheme area mean, day 1 day 5 day 10 99th percentile, day 10
Centered 1.30e-2 8.01e-3 7.44e-3 9.10e-2
FV 4.60e-4 4.86e-4 4.52e-4 7.88e-3

Day 1 and 10 maps, daily statistics and the plotting script are on the portal. The pressure-gradient tendency cost 9.0 s with FV and 1.0 s with Centered, out of under 3 minutes per run on 6 nodes.

p-star, TEOS-10

The horiz_press_grad tasks are two-column tests of the pressure-gradient acceleration over a single step; overflow and the realistic runs integrate in time.

test Centered FV
horiz_press_grad, all 8 tasks: Omega vs Python FV kernel RMS ≤ 1.6e-13 m/s²
ztilde_gradient (tilted coordinate), convergence 4 km → 0.5 km slope 1.07 slope 1.97
surface_pressure_gradient, convergence slope 2.00 slope 2.00, error 2.2x smaller
bathymetry_step (resting, stepped sea floor up to 200 m/km), max RMS acceleration 2.3e-5 m/s² 1.9e-8 m/s² (≥ 1170x smaller)
bathymetry_step_linear (T, S linear in pressure) ≥ 58,800x smaller
overflow/nonlinear/pstar/smoke_test_horiz_adv_order_4_del4, 12 min runs runs; differs from Centered by 0.055 m/s (max |u| ≈ 1.4)

In temperature_gradient and salinity_gradient, the error is the same for both schemes (slopes 1.63 and 1.80). realistic_global/QU.240km (TEOS-10, FV by default) also runs and restarts on every machine below.

Limits of second order

Tilted p-star coordinate. In hydrostatic_consistency (resting state, tilts 0.05 to 50 m/km), the error of both schemes grows linearly with tilt (exponent 0.96 to 1.00 with TEOS-10). FV is only 2.6x to 4.8x smaller than Centered (7.7x to 150x when T and S are linear in pressure). FV lowers the constant, not the order.

Sigma. Sigma, like the coordinate under an ice shelf, is far from p-star. FV overturns in all four sigma seamount configurations, while Centered stays stable. At 1 h, FV is still 1.6x to 10x better than Centered, so the problem grows over time. On z-star, by contrast, FV is 17x to 7e7x better with a linear EOS, and with TEOS-10 it stays stable where Centered overturns.

seamount (6 days), max |u| (m/s) Centered FV
linear / exponential / sigma 1.1e-2 0.54 (overturns day 1.6)
linear / linear_pressure / sigma 3.4e-5 0.67 (day 4.0)
TEOS-10 / exponential / sigma 1.35e-2 1.42 (day 1.4)
TEOS-10 / linear_pressure / sigma 4.1e-3 0.94 (day 2.3)

omega_pr suite

On Chrysalis (intel), pm-cpu (gnu), pm-gpu (gnugpu), Aurora (intel, intelgpu) and Frontier (craygnu, craygnu-mphipcc), all 31 tasks run to completion against develop with #600, and all 59 CTests pass, with no new build warnings. Tasks that switch the pressure gradient off (vmix_*, thermo, merry_go_round, cosine_bell) are bit-for-bit. Tasks with no horizontal density gradient differ only at round-off, if at all (barotropic_*, manufactured_solution, ekman, inertial, ≤ 1e-11). The baroclinic and overflow tasks differ as expected for a new scheme. The per-machine reports are in the comments above.

Where the runs are

Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@xylar

xylar commented Oct 12, 2026

Copy link
Copy Markdown
Author

@sbrus89 and @hyungyukang, this needs to be reviewed in concert with:
E3SM-Project/polaris#675
E3SM-Project/polaris#681
E3SM-Project/polaris#847
I'll mark both of you as reviewers on those as well.

@xylar xylar added the Omega label Oct 21, 2026
Comment thread components/omega/src/ocn/Eos.cpp Outdated
Comment thread components/omega/src/ocn/PGrad.cpp Outdated

// Pass one: the two integrals of the matched-pressure integrand over each
// edge layer, from the same quadrature points.
parallelFor(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to use hierarchical parallelism here and only compute over active layers?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 88bba35. computeReconSlopes and the integrals now use parallelForOuter with parallelForInner over the active layers, and the integrals share the column scan's per-edge team with a teamBarrier, so the two passes are one kernel. On Chrysalis (intel), CTests pass and the horiz_press_grad, seamount and overflow tasks are bit-for-bit with 480626b, and the FV pressure-gradient time dropped by about a third. pm-gpu testing will follow.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

Comment thread components/omega/src/ocn/PGrad.cpp Outdated
Comment on lines +346 to +349
const I4 KTop = LocMinLayerEdgeBot(IEdge);
const I4 KBot = LocMaxLayerEdgeTop(IEdge);
if (K < KTop || K > KBot)
return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inactive layers are being skipped here, but it may be better to use hierarchical parallelism instead.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, done in 88bba35 (see above).


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

xylar and others added 2 commits October 23, 2026 15:18
The counter only saw host-side calls to the Eos compute methods, which
the pressure gradient cannot make through its const Eos pointer. An
evaluation inside the quadrature loop would be a device-side call the
counter never sees, so the cost check could not catch the regression it
was written for. Remove the counter, the check and the references to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Launch the reconstruction slopes and the matched-pressure integrals as
team loops over each column's active layers, as the other pressure
gradient kernels do, instead of flat loops over every layer that skip
the inactive ones. The integrals now share the column scan's per-edge
team, with a barrier before the scan, so the two passes run as one
kernel. Nothing reads these arrays outside the active layers, so the
inactive entries no longer need zeroing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@xylar

xylar commented Oct 23, 2026

Copy link
Copy Markdown
Author

@sbrus89, good call! I'm retesting with the fixes on Chrysalis and pm-gpu.

@xylar

xylar commented Oct 23, 2026

Copy link
Copy Markdown
Author

Testing: chrysalis, intel, openmpi

CTests on 88bba35: 59 of 59 pass. The 8 horiz_press_grad tasks, the 8 seamount default tasks (shortened to 24 hours) and the pstar overflow FV smoke test pass and are bit-for-bit with 480626b, built the same way. The FV pressure-gradient time dropped by about a third (seamount 12.8 s to 8.1 s, overflow 0.47 s to 0.30 s). Polaris fv-pgf-integration; work directories:

/lcrc/group/e3sm/ac.xylar/polaris_1.2/chrysalis/test_20261023/pr493-teams
/lcrc/group/e3sm/ac.xylar/polaris_1.2/chrysalis/test_20261023/pr493-teams-seamount
/lcrc/group/e3sm/ac.xylar/polaris_1.2/chrysalis/test_20261023/pr493-teams-base
/lcrc/group/e3sm/ac.xylar/polaris_1.2/chrysalis/test_20261023/pr493-teams-base-seamount

Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@vanroekel vanroekel added this to the 1st E3SM Merge milestone Oct 24, 2026
@xylar

xylar commented Oct 24, 2026

Copy link
Copy Markdown
Author

Testing: pm-gpu, gnugpu, mpich

CTests on 88bba35: 59 of 59 pass, including PGRAD_TEST and PGRAD_SINGLE_PRECISION_TEST. On pm-gpu, computeReconSlopes and the fused integrals and column scan now run as GPU teams, and they give the same answers as before. I ran 18 Polaris tasks in builds of 88bba35 and 480626b made the same way, all from Polaris fv-pgf-integration (781fdcb08):

  • the 8 horiz_press_grad tasks
  • the 8 seamount default tasks, shortened to 24 hours
  • the pstar overflow smoke test
  • realistic_global/QU.240km/restart

All 18 pass in both builds, and all 740 output NetCDF files are bit-for-bit identical between them. Omega and the Python reference agree to 5.4e-15 m/s2 (FV) and 9.5e-14 m/s2 (centered), unchanged.

The FV pressure-gradient time (Tend:pressureGradTerm, fenced, mean of 3 interleaved runs each) dropped by 11-12% on the larger cases:

run 88bba35 480626b
overflow, 80 steps 0.0159 s 0.0178 s
QU240, 10 days (240 steps) 0.129 s 0.147 s
seamount, 24 h (8 runs, per run) 0.046 s 0.047 s

The seamount mesh is too small on a GPU for the kernels to matter much. Total run-loop times are the same within noise.

The PR branch predates the fix for #599, so a plain standalone pm-gpu build links omega.exe without GTL and every run aborts at MPI_Init. I built both commits with -DMPILIB=mpich, which CIME builds pass and which the fix now sets. Merging develop would remove the need for it. Work directories:

/pscratch/sd/x/xylar/polaris_1.2/pm-cpu/test_20261023/pr493_pm-gpu/polaris_88bba35313
/pscratch/sd/x/xylar/polaris_1.2/pm-cpu/test_20261023/pr493_pm-gpu/polaris_480626b4f6
/pscratch/sd/x/xylar/polaris_1.2/pm-cpu/test_20261023/pr493_pm-gpu/ctest_88bba35313
/pscratch/sd/x/xylar/polaris_1.2/pm-cpu/test_20261023/pr493_pm-gpu/timing

Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@xylar

xylar commented Oct 24, 2026

Copy link
Copy Markdown
Author

The PR branch predates the fix for #599, so a plain standalone pm-gpu build links omega.exe without GTL and every run aborts at MPI_Init. I built both commits with -DMPILIB=mpich, which CIME builds pass and which the fix now sets. Merging develop would remove the need for it.

It seems like the agent made a mistake and that merging with develop should be the standard practice. I think the instructions should be clear but we can see if this kind of failure happens again.

@xylar

xylar commented Oct 24, 2026

Copy link
Copy Markdown
Author

Testing on Aurora (with a fix) and Frontier now.

@xylar

xylar commented Oct 24, 2026 •

Copy link
Copy Markdown
Author

Testing: frontier, craygnu and craygnu-mphipcc, mpich

CTests on 966aa04 pass 62 of 62 with both compilers (develop at 23b13af has 62 CTests, not 59). In builds of 966aa04 and 5bce79b made the same way, all 18 tasks run to completion and are bit-for-bit between the two builds on both rows. 16 tasks pass. The other 2 fail in both builds because of a Polaris metadata bug (below).

I set up the new build again with the old work directory as its baseline. On both rows, all 362 steps with baseline variables pass, with zero norms. QU.240km/restart has no baseline variables, so I also compared the data in every NetCDF file with xarray (Dataset.equals): all 744 are equal on both rows.

Tend:pressureGradTerm from the first runs (one run each, mean per rank):

run craygnu new craygnu old craygnu-mphipcc new craygnu-mphipcc old
overflow (14 calls) 27.2 ms 34.5 ms 2.68 ms 3.07 ms
QU240 full_run (6 calls) 13.0 ms 15.6 ms 2.52 ms 2.77 ms

Work directories (new_vs_old_* is the baseline comparison):

/lustre/orion/cli115/scratch/xylar/polaris_1.1/frontier/test_pr493_20261024/runs/new_craygnu
/lustre/orion/cli115/scratch/xylar/polaris_1.1/frontier/test_pr493_20261024/runs/old_craygnu
/lustre/orion/cli115/scratch/xylar/polaris_1.1/frontier/test_pr493_20261024/runs/new_vs_old_craygnu
/lustre/orion/cli115/scratch/xylar/polaris_1.1/frontier/test_pr493_20261024/runs/new_craygnu-mphipcc
/lustre/orion/cli115/scratch/xylar/polaris_1.1/frontier/test_pr493_20261024/runs/old_craygnu-mphipcc
/lustre/orion/cli115/scratch/xylar/polaris_1.1/frontier/test_pr493_20261024/runs/new_vs_old_craygnu-mphipcc
/lustre/orion/cli115/scratch/xylar/polaris_1.1/frontier/test_pr493_20261024/ctest/build_omega

Unrelated failure

seamount/linear/linear_pressure/{sigma,zstar}/default fail the CF check on init/init.nc: Salinity: (3.1): Invalid units: PSU. The linear-in-pressure profile from E3SM-Project/polaris#681 still writes PSU for practical salinity (polaris/tasks/ocean/seamount/init_utils.py), which E3SM-Project/polaris#811 replaced with 1 on main. Every step of both tasks runs successfully, and their baseline comparisons pass.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@xylar

xylar commented Oct 24, 2026

Copy link
Copy Markdown
Author

Testing: aurora, intel and intelgpu, mpich

CTests on 966aa04 (#493 at 88bba35 merged into develop 23b13af) pass 62 of 62 with both compilers. I ran the same 18 tasks as on pm-gpu, with seamount shortened to 24 hours, in builds of 966aa04 and 5bce79b (#493 at 480626b merged into the same develop), then ran 966aa04 again with the 5bce79b runs as the Polaris baseline. On both rows, all 17 tasks with baseline variables pass the baseline comparison, and all 743 NetCDF output files have the same data in both builds (QU240 restart has no baseline variables, so only the data check covers it).

Tend:pressureGradTerm, mean per rank, one run each:

run intel 966aa04 intel 5bce79b intelgpu 966aa04 intelgpu 5bce79b
overflow (14 calls) 19.9 ms 24.0 ms 1.67 ms 2.38 ms
QU240 full_run (6 calls) 10.9 ms 13.4 ms 1.78 ms 2.46 ms
Work directories
/lus/flare/projects/E3SM_Dec/xylar/scratch/pr493_aurora_899/new_intel
/lus/flare/projects/E3SM_Dec/xylar/scratch/pr493_aurora_899/old_intel
/lus/flare/projects/E3SM_Dec/xylar/scratch/pr493_aurora_899/new_vs_old_intel
/lus/flare/projects/E3SM_Dec/xylar/scratch/pr493_aurora_899/new_intelgpu
/lus/flare/projects/E3SM_Dec/xylar/scratch/pr493_aurora_899/old_intelgpu
/lus/flare/projects/E3SM_Dec/xylar/scratch/pr493_aurora_899/new_vs_old_intelgpu
/lus/flare/projects/E3SM_Dec/xylar/scratch/pr493_aurora_899/ctest/build_omega

Unrelated failure

seamount/linear/linear_pressure/{sigma,zstar}/default fail only the CF check on init/init.nc (Salinity: (3.1): Invalid units: PSU) in all four builds. The linear-in-pressure profile from E3SM-Project/polaris#681 still writes PSU, which E3SM-Project/polaris#811 replaced with 1 on main.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

@xylar

xylar commented Oct 25, 2026

Copy link
Copy Markdown
Author

How FiniteVolume differs from Centered, on one edge

These figures come from a Python model of one edge that mirrors PGrad.cpp and matches the Polaris reference to round-off. The two columns always hold identical water at rest, so the right answer is zero.

what each scheme compares

Centered compares layer k with layer k, which sit at different pressures when layers tilt. FiniteVolume compares the two columns at the same pressure.

reconstruction at matched pressure

At each pressure, each column supplies its own linear reconstruction of T and S. For water linear in pressure the two agree exactly and there is nothing to integrate. For curved water they differ slightly, and that difference is the error. A parabolic reconstruction, planned for fourth order, would make quadratic water exact too.

error vs layer thickness

With cells 10 km apart and interfaces offset by up to 150 m, FiniteVolume is at round-off for uniform or linear water at every layer thickness. For curved water it is 3 to 800 times below Centered.

sloping floor

The exception is terrain-following layers over a sloping floor. The column scan starts from the sea floor and integrates the first-order equation of state across the whole bottom-pressure difference, so FiniteVolume's advantage is gone by about 100 m/km. p-star with partial bottom cells keeps that difference within the bottom cell, and there Polaris bathymetry_step shows FiniteVolume at least 1000 times better.

Scripts: https://web.lcrc.anl.gov/public/e3sm/diagnostic_output/xasaydavis/omega_pr493_fv_pgf_illustrations_20261025/scripts/


Posted by Claude Code on @xylar's behalf. The figures, analysis and wording above are AI-authored; please check them accordingly.

xylar and others added 2 commits October 25, 2026 05:53
The pressure gradient is exact for linear-in-pressure water only where
the two columns' sea floors are at the same pressure. Over a sloping
floor the sea-floor anchor leaves a residual that grows with the cube
of the floor pressure difference, and on terrain-following layers it
approaches the centered scheme's error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@xylar

xylar commented Oct 25, 2026

Copy link
Copy Markdown
Author

Sigma seamount instability: not the sea-floor anchor

I tested whether the sea-floor anchor error in the last figure above causes the sigma seamount instability noted in the description. It does not. An anchor that avoids that error starts the linear-in-pressure sigma run about 700 times lower in kinetic energy, but the same mode then grows at the same rate, about 2.6 per day, to the same state. Also turning off extrapolation below a column's floor makes things worse. z-star stays clean throughout, and the monotonic tracer limiter was already on.

sigma seamount bottom variants

The anchor error itself is fixable by anchoring at the deeper column's interface just below the shallower floor, so the anchor never spans more than one layer. In the Python model that is bit-for-bit identical to the current anchor on flat floors and on p-star with partial bottom cells, and removes the error on sigma. The anchor fix and the instability are follow-ups. If either is unsolved when this PR and the Polaris tests merge, we'll open issues for them.


Posted by Claude Code on @xylar's behalf. The testing, analysis and wording above are AI-authored; please check them accordingly.

The functor's inputs now match the design's signature, so drop the claim
that they differ and keep only the reason. The sea-floor anchor is chosen
for exactness, not conditioning, as design section 3.7.4 now says.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

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

Labels

enhancement New feature or request Omega

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants