Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
23dd90b
Fix biharmonic velocity mixing at boundary vertices
xylar Jul 2, 2026
fbaf741
Add a design document for fixing fill values
xylar Jun 3, 2026
985b8e6
Add fill value constant
xylar Jun 3, 2026
e96a067
Fill fields with fill values during attachData()
xylar Jun 3, 2026
0a7aec5
Update fill values in all fields
xylar Jun 3, 2026
f9f83d7
Determine fill values automatically by array type
xylar Jun 3, 2026
515b930
Drop manually assigned fill values
xylar Jun 3, 2026
f4013d0
Make detection of fill values in VertCoord reads less brittle
xylar Jun 3, 2026
775af6a
Add FillOnAttach argument to attachData()
xylar Jun 3, 2026
3d2c737
Reorder 2 cases so we attach first, then fill in the array
xylar Jun 3, 2026
4505f49
Fill fluxes on edges first with zeros over full physical range
xylar Jun 3, 2026
cc58ac2
Add fill-value CTests
xylar Jun 3, 2026
48e65e6
Update the docs
xylar Jun 3, 2026
02cf466
Enforce fill and zero values in NormalVelocity from IC
xylar Jun 5, 2026
7437516
Fix bounds on DivHU computation to avoid inactive layers
xylar Jun 6, 2026
56192c7
Mask NormalVelocity in time stepper
xylar Jun 6, 2026
25c9a32
Apply suggestions from code review
xylar Jun 22, 2026
3961f3d
Apply edge layer mask to NormalVelocity in OceanState constructor
cbegeman Jun 22, 2026
2052fa2
Create applyCellLayerMask, applyVertexLayerMask
cbegeman Jun 22, 2026
e145479
Use single method in OceanRun for applying fill values to OceanState …
cbegeman Jun 22, 2026
5e06ba3
Fixup rebase on vmix shear changes
cbegeman Jun 25, 2026
35bb0b0
Fixup rebase on surface stress changes
cbegeman Jun 26, 2026
0e9e234
Add unit tests for applyCellLayerMask and applyVertexLayerMask
xylar Jun 30, 2026
621e04f
Update FillValues design doc to match implementation
xylar Jun 30, 2026
b569502
Document VertCoord layer masking in dev and user guides
xylar Jun 30, 2026
bbbbfdc
Correct comment on where BottomGeomDepth gets read from
xylar Jun 3, 2026
5298860
Make VertCoord the owner of SurfacePressure
xylar Jul 2, 2026
e7db968
Test SurfacePressure as a VertCoord field
xylar Jul 2, 2026
3fc9579
Document SurfacePressure as a VertCoord field
xylar Jul 2, 2026
f0dc3aa
Add optional-read support for IO stream fields
xylar Jul 2, 2026
9cb78c2
Test optional-read handling in IOStream
xylar Jul 2, 2026
26d011a
Make SurfacePressure an optional read defaulting to zero
xylar Jul 2, 2026
5c8bb0e
Document optional-read fields and SurfacePressure default
xylar Jul 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
501 changes: 501 additions & 0 deletions components/omega/doc/design/FillValues.md

Large diffs are not rendered by default.

30 changes: 26 additions & 4 deletions components/omega/doc/devGuide/Field.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ Fields are created with standard metadata using
StdName, ///< [in] CF standard Name (string)
ValidMin, ///< [in] min valid field value (same type as
ValidMax, ///< [in] max valid field value field data)
FillValue, ///< [in] scalar used for undefined entries
NumDims, ///< [in] number of dimensions (int)
Dimensions, ///< [in] dim names (vector of strings)
TimeDependent ///< [in] (opt, default true) if time varying
Expand All @@ -49,9 +48,13 @@ not exist, an empty string can be provided. This is uncommon for most fields
since the CF conventions maintain a fairly complete list, but can be the case
for some intermediate calculations or unique analyses. If there is no
restriction on valid range, an appropriately large range should be provided for
the data type. Similarly, if a FillValue is not being used, a very unique
number should be supplied to prevent accidentally treating valid data as a
FillValue. The optional TimeDependent argument can be omitted and is assumed
the data type. The fill value is not specified when creating a field; it is
automatically deduced from the element type of the array passed to
`attachData()` and set to the corresponding standard constant from `FillValues.h`
(`FillValueI4`, `FillValueI8`, `FillValueR4`, or `FillValueR8`). These match
the NetCDF-C `NC_FILL_*` standard values recognized by analysis tools such as
ncview, Xarray, and NCO. The optional TimeDependent argument can be omitted
and is assumed
to be true by default. Fields with this attribute will be output with the
unlimited time dimension added. Time should not be added explicitly in the
dimension list since it will be added during I/O. Fields that do not change
Expand Down Expand Up @@ -113,6 +116,13 @@ is provided using the field name:
InDataArray // [in] Array with data to attach
);
```
When `attachData` is called, every element of the attached array is automatically
initialized to the field's declared fill value. This ensures that inactive entries
(e.g., ocean layers below `MaxLayerCell`) hold a well-defined NetCDF-standard
sentinel in output without any explicit initialization in module code. Subsequent
compute calls overwrite active entries; the fill value persists only where no valid
data is written.

Note that the data is assumed to reside in only one location so if a mirror
array exists (eg if replicated on host and device), a separate Field may be
needed. However, it is is better to define only one location and allow the
Expand Down Expand Up @@ -182,6 +192,18 @@ The first determines whether the unlimited time dimension should be added
during IO operations. The second determines whether any of the dimensions
are distributed across MPI tasks so that parallel IO is required.

A field can also be marked as an optional read, which controls what happens
when the field's variable is missing from an input file:
```c++
MyField->setOptionalRead(true);
bool IsOptionalRead = MyField->isOptionalRead();
```
Fields are required by default (`isOptionalRead()` returns `false`). When a
field is marked optional and its variable is absent from a read stream's file,
the [IOStream](#omega-dev-iostreams) read skips it, leaving the attached array
at its [fill value](#omega-design-fill-values) instead of failing. The owning
module is responsible for detecting that fill value and substituting a default.

The data and metadata stored in a field can be retrieved using several
functions. To retrieve a pointer to the full Field, use:
```c++
Expand Down
14 changes: 14 additions & 0 deletions components/omega/doc/devGuide/IOStreams.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ input stream is read using:
The returned error code typically means that a field in the stream could
not be found in the input file - most other errors abort immediately. The
calling routine is then responsible for deciding what action to take.

By default every field listed in a stream's contents must be present in the
input file or the read returns an error. A field can instead be marked as an
optional read using
```c++
FieldPtr->setOptionalRead(true);
```
When such a field's variable is missing from the file, the read logs an
informational message, leaves the field's attached array at the fill value set
by `attachData` (see [Fill Values](#omega-design-fill-values)), and continues
without contributing an error. The owning module is then responsible for
detecting that fill value after the read and substituting a default value. This
is how `VertCoord` allows `SurfacePressure` to be absent from the
initial-condition or restart file and default to zero.
The ReqMetadata argument is a variable of type Metadata (defined in Field but
essentially a ``std::map<std::string, std::any>`` for the name/value pair).
This variable should include the names of global metadata that are desired
Expand Down
10 changes: 9 additions & 1 deletion components/omega/doc/devGuide/OceanState.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ OceanState::create(const std::string &Name, ///< [in] Name for mesh
);
```
allocates the `NormalVelocity` and `PseudoThickness` arrays for a given number of time levels.
The current time level is then registered with the IO infrastructure.
The current time level of `NormalVelocity` and `PseudoThickness` are registered with the IO
infrastructure and added to the `State` and `Restart` field groups.
The `SurfacePressure` array is owned by [`VertCoord`](omega-dev-vert-coord) but is also added to
these two groups so it is written to restart files and read from the initial-condition and restart
files when present. Its read is optional, so it defaults to zero when absent (see the
[`VertCoord`](omega-dev-vert-coord) guide).

After initialization, the default state object can be retrieved via:
```
Expand Down Expand Up @@ -71,6 +76,9 @@ HostArray2DReal NormVelH = State->getNormalVelocityH(TimeLevel);
for the host arrays. These functions return the arrays directly and will abort
via `OMEGA_REQUIRE` if an invalid `TimeLevel` is provided.

The `SurfacePressure` array used as the top boundary condition for layer pressures is owned by
[`VertCoord`](omega-dev-vert-coord); see that guide for how it is accessed and initialized.

The time level convention is:
| time level | `TimeLevel` |
|------------|-------------|
Expand Down
18 changes: 12 additions & 6 deletions components/omega/doc/devGuide/Tracers.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,23 @@ static void defineAllTracers() {
"sea_water_potential_temperature", ///< [in] CF standard Name
-273.15, ///< [in] min valid field value
100.0, ///< [in] max valid field value
1.e33, ///< [in] value for undef entries
FillValueReal, ///< [in] value for undef entries
IndxTemp); ///< [out] (optional) static index

define("Salt", "Salinity", "psu", "sea_water_salinity", 0.0, 50.0, 1.e33,
IndxSalt);
define("Debug1", "Debug Tracer 1", "none", "none", 0.0, 100.0, 1.e33);
define("Debug2", "Debug Tracer 2", "none", "none", 0.0, 100.0, 1.e33);
define("Debug3", "Debug Tracer 3", "none", "none", 0.0, 100.0, 1.e33);
define("Salt", "Salinity", "psu", "sea_water_salinity", 0.0, 50.0,
FillValueReal, IndxSalt);
define("Debug1", "Debug Tracer 1", "none", "none", 0.0, 100.0, FillValueReal);
define("Debug2", "Debug Tracer 2", "none", "none", 0.0, 100.0, FillValueReal);
define("Debug3", "Debug Tracer 3", "none", "none", 0.0, 100.0, FillValueReal);
}
```

The `FillValueReal` constant is defined in `FillValues.h` and matches the
NetCDF-C standard fill value (`NC_FILL_DOUBLE` or `NC_FILL_FLOAT` depending on
the build precision). It is automatically in scope in `TracerDefs.inc` via
`Field.h`. New tracers should always use `FillValueReal` (or `FillValueI4` /
`FillValueI8` for integer fields) rather than hardcoded literals.

To add a new tracer, simply call the `define` function with the appropriate
arguments. Index argument is optional one that allows to access the tracer
data using the given tracer index variable.
Expand Down
49 changes: 49 additions & 0 deletions components/omega/doc/devGuide/VertCoord.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,33 @@ A list of member variables along with their types and dimension sizes is below:
| VertCoordMovementWeights | Real | NCellsSize, NVertLayers |
| RefPseudoThickness | Real | NCellsSize, NVertLayers |
| BottomGeomDepth | Real | NCellsSize |
| SurfacePressure | Real | NCellsSize |

### Surface pressure

`SurfacePressure` is the relative pressure (gauge pressure) at the top of the ocean column and
is the top boundary condition used by `computePressure`. It is owned by `VertCoord` and its host
mirror is `SurfacePressureH`.

Unlike the other `VertCoord` fields, whose data come from the mesh file (`InitVertCoord` group)
during construction, `SurfacePressure` is a prognostic quantity read from the initial-condition
or restart file. `defineFields()` therefore registers it and adds it to the `State` and `Restart`
field groups (creating those groups if `VertCoord` initializes before `OceanState`). Because
those streams are read later in `ocnInit`, the data are written directly into the attached device
array.

`SurfacePressure` is registered as an [optional read](omega-dev-iostreams)
(`Field::setOptionalRead(true)`), so it is not required to be present in the initial-condition or
restart file. When the variable is absent, the read is skipped and the array retains the fill
value written by `attachData`. After the read, `initSurfacePressure()` must be called: it detects
that leftover fill value on the owned cells and, if found, defaults the whole array to zero, then
exchanges the halo and copies the device array to the host mirror:
```c++
VertCoord::getDefault()->initSurfacePressure(Halo::getDefault());
```
Eventually `SurfacePressure` will be updated each timestep via the coupler as a weighted sum of
atmosphere, sea-ice, and land-ice pressure; that forcing update will live in a separate forcing
class that writes into this array.

### Removal

Expand Down Expand Up @@ -128,3 +155,25 @@ This `parallel_for` iterates over vertical chunks to facilitate vectorization on
The vector length on GPUs is set to 1 to maximize parallelism.
The `computeGeopotential` method uses hierarchical parallelism in a very similar way to `computeTargetThickness`, except that it doesn't require a column sum.
It has an outer `parallel_for` that splits horizontal cells into teams and an inner `parallel_for` that does vertical computations in chunks.

### Layer masking

Field arrays are auto-filled with `FillValueReal` when they are attached to a `Field` (see the [Fill Values design](../design/FillValues.md)).
Compute kernels only write to active layers, so inactive layers naturally retain the fill value.
After an initial-condition or restart read, however, state and tracer arrays come from the input file and may hold zeros (or arbitrary values) in inactive and boundary layers.
`VertCoord` provides a family of helper methods that enforce the correct layer pattern on such arrays.
All of them use the inclusive `[Min, Max]` active-layer convention consistent with `computeGeomZHeight`/`computePressure`, and use hierarchical parallelism (an outer `parallelForOuter` over the horizontal dimension and an inner `parallelForInner` over the vertical layers).

| Method | Element | Zones applied |
|--------|---------|---------------|
| `zeroEdgeField(Arr, NEdgesAll)` | edge | sets `[MinLayerEdgeTop, MaxLayerEdgeBot]` to 0; layers outside retain their fill value |
| `applyEdgeLayerMask(Arr, NEdgesAll)` | edge | three-zone: outside `[MinLayerEdgeTop, MaxLayerEdgeBot]` → `FillValueReal`; inside that but outside `[MinLayerEdgeBot, MaxLayerEdgeTop]` → 0; active layers unchanged |
| `applyCellLayerMask(Arr, NCellsAll)` | cell | two-zone: outside `[MinLayerCell, MaxLayerCell]` → `FillValueReal`; active layers unchanged |
| `applyVertexLayerMask(Arr, NVerticesAll)` | vertex | two-zone: outside `[MinLayerVertexTop, MaxLayerVertexBot]` → `FillValueReal`; active layers unchanged |

Cell fields have no boundary (zero) zone because a cell column is either active or inactive at a given layer, with no neighbor-dependent partial-activity range.
Vertex fields also have no zeroed boundary zone, but for a different reason: a boundary vertex with one or more active surrounding cells holds valid, generally non-zero data (for example, relative vorticity computed from its active surrounding edges over the full `[MinLayerVertexTop, MaxLayerVertexBot]` range), so the entire valid range is kept rather than zeroed.
This is unlike a flux-type edge field, where the normal flux through a face bordering land genuinely vanishes.
The flux-type edge fields handled by `zeroEdgeField` (for example `NormalVelocityTend` and `VelocityDel2Aux.Del2Edge`) are zeroed before being recomputed each time step so that boundary edges carry 0 rather than `FillValueReal`.
The `apply*LayerMask` methods are driven through `OceanState::applyLayerMasks`, which is called once from `ocnInit` after the IC/restart read: it applies the edge mask to `NormalVelocity` and the cell mask to `PseudoThickness`, `Temperature`, and `Salinity`.
`applyVertexLayerMask` has no production caller yet because no vertex-based state field is read from the IC/restart; it is provided for completeness and verified by the fill-value CTest.
1 change: 1 addition & 0 deletions components/omega/doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ design/Decomp
design/Driver
design/EOS
design/Error
design/FillValues
design/Halo
design/HorzMeshClass
design/Logging
Expand Down
13 changes: 11 additions & 2 deletions components/omega/doc/userGuide/OceanState.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

## Ocean State

The `OceanState` class provides a container for the non-tracer prognostic variables in Omega, namely `NormalVelocity` and `PseudoThickness`.
Upon creation of a `OceanState` instance, these variables are allocated and registered with the IO infrastructure.
The `OceanState` class provides a container for the non-tracer prognostic variables in Omega:
`NormalVelocity` and `PseudoThickness`.
Upon creation of an `OceanState` instance, these variables are allocated and registered with the
IO infrastructure as part of the `State` and `Restart` field groups, so they are read from the
initial-condition file and written to restart files.
The class contains a method to update the time levels for the state variables between timesteps.
This involves a halo update, time level index update, and updating the `IOFields` data references.

The relative pressure at the top of the ocean column, `SurfacePressure`, is owned by the
[vertical coordinate](omega-user-vert-coord) rather than the `OceanState`, since it is the top
boundary condition of the pressure field. It is still registered in the `State` and `Restart`
field groups, so it continues to be written to restart files and read from the initial-condition
and restart files when present; if it is absent, it defaults to zero.
18 changes: 12 additions & 6 deletions components/omega/doc/userGuide/Tracers.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,23 @@ static void defineAllTracers() {
"sea_water_potential_temperature", ///< [in] CF standard Name
-273.15, ///< [in] min valid field value
100.0, ///< [in] max valid field value
1.e33, ///< [in] value for undef entries
FillValueReal, ///< [in] value for undef entries
IndxTemp); ///< [out] (optional) static index

define("Salt", "Salinity", "psu", "sea_water_salinity", 0.0, 50.0, 1.e33,
IndxSalt);
define("Debug1", "Debug Tracer 1", "none", "none", 0.0, 100.0, 1.e33);
define("Debug2", "Debug Tracer 2", "none", "none", 0.0, 100.0, 1.e33);
define("Debug3", "Debug Tracer 3", "none", "none", 0.0, 100.0, 1.e33);
define("Salt", "Salinity", "psu", "sea_water_salinity", 0.0, 50.0,
FillValueReal, IndxSalt);
define("Debug1", "Debug Tracer 1", "none", "none", 0.0, 100.0, FillValueReal);
define("Debug2", "Debug Tracer 2", "none", "none", 0.0, 100.0, FillValueReal);
define("Debug3", "Debug Tracer 3", "none", "none", 0.0, 100.0, FillValueReal);
}
```

The `FillValueReal` constant is defined in `FillValues.h` and matches the
NetCDF-C standard fill value (`NC_FILL_DOUBLE` or `NC_FILL_FLOAT` depending on
the build precision). It is automatically in scope in `TracerDefs.inc` via
`Field.h`. New tracers should always use `FillValueReal` (or `FillValueI4` /
`FillValueI8` for integer fields) rather than hardcoded literals.

To add a new tracer, simply call the `define` function with the appropriate
arguments. Index argument is optional one that allows to access the tracer
data using the given tracer index variable.
Expand Down
Loading
Loading