Skip to content
Merged
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
59b1666
Cleanup branch starting with new commit.
overfelt Nov 9, 2025
aec0440
Rename flag used to trigger higher order advection.
overfelt Nov 18, 2025
9f07fd9
Rename flag used to trigger higher order advection.
overfelt Nov 18, 2025
fc80b1b
Rename flag used to trigger higher order advection.
overfelt Nov 18, 2025
b73ed2e
Rename flag used to trigger higher order advection.
overfelt Nov 18, 2025
2da7e1d
Clang format.
overfelt Nov 18, 2025
9bd0099
Clang format with newer version.
overfelt Nov 18, 2025
84caf50
Add documentation for the higher order horizontal flux calculation.
overfelt Dec 2, 2025
de4fcf1
Remove whitespace the linter disapproves of.
overfelt Dec 2, 2025
01659de
Fix off-by-one array size in unit test.
overfelt Dec 9, 2025
58781e7
Fix a couple of signed/unsigned int comparison compiler warnings.
overfelt Dec 9, 2025
24358e6
Fix Kokkos View host/device incompatibility errors.
overfelt Dec 9, 2025
530f89f
Fix up use of VCoord after rebase from develop.
overfelt Dec 9, 2025
1312bac
Clang format
overfelt Dec 9, 2025
f4081c2
Clang Format.
overfelt Jan 3, 2026
c9870a5
Fix compiler errors after merge from develop.
overfelt Jan 3, 2026
afe0652
Forgot to zero out tendecies before summing into values.
overfelt Jan 16, 2026
27517c9
Fix for parallel: NCellsOwned -> NCellsAll/
overfelt Jan 27, 2026
565206d
Fix conservation issue in high order advection
mwarusz Feb 10, 2026
ad8f892
Clang format.
overfelt Feb 19, 2026
a776bc0
Fix for unit test. Use actual earth radius: REarth.
overfelt Feb 20, 2026
8b39eb8
Fix setting of TracerHorzAdvTendencyEnable flag based on code review.
overfelt Feb 20, 2026
8f19d72
Clang format.
overfelt Feb 21, 2026
9c1830b
Change parameter name from TracerHorzAdvTendencyOrder to HorzTracerFl…
overfelt Feb 24, 2026
06fab60
Changing the variable names to PascalCase to follow Omega’s naming co…
overfelt Feb 25, 2026
6a361f2
Add ForceLowOrder option to high-order tracer advection class
mwarusz Feb 25, 2026
faba875
Use high-order tracer adv class in tendency terms test
mwarusz Feb 25, 2026
4b869b7
Only use high-order tracer adv in tendencies
mwarusz Feb 25, 2026
8f99187
Rip out old tracer advection
mwarusz Feb 25, 2026
f75d949
Rename: TracerHighOrderHorzAdv -> TracerHorzAdv
mwarusz Feb 25, 2026
595bca4
Move some options to the advection section
mwarusz Feb 25, 2026
a5bd1cf
Fix size of HighOrderFlxHorz
mwarusz Feb 25, 2026
39e88c7
Merge pull request #2 from mwarusz/mwarusz/omega/high-order-adv-for-j…
overfelt Feb 26, 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
3 changes: 2 additions & 1 deletion components/omega/configs/Default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Omega:
NTimeLevels: 2
Advection:
FluxThicknessType: Center
FluxTracerType: Center
HorzTracerFluxOrder: 2
Coef3rdOrder: 0.25
WindStress:
InterpType: Isotropic
VertCoord:
Expand Down
25 changes: 17 additions & 8 deletions components/omega/doc/design/Tendencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,23 @@ retrieval.
```c++
class Tendencies{
public:
// Arrays for accumulating tendencies
Array2DReal LayerThicknessTend;
Array2DReal NormalVelocityTend;
Array3DReal TracerTend;
// Instances of tendency terms
ThicknessFluxDivOnCell ThicknessFluxDiv;
PotentialVortHAdvOnEdge PotientialVortHAdv;
KEGradOnEdge KEGrad;
SSHGradOnEdge SSHGrad;
VelocityDiffusionOnEdge VelocityDiffusion;
VelocityHyperDiffOnEdge VelocityHyperDiff;
WindForcingOnEdge WindForcing;
BottomDragOnEdge BottomDrag;
TracerDiffOnCell TracerDiffusion;
TracerHyperDiffOnCell TracerHyperDiff;
TracerHorzAdvOnCell TracerHorzAdv;
TracerHighOrderHorzAdvOnCell TracerHighOrderHorzAdv;
private:
static Tendencies *DefaultTendencies;
static std::map<std::string, std::unique_ptr<Tendencies>> AllTendencies;
Expand Down Expand Up @@ -79,29 +88,29 @@ Tendencies* Tendencies::create(const std::string &Name, const HorzMesh *Mesh, in
#### 4.2.2 Initialization
The init method will create the default tendencies and return an error code:
```c++
int Tendencies::init();
static void Tendencies::init();
```

#### 4.2.3 Retrieval
There will be methods for getting the default and non-default tendencies instances:
```c++
Tendencies *Tendencies::getDefault();
Tendencies *Tendencies::get(const std::string &Name);
static Tendencies *Tendencies::getDefault();
static Tendencies *Tendencies::get(const std::string &Name);
```

#### 4.2.4 Computation
The 'computeAll' method will compute and accumulate all layer thickness and normal velocity tendencies using the ocean
state and auxiliary state from a given time level:
```c++
void Tendencies::computeAllTendencies(const OceanState *State, const AuxilaryState *AuxState, int TimeLevel);
void Tendencies::computeAllTendencies(const OceanState *State, const AuxilaryState *AuxState, const Array3DReal &TracerArray, int TimeLevel, int VelTimeLevel, TimeInstant Time);
```
The layer thickness tendencies will be computed with a method:
```c++
void Tendencies::computeThicknessTendencies(const OceanState *State, const AuxilaryState *AuxState, int TimeLevel);
void Tendencies::computeThicknessTendencies(const OceanState *State, const AuxilaryState *AuxState, int TimeLevel, int VelTimeLevel, TimeInstant Time);
```
The normal velocity tendencies will be computed with a method:
```c++
void Tendencies::computeVelocityTendencies(const OceanState *State, const AuxilaryState *AuxState, int TimeLevel);
void Tendencies::computeVelocityTendencies(const OceanState *State, const AuxilaryState *AuxState, int TimeLevel, int VelTimeLevel, TimeInstant Time);
```


Expand All @@ -111,8 +120,8 @@ when they are no longer in scope. The erase method
will remove a named tendencies instance, whereas the clear method will remove all of
them. Both will call the destructor in the process.
```c++
void Tendencies::erase(const std::string &Name);
void Tendencies::clear();
static void Tendencies::erase(const std::string &Name);
static void Tendencies::clear();
```

## 5 Verification and Testing
Expand Down
52 changes: 30 additions & 22 deletions components/omega/doc/design/Tendency.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,25 @@ The class contains private member variables for any constant data that are defin
Each tendency term will have a `bool` variable which can be set to enable/disable the computation of the tendency.

```c++
class ThicknessFluxDivergenceOnCell {
class ThicknessFluxDivOnCell {
public:

bool enabled = false;

ThicknessFluxDivergenceOnCell(const HorzMesh *Mesh, Config *Options);
ThicknessFluxDivOnCell(const HorzMesh *Mesh, Config *Options);

KOKKOS_FUNCTION void operator()(Array2DReal &Tend
int ICell,
int KChunk,
const Array2DReal &ThicknessFlux);
const Array2DReal &ThicknessFlux,
const Array2DReal &NormalVelEdge);

private:
Array1DI4 NEdgesOnCell;
Array2DI4 EdgesOnCell;
Array1DR8 DvEdge;
Array1DR8 AreaCell;
Array2DR8 EdgeSignOnCell;
Array1DReal DvEdge;
Array1DReal AreaCell;
Array2DReal EdgeSignOnCell;
};
```

Expand All @@ -66,31 +67,38 @@ class ThicknessFluxDivergenceOnCell {
Tendency functor constructors are responsible for initializing the private member variables:

```c++
ThicknessFluxDivergenceOnCell::ThicknessFluxDivergenceOnCell(HorzMesh const *Mesh, Config *Options)
ThicknessFluxDivOnCell::ThicknessFluxDivOnCell(HorzMesh const *Mesh)
: NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell),
DvEdge(Mesh->DvEdge), AreaCell(Mesh->AreaCell),
EdgeSignOnCell(Mesh->EdgeSignOnCell) {

enabled = Options->get("ThicknessFluxTendencyEnable")
}
EdgeSignOnCell(Mesh->EdgeSignOnCell) { }
```

#### 4.2.2 operator
The operator method implements the tendency computation for a chunk of vertical layers at a given horizontal mesh location.
The inner loop over a chunk of vertical layers enables CPU vectorization.

```c++
KOKKOS_FUNCTION void ThicknessFluxDivergenceOnCell::operator()(Array2DReal &Tend
int ICell,
int KChunk,
const Array2DReal &ThicknessFlux) const {
const Real InvAreaCell = 1. / AreaCell(iCell);
for (int J = 0; J < NEdgesOnCell(ICell); ++J) {
const int JEdge = EdgesOnCell(ICell, J);
for (int K = KChunk * VecLength; K < (KChunk + 1) * VecLength; ++K) {
Tend(ICell, K) -= DvEdge(JEdge) * EdgeSignOnCell(ICell, J) * ThicknessFlux(JEdge, K) * InvAreaCell;
}
}
KOKKOS_FUNCTION void ThicknessFluxDivOnCell::operator()(Array2DReal &Tend
int ICell,
int KChunk,
const Array2DReal &ThicknessFlux,
const Array2DReal &NormalVelEdge) const {
const I4 KStart = KChunk * VecLength;
const Real InvAreaCell = 1._Real / AreaCell(ICell);
Real DivTmp[VecLength] = {0};
for (int J = 0; J < NEdgesOnCell(ICell); ++J) {
const I4 JEdge = EdgesOnCell(ICell, J);
for (int KVec = 0; KVec < VecLength; ++KVec) {
const I4 K = KStart + KVec;
DivTmp[KVec] -= DvEdge(JEdge) * EdgeSignOnCell(ICell, J) *
ThicknessFlux(JEdge, K) * NormalVelEdge(JEdge, K) *
InvAreaCell;
}
}
for (int KVec = 0; KVec < VecLength; ++KVec) {
const I4 K = KStart + KVec;
Tend(ICell, K) -= DivTmp[KVec];
}
}
```

Expand Down
5 changes: 3 additions & 2 deletions components/omega/doc/devGuide/TendencyTerms.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ implemented:
- `SSHGradOnEdge`
- `VelocityDiffusionOnEdge`
- `VelocityHyperDiffOnEdge`
- `WindForcingOnEdge`
- `BottomDragOnEdge`
- `TracerHorzAdvOnCell`
- `TracerHighOrderHorzAdvOnCell`
- `TracerDiffOnCell`
- `TracerHyperDiffOnCell`
- `WindForcingOnEdge`
- `BottomDragOnEdge`
86 changes: 86 additions & 0 deletions components/omega/doc/userGuide/TendencyTerms.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tendency terms are currently implemented:
| VelocityDiffusionOnEdge | Laplacian horizontal mixing, defined on edges
| VelocityHyperDiffOnEdge | biharmonic horizontal mixing, defined on edges
| TracerHorzAdvOnCell | horizontal advection of thickness-weighted tracers
| TracerHighOrderHorzAdvOnCell | second order horizontal advection of thickness-weighted tracers
| TracerDiffOnCell | horizontal diffusion of thickness-weighted tracers
| TracerHyperDiffOnCell | biharmonic horizontal mixing of thickness-weighted tracers
| WindForcingOnEdge | forcing by wind stress, defined on edges
Expand Down Expand Up @@ -43,10 +44,95 @@ the currently available tendency terms:
| | ViscDel4 | horizontal biharmonic mixing coefficient for normal velocity
| | DivFactor | scale factor for the divergence term
| TracerHorzAdvOnCell | TracerHorzAdvTendencyEnable | enable/disable term
| | HorzTracerFluxOrder | 1 for standard linear advection
| TracerHighOrderHorzAdvOnCell | TracerHorzAdvTendencyEnable | enable/disable term
| | HorzTracerFluxOrder | 2 for second order advection algorithm
| TracerDiffOnCell | TracerDiffTendencyEnable | enable/disable term
| | EddyDiff2 | horizontal diffusion coefficient
| TracerHyperDiffOnCell | TracerHyperDiffTendencyEnable | enable/disable term
| | EddyDiff4 | biharmonic horizontal mixing coeffienct for tracers
| WindForcingOnEdge | WindForcingTendencyEnable | enable/disable term
| BottomDragOnEdge | BottomDragTendencyEnable | enable/disable term
| | BottomDragCoeff | bottom drag coefficient


## Second Order Horizontal Advection Algorithm

The horizontal advection is done independently within each ocean layer
so a two dimensional scheme is required to advect mixing ratios.
The second order horizontal advection scheme is described in the paper

William C. Skamarock and Almut Gassmann,
"Conservative Transport Schemes for Spherical Geodesic Grids: High-Order Flux Operators for ODE-Based Time Integration"
Monthly Weather Review, Vol 139: Issue 9, pp 2962–2975, 2011. doi.org/10.1175/MWR-D-10-05056.1

and only a summary of a few equations from that paper are reproduced here.

The conservative form of the scalar transport equation
within a fluid is given in equation (1) of Skamarock and Gassmann,
$$
\frac{\partial(\rho\psi)}{\partial t} = -\nabla\cdot\mathbf{V}\rho\psi
$$
where $\rho$ is the fluid density, $\psi$ is the mixing ratio, and $\mathbf{V}$ is
the fluid velocity. This is simplified by assuming that $\rho$ is constant and cancels out.
The advection scheme for a finite volume method involves approximating this equation along an element
edge at a certain time step and multiplying by the time step length to get an approximate scalar transport
from element to element during the time step.
The left hand side of this equation is a time derivative and for Omega this is handled by standard Runge—Kutta methods and
not discussed here but can be found in the Skamarock and Gassmann paper.
The right hand side of this equation is the spatial derivative that needs to be evaluated by the Omega horizontal advection scheme.
Omega ocean uses unstructured Voronoi meshes and a
finite volume scheme with $\psi$ defined on cell centers. A first order approximation
to the spatial derivative of the left hand termacross a single edge of an element would be the difference of $\psi$ at
element centroids divided by the distance from cell centroid to cell centroid. This is expressed in equation (5) of the paper.
For edge $i$ between elements $i+1/2$ and $i-1/2$,
$$
\frac{\partial(u\psi_i)}{\partial x} = \frac{1}{\Delta x}[F_{i+1/2}(u\psi) - F_{i-1/2}(u\psi)] + O(\Delta x^2).
$$
Where $u$ is $\mathbf{V}\cdot\mathbf{n}$ where $\mathbf{n}$ is the unit normal along the edge being evaluated and $F$ is the evaluation
of $\psi$ for the elements on each side of the edge being evaluated.
This is used when the TracerHorzAdvOnCell user option is active and HorzTracerFluxOrder is 1.
To make the comparison to higher order methods explicit, this first order method is equivalent to defining $\psi$ as a linear function,
$\psi = c_0 + c_x x + c_y y$, between the two elements sharing the edge $i$ and taking the directed derivative along the edge.


For higher order flux calculations, higher order derivatives of $\psi$ are needed and for the user option of
TracerHorzAdvOnCell along with HorzTracerFluxOrder set to 2, a quadratic approximation of $\psi$ is used,
$$
\psi = c_0 + c_x x + c_y y + c_{xx} x^2 + c_{xy} xy + c_{yy} y^2,
$$
defined for each edge in the mesh. The six coefficients in this quadratic equation are calculated from
a least squares fit of $\psi$ in a neighborhood of elements around an edge. The
least squares fit is defined in equation (12) of the paper,
$$
\mathbf{f = (P^T P)^{-1}P^T s = Bs}
$$
where the vector $\mathbf{f} = [c_0,c_x,c_y,c_{xx},c_{xy},c_{yy}]$ and $\mathbf{P}$ is an $m\times 6$ matrix with each row,
$(1,x_i,y_i,x_i^2,x_iy_i,y_i^2)$, based on the cell center coordinates where there is one row for each of the $m$ elements in the neighborhood.
This results in a $6\times m$ matrix $\mathbf{B}$. The values of $\mathbf{s}$ are the mixing ratios,
$\mathbf{s}=[\psi_0, \psi_1,...,\psi_m]$, of the elements in the neighborhood at the time step being evaluated.
Note that $\mathbf{B}$ depends on the mesh geometry only, so only needs to
be computed once for each edge during initialization. Since only the second order terms are used to determine the
second order derivatives needed for the higher order flux corrections to the lower order linear flux given above
and the derivatives are taken along a line connecting
cell centers, the amount of data that needs to be stored is minimized and $\mathbf{B}$ reduces to a vector.

The second order derivatives of $\psi$ are then combined as shown in equation (11) of Skamarock and Gassmann to get
a second order horizontal transport algorithm.

The computation of these second order fluxes is complicated by having to do these computations on a sphere.
Figure 2 from the Skamarock and Gassmann paper describs the slight modifications needed to compute the entries
of the $\mathbf{P}$ matrix for a spherical geometry where the distances need to be measured as arc lengths along
the surface.

In practice, the convergence of this higher order algorithm on a sphere is slightly below
second order. For the advection of a $\psi$ in the shape of a cosine bell
being advected around a sphere and compared with the analytic solution, the spatial convergence for grid refinement
is about order 1.7 as shown in {numref}`tracer-higher-order-convergence`:

```{figure} images/higher_order_tracer_convergence_on_sphere.jpeg
:name: tracer-higher-order-convergence
:align: center
:width: 600 px
Tracer higer order convergence example of a cosine bell advected on a sphere showing an order 1.71 convergence rate
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 13 additions & 11 deletions components/omega/src/infra/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,7 @@ int Config::NumReadGroups = 0;
MPI_Comm Config::ConfigComm;
Config Config::ConfigAll;

//------------------------------------------------------------------------------
// Constructor that creates configuration with a given name and an emtpy
// YAML node that will be filled later with either a readAll or get call.
Config::Config(const std::string &InName // [in] name of config, node
) {

// If this is the first Config created, set variables for reading the
// input configuration file. In particular, to avoid too many MPI tasks
// reading the same input stream, we divide the tasks into groups and
// only one group at a time loads the full configuration from a stream

void Config::Initialize() {
if (NotInitialized) {
// Determine MPI variables
MachEnv *DefEnv = MachEnv::getDefault();
Expand All @@ -58,6 +48,18 @@ Config::Config(const std::string &InName // [in] name of config, node

NotInitialized = false; // now initialized for future calls
}
}
//------------------------------------------------------------------------------
// Constructor that creates configuration with a given name and an emtpy
// YAML node that will be filled later with either a readAll or get call.
Config::Config(const std::string &InName // [in] name of config, node
) {

// If this is the first Config created, set variables for reading the
// input configuration file. In particular, to avoid too many MPI tasks
// reading the same input stream, we divide the tasks into groups and
// only one group at a time loads the full configuration from a stream
Initialize();

// Set the name - this is also used as the name of the root YAML node
// (a map node) in this configuration.
Expand Down
2 changes: 2 additions & 0 deletions components/omega/src/infra/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class Config {
public:
// Methods

static void Initialize();

/// Constructor that creates a Configuration with a given name and an
/// empty YAML::Node. The node will be filled later with either a read
/// or get operation.
Expand Down
29 changes: 0 additions & 29 deletions components/omega/src/ocn/AuxiliaryState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,22 +222,6 @@ void AuxiliaryState::computeAll(const OceanState *State,

computeMomAux(State, ThickTimeLevel, VelTimeLevel);

Pacer::start("AuxState:edgeAuxState4", 2);
parallelForOuter(
"edgeAuxState4", {NTracers, Mesh->NEdgesAll},
KOKKOS_LAMBDA(int LTracer, int IEdge, const TeamMember &Team) {
const int KMin = MinLayerEdgeBot(IEdge);
const int KMax = MaxLayerEdgeTop(IEdge);
const int KRange = vertRangeChunked(KMin, KMax);
parallelForInner(
Team, KRange, INNER_LAMBDA(int KChunk) {
LocTracerAux.computeVarsOnEdge(LTracer, IEdge, KChunk,
NormalVelEdge, LayerThickCell,
TracerArray);
});
});
Pacer::stop("AuxState:edgeAuxState4", 2);

const auto &MeanLayerThickEdge = LayerThicknessAux.MeanLayerThickEdge;

Pacer::start("AuxState:cellAuxState4", 2);
Expand Down Expand Up @@ -353,19 +337,6 @@ void AuxiliaryState::readConfigOptions(Config *OmegaConfig) {
ABORT_ERROR("AuxiliaryState: Unknown FluxThicknessType requested");
}

std::string FluxTracerTypeStr;
Err += AdvectConfig.get("FluxTracerType", FluxTracerTypeStr);
CHECK_ERROR_ABORT(
Err, "AuxiliaryState: FluxTracerType not found in AdvectConfig");

if (FluxTracerTypeStr == "Center") {
this->TracerAux.TracersOnEdgeChoice = FluxTracerEdgeOption::Center;
} else if (FluxTracerTypeStr == "Upwind") {
this->TracerAux.TracersOnEdgeChoice = FluxTracerEdgeOption::Upwind;
} else {
ABORT_ERROR("AuxiliaryState: Unknown FluxTracerType requested");
}

Config WindStressConfig("WindStress");
Err += OmegaConfig->get(WindStressConfig);

Expand Down
4 changes: 2 additions & 2 deletions components/omega/src/ocn/CustomTendencyTerms.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
//
//===----------------------------------------------------------------------===//

#include "HorzMesh.h"
#include "TendencyTerms.h"
#include "AuxiliaryState.h"
#include "OceanState.h"
#include "TimeMgr.h"

namespace OMEGA {
Expand Down
Loading
Loading