Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bc1749a
Add TEOS-10 first derivatives of specific volume
xylar Jul 29, 2026
d4aa9d8
Add specific volume derivatives for linear and constant EOS
xylar Jul 29, 2026
c06c17f
Add Eos::computeSpecVolAndDerivs
xylar Jul 29, 2026
992b65e
Add unit tests for TEOS-10 specific volume derivatives
xylar Jul 29, 2026
256c8b0
Compute Brunt-Vaisala coefficients from the specific volume derivatives
xylar Jul 29, 2026
222301e
Document specific volume derivatives in the EOS guides
xylar Jul 29, 2026
0558a14
Distinguish point-wise and chunk-wise derivative calls by name
xylar Aug 2, 2026
6ef8ceb
Explain when the specific volume derivatives are computed
xylar Aug 2, 2026
4e19a01
Say why alpha and beta cannot reuse the stored derivatives
xylar Aug 2, 2026
b1a9569
Distinguish the two GSW-C based derivative tests
xylar Aug 2, 2026
c508b3a
Correct which calls need the specific volume derivatives
xylar Aug 2, 2026
346efda
Check cross-type reads of non-distributed variables in IOTest
xylar Aug 2, 2026
27ee6ee
Convert data types when reading non-distributed variables
xylar Aug 2, 2026
6f58564
Add a single-precision VertCoord test
xylar Aug 2, 2026
f6aa79a
Rename the pressure gradient type and functor to FiniteVolume
xylar Aug 1, 2026
d6f9827
Add the FiniteVolume pressure gradient configuration options
xylar Aug 1, 2026
1516d71
Widen the pressure gradient interfaces for the FiniteVolume scheme
xylar Aug 1, 2026
9cd9c31
Add a unit test for the centered pressure gradient identity
xylar Aug 1, 2026
d56f85d
Abort on an unrecognized PressureGradType
xylar Aug 1, 2026
1d79dda
Add the mean-preserving linear reconstruction in pressure
xylar Aug 1, 2026
af02536
Add a unit test for the reconstruction estimator
xylar Aug 1, 2026
9abc2e8
Add the edge-shared EOS expansion and the per-column pressure lookup
xylar Aug 1, 2026
f4a7803
Add the matched-pressure integrand
xylar Aug 1, 2026
df21f95
Add the column scan, its sea-floor anchor and the recurrence
xylar Aug 1, 2026
8f478ce
Assemble the FiniteVolume pressure gradient tendency
xylar Aug 1, 2026
f7665de
Add the exactness gate and its guards
xylar Aug 1, 2026
6cd10f1
Run the exactness gate in single precision
xylar Aug 1, 2026
6e2302e
Add the equation-of-state cost check
xylar Aug 1, 2026
e4b377c
Document the FiniteVolume pressure gradient
xylar Aug 1, 2026
d9cc591
Harden the FiniteVolume dispatch check
xylar Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions components/omega/configs/Default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ Omega:
TracersToRestore: [Temperature, Salinity]
PistonVelocity: 1.585e-5
PressureGrad:
# Centered | FiniteVolume
PressureGradType: Centered
# The remaining options apply to the FiniteVolume scheme only
# 2 = two-cell stencil (Phase 1); 4 = wide stencil (Phase 2)
HorzOrder: 2
# 'linear' (Phase 1) | 'ppm' (Phase 2)
VerticalReconstruction: linear
# quadrature points per edge layer; an accuracy setting only
QuadraturePoints: 2
Tendencies:
ThicknessFluxTendencyEnable: true
PVTendencyEnable: true
Expand Down
91 changes: 91 additions & 0 deletions components/omega/doc/devGuide/EOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,97 @@ volume arrays, do
Eos.computeBruntVaisalaFreqSq(ConservTemp, AbsSalinity, Pressure, SpecVol);
```

## First derivatives of specific volume

The `Eos` class can also compute the first derivatives of the specific volume
with respect to conservative temperature, absolute salinity, and pressure,
together with the specific volume itself:

```c++
Eos.computeSpecVolAndDerivs(ConservTemp, AbsSalinity, Pressure);
```

`Pressure` is the relative pressure (gauge pressure in Pa) as elsewhere in
`Eos`, and the derivatives are returned per `degC`, per `(g/kg)`, and per `Pa`
respectively. Note the pressure derivative is per Pascal, not per decibar.

The results are stored in the `SpecVolDCt`, `SpecVolDSa` and `SpecVolDP`
members alongside `SpecVol`, and all three are registered as fields in the
`Eos` group so they can be written to a stream. Because `SpecVol` is computed
here as well, `computeSpecVolAndDerivs` replaces a call to `computeSpecVol`
rather than accompanying one; calling both would evaluate the equation of state
twice. The valid range of the derivative fields spans the full range of `Real`
rather than starting at zero, since the salinity derivative is negative
everywhere and the temperature derivative is negative in cold, nearly fresh
water.

The two methods are kept separate rather than always computing the derivatives
because the derivatives roughly double the TEOS-10 arithmetic per cell and
layer, and not every call needs them. `AuxiliaryState::computeMomVertAux` is
the only place that calls `computeSpecVol`; everything else consumes the
`Eos::SpecVol` array rather than recomputing it, including
`computeBruntVaisalaFreqSq`, which takes the specific volume as an argument.
That one call site is reached once per time stepper stage through
`computeMomAux` and `computeAll` in the tendency calculation, and once more
from `VertMix::VertMixImplicit`, which refreshes the pressure and specific
volume before the vertical mixing coefficients are formed.

A run using the higher-order pressure gradient therefore needs the derivatives
at every time step, and at those call sites `computeSpecVolAndDerivs` takes the
place of the `computeSpecVol` call that would otherwise be made, leaving one
evaluation of the equation of state where there was one before. Two things
still call for the plain `computeSpecVol`. First, `PressureGradType` is a
runtime option that defaults to `Centered`, so a run may never need the
derivatives at all. Second, even with the higher-order pressure gradient
selected, the `VertMix::VertMixImplicit` update feeds only
`computeGeomZHeight` and `computeBruntVaisalaFreqSq`, neither of which reads
the derivatives, so computing them there would be work that nothing consumes.
Which method to call is thus a decision for each call site, not one the `Eos`
class should make for it.

There is no displaced counterpart to `computeSpecVolAndDerivs`. The pressure
gradient needs the derivatives at the in-situ pressure of the layer, whereas
`computeSpecVolDisp` exists to evaluate the specific volume at the pressure of
a displaced layer. Nothing about the derivatives prevents an adiabatically
displaced version: it would take the same `KDisp` argument as
`computeSpecVolDisp` and evaluate the same coefficients at the displaced
pressure, with no new polynomial. It is left out here only because no caller
needs it yet, and it would mean carrying three more model-sized arrays and
fields.

All four values come from a single pass over the equation of state. For
`EosType::Teos10Eos` the derivatives are the analytic derivatives of the same
75-term polynomial used for the specific volume, evaluated at the same
normalized state, so no second call to the equation of state is made. The
pressure derivative reuses the pressure coefficients already assembled for the
specific volume; the temperature and salinity derivatives need coefficient sets
of their own but share the normalization and the square root. For
`EosType::LinearEos` the derivatives are `-DRhoDT` and `-DRhoDS` times the
square of the specific volume, with no pressure dependence, and for
`EosType::ConstantEos` all three vanish.

The thermal expansion and haline contraction coefficients used by the
`BruntVaisalaFreqSq` calculation are formed from these same derivatives,
`alpha = SpecVolDCt / SpecVol` and `beta = -SpecVolDSa / SpecVol`, so the
polynomial coefficients exist in only one place.

### A note on GSW-C

The GSW toolbox may be redistributed only without modification, so the
derivative routines in GSW-C are not ported or adapted here; they also could
not be called from a Kokkos device kernel. The implementation instead
differentiates the published Roquet et al. 2015 polynomial that `Teos10Eos`
already carries. GSW-C is used unmodified, through its public API, as an
independent check in the unit test.

That test compares against `gsw_specvol_first_derivatives` over a range of
states and finds agreement of order `1e-14` for the temperature and salinity
derivatives. The pressure derivative agrees only to about `2e-12`, and the
difference is on the GSW-C side: its `v_P` is evaluated from coefficients that
have been pre-multiplied by their pressure exponents and rounded, whereas the
Omega implementation differentiates the full-precision coefficients and matches
the exact derivative to roughly `1e-16`.

## Helper functions for conversion

The TEOS-10 implementation includes helper functions for temperature
Expand Down
19 changes: 15 additions & 4 deletions components/omega/doc/devGuide/IO.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,30 @@ IO::writeArray(&Array, Size, &FillValue, FileID, DecompID, VarID, Frame);
For arrays or scalars that are not distributed, the non-distributed variable
interface must be used:
```c++
Error Err = IO::readNDVar(&Array, VariableName, FileID, VarID);
Error Err = IO::readNDVar(&Array, VarType, VariableName, FileID, VarID);
IO::writeNDVar(&Array, FileID, VarID);
```
with arguments similar to the distributed array calls above. Note that
when defining dimensions for these fields, the dimensions must be
with arguments similar to the distributed array calls above. The read
requires a VarType argument giving the data type of the destination array
(``IO::IOTypeI4``, ``IOTypeI8``, ``IOTypeR4`` or ``IOTypeR8``). This need
not be the type the variable has in the file and the values are converted
on read. Supplying it is required rather than optional: without it the
underlying SCORPIO call fills the destination using the type stored in the
file, so reading a double variable into a single-precision array would
write eight bytes per element into four-byte slots and run off the end of
the array. Distributed reads get the same information from the
decomposition, so ``readArray`` needs no equivalent argument.

Note that when defining dimensions for these fields, the dimensions must be
non-distributed. For scalars, the number of dimensions should be zero.
Multiple time slices can be also be read/written for non-distributed fields,
but require two additional arguments. As in the distributed array, the
Frame (index of the time slice) must be provided. In addition, a vector
``std::vector<int> DimLengths`` containing the length of the non-time
dimensions must be provided:
```c++
Error Err = IO::readNDVar(&Array, VariableName, FileID, VarID, Frame, DimLengths);
Error Err = IO::readNDVar(&Array, VarType, VariableName, FileID, VarID, Frame,
DimLengths);
IO::writeNDVar(&Array, FileID, VarID, Frame, DimLengths);
```
Note that the full arrays in this case are written so if any masking or
Expand Down
Loading
Loading