From 3f1d8e31f27031919c4c2ec60a3eabd88c4ed7f7 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Tue, 10 Feb 2026 16:34:10 -0700 Subject: [PATCH 001/152] Use accessor functions to get state variables --- components/omega/src/ocn/Tendencies.cpp | 15 ++++++++------- components/omega/src/timeStepping/TimeStepper.cpp | 15 +++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 7cf8a7a401a8..512d0e87c23f 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -348,8 +348,6 @@ void Tendencies::computeVelocityTendenciesOnly( }); }); - const Array2DReal &NormalVelEdge = State->NormalVelocity[VelTimeLevel]; - // Compute potential vorticity horizontal advection const Array2DReal &FluxLayerThickEdge = AuxState->LayerThicknessAux.FluxLayerThickEdge; @@ -473,7 +471,7 @@ void Tendencies::computeVelocityTendenciesOnly( Pacer::start("Tend:bottomDrag", 2); parallelFor( {Mesh->NEdgesAll}, KOKKOS_LAMBDA(int IEdge) { - LocBottomDrag(LocNormalVelocityTend, IEdge, NormalVelEdge, KECell, + LocBottomDrag(LocNormalVelocityTend, IEdge, NormVelEdge, KECell, MeanLayerThickEdge); }); Pacer::stop("Tend:bottomDrag", 2); @@ -521,8 +519,9 @@ void Tendencies::computeTracerTendenciesOnly( }); // compute tracer horizotal advection - const Array2DReal &NormalVelEdge = State->NormalVelocity[VelTimeLevel]; - const Array3DReal &HTracersEdge = AuxState->TracerAux.HTracersEdge; + Array2DReal NormalVelEdge; + State->getNormalVelocity(NormalVelEdge, VelTimeLevel); + const Array3DReal &HTracersEdge = AuxState->TracerAux.HTracersEdge; if (LocTracerHorzAdv.Enabled) { Pacer::start("Tend:tracerHorzAdv", 2); parallelForOuter( @@ -651,9 +650,11 @@ void Tendencies::computeTracerTendencies( int VelTimeLevel, ///< [in] Time level TimeInstant Time ///< [in] Time ) { + Array2DReal LayerThickCell; + Array2DReal NormalVelEdge; + State->getLayerThickness(LayerThickCell, ThickTimeLevel); + State->getNormalVelocity(NormalVelEdge, VelTimeLevel); OMEGA_SCOPE(TracerAux, AuxState->TracerAux); - OMEGA_SCOPE(LayerThickCell, State->LayerThickness[ThickTimeLevel]); - OMEGA_SCOPE(NormalVelEdge, State->NormalVelocity[VelTimeLevel]); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); OMEGA_SCOPE(MinLayerEdgeBot, VCoord->MinLayerEdgeBot); diff --git a/components/omega/src/timeStepping/TimeStepper.cpp b/components/omega/src/timeStepping/TimeStepper.cpp index 4f7567f33266..c7e9b30ed3b6 100644 --- a/components/omega/src/timeStepping/TimeStepper.cpp +++ b/components/omega/src/timeStepping/TimeStepper.cpp @@ -477,8 +477,9 @@ void TimeStepper::updateTracersByTend(const Array3DReal &NextTracers, OceanState *State2, int TimeLevel2, TimeInterval Coeff) const { - const auto &LayerThick1 = State1->LayerThickness[TimeLevel1]; - const auto &LayerThick2 = State2->LayerThickness[TimeLevel2]; + Array2DReal LayerThick1, LayerThick2; + State1->getLayerThickness(LayerThick1, TimeLevel1); + State2->getLayerThickness(LayerThick2, TimeLevel2); OMEGA_SCOPE(TracerTend, Tend->TracerTend); const int NTracers = TracerTend.extent(0); @@ -511,8 +512,9 @@ void TimeStepper::weightTracers(const Array3DReal &NextTracers, const Array3DReal &CurTracers, OceanState *CurState, int TimeLevel1) const { - const Array2DReal &CurThickness = CurState->LayerThickness[TimeLevel1]; - const int NTracers = NextTracers.extent(0); + Array2DReal CurThickness; + CurState->getLayerThickness(CurThickness, TimeLevel1); + const int NTracers = NextTracers.extent(0); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); @@ -566,8 +568,9 @@ void TimeStepper::finalizeTracersUpdate(const Array3DReal &NextTracers, OceanState *State, int TimeLevel) const { - const Array2DReal &NextThick = State->LayerThickness[TimeLevel]; - const int NTracers = NextTracers.extent(0); + Array2DReal NextThick; + State->getLayerThickness(NextThick, TimeLevel); + const int NTracers = NextTracers.extent(0); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); From df74703a61ecb85efdbb9ed959e1c421f15aa49e Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Thu, 12 Feb 2026 13:37:54 -0700 Subject: [PATCH 002/152] Require valid time index in state getters --- components/omega/src/ocn/AuxiliaryState.cpp | 12 +-- components/omega/src/ocn/OceanState.cpp | 96 +++++-------------- components/omega/src/ocn/OceanState.h | 17 ++-- components/omega/src/ocn/Tendencies.cpp | 21 ++-- .../omega/src/timeStepping/TimeStepper.cpp | 33 ++----- .../omega/test/ocn/AuxiliaryStateTest.cpp | 6 +- components/omega/test/ocn/StateTest.cpp | 69 ++++++------- components/omega/test/ocn/TendenciesTest.cpp | 6 +- .../test/timeStepping/TimeStepperTest.cpp | 25 ++--- 9 files changed, 98 insertions(+), 187 deletions(-) diff --git a/components/omega/src/ocn/AuxiliaryState.cpp b/components/omega/src/ocn/AuxiliaryState.cpp index 9643fe264cd5..dedd070704cb 100644 --- a/components/omega/src/ocn/AuxiliaryState.cpp +++ b/components/omega/src/ocn/AuxiliaryState.cpp @@ -60,10 +60,8 @@ AuxiliaryState::~AuxiliaryState() { // Compute the auxiliary variables needed for momentum equation void AuxiliaryState::computeMomAux(const OceanState *State, int ThickTimeLevel, int VelTimeLevel) const { - Array2DReal LayerThickCell; - Array2DReal NormalVelEdge; - State->getLayerThickness(LayerThickCell, ThickTimeLevel); - State->getNormalVelocity(NormalVelEdge, VelTimeLevel); + Array2DReal LayerThickCell = State->getLayerThickness(ThickTimeLevel); + Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); OMEGA_SCOPE(LocKineticAux, KineticAux); OMEGA_SCOPE(LocLayerThicknessAux, LayerThicknessAux); @@ -209,10 +207,8 @@ void AuxiliaryState::computeMomAux(const OceanState *State, int ThickTimeLevel, void AuxiliaryState::computeAll(const OceanState *State, const Array3DReal &TracerArray, int ThickTimeLevel, int VelTimeLevel) const { - Array2DReal LayerThickCell; - Array2DReal NormalVelEdge; - State->getLayerThickness(LayerThickCell, ThickTimeLevel); - State->getNormalVelocity(NormalVelEdge, VelTimeLevel); + Array2DReal LayerThickCell = State->getLayerThickness(ThickTimeLevel); + Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); const int NTracers = TracerArray.extent_int(0); diff --git a/components/omega/src/ocn/OceanState.cpp b/components/omega/src/ocn/OceanState.cpp index fea924e84ca4..aef68a66ef51 100644 --- a/components/omega/src/ocn/OceanState.cpp +++ b/components/omega/src/ocn/OceanState.cpp @@ -10,6 +10,7 @@ #include "OceanState.h" #include "DataTypes.h" #include "Decomp.h" +#include "Error.h" #include "Field.h" #include "Halo.h" #include "Logging.h" @@ -234,8 +235,7 @@ void OceanState::defineFields() { FieldGroup::addFieldToGroup(LayerThicknessFldName, "Restart"); // Associate Field with data - I4 TimeIndex; - int Err = getTimeIndex(TimeIndex, 0); + const I4 TimeIndex = getTimeIndex(0); NormalVelocityField->attachData(NormalVelocity[TimeIndex]); LayerThicknessField->attachData(LayerThickness[TimeIndex]); @@ -244,103 +244,64 @@ void OceanState::defineFields() { //------------------------------------------------------------------------------ // Get layer thickness device array -I4 OceanState::getLayerThickness(Array2DReal &LayerThick, - const I4 TimeLevel) const { - I4 Err = 0; - I4 TimeIndex; - - Err = getTimeIndex(TimeIndex, TimeLevel); - LayerThick = LayerThickness[TimeIndex]; - - return Err; +Array2DReal OceanState::getLayerThickness(const I4 TimeLevel) const { + const I4 TimeIndex = getTimeIndex(TimeLevel); + return LayerThickness[TimeIndex]; } //------------------------------------------------------------------------------ // Get layer thickness host array -I4 OceanState::getLayerThicknessH(HostArray2DReal &LayerThickH, - const I4 TimeLevel) const { - I4 Err = 0; - I4 TimeIndex; - - Err = getTimeIndex(TimeIndex, TimeLevel); - LayerThickH = LayerThicknessH[TimeIndex]; - - return Err; +HostArray2DReal OceanState::getLayerThicknessH(const I4 TimeLevel) const { + const I4 TimeIndex = getTimeIndex(TimeLevel); + return LayerThicknessH[TimeIndex]; } //------------------------------------------------------------------------------ // Get normal velocity device array -I4 OceanState::getNormalVelocity(Array2DReal &NormVel, - const I4 TimeLevel) const { - - I4 Err = 0; - I4 TimeIndex; - - Err = getTimeIndex(TimeIndex, TimeLevel); - NormVel = NormalVelocity[TimeIndex]; - - return Err; +Array2DReal OceanState::getNormalVelocity(const I4 TimeLevel) const { + const I4 TimeIndex = getTimeIndex(TimeLevel); + return NormalVelocity[TimeIndex]; } //------------------------------------------------------------------------------ // Get normal velocity host array -I4 OceanState::getNormalVelocityH(HostArray2DReal &NormVelH, - const I4 TimeLevel) const { - I4 Err = 0; - I4 TimeIndex; - - Err = getTimeIndex(TimeIndex, TimeLevel); - NormVelH = NormalVelocityH[TimeIndex]; - - return Err; +HostArray2DReal OceanState::getNormalVelocityH(const I4 TimeLevel) const { + const I4 TimeIndex = getTimeIndex(TimeLevel); + return NormalVelocityH[TimeIndex]; } //------------------------------------------------------------------------------ // Perform copy to device for state variables // TimeLevel == [1:new, 0:current, -1:previous, -2:two times ago, ...] -I4 OceanState::copyToDevice(const I4 TimeLevel) { - - I4 Err; - I4 TimeIndex; +void OceanState::copyToDevice(const I4 TimeLevel) { - Err = getTimeIndex(TimeIndex, TimeLevel); + const I4 TimeIndex = getTimeIndex(TimeLevel); deepCopy(LayerThickness[TimeIndex], LayerThicknessH[TimeIndex]); deepCopy(NormalVelocity[TimeIndex], NormalVelocityH[TimeIndex]); - - return Err; } // end copyToDevice //------------------------------------------------------------------------------ // Perform copy to host for state variables // TimeLevel == [1: new, 0:current, -1:previous, -2:two times ago, ...] -I4 OceanState::copyToHost(const I4 TimeLevel) { - - I4 Err; - I4 TimeIndex; +void OceanState::copyToHost(const I4 TimeLevel) { - Err = getTimeIndex(TimeIndex, TimeLevel); + const I4 TimeIndex = getTimeIndex(TimeLevel); deepCopy(LayerThicknessH[TimeIndex], LayerThickness[TimeIndex]); deepCopy(NormalVelocityH[TimeIndex], NormalVelocity[TimeIndex]); - - return Err; } // end copyToHost //------------------------------------------------------------------------------ // Perform state halo exchange // TimeLevel == [1:new, 0:current, -1:previous, -2:two times ago, ...] -I4 OceanState::exchangeHalo(const I4 TimeLevel) { +void OceanState::exchangeHalo(const I4 TimeLevel) { - I4 Err; - I4 TimeIndex; - Err = getTimeIndex(TimeIndex, TimeLevel); + const I4 TimeIndex = getTimeIndex(TimeLevel); MeshHalo->exchangeFullArrayHalo(LayerThickness[TimeIndex], OnCell); MeshHalo->exchangeFullArrayHalo(NormalVelocity[TimeIndex], OnEdge); - return Err; - } // end exchangeHalo //------------------------------------------------------------------------------ @@ -391,19 +352,14 @@ OceanState *OceanState::get(const std::string Name ///< [in] Name of state //------------------------------------------------------------------------------ // Get time index from time level // TimeLevel == [1:new, 0:current, -1:previous, -2:two times ago, ...] -I4 OceanState::getTimeIndex(I4 &TimeIndex, const I4 TimeLevel) const { - - // Check if time level is valid - if (NTimeLevels > 1 && (TimeLevel > 1 || (TimeLevel + NTimeLevels) <= 1)) { - LOG_ERROR("OceanState: Time level {} is out of range for NTimeLevels {}", - TimeLevel, NTimeLevels); - return -1; - } - - TimeIndex = (TimeLevel + CurTimeIndex + NTimeLevels) % NTimeLevels; +I4 OceanState::getTimeIndex(const I4 TimeLevel) const { - return 0; + OMEGA_REQUIRE(NTimeLevels <= 1 || + !(TimeLevel > 1 || (TimeLevel + NTimeLevels) <= 1), + "OceanState: Time level {} is out of range for NTimeLevels {}", + TimeLevel, NTimeLevels); + return (TimeLevel + CurTimeIndex + NTimeLevels) % NTimeLevels; } // end get time index } // end namespace OMEGA diff --git a/components/omega/src/ocn/OceanState.h b/components/omega/src/ocn/OceanState.h index 27a398883667..b8e9316cd16a 100644 --- a/components/omega/src/ocn/OceanState.h +++ b/components/omega/src/ocn/OceanState.h @@ -54,7 +54,7 @@ class OceanState { I4 CurTimeIndex; ///< Time dimension array index for current level /// Get the current time level index associated with a time level - I4 getTimeIndex(I4 &TimeIndex, const I4 TimeLevel) const; + I4 getTimeIndex(const I4 TimeLevel) const; public: // Variables @@ -110,29 +110,28 @@ class OceanState { ); /// Get layer thickness device array at given time level - I4 getLayerThickness(Array2DReal &LayerThick, const I4 TimeLevel) const; + Array2DReal getLayerThickness(const I4 TimeLevel) const; /// Get layer thickness host array at given time level - I4 getLayerThicknessH(HostArray2DReal &LayerThickH, - const I4 TimeLevel) const; + HostArray2DReal getLayerThicknessH(const I4 TimeLevel) const; /// Get normal velocity device array at given time level - I4 getNormalVelocity(Array2DReal &NormVel, const I4 TimeLevel) const; + Array2DReal getNormalVelocity(const I4 TimeLevel) const; /// Get normal velocity host array at given time level - I4 getNormalVelocityH(HostArray2DReal &NormVelH, const I4 TimeLevel) const; + HostArray2DReal getNormalVelocityH(const I4 TimeLevel) const; /// Exchange halo - I4 exchangeHalo(const I4 TimeLevel); + void exchangeHalo(const I4 TimeLevel); /// Swap time levels to update state arrays void updateTimeLevels(); /// Copy state variables from host to device - I4 copyToDevice(const I4 TimeLevel); + void copyToDevice(const I4 TimeLevel); /// Copy state variables from device to host - I4 copyToHost(const I4 TimeLevel); + void copyToHost(const I4 TimeLevel); /// Destructor - deallocates all memory and deletes an OceanState ~OceanState(); diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 512d0e87c23f..ac95a8b8c4b2 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -262,8 +262,7 @@ void Tendencies::computeThicknessTendenciesOnly( OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); - Array2DReal NormalVelEdge; - State->getNormalVelocity(NormalVelEdge, VelTimeLevel); + Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); Pacer::start("Tend:computeThicknessTendenciesOnly", 1); @@ -353,8 +352,7 @@ void Tendencies::computeVelocityTendenciesOnly( AuxState->LayerThicknessAux.FluxLayerThickEdge; const Array2DReal &NormRVortEdge = AuxState->VorticityAux.NormRelVortEdge; const Array2DReal &NormFEdge = AuxState->VorticityAux.NormPlanetVortEdge; - Array2DReal NormVelEdge; - State->getNormalVelocity(NormVelEdge, VelTimeLevel); + Array2DReal NormVelEdge = State->getNormalVelocity(VelTimeLevel); if (LocPotientialVortHAdv.Enabled) { Pacer::start("Tend:potientialVortHAdv", 2); parallelForOuter( @@ -519,8 +517,7 @@ void Tendencies::computeTracerTendenciesOnly( }); // compute tracer horizotal advection - Array2DReal NormalVelEdge; - State->getNormalVelocity(NormalVelEdge, VelTimeLevel); + Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); const Array3DReal &HTracersEdge = AuxState->TracerAux.HTracersEdge; if (LocTracerHorzAdv.Enabled) { Pacer::start("Tend:tracerHorzAdv", 2); @@ -592,10 +589,8 @@ void Tendencies::computeThicknessTendencies( TimeInstant Time ///< [in] Time ) { // only need LayerThicknessAux on edge - Array2DReal LayerThick; - Array2DReal NormVel; - State->getLayerThickness(LayerThick, ThickTimeLevel); - State->getNormalVelocity(NormVel, VelTimeLevel); + Array2DReal LayerThick = State->getLayerThickness(ThickTimeLevel); + Array2DReal NormVel = State->getNormalVelocity(VelTimeLevel); OMEGA_SCOPE(LayerThicknessAux, AuxState->LayerThicknessAux); OMEGA_SCOPE(LayerThickCell, LayerThick); OMEGA_SCOPE(NormalVelEdge, NormVel); @@ -650,10 +645,8 @@ void Tendencies::computeTracerTendencies( int VelTimeLevel, ///< [in] Time level TimeInstant Time ///< [in] Time ) { - Array2DReal LayerThickCell; - Array2DReal NormalVelEdge; - State->getLayerThickness(LayerThickCell, ThickTimeLevel); - State->getNormalVelocity(NormalVelEdge, VelTimeLevel); + Array2DReal LayerThickCell = State->getLayerThickness(ThickTimeLevel); + Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); OMEGA_SCOPE(TracerAux, AuxState->TracerAux); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); diff --git a/components/omega/src/timeStepping/TimeStepper.cpp b/components/omega/src/timeStepping/TimeStepper.cpp index c7e9b30ed3b6..d8fbb5a06feb 100644 --- a/components/omega/src/timeStepping/TimeStepper.cpp +++ b/components/omega/src/timeStepping/TimeStepper.cpp @@ -386,13 +386,8 @@ void TimeStepper::updateThicknessByTend(OceanState *State1, int TimeLevel1, OceanState *State2, int TimeLevel2, TimeInterval Coeff) const { - Array2DReal LayerThick1; - Array2DReal LayerThick2; - I4 Err; - Err = State1->getLayerThickness(LayerThick1, TimeLevel1); - Err = State2->getLayerThickness(LayerThick2, TimeLevel2); - if (Err != 0) - ABORT_ERROR("TimeStepper updateThickness: error retrieving layer thick"); + Array2DReal LayerThick1 = State1->getLayerThickness(TimeLevel1); + Array2DReal LayerThick2 = State2->getLayerThickness(TimeLevel2); R8 CoeffSeconds; Coeff.get(CoeffSeconds, TimeUnits::Seconds); @@ -426,13 +421,8 @@ void TimeStepper::updateVelocityByTend(OceanState *State1, int TimeLevel1, OceanState *State2, int TimeLevel2, TimeInterval Coeff) const { - Array2DReal NormalVel1; - Array2DReal NormalVel2; - I4 Err; - Err = State1->getNormalVelocity(NormalVel1, TimeLevel1); - Err = State2->getNormalVelocity(NormalVel2, TimeLevel2); - if (Err != 0) - ABORT_ERROR("TimeStepper updateVelocity: error retrieving velocity"); + Array2DReal NormalVel1 = State1->getNormalVelocity(TimeLevel1); + Array2DReal NormalVel2 = State2->getNormalVelocity(TimeLevel2); R8 CoeffSeconds; Coeff.get(CoeffSeconds, TimeUnits::Seconds); @@ -477,9 +467,8 @@ void TimeStepper::updateTracersByTend(const Array3DReal &NextTracers, OceanState *State2, int TimeLevel2, TimeInterval Coeff) const { - Array2DReal LayerThick1, LayerThick2; - State1->getLayerThickness(LayerThick1, TimeLevel1); - State2->getLayerThickness(LayerThick2, TimeLevel2); + Array2DReal LayerThick1 = State1->getLayerThickness(TimeLevel1); + Array2DReal LayerThick2 = State2->getLayerThickness(TimeLevel2); OMEGA_SCOPE(TracerTend, Tend->TracerTend); const int NTracers = TracerTend.extent(0); @@ -512,9 +501,8 @@ void TimeStepper::weightTracers(const Array3DReal &NextTracers, const Array3DReal &CurTracers, OceanState *CurState, int TimeLevel1) const { - Array2DReal CurThickness; - CurState->getLayerThickness(CurThickness, TimeLevel1); - const int NTracers = NextTracers.extent(0); + Array2DReal CurThickness = CurState->getLayerThickness(TimeLevel1); + const int NTracers = NextTracers.extent(0); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); @@ -568,9 +556,8 @@ void TimeStepper::finalizeTracersUpdate(const Array3DReal &NextTracers, OceanState *State, int TimeLevel) const { - Array2DReal NextThick; - State->getLayerThickness(NextThick, TimeLevel); - const int NTracers = NextTracers.extent(0); + Array2DReal NextThick = State->getLayerThickness(TimeLevel); + const int NTracers = NextTracers.extent(0); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); diff --git a/components/omega/test/ocn/AuxiliaryStateTest.cpp b/components/omega/test/ocn/AuxiliaryStateTest.cpp index 543f074d5f24..0e27026d74c8 100644 --- a/components/omega/test/ocn/AuxiliaryStateTest.cpp +++ b/components/omega/test/ocn/AuxiliaryStateTest.cpp @@ -55,10 +55,8 @@ int initState() { auto *State = OceanState::getDefault(); auto *VCoord = VertCoord::getDefault(); - Array2DReal LayerThickCell; - Array2DReal NormalVelEdge; - Err = State->getLayerThickness(LayerThickCell, 0); - Err += State->getNormalVelocity(NormalVelEdge, 0); + Array2DReal LayerThickCell = State->getLayerThickness(0); + Array2DReal NormalVelEdge = State->getNormalVelocity(0); Array3DReal TracerArray; Err += Tracers::getAll(TracerArray, 0); diff --git a/components/omega/test/ocn/StateTest.cpp b/components/omega/test/ocn/StateTest.cpp index b83333785455..0c2f631db277 100644 --- a/components/omega/test/ocn/StateTest.cpp +++ b/components/omega/test/ocn/StateTest.cpp @@ -126,10 +126,10 @@ int checkHost(OceanState *RefState, OceanState *TstState, int RefTimeLevel, int TstTimeLevel) { int count = 0; - HostArray2DReal LayerThicknessHRef; - HostArray2DReal LayerThicknessHTst; - RefState->getLayerThicknessH(LayerThicknessHRef, RefTimeLevel); - TstState->getLayerThicknessH(LayerThicknessHTst, TstTimeLevel); + HostArray2DReal LayerThicknessHRef = + RefState->getLayerThicknessH(RefTimeLevel); + HostArray2DReal LayerThicknessHTst = + TstState->getLayerThicknessH(TstTimeLevel); for (int Cell = 0; Cell < RefState->NCellsAll; Cell++) { for (int Layer = 0; Layer < RefState->NVertLayers; Layer++) { if (LayerThicknessHRef(Cell, Layer) != @@ -139,10 +139,10 @@ int checkHost(OceanState *RefState, OceanState *TstState, int RefTimeLevel, } } - HostArray2DReal NormalVelocityHRef; - HostArray2DReal NormalVelocityHTst; - RefState->getNormalVelocityH(NormalVelocityHRef, RefTimeLevel); - TstState->getNormalVelocityH(NormalVelocityHTst, TstTimeLevel); + HostArray2DReal NormalVelocityHRef = + RefState->getNormalVelocityH(RefTimeLevel); + HostArray2DReal NormalVelocityHTst = + TstState->getNormalVelocityH(TstTimeLevel); for (int Edge = 0; Edge < RefState->NEdgesAll; Edge++) { for (int Layer = 0; Layer < RefState->NVertLayers; Layer++) { if (NormalVelocityHRef(Edge, Layer) != @@ -161,10 +161,8 @@ int checkDevice(OceanState *RefState, OceanState *TstState, int RefTimeLevel, int TstTimeLevel) { int count1; - Array2DReal LayerThicknessRef; - Array2DReal LayerThicknessTst; - RefState->getLayerThickness(LayerThicknessRef, RefTimeLevel); - TstState->getLayerThickness(LayerThicknessTst, TstTimeLevel); + Array2DReal LayerThicknessRef = RefState->getLayerThickness(RefTimeLevel); + Array2DReal LayerThicknessTst = TstState->getLayerThickness(TstTimeLevel); parallelReduce( "reduce", {RefState->NCellsAll, RefState->NVertLayers}, KOKKOS_LAMBDA(int Cell, int Layer, int &Accum) { @@ -176,10 +174,8 @@ int checkDevice(OceanState *RefState, OceanState *TstState, int RefTimeLevel, count1); int count2; - Array2DReal NormalVelocityRef; - Array2DReal NormalVelocityTst; - RefState->getNormalVelocity(NormalVelocityRef, RefTimeLevel); - TstState->getNormalVelocity(NormalVelocityTst, TstTimeLevel); + Array2DReal NormalVelocityRef = RefState->getNormalVelocity(RefTimeLevel); + Array2DReal NormalVelocityTst = TstState->getNormalVelocity(TstTimeLevel); parallelReduce( "reduce", {RefState->NCellsAll, RefState->NVertLayers}, KOKKOS_LAMBDA(int Cell, int Layer, int &Accum) { @@ -234,14 +230,11 @@ int main(int argc, char *argv[]) { } // Get default state arrays and check for reasonable values - HostArray2DReal LayerThickHDef; - HostArray2DReal NormalVelocityHDef; - Array2DReal LayerThickDef; - Array2DReal NormalVelocityDef; - DefState->getLayerThicknessH(LayerThickHDef, CurTime); - DefState->getLayerThickness(LayerThickDef, CurTime); - DefState->getNormalVelocityH(NormalVelocityHDef, CurTime); - DefState->getNormalVelocity(NormalVelocityDef, CurTime); + HostArray2DReal LayerThickHDef = DefState->getLayerThicknessH(CurTime); + HostArray2DReal NormalVelocityHDef = + DefState->getNormalVelocityH(CurTime); + Array2DReal LayerThickDef = DefState->getLayerThickness(CurTime); + Array2DReal NormalVelocityDef = DefState->getNormalVelocity(CurTime); int Count1 = 0; int Count2 = 0; @@ -282,17 +275,15 @@ int main(int argc, char *argv[]) { NTimeLevels); } - // Create test arrays - HostArray2DReal LayerThickHRef; - HostArray2DReal LayerThickHTst; - HostArray2DReal NormalVelocityHRef; - HostArray2DReal NormalVelocityHTst; - - // Fill reference and test states at current time with Default state - RefState->getLayerThicknessH(LayerThickHRef, CurTime); - TstState->getLayerThicknessH(LayerThickHTst, CurTime); - RefState->getNormalVelocityH(NormalVelocityHRef, CurTime); - TstState->getNormalVelocityH(NormalVelocityHTst, CurTime); + // Create test arrays and fill reference and test states at current + // time with Default state + HostArray2DReal LayerThickHRef = RefState->getLayerThicknessH(CurTime); + HostArray2DReal LayerThickHTst = TstState->getLayerThicknessH(CurTime); + HostArray2DReal NormalVelocityHRef = + RefState->getNormalVelocityH(CurTime); + HostArray2DReal NormalVelocityHTst = + TstState->getNormalVelocityH(CurTime); + for (int Cell = 0; Cell < NCellsAll; Cell++) { for (int Layer = 0; Layer < NVertLayers; Layer++) { LayerThickHRef(Cell, Layer) = LayerThickHDef(Cell, Layer); @@ -312,10 +303,10 @@ int main(int argc, char *argv[]) { // Fill reference and test states at next time levels with the // Default state + 1 - RefState->getLayerThicknessH(LayerThickHRef, NewTime); - TstState->getLayerThicknessH(LayerThickHTst, NewTime); - RefState->getNormalVelocityH(NormalVelocityHRef, NewTime); - TstState->getNormalVelocityH(NormalVelocityHTst, NewTime); + LayerThickHRef = RefState->getLayerThicknessH(NewTime); + LayerThickHTst = TstState->getLayerThicknessH(NewTime); + NormalVelocityHRef = RefState->getNormalVelocityH(NewTime); + NormalVelocityHTst = TstState->getNormalVelocityH(NewTime); for (int Cell = 0; Cell < NCellsAll; Cell++) { for (int Layer = 0; Layer < NVertLayers; Layer++) { LayerThickHRef(Cell, Layer) = LayerThickHDef(Cell, Layer) + 1; diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 4595bfea6d88..63132d9bcbf7 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -57,10 +57,8 @@ int initState() { auto *VCoord = VertCoord::getDefault(); auto *State = OceanState::getDefault(); - Array2DReal LayerThickCell; - Array2DReal NormalVelEdge; - Err += State->getLayerThickness(LayerThickCell, 0); - Err += State->getNormalVelocity(NormalVelEdge, 0); + Array2DReal LayerThickCell = State->getLayerThickness(0); + Array2DReal NormalVelEdge = State->getNormalVelocity(0); Array3DReal TracersArray; Err += Tracers::getAll(TracersArray, 0); diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 8188bac63f60..9798340f1d93 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -56,10 +56,9 @@ struct DecayVelocityTendency { const AuxiliaryState *AuxState, int ThickTimeLevel, int VelTimeLevel, TimeInstant Time) const { - auto *Mesh = HorzMesh::getDefault(); - auto NVertLayers = NormalVelTend.extent_int(1); - Array2DReal NormalVelEdge; - State->getNormalVelocity(NormalVelEdge, VelTimeLevel); + auto *Mesh = HorzMesh::getDefault(); + auto NVertLayers = NormalVelTend.extent_int(1); + Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); OMEGA_SCOPE(LocCoeff, Coeff); @@ -78,10 +77,8 @@ int initState() { Array3DReal TracerArray; Err = Tracers::getAll(TracerArray, 0); - Array2DReal LayerThickCell; - Array2DReal NormalVelEdge; - State->getLayerThickness(LayerThickCell, 0); - State->getNormalVelocity(NormalVelEdge, 0); + Array2DReal LayerThickCell = State->getLayerThickness(0); + Array2DReal NormalVelEdge = State->getNormalVelocity(0); // Initially set thickness and velocity and tracers to 1 deepCopy(LayerThickCell, 1); @@ -102,10 +99,8 @@ int createExactSolution(Real TimeEnd) { auto *ExactState = OceanState::create("Exact", DefMesh, DefHalo, NVertLayers, 1); - Array2DReal LayerThickCell; - Array2DReal NormalVelEdge; - ExactState->getLayerThickness(LayerThickCell, 0); - ExactState->getNormalVelocity(NormalVelEdge, 0); + Array2DReal LayerThickCell = ExactState->getLayerThickness(0); + Array2DReal NormalVelEdge = ExactState->getNormalVelocity(0); // There are no thickness tendencies in this test, so exact thickness == // initial thickness @@ -124,10 +119,8 @@ ErrorMeasures computeErrors() { const auto *State = OceanState::get("TestState"); const auto *ExactState = OceanState::get("Exact"); - Array2DReal NormalVelEdge; - Array2DReal ExactNormalVelEdge; - State->getNormalVelocity(NormalVelEdge, 0); - ExactState->getNormalVelocity(ExactNormalVelEdge, 0); + Array2DReal NormalVelEdge = State->getNormalVelocity(0); + Array2DReal ExactNormalVelEdge = ExactState->getNormalVelocity(0); // Only velocity errors matters, because thickness remains constant ErrorMeasures VelErrors; From 82b0e7d3d6c83245cfdd28d1242887855df91601 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Fri, 13 Feb 2026 14:37:24 -0700 Subject: [PATCH 003/152] Require valid time index in tracer getters --- components/omega/src/ocn/OceanInit.cpp | 5 +- components/omega/src/ocn/Tracers.cpp | 75 +++++++------------ components/omega/src/ocn/Tracers.h | 14 ++-- .../timeStepping/ForwardBackwardStepper.cpp | 7 +- .../src/timeStepping/RungeKutta2Stepper.cpp | 9 +-- .../src/timeStepping/RungeKutta4Stepper.cpp | 9 +-- .../omega/test/ocn/AuxiliaryStateTest.cpp | 8 +- components/omega/test/ocn/TendenciesTest.cpp | 16 ++-- components/omega/test/ocn/TracersTest.cpp | 22 +----- .../test/timeStepping/TimeStepperTest.cpp | 14 ++-- 10 files changed, 58 insertions(+), 121 deletions(-) diff --git a/components/omega/src/ocn/OceanInit.cpp b/components/omega/src/ocn/OceanInit.cpp index 884a9cc51d7b..db422f315c2c 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -193,10 +193,7 @@ int initOmegaModules(MPI_Comm Comm) { if (Err != 0) { ABORT_ERROR("Error updating tracer halo after restart"); } - Err = Tracers::copyToHost(CurTimeLevel); - if (Err != 0) { - ABORT_ERROR("Error updating tracer device arrays after restart"); - } + Tracers::copyToHost(CurTimeLevel); return Err; diff --git a/components/omega/src/ocn/Tracers.cpp b/components/omega/src/ocn/Tracers.cpp index 04cb5b022a9d..1e2e603d103d 100644 --- a/components/omega/src/ocn/Tracers.cpp +++ b/components/omega/src/ocn/Tracers.cpp @@ -266,14 +266,10 @@ I4 Tracers::getName(std::string &TracerName, const I4 TracerIndex) { // get Tracer arrays // TimeLevel == [0:current, -1:previous, -2:two times ago, ...] //--------------------------------------------------------------------------- -I4 Tracers::getAll(Array3DReal &TracerArray, const I4 TimeLevel) { - I4 Err = 0; - I4 TimeIndex; - - Err = getTimeIndex(TimeIndex, TimeLevel); - TracerArray = TracerArrays[TimeIndex]; +Array3DReal Tracers::getAll(const I4 TimeLevel) { + const I4 TimeIndex = getTimeIndex(TimeLevel); - return Err; + return TracerArrays[TimeIndex]; } I4 Tracers::getByIndex(Array2DReal &TracerArray, const I4 TimeLevel, @@ -285,13 +281,10 @@ I4 Tracers::getByIndex(Array2DReal &TracerArray, const I4 TimeLevel, return -2; } - I4 Err = 0; - I4 TimeIndex; - - Err = getTimeIndex(TimeIndex, TimeLevel); - TracerArray = Kokkos::subview(TracerArrays[TimeIndex], TracerIndex, - Kokkos::ALL, Kokkos::ALL); - return Err; + const I4 TimeIndex = getTimeIndex(TimeLevel); + TracerArray = Kokkos::subview(TracerArrays[TimeIndex], TracerIndex, + Kokkos::ALL, Kokkos::ALL); + return 0; } I4 Tracers::getByName(Array2DReal &TracerArray, const I4 TimeLevel, @@ -309,15 +302,11 @@ I4 Tracers::getByName(Array2DReal &TracerArray, const I4 TimeLevel, return 0; } -I4 Tracers::getAllHost(HostArray3DReal &TracerArrayH, const I4 TimeLevel) { +HostArray3DReal Tracers::getAllHost(const I4 TimeLevel) { - I4 Err = 0; - I4 TimeIndex; - - Err = getTimeIndex(TimeIndex, TimeLevel); - TracerArrayH = TracerArraysH[TimeIndex]; + const I4 TimeIndex = getTimeIndex(TimeLevel); - return Err; + return TracerArraysH[TimeIndex]; } I4 Tracers::getHostByIndex(HostArray2DReal &TracerArrayH, const I4 TimeLevel, @@ -329,12 +318,10 @@ I4 Tracers::getHostByIndex(HostArray2DReal &TracerArrayH, const I4 TimeLevel, return -2; } - I4 TimeIndex; - - I4 Err = getTimeIndex(TimeIndex, TimeLevel); - TracerArrayH = Kokkos::subview(TracerArraysH[TimeIndex], TracerIndex, - Kokkos::ALL, Kokkos::ALL); - return Err; + const I4 TimeIndex = getTimeIndex(TimeLevel); + TracerArrayH = Kokkos::subview(TracerArraysH[TimeIndex], TracerIndex, + Kokkos::ALL, Kokkos::ALL); + return 0; } I4 Tracers::getHostByName(HostArray2DReal &TracerArrayH, const I4 TimeLevel, @@ -429,25 +416,18 @@ bool Tracers::isGroupMemberByName(const std::string &TracerName, // deep copy at a time level // TimeLevel == [1:new, 0:current, -1:previous, -2:two times ago, ...] //--------------------------------------------------------------------------- -I4 Tracers::copyToDevice(const I4 TimeLevel) { +void Tracers::copyToDevice(const I4 TimeLevel) { - I4 Err = 0; - I4 TimeIndex; + const I4 TimeIndex = getTimeIndex(TimeLevel); - Err = getTimeIndex(TimeIndex, TimeLevel); deepCopy(TracerArrays[TimeIndex], TracerArraysH[TimeIndex]); - - return Err; } -I4 Tracers::copyToHost(const I4 TimeLevel) { +void Tracers::copyToHost(const I4 TimeLevel) { - I4 TimeIndex; + const I4 TimeIndex = getTimeIndex(TimeLevel); - I4 Err = getTimeIndex(TimeIndex, TimeLevel); deepCopy(TracerArraysH[TimeIndex], TracerArrays[TimeIndex]); - - return Err; } //--------------------------------------------------------------------------- @@ -457,9 +437,9 @@ I4 Tracers::copyToHost(const I4 TimeLevel) { I4 Tracers::exchangeHalo(const I4 TimeLevel) { I4 Err = 0; - I4 TimeIndex; - Err = getTimeIndex(TimeIndex, TimeLevel); + const I4 TimeIndex = getTimeIndex(TimeLevel); + Err = MeshHalo->exchangeFullArrayHalo(TracerArrays[TimeIndex], OnCell); if (Err != 0) return -1; @@ -499,18 +479,15 @@ void Tracers::updateTimeLevels() { // get index for time level // TimeLevel == [1:new, 0:current, -1:previous, -2:two times ago, ...] //--------------------------------------------------------------------------- -I4 Tracers::getTimeIndex(I4 &TimeIndex, const I4 TimeLevel) { +I4 Tracers::getTimeIndex(const I4 TimeLevel) { // Check if time level is valid - if (NTimeLevels > 1 && (TimeLevel > 1 || (TimeLevel + NTimeLevels) <= 1)) { - LOG_ERROR("Tracers: Time level {} is out of range for NTimeLevels {}", - TimeLevel, NTimeLevels); - return -1; - } + OMEGA_REQUIRE(NTimeLevels <= 1 || + !(TimeLevel > 1 || (TimeLevel + NTimeLevels) <= 1), + "Tracers: Time level {} is out of range for NTimeLevels {}", + TimeLevel, NTimeLevels); - TimeIndex = (TimeLevel + CurTimeIndex + NTimeLevels) % NTimeLevels; - - return 0; + return (TimeLevel + CurTimeIndex + NTimeLevels) % NTimeLevels; } } // namespace OMEGA diff --git a/components/omega/src/ocn/Tracers.h b/components/omega/src/ocn/Tracers.h index f0077a05ebb9..d641be2a3806 100644 --- a/components/omega/src/ocn/Tracers.h +++ b/components/omega/src/ocn/Tracers.h @@ -71,7 +71,7 @@ class Tracers { static I4 CurTimeIndex; ///< Time dimension array index for current level /// get the time level index - static I4 getTimeIndex(I4 &TimeIndex, const I4 TimeLevel); + static I4 getTimeIndex(const I4 TimeLevel); /// locally defines all tracers but do not allocates memory static I4 @@ -116,8 +116,7 @@ class Tracers { ); /// get a device array for all tracers - static I4 getAll(Array3DReal &TracerArray, ///< [out] tracer device array - const I4 TimeLevel ///< [in] time level index + static Array3DReal getAll(const I4 TimeLevel ///< [in] time level index ); /// get a device array by tracer index @@ -134,9 +133,8 @@ class Tracers { ); /// get a host array for all tracers - static I4 - getAllHost(HostArray3DReal &TracerArrayH, ///< [out] tracer host array - const I4 TimeLevel ///< [in] time level index + static HostArray3DReal getAllHost( ///< [out] tracer host array + const I4 TimeLevel ///< [in] time level index ); /// get a host array by tracer index @@ -203,11 +201,11 @@ class Tracers { //--------------------------------------------------------------------------- /// Copy tracers variables from host to device - static I4 copyToDevice(const I4 TimeLevel ///< [in] tracer time level + static void copyToDevice(const I4 TimeLevel ///< [in] tracer time level ); /// Copy tracers variables from device to host - static I4 copyToHost(const I4 TimeLevel ///< [in] tracer time level + static void copyToHost(const I4 TimeLevel ///< [in] tracer time level ); //--------------------------------------------------------------------------- diff --git a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp index c8751e3602c9..41e6252aecda 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp @@ -34,11 +34,8 @@ void ForwardBackwardStepper::doStep( const int CurLevel = 0; const int NextLevel = 1; - Array3DReal CurTracerArray, NextTracerArray; - Err = Tracers::getAll(CurTracerArray, CurLevel); - Err = Tracers::getAll(NextTracerArray, NextLevel); - if (Err != 0) - ABORT_ERROR("ForwardBackward doStep: error retrieving tracers"); + Array3DReal CurTracerArray = Tracers::getAll(CurLevel); + Array3DReal NextTracerArray = Tracers::getAll(NextLevel); if (State == nullptr) LOG_CRITICAL("Invalid State"); diff --git a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp index 3ac9e00056b8..0cabb62b32be 100644 --- a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp @@ -28,16 +28,11 @@ void RungeKutta2Stepper::doStep(OceanState *State, // model state TimeInstant &SimTime // current simulation time ) const { - int Err = 0; - const int CurLevel = 0; const int NextLevel = 1; - Array3DReal CurTracerArray, NextTracerArray; - Err = Tracers::getAll(CurTracerArray, CurLevel); - Err = Tracers::getAll(NextTracerArray, NextLevel); - if (Err != 0) - ABORT_ERROR("RungeKutt2 doStep: error retrieving tracers"); + Array3DReal CurTracerArray = Tracers::getAll(CurLevel); + Array3DReal NextTracerArray = Tracers::getAll(NextLevel); // q = (h,u,phi) // R_q^{n} = RHS_q(u^{n}, h^{n}, phi^{n}, t^{n}) diff --git a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp index d9db7e9b8331..24b3c1c251be 100644 --- a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp @@ -69,18 +69,13 @@ void RungeKutta4Stepper::doStep(OceanState *State, // model state TimeInstant &SimTime // current simulation time ) const { - int Err = 0; - const MPI_Comm Comm = MeshHalo->getComm(); const int CurLevel = 0; const int NextLevel = 1; - Array3DReal NextTracerArray, CurTracerArray; - Err = Tracers::getAll(CurTracerArray, CurLevel); - Err = Tracers::getAll(NextTracerArray, NextLevel); - if (Err != 0) - ABORT_ERROR("RungeKutte4 doStep: error retrieving tracers."); + Array3DReal CurTracerArray = Tracers::getAll(CurLevel); + Array3DReal NextTracerArray = Tracers::getAll(NextLevel); for (int Stage = 0; Stage < NStages; ++Stage) { const TimeInstant StageTime = SimTime + RKC[Stage] * TimeStep; diff --git a/components/omega/test/ocn/AuxiliaryStateTest.cpp b/components/omega/test/ocn/AuxiliaryStateTest.cpp index 0e27026d74c8..1952f70744ae 100644 --- a/components/omega/test/ocn/AuxiliaryStateTest.cpp +++ b/components/omega/test/ocn/AuxiliaryStateTest.cpp @@ -57,8 +57,7 @@ int initState() { Array2DReal LayerThickCell = State->getLayerThickness(0); Array2DReal NormalVelEdge = State->getNormalVelocity(0); - Array3DReal TracerArray; - Err += Tracers::getAll(TracerArray, 0); + Array3DReal TracerArray = Tracers::getAll(0); int NTracers = Tracers::getNumTracers(); @@ -190,9 +189,8 @@ int testAuxState() { deepCopy(DefAuxState->TracerAux.Del2TracersCell, NAN); // compute auxiliary variables - const auto *State = OceanState::getDefault(); - Array3DReal TracerArray; - Err += Tracers::getAll(TracerArray, 0); + const auto *State = OceanState::getDefault(); + Array3DReal TracerArray = Tracers::getAll(0); DefAuxState->computeAll(State, TracerArray, 0); // check that everything got computed correctly diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 63132d9bcbf7..8ef4caa84f0e 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -60,9 +60,8 @@ int initState() { Array2DReal LayerThickCell = State->getLayerThickness(0); Array2DReal NormalVelEdge = State->getNormalVelocity(0); - Array3DReal TracersArray; - Err += Tracers::getAll(TracersArray, 0); - const auto &TracersCell = TracersArray; + Array3DReal TracersArray = Tracers::getAll(0); + const auto &TracersCell = TracersArray; int NTracers = Tracers::getNumTracers(); @@ -188,12 +187,11 @@ int testTendencies() { deepCopy(DefTendencies->TracerTend, NAN); // compute tendencies - const auto *State = OceanState::getDefault(); - const auto *AuxState = AuxiliaryState::getDefault(); - Array3DReal TracerArray; - Err += Tracers::getAll(TracerArray, 0); - int ThickTimeLevel = 0; - int VelTimeLevel = 0; + const auto *State = OceanState::getDefault(); + const auto *AuxState = AuxiliaryState::getDefault(); + Array3DReal TracerArray = Tracers::getAll(0); + int ThickTimeLevel = 0; + int VelTimeLevel = 0; TimeInstant Time; DefTendencies->computeAllTendencies(State, AuxState, TracerArray, ThickTimeLevel, VelTimeLevel, Time); diff --git a/components/omega/test/ocn/TracersTest.cpp b/components/omega/test/ocn/TracersTest.cpp index daf85e6f3536..502f0195bad7 100644 --- a/components/omega/test/ocn/TracersTest.cpp +++ b/components/omega/test/ocn/TracersTest.cpp @@ -236,14 +236,7 @@ int main(int argc, char *argv[]) { // intialize tracer elements of all time levels for (I4 TimeLevel = 1; TimeLevel + NTimeLevels > 1; --TimeLevel) { - HostArray3DReal TempHostArray; - Err = Tracers::getAllHost(TempHostArray, TimeLevel); - if (Err != 0) { - LOG_ERROR("getAllHost(TempHostArray, TimeLevel) returns non-zero " - "code: {}", - Err); - RetVal += 1; - } + HostArray3DReal TempHostArray = Tracers::getAllHost(TimeLevel); for (I4 Tracer = 0; Tracer < NTracers; ++Tracer) { for (I4 Cell = 0; Cell < NCellsSize; Cell++) { @@ -263,11 +256,7 @@ int main(int argc, char *argv[]) { Array3DReal RefArray = Array3DReal("RefArray", NTracers, NCellsSize, NVertLayers); - Err = Tracers::getAll(RefArray, 1); - if (Err != 0) { - LOG_ERROR("getAll(RefArray, 1) returns non-zero code: {}", Err); - RetVal += 1; - } + RefArray = Tracers::getAll(1); // deepCopy(RefArray, RefArray); TODO: remove this @@ -284,12 +273,7 @@ int main(int argc, char *argv[]) { Tracers::updateTimeLevels(); // getAll of current time level(0) should return the same to RefArray - Array3DReal CurArray; - Err = Tracers::getAll(CurArray, 0); - if (Err != 0) { - LOG_ERROR("getAll(CurArray, 0) returns non-zero code: {}", Err); - RetVal += 1; - } + Array3DReal CurArray = Tracers::getAll(0); count = -1; diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 9798340f1d93..56b14d713a02 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -72,10 +72,9 @@ struct DecayVelocityTendency { int initState() { int Err = 0; - auto *Mesh = HorzMesh::getDefault(); - auto *State = OceanState::get("TestState"); - Array3DReal TracerArray; - Err = Tracers::getAll(TracerArray, 0); + auto *Mesh = HorzMesh::getDefault(); + auto *State = OceanState::get("TestState"); + Array3DReal TracerArray = Tracers::getAll(0); Array2DReal LayerThickCell = State->getLayerThickness(0); Array2DReal NormalVelEdge = State->getNormalVelocity(0); @@ -91,10 +90,9 @@ int initState() { int createExactSolution(Real TimeEnd) { int Err = 0; - auto *DefHalo = Halo::getDefault(); - auto *DefMesh = HorzMesh::getDefault(); - Array3DReal TracerArray; - Err = Tracers::getAll(TracerArray, 0); + auto *DefHalo = Halo::getDefault(); + auto *DefMesh = HorzMesh::getDefault(); + Array3DReal TracerArray = Tracers::getAll(0); auto *ExactState = OceanState::create("Exact", DefMesh, DefHalo, NVertLayers, 1); From ae37c58d882c5b6103333064c2ef8315ebdd637d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 21:40:04 +0000 Subject: [PATCH 004/152] Update OceanState and Tracers documentation to reflect new getter API --- components/omega/doc/devGuide/OceanState.md | 24 ++++++++++----------- components/omega/doc/devGuide/Tracers.md | 23 +++++++------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/components/omega/doc/devGuide/OceanState.md b/components/omega/doc/devGuide/OceanState.md index d817b520e148..b06ae4d5d943 100644 --- a/components/omega/doc/devGuide/OceanState.md +++ b/components/omega/doc/devGuide/OceanState.md @@ -40,18 +40,19 @@ For now, member variables that are host arrays have variable names are appended `H`. Array variable names not ending in `H` are device arrays. For a given time level, host to device array is performed via: ```c++ -Err = State->copyToDevice(TimeLevel); +State->copyToDevice(TimeLevel); ``` and a copy from device to host is performed by: ```c++ -Err = State->copyToHost(TimeLevel); +State->copyToHost(TimeLevel); ``` +These functions validate the `TimeLevel` via `OMEGA_REQUIRE` and will abort if invalid. Eventually, the host arrays will be eliminated when `IO` and `Halo` are extended to handle host <-> device transfers. A time level update to advance the solution at the end of a model timestep is done by: ```c++ -Err = State->updateTimeLevels(); +State->updateTimeLevels(); ``` This shifts the time level indices within the `State` instance. A halo exchange is also performed on these arrays and the IOFields data pointer is attached @@ -59,19 +60,18 @@ to the current time level. The arrays associated with a given time level can be accessed with the functions: ```c++ -Array2DReal LayerThick; -Err = State->getLayerThickness(LayerThick, TimeLevel); -Array2DReal NormVel; -Err = State->getNormalVelocity(NormVel, TimeLevel); +Array2DReal LayerThick = State->getLayerThickness(TimeLevel); +Array2DReal NormVel = State->getNormalVelocity(TimeLevel); ``` for the device arrays and ```c++ -HostArray2DReal LayerThickH; -Err = State->getLayerThicknessH(LayerThickH, TimeLevel); -HostArray2DReal NormVelH; -Err = State->getNormalVelocityH(NormVelH, TimeLevel); +HostArray2DReal LayerThickH = State->getLayerThicknessH(TimeLevel); +HostArray2DReal NormVelH = State->getNormalVelocityH(TimeLevel); ``` -for the host arrays. The time level convention is: +for the host arrays. These functions return the arrays directly and will abort +via `OMEGA_REQUIRE` if an invalid `TimeLevel` is provided. + +The time level convention is: | time level | `TimeLevel` | |------------|-------------| | New | 1 | diff --git a/components/omega/doc/devGuide/Tracers.md b/components/omega/doc/devGuide/Tracers.md index 17f3d4fca937..650e58fb1091 100644 --- a/components/omega/doc/devGuide/Tracers.md +++ b/components/omega/doc/devGuide/Tracers.md @@ -85,10 +85,7 @@ I4 Err = Tracers::getGroupRange(GroupRange, GroupName); auto [StartIndex, GroupLength] = GroupRange; // Get all tracers at the current time level (0) -Array3DReal TracerArray; -Err = OMEGA::Tracers::getAll(TracerArray, 0); -if (Err != 0) - LOG_ERROR("getAll returns an error code: {}.", Err); +Array3DReal TracerArray = OMEGA::Tracers::getAll(0); OMEGA::parallelFor( @@ -112,12 +109,10 @@ previous time levels: ```c++ // Get all tracers at the current time level (0) -Array3DReal CurrentTracerArray; -I4 Err1 = OMEGA::Tracers::getAll(CurrentTracerArray, 0); +Array3DReal CurrentTracerArray = OMEGA::Tracers::getAll(0); // Get all tracers at the previous time level (-1) -Array3DReal PreviousTracerArray; -I4 Err2 = OMEGA::Tracers::getAll(PreviousTracerArray, -1); +Array3DReal PreviousTracerArray = OMEGA::Tracers::getAll(-1); ``` ## Initialization and Finalization @@ -132,16 +127,14 @@ OMEGA itself, so users may not need to call them separately. ### `getAll` and `getAllHost` These functions return all device and host tracer arrays, respectively. If -the specified `TimeLevel` does not exist, they return a negative integer. +the specified `TimeLevel` does not exist, they will abort via `OMEGA_REQUIRE`. ```c++ static HostArray3DReal getAllHost( - HostArray3DReal &TracerArrayH, ///< [out] tracer host array const I4 TimeLevel ///< [in] Time level index ); static Array3DReal getAll( - Array3DReal &TracerArray, ///< [out] tracer device array const I4 TimeLevel ///< [in] Time level index ); ``` @@ -162,17 +155,17 @@ static I4 getGroupRange( ### `getByIndex` and `getHostByIndex` These functions return device and host tracer arrays, respectively, based -on the `TracerIndex`. If the specified `TimeLevel` and/or `TracerIndex` -does not exist, they return a negative integer. +on the `TracerIndex`. If the specified `TimeLevel` is invalid, they will abort +via `OMEGA_REQUIRE`. If the `TracerIndex` is invalid, they return a negative error code. ```c++ -static Array2DReal getByIndex( +static I4 getByIndex( Array2DReal &TracerArray, ///< [out] tracer device array const I4 TimeLevel, ///< [in] Time level index const I4 TracerIndex ///< [in] Global tracer index ); -static HostArray2DReal getHostByIndex( +static I4 getHostByIndex( HostArray2DReal &TracerArrayH, ///< [out] tracer host array const I4 TimeLevel, ///< [in] Time level index const I4 TracerIndex ///< [in] Global tracer index From 95a4781748fa1a78e037e73da2fd434bd81ecf96 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Thu, 12 Feb 2026 15:25:46 -0700 Subject: [PATCH 005/152] Refactor Tracer::getByIndex and Tracers::getByName --- components/omega/src/ocn/Tracers.cpp | 70 ++++++------------- components/omega/src/ocn/Tracers.h | 29 ++++---- .../timeStepping/ForwardBackwardStepper.cpp | 2 - components/omega/test/infra/IOStreamTest.cpp | 4 +- components/omega/test/ocn/TracersTest.cpp | 25 ++----- 5 files changed, 42 insertions(+), 88 deletions(-) diff --git a/components/omega/src/ocn/Tracers.cpp b/components/omega/src/ocn/Tracers.cpp index 1e2e603d103d..efa7b00b635f 100644 --- a/components/omega/src/ocn/Tracers.cpp +++ b/components/omega/src/ocn/Tracers.cpp @@ -268,77 +268,56 @@ I4 Tracers::getName(std::string &TracerName, const I4 TracerIndex) { //--------------------------------------------------------------------------- Array3DReal Tracers::getAll(const I4 TimeLevel) { const I4 TimeIndex = getTimeIndex(TimeLevel); - return TracerArrays[TimeIndex]; } -I4 Tracers::getByIndex(Array2DReal &TracerArray, const I4 TimeLevel, - const I4 TracerIndex) { +Array2DReal Tracers::getByIndex(const I4 TimeLevel, const I4 TracerIndex) { // Check if tracer index is valid - if (TracerIndex < 0 || TracerIndex >= NumTracers) { - LOG_ERROR("Tracers: Tracer index {} is out of range", TracerIndex); - return -2; - } + OMEGA_REQUIRE(TracerIndex >= 0 && TracerIndex < NumTracers, + "Tracers: Tracer index {} is out of range", TracerIndex); const I4 TimeIndex = getTimeIndex(TimeLevel); - TracerArray = Kokkos::subview(TracerArrays[TimeIndex], TracerIndex, - Kokkos::ALL, Kokkos::ALL); - return 0; + return Kokkos::subview(TracerArrays[TimeIndex], TracerIndex, Kokkos::ALL, + Kokkos::ALL); } -I4 Tracers::getByName(Array2DReal &TracerArray, const I4 TimeLevel, - const std::string &TracerName) { - // Check if tracer exists - if (TracerIndexes.find(TracerName) == TracerIndexes.end()) { - LOG_ERROR("Tracers: Tracer '{}' does not exist", TracerName); - return -1; - } +Array2DReal Tracers::getByName(const I4 TimeLevel, + const std::string &TracerName) { - int Err = getByIndex(TracerArray, TimeLevel, TracerIndexes[TracerName]); - if (Err != 0) - return -2; + // Check if tracer exists + OMEGA_REQUIRE(TracerIndexes.find(TracerName) != TracerIndexes.end(), + "Tracers: Tracer '{}' does not exist", TracerName); - return 0; + return getByIndex(TimeLevel, TracerIndexes[TracerName]); } HostArray3DReal Tracers::getAllHost(const I4 TimeLevel) { - const I4 TimeIndex = getTimeIndex(TimeLevel); - return TracerArraysH[TimeIndex]; } -I4 Tracers::getHostByIndex(HostArray2DReal &TracerArrayH, const I4 TimeLevel, - const I4 TracerIndex) { +HostArray2DReal Tracers::getHostByIndex(const I4 TimeLevel, + const I4 TracerIndex) { // Check if tracer index is valid - if (TracerIndex < 0 || TracerIndex >= NumTracers) { - LOG_ERROR("Tracers: Tracer index {} is out of range", TracerIndex); - return -2; - } + OMEGA_REQUIRE(TracerIndex >= 0 && TracerIndex < NumTracers, + "Tracers: Tracer index {} is out of range", TracerIndex); const I4 TimeIndex = getTimeIndex(TimeLevel); - TracerArrayH = Kokkos::subview(TracerArraysH[TimeIndex], TracerIndex, - Kokkos::ALL, Kokkos::ALL); - return 0; + return Kokkos::subview(TracerArraysH[TimeIndex], TracerIndex, Kokkos::ALL, + Kokkos::ALL); } - -I4 Tracers::getHostByName(HostArray2DReal &TracerArrayH, const I4 TimeLevel, - const std::string &TracerName) { +HostArray2DReal Tracers::getHostByName(const I4 TimeLevel, + const std::string &TracerName) { // Check if tracer exists - if (TracerIndexes.find(TracerName) == TracerIndexes.end()) { - LOG_ERROR("Tracers: Tracer '{}' does not exist", TracerName); - return -1; - } + OMEGA_REQUIRE(TracerIndexes.find(TracerName) != TracerIndexes.end(), + "Tracers: Tracer '{}' does not exist", TracerName); // Get the index of the tracer I4 TracerIndex = TracerIndexes[TracerName]; - I4 Err = getHostByIndex(TracerArrayH, TimeLevel, TracerIndex); - if (Err != 0) - return -2; - return 0; + return getHostByIndex(TimeLevel, TracerIndex); } //--------------------------------------------------------------------------- @@ -436,8 +415,7 @@ void Tracers::copyToHost(const I4 TimeLevel) { //--------------------------------------------------------------------------- I4 Tracers::exchangeHalo(const I4 TimeLevel) { - I4 Err = 0; - + I4 Err = 0; const I4 TimeIndex = getTimeIndex(TimeLevel); Err = MeshHalo->exchangeFullArrayHalo(TracerArrays[TimeIndex], OnCell); @@ -480,8 +458,6 @@ void Tracers::updateTimeLevels() { // TimeLevel == [1:new, 0:current, -1:previous, -2:two times ago, ...] //--------------------------------------------------------------------------- I4 Tracers::getTimeIndex(const I4 TimeLevel) { - - // Check if time level is valid OMEGA_REQUIRE(NTimeLevels <= 1 || !(TimeLevel > 1 || (TimeLevel + NTimeLevels) <= 1), "Tracers: Time level {} is out of range for NTimeLevels {}", diff --git a/components/omega/src/ocn/Tracers.h b/components/omega/src/ocn/Tracers.h index d641be2a3806..6480880a6235 100644 --- a/components/omega/src/ocn/Tracers.h +++ b/components/omega/src/ocn/Tracers.h @@ -120,35 +120,32 @@ class Tracers { ); /// get a device array by tracer index - static I4 getByIndex(Array2DReal &TracerArray, ///< [out] tracer device array - const I4 TimeLevel, ///< [in] time level index - const I4 TracerIndex ///< [in] global tracer index + static Array2DReal + getByIndex(const I4 TimeLevel, ///< [in] time level index + const I4 TracerIndex ///< [in] global tracer index ); /// get a device array by tracer name - static I4 - getByName(Array2DReal &TracerArray, ///< [out] tracer device array - const I4 TimeLevel, ///< [in] time level index + static Array2DReal + getByName(const I4 TimeLevel, ///< [in] time level index const std::string &TracerName ///< [in] global tracer name ); /// get a host array for all tracers - static HostArray3DReal getAllHost( ///< [out] tracer host array - const I4 TimeLevel ///< [in] time level index + static HostArray3DReal + getAllHost(const I4 TimeLevel ///< [in] time level index ); /// get a host array by tracer index - static I4 - getHostByIndex(HostArray2DReal &TracerArrayH, ///< [out] tracer host array - const I4 TimeLevel, ///< [in] time level index - const I4 TracerIndex ///< [in] global tracer index + static HostArray2DReal + getHostByIndex(const I4 TimeLevel, ///< [in] time level index + const I4 TracerIndex ///< [in] global tracer index ); /// get a host array by tracer name - static I4 - getHostByName(HostArray2DReal &TracerArrayH, ///< [out] tracer host array - const I4 TimeLevel, ///< [in] time level index - const std::string &TracerName ///< [in] global tracer name + static HostArray2DReal getHostByName( ///< [out] tracer host array + const I4 TimeLevel, ///< [in] time level index + const std::string &TracerName ///< [in] global tracer name ); /// get a field by tracer index. If not found, returns nullptr diff --git a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp index 41e6252aecda..12c53a59a436 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp @@ -29,8 +29,6 @@ void ForwardBackwardStepper::doStep( TimeInstant &SimTime // current simulation time ) const { - int Err = 0; - const int CurLevel = 0; const int NextLevel = 1; diff --git a/components/omega/test/infra/IOStreamTest.cpp b/components/omega/test/infra/IOStreamTest.cpp index a193670185f7..ff89d97f71b5 100644 --- a/components/omega/test/infra/IOStreamTest.cpp +++ b/components/omega/test/infra/IOStreamTest.cpp @@ -171,9 +171,7 @@ int main(int argc, char **argv) { // Overwrite salinity array with values associated with global cell // ID to test proper indexing of IO Array2DReal Test("Test", NCellsSize, NVertLayers); - Array2DReal Salt; - Err1 = Tracers::getByIndex(Salt, 0, Tracers::IndxSalt); - TestEval("Retrieve Salinity", Err1, ErrRef, Err); + Array2DReal Salt = Tracers::getByIndex(0, Tracers::IndxSalt); parallelFor( {NCellsSize, NVertLayers}, KOKKOS_LAMBDA(int Cell, int K) { diff --git a/components/omega/test/ocn/TracersTest.cpp b/components/omega/test/ocn/TracersTest.cpp index 502f0195bad7..ec5a6889971f 100644 --- a/components/omega/test/ocn/TracersTest.cpp +++ b/components/omega/test/ocn/TracersTest.cpp @@ -302,14 +302,7 @@ int main(int argc, char *argv[]) { std::string TracerName; Tracers::getName(TracerName, Tracer); - Array2DReal CurTracer; - Err = Tracers::getByName(CurTracer, 0, TracerName); - if (Err != 0) { - LOG_ERROR("getByName(CurTracer, 0, TracerName) returns non-zero " - "code: {}", - Err); - RetVal += 1; - } + Array2DReal CurTracer = Tracers::getByName(0, TracerName); count = -1; @@ -402,11 +395,10 @@ int main(int argc, char *argv[]) { count = 0; - Array2DReal SaltTracerByName; - Err = Tracers::getByName(SaltTracerByName, 1, "Salinity"); + Array2DReal SaltTracerByName = Tracers::getByName(1, "Salinity"); - Array2DReal SaltTracerByIndexVar; - Err = Tracers::getByIndex(SaltTracerByIndexVar, 1, Tracers::IndxSalt); + Array2DReal SaltTracerByIndexVar = + Tracers::getByIndex(1, Tracers::IndxSalt); count = -1; @@ -436,14 +428,7 @@ int main(int argc, char *argv[]) { std::string TracerName; Tracers::getName(TracerName, Tracer); - HostArray2DReal TestHostArray; - Err = Tracers::getHostByName(TestHostArray, 1, TracerName); - if (Err != 0) { - LOG_ERROR("getHostByName(TestHostArray, 1, TracerName) returns " - "non-zero code: {}", - Err); - RetVal += 1; - } + HostArray2DReal TestHostArray = Tracers::getHostByName(1, TracerName); for (I4 Cell = 0; Cell < NCellsOwned; Cell++) { for (I4 Vert = 0; Vert < NVertLayers; Vert++) { From 015c2a4b0c7e712bd20b990570d593d8544a24c5 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Fri, 20 Feb 2026 11:32:12 -0700 Subject: [PATCH 006/152] Update Tracers dev docs --- components/omega/doc/devGuide/Tracers.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/components/omega/doc/devGuide/Tracers.md b/components/omega/doc/devGuide/Tracers.md index 650e58fb1091..5bb55c1e158a 100644 --- a/components/omega/doc/devGuide/Tracers.md +++ b/components/omega/doc/devGuide/Tracers.md @@ -87,7 +87,6 @@ auto [StartIndex, GroupLength] = GroupRange; // Get all tracers at the current time level (0) Array3DReal TracerArray = OMEGA::Tracers::getAll(0); - OMEGA::parallelFor( "ComputeGroupTendency", {GroupLength, NCells, NVertLayers}, @@ -126,8 +125,9 @@ OMEGA itself, so users may not need to call them separately. ### `getAll` and `getAllHost` -These functions return all device and host tracer arrays, respectively. If -the specified `TimeLevel` does not exist, they will abort via `OMEGA_REQUIRE`. +These functions return all device and host tracer arrays, respectively. +If the specified `TimeLevel` does not exist, the program will abort with +an error message. ```c++ static HostArray3DReal getAllHost( @@ -155,18 +155,16 @@ static I4 getGroupRange( ### `getByIndex` and `getHostByIndex` These functions return device and host tracer arrays, respectively, based -on the `TracerIndex`. If the specified `TimeLevel` is invalid, they will abort -via `OMEGA_REQUIRE`. If the `TracerIndex` is invalid, they return a negative error code. +on the `TracerIndex`. If the specified `TimeLevel` and/or `TracerIndex` +does not exist, the program will abort with an error message. ```c++ -static I4 getByIndex( - Array2DReal &TracerArray, ///< [out] tracer device array +static Array2DReal getByIndex( const I4 TimeLevel, ///< [in] Time level index const I4 TracerIndex ///< [in] Global tracer index ); -static I4 getHostByIndex( - HostArray2DReal &TracerArrayH, ///< [out] tracer host array +static HostArray2DReal getHostByIndex( const I4 TimeLevel, ///< [in] Time level index const I4 TracerIndex ///< [in] Global tracer index ); From 91df52a28a27246af2ccd7d42e2ea85af4997872 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Fri, 20 Feb 2026 11:36:30 -0700 Subject: [PATCH 007/152] Fix whitespace --- components/omega/doc/devGuide/OceanState.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/doc/devGuide/OceanState.md b/components/omega/doc/devGuide/OceanState.md index b06ae4d5d943..1d4463ef55b5 100644 --- a/components/omega/doc/devGuide/OceanState.md +++ b/components/omega/doc/devGuide/OceanState.md @@ -68,7 +68,7 @@ for the device arrays and HostArray2DReal LayerThickH = State->getLayerThicknessH(TimeLevel); HostArray2DReal NormVelH = State->getNormalVelocityH(TimeLevel); ``` -for the host arrays. These functions return the arrays directly and will abort +for the host arrays. These functions return the arrays directly and will abort via `OMEGA_REQUIRE` if an invalid `TimeLevel` is provided. The time level convention is: From b0d37f87c1dc4d17fb52144536a4bd2fb2f61ab7 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 11 Dec 2025 13:56:16 +0100 Subject: [PATCH 008/152] Fix incorrect $\rho_0$ multipliers up to (37) Surface flux terms, body forces and pressure terms were all getting multiplied by $\rho_0$, leading to incorrect dimensions and inconsistency with the rest of the derivation. --- .../omega/doc/design/OmegaV1GoverningEqns.md | 112 ++++++++++-------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/components/omega/doc/design/OmegaV1GoverningEqns.md b/components/omega/doc/design/OmegaV1GoverningEqns.md index 2a972016e58d..ef1b34588420 100644 --- a/components/omega/doc/design/OmegaV1GoverningEqns.md +++ b/components/omega/doc/design/OmegaV1GoverningEqns.md @@ -207,7 +207,7 @@ To facilitate integration over a fixed horizontal domain, we introduce the follo - $A$ is the horizontal footprint (in the $x$–$y$ plane) of the control volume $V(t)$. - $dA$ is the horizontal area element. - $\partial A$ is the boundary of $A$, and $dl$ is the line element along this boundary. -- ${\bf n}_\perp$ is the outward-pointing unit normal vector in the horizontal plane, defined on $\partial A$. It lies in the $x$–$y$ plane and is orthogonal to $dl$. +- $\mathbf{n}_\perp$ is the outward-pointing unit normal vector in the horizontal plane, defined on $\partial A$. It lies in the $x$–$y$ plane and is orthogonal to $dl$. We now project the tracer equation [](#tr-vh-split-pseudo) onto a horizontal domain $A$ (the footprint of the control volume), over which the top and bottom boundaries vary in height. The side walls remain fixed in time and space, but not vertical extent. @@ -250,7 +250,7 @@ $\tilde{W}_{tr}$ will be introduced and defined in [](#notational-simplification $$ \frac{d}{dt} \int_A \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \, \varphi \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \varphi \, {\bf u} \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl \\ +& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \varphi \, {\bf u} \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ & + \int_A \rho_0 \left\{\varphi \left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{top}}} dA \\ & - \int_A \rho_0 \left\{\varphi \left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{bot}}} dA = 0 @@ -266,9 +266,9 @@ Similar expressions to [](#tr-vh-separation-pseudo) hold for mass and momentum: $$ \frac{d}{dt} \int_A \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \, {\bf u} \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl \\ -& + \int_A \rho_0 \left\{\left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{top}}} dA \\ -& - \int_A \rho_0 \left\{\left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{bot}}} dA +& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \, {\bf u} \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ +& + \int_A \left\{\left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{top}}} dA \\ +& - \int_A \left\{\left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{bot}}} dA = 0 $$ (vh-mass-pseudo) @@ -276,9 +276,9 @@ $$ (vh-mass-pseudo) $$ \frac{d}{dt} \int_A \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \varphi \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \varphi \, {\bf u} \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl & \\ -& + \int_A \rho_0 \left\{\varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}_{\tilde{z} = \tilde{z}^{\text{top}}} \, dA & \\ -& - \int_A \rho_0 \left\{\varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}_{\tilde{z} = \tilde{z}^{\text{bot}}}\, dA +& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \varphi \, {\bf u} \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl & \\ +& + \int_A \left\{\varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}_{\tilde{z} = \tilde{z}^{\text{top}}} \, dA & \\ +& - \int_A \left\{\varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}_{\tilde{z} = \tilde{z}^{\text{bot}}}\, dA & = 0 $$ (vh-tracer-pseudo) @@ -287,15 +287,15 @@ $$ (vh-tracer-pseudo) $$ \frac{d}{dt} \int_{A} \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \, {\bf u} \, d\tilde{z} \, dA &+ -\int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \, {\bf u} \otimes {\bf u} \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl \\ +\int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \, {\bf u} \otimes {\bf u} \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ &+ -\int_A \rho_0 \, \left\{{\bf u} \left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{top}}} \, dA +\int_A \, \left\{{\bf u} \left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{top}}} \, dA - -\int_A \rho_0 \, \left\{{\bf u} \left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{bot}}} \, dA \\ +\int_A \, \left\{{\bf u} \left[ \tilde{w}_{tr} - \tilde{ u} \right]\right\}_{\tilde{z} = \tilde{z}^{\text{bot}}} \, dA \\ &= \int_A \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} {\bf b}_{\perp} \, d\tilde{z} \, dA + -\int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \frac{{\bf \tau}}{\rho} \, d\tilde{z} \right) {\bf n}_\perp dl \\ +\int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \frac{{\bf \tau}}{\rho} \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ &\quad + \int_A \left[ \frac{{\bf \tau}}{\rho} \right]_{\tilde{z} = \tilde{z}^{\text{top}}} \, dA - \int_A \left[ \frac{{\bf \tau}}{\rho} \right]_{\tilde{z} = \tilde{z}^{\text{bot}}} \, dA @@ -404,9 +404,9 @@ We first Reynolds' average [](#vh-tracer-pseudo) and given the definition of the $$ \frac{d}{dt} \int_A \int_{\tilde{z}_{k+1}^{\text{top}}}^{\tilde{z}_k^{\text{top}}} \left<\varphi\right> \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}_{k+1}^{\text{bot}}}^{\tilde{z}_k^{\text{bot}}} \left<\varphi \, {\bf u} \right> \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl & \\ -& + \int_A \left<\left\{ \rho_0 \varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}\right>_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ -& - \int_A \left<\left\{ \rho_0 \varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}\right>_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA +& + \int_{\partial A} \left( \int_{\tilde{z}_{k+1}^{\text{bot}}}^{\tilde{z}_k^{\text{bot}}} \left<\varphi \, {\bf u} \right> \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl & \\ +& + \int_A \left<\left\{ \varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}\right>_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ +& - \int_A \left<\left\{ \varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}\right>_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA & = 0. $$ (Aintegral-tracer-first-reynolds) @@ -415,9 +415,9 @@ Next, we use a Reynolds' decomposition on any terms involving products inside a $$ \frac{d}{dt} \int_A \int_{\tilde{z}_{k}^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left<\varphi \right> \, d\tilde{z} \, dA & + -\int_{\partial A} \left( \int_{\tilde{z}_{k}^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left(\left<{\varphi}\right>\left<{\bf u}\right> + \left<\varphi^{\prime}{\bf u}^{\prime}\right> \right) d\tilde{z} \right) \, \cdot {\bf n}_\perp \, dl & \\ -& + \int_A \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> -\left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ -& - \int_A \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA +\int_{\partial A} \left( \int_{\tilde{z}_{k}^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left(\left<{\varphi}\right>\left<{\bf u}\right> + \left<\varphi^{\prime}{\bf u}^{\prime}\right> \right) d\tilde{z} \right) \, \cdot \mathbf{n}_\perp \, dl & \\ +& + \int_A \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> -\left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ +& - \int_A \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA & = 0. $$ (Aintegral-tracer-reynolds) @@ -425,9 +425,9 @@ We can rewrite the first two terms in [](#Aintegral-tracer-reynolds) using [](#d $$ \frac{d}{dt} \int_A \tilde{h}_k \, \overline{\left<\varphi\right>}^{\tilde{z}}_k \, dA -& + \int_{\partial A} \left( \tilde{h}_k \, \left[\overline{\left<\varphi\right> \left<{\bf u}\right> + \left<\varphi^{\prime}{\bf u}^{\prime}\right>}^{\tilde{z}}_k \right] \right) \cdot {\bf n}_\perp \, dl & \\ -& + \int_A \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> -\left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ -& - \int_A \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA +& + \int_{\partial A} \left( \tilde{h}_k \, \left[\overline{\left<\varphi\right> \left<{\bf u}\right> + \left<\varphi^{\prime}{\bf u}^{\prime}\right>}^{\tilde{z}}_k \right] \right) \cdot \mathbf{n}_\perp \, dl & \\ +& + \int_A \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> -\left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ +& - \int_A \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA & = 0. $$ (Aintegral-tracer) @@ -435,9 +435,9 @@ The second term is expanded utilizing [](#vertical-decomposition) $$ \frac{d}{dt} \int_A \tilde{h}_k \, \overline{\left<\varphi\right>}^{\tilde{z}}_k \, dA -& + \int_{\partial A} \left( \tilde{h}_k \, \overline{\left(\overline{\left<\varphi\right>}^{\tilde{z}}_k + \delta \varphi\right)\left(\overline{\left<{\bf u}\right>}^{\tilde{z}}_k + \delta {\bf u}\right)}^{\tilde{z}}_k + \tilde{h}_k \overline{\left<\varphi^{\prime}{\bf u}^{\prime}\right>}^{\tilde{z}}_k \right) \cdot {\bf n}_\perp \, dl & \\ -& + \int_A \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> -\left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ -& - \int_A \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA +& + \int_{\partial A} \left( \tilde{h}_k \, \overline{\left(\overline{\left<\varphi\right>}^{\tilde{z}}_k + \delta \varphi\right)\left(\overline{\left<{\bf u}\right>}^{\tilde{z}}_k + \delta {\bf u}\right)}^{\tilde{z}}_k + \tilde{h}_k \overline{\left<\varphi^{\prime}{\bf u}^{\prime}\right>}^{\tilde{z}}_k \right) \cdot \mathbf{n}_\perp \, dl & \\ +& + \int_A \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> -\left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ +& - \int_A \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA & = 0. $$ (Aintegral-tracer2) @@ -445,9 +445,9 @@ And then is simplified using [](#delta-vert-average) $$ \frac{d}{dt} \int_A \tilde{h}_k \, \overline{\left<\varphi\right>}^{\tilde{z}}_k \, dA -& + \int_{\partial A} \left[ \tilde{h}_k \, \left(\overline{\left<\varphi\right>}^{\tilde{z}}_k \overline{\left<{\bf u}\right>}^{\tilde{z}}_k + \overline{\delta \varphi \delta {\bf u}}^{\tilde{z}}_k + \overline{\left<\varphi^{\prime}{\bf u}^{\prime}\right>}^{\tilde{z}}_k \right)\right] \cdot {\bf n}_\perp \, dl & \\ -& + \int_A \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> -\left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ -& - \int_A \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA +& + \int_{\partial A} \left[ \tilde{h}_k \, \left(\overline{\left<\varphi\right>}^{\tilde{z}}_k \overline{\left<{\bf u}\right>}^{\tilde{z}}_k + \overline{\delta \varphi \delta {\bf u}}^{\tilde{z}}_k + \overline{\left<\varphi^{\prime}{\bf u}^{\prime}\right>}^{\tilde{z}}_k \right)\right] \cdot \mathbf{n}_\perp \, dl & \\ +& + \int_A \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> -\left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ +& - \int_A \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA & = 0. $$ @@ -456,8 +456,8 @@ Given that the horizontal area is not changing in time, we can move the time der $$ \int_A \bigl\{ \frac{\partial \tilde{h}_k \, \overline{\left<\varphi\right>}^{\tilde{z}}_k}{\partial t} & + \nabla \cdot \left[ \tilde{h}_k \, \left(\overline{\left<\varphi\right>}^{\tilde{z}}_k \overline{\left<{\bf u}\right>}^{\tilde{z}}_k + \overline{\delta \varphi \delta {\bf u}}^{\tilde{z}}_k + \overline{\left<\varphi^{\prime}{\bf u}^{\prime}\right>}^{\tilde{z}}_k \right)\right] & \\ -& + \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} & \\ -& - \left\{ \rho_0 \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \bigr\} \, dA +& + \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{top}}} & \\ +& - \left\{ \left[ \left<\varphi\right>\left<\tilde{w}_{tr}\right> + \left<\varphi^{\prime} \tilde{w}_{tr}^\prime \right> - \left<\varphi\right> \left<\tilde{ u}\right> - \left<\varphi^{\prime} \tilde{ u}^{\prime}\right> \right] \right\}_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \bigr\} \, dA & = 0. $$ (reynolds-tracer-final) @@ -470,9 +470,9 @@ Finally, given that the area integral operates on the entire equation, it is val $$ \frac{\partial {\tilde h}_k \overline{\left<\varphi\right>}^{\tilde{z}}_k }{\partial t} & + \nabla \cdot \left({\tilde h}_k \overline{\left<\varphi\right>}^{\tilde{z}}_k \overline{\left<{\bf u}\right>}^{\tilde{z}}_k\right) \\ -& + \rho_0 \left\{ \left[ \left<\varphi\right> \left<{\tilde w}_{tr}\right> - \left<\varphi\right>\left<\tilde{ u}\right> \right]_{{\tilde z}={\tilde z}_k^{\text{top}}} - \left[ \left<\varphi\right> \left<{\tilde w}_{tr}\right> - \left<\varphi\right>\left<\tilde{ u}\right> \right]_{{\tilde z}={\tilde z}_k^{\text{bot}}} \right\} \\ +& + \left\{ \left[ \left<\varphi\right> \left<{\tilde w}_{tr}\right> - \left<\varphi\right>\left<\tilde{ u}\right> \right]_{{\tilde z}={\tilde z}_k^{\text{top}}} - \left[ \left<\varphi\right> \left<{\tilde w}_{tr}\right> - \left<\varphi\right>\left<\tilde{ u}\right> \right]_{{\tilde z}={\tilde z}_k^{\text{bot}}} \right\} \\ & = - \nabla \cdot \left({\tilde h}_k \overline{\left<\varphi^{\prime} {\bf u}^{\prime}\right>}^{\tilde{z}}_k \, + {\tilde h}_k \overline{\delta \varphi \delta {\bf u}}^{\tilde{z}}_k \right) \\ -& - \rho_0 \left\{ \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{top}}} - \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}}\right\}. +& - \left\{ \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{top}}} - \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}}\right\}. $$ (layer-tracer) A few notes on the layer-averaged tracer equation. In this complete form, it includes three types of fluctuating quantities that must be dealt with: (1) the layer averaged, Reynolds' averaged horizontal turbulent flux $\left( \overline{\left<\varphi^{\prime} {\bf u}^{\prime}\right>}^{\tilde{z}}_k \right)$, (2) the Reynolds' average vertical turbulent flux $\left( \left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{u}^{\prime}\right> \right)$, and (3) the layer-averaged product of deviations from the layer-integrated variables $\left(\overline{\delta \varphi \delta {\bf u}}^{\tilde{z}}_k \right)$. The details of the first two quantities will be discussed later in this document and follow-on design documents. The terms involving perturbations from the layer integrated quantity are necessary to extend beyond piecewise constant represenation of variables. In this equation, variables with no overline are the full field variable at the interfaces. @@ -484,8 +484,8 @@ The mass equation is identical to the tracer equation with $\varphi=1$. $$ \frac{\partial {\tilde h}_k }{\partial t} + \nabla \cdot \left({\tilde h}_k \overline{\left<{\bf u}\right>}^{\tilde{z}}_k\right) -+ \rho_0 \left[ \left<{\tilde w}_{tr}\right> - \left<\tilde{ u}\right> \right]_{{\tilde z}={\tilde z}_k^{\text{top}}} -- \rho_0 \left[ \left<{\tilde w}_{tr}\right> - \left<\tilde{ u}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}} ++ \left[ \left<{\tilde w}_{tr}\right> - \left<\tilde{ u}\right> \right]_{{\tilde z}={\tilde z}_k^{\text{top}}} +- \left[ \left<{\tilde w}_{tr}\right> - \left<\tilde{ u}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}} = 0. $$ (layer-mass) @@ -493,17 +493,29 @@ $$ (layer-mass) We now derive the horizontal momentum equation in our non-Boussinesq, hydrostatic framework, following the same finite-volume approach used for mass and tracer conservation. -We begin from [](#vh-momentum-pseudo) and specify the body forces ${\bf b}$ and surface forces ${\bf \tau}$ +We specify the body forces: + +$$ +\mathbf{b}_{\perp} = {\bf f} \times \mathbf{u} + \nabla \Phi +$$ (body-forces) + +and surface force: + +$$ +\tau = p +$$ (surface-forces) + +Substituting these into [](#vh-momentum-pseudo), we arrive at: $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} {\bf u} \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} {\bf u} \otimes {\bf u} \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl \\ -& + \int_A \rho_0 \, {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA -- \int_A \rho_0 \, {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ -& = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \left({\bf f} \times \mathbf{u} + \nabla \Phi \right) \, d\tilde{z} \, dA -- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \frac{\rho_0 p}{\rho} \, d\tilde{z} \right) {\bf n}_\perp dl \\ -& - \int_A \left[ \frac{\rho_0 p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA -+ \int_A \left[ \frac{\rho_0 p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} {\bf u} \otimes {\bf u} \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ +& + \int_A \, {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA +- \int_A \, {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ +& = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left({\bf f} \times \mathbf{u} + \nabla \Phi \right) \, d\tilde{z} \, dA +- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \frac{p}{\rho} \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ +& - \int_A \left[ \frac{p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA ++ \int_A \left[ \frac{p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-forces) In this equation: @@ -517,11 +529,11 @@ As with the tracer derivation, we next Reynolds' average [](#vh-momentum-forces) $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \right> \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \otimes {\bf u} \right> \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl \\ +& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \otimes {\bf u} \right> \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ & + \int_A \rho_0 \,\left< {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \right> \, dA - \int_A \rho_0 \, \left< {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right> \, dA \\ & = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \, \left<\left( {\bf f} \times \mathbf{u} + \nabla \Phi \right) \right> \, d\tilde{z} \, dA -- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \left< \frac{p}{\rho} \right> \, d\tilde{z} \right) {\bf n}_\perp dl \\ +- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \left< \frac{p}{\rho} \right> \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ & - \int_A \rho_0 \left[ \left< \frac{p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA + \int_A \rho_0 \left[ \left< \frac{p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-reynolds1) @@ -530,11 +542,11 @@ Here we have also moved the Reynolds' average through the spatial integrals give $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \right> \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}^{\text{top}}} \left( \left< {\bf u} \right> \otimes \left< {\bf u} \right> + \left< {\bf u}^\prime \otimes {\bf u}^\prime \right> \right) \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl \\ +& + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}^{\text{top}}} \left( \left< {\bf u} \right> \otimes \left< {\bf u} \right> + \left< {\bf u}^\prime \otimes {\bf u}^\prime \right> \right) \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ & + \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & - \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ & = - \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \, \left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right) \, d\tilde{z} \, dA \\ -& - \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \left(\left< \alpha \right> \left

+ \left<\alpha^\prime p^\prime\right> \right) \, d\tilde{z} \right) {\bf n}_\perp dl \\ +& - \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \left(\left< \alpha \right> \left

+ \left<\alpha^\prime p^\prime\right> \right) \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ & - \int_A \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}}\right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & + \int_A \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}}\right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-reynolds2) @@ -543,11 +555,11 @@ In [](#vh-momentum-reynolds2) we have also used $\alpha = \frac{1}{\rho}$ for no $$ \frac{d}{dt} \int_A \tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \, dA -& + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right> \otimes \left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot {\bf n}_\perp \, dl \\ +& + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right> \otimes \left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot \mathbf{n}_\perp \, dl \\ & + \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & - \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ & = - \int_A \rho_0 \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA \\ -& - \int_{\partial A} \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right> \left

}^{\tilde{z}}_k + \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) {\bf n}_\perp dl \\ +& - \int_{\partial A} \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right> \left

}^{\tilde{z}}_k + \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \mathbf{n}_\perp dl \\ & - \int_A \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & + \int_A \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-reynolds-lay-avg) @@ -556,11 +568,11 @@ The next step is to decompose vertical averages of products. This yields $$ \frac{d}{dt} \int_{A} \tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \, dA -& + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot {\bf n}_\perp \, dl \\ +& + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot \mathbf{n}_\perp \, dl \\ & + \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & - \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ & = - \int_A \rho_0 \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA \\ -& - \int_{\partial A} \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) {\bf n}_\perp dl \\ +& - \int_{\partial A} \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \mathbf{n}_\perp dl \\ & - \int_A \rho_0 \left[\left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & + \int_A \rho_0 \left[\left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-reynolds-lay-avg2) @@ -699,7 +711,7 @@ In [](#layer-tracer-final-simple) and [](#layer-momentum-final-simple) we have n ## 10. Discrete Equations -In the following equations, the subscripts $i$, $e$, and $v$ indicate cell, edge, and vertex locations and subscript $k$ is the layer. Square brackets $[\cdot]_e$ and $[\cdot]_v$ are quantities that are interpolated to edge and vertex locations. For vector quantities, $u_{e,k}$ denotes the normal component at the center of the edge (where the normal component is the local ${\bf n}_\perp$ described in above sections), while $u_{e,k}^\perp$ denotes the tangential component. We have switched from $\varphi_{i,k}^{bot}$ to the identical $\varphi_{i,k+1}^{top}$ for all variables in order for the notation to match the array names in the code. It is important to note that any term without a subscript $k$ are quantities evaluated at that location and are ***not*** individual layer averages, but will be reconstructed as a function of neighboring layer average values. For example, Eq (44) of [White and Adcroft (2008)](https://www.sciencedirect.com/science/article/pii/S0021999108002593) shows a third order reconstruction. Finally, we note that in the equations below, it is assumed that $k$ increases away from the surface. +In the following equations, the subscripts $i$, $e$, and $v$ indicate cell, edge, and vertex locations and subscript $k$ is the layer. Square brackets $[\cdot]_e$ and $[\cdot]_v$ are quantities that are interpolated to edge and vertex locations. For vector quantities, $u_{e,k}$ denotes the normal component at the center of the edge (where the normal component is the local $\mathbf{n}_\perp$ described in above sections), while $u_{e,k}^\perp$ denotes the tangential component. We have switched from $\varphi_{i,k}^{bot}$ to the identical $\varphi_{i,k+1}^{top}$ for all variables in order for the notation to match the array names in the code. It is important to note that any term without a subscript $k$ are quantities evaluated at that location and are ***not*** individual layer averages, but will be reconstructed as a function of neighboring layer average values. For example, Eq (44) of [White and Adcroft (2008)](https://www.sciencedirect.com/science/article/pii/S0021999108002593) shows a third order reconstruction. Finally, we note that in the equations below, it is assumed that $k$ increases away from the surface. **Mass:** From 5037c7337274870a3f016769189546b16e41096f Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 11 Dec 2025 14:59:45 +0100 Subject: [PATCH 009/152] More fixes to the momentum derivation --- .../omega/doc/design/OmegaV1GoverningEqns.md | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/components/omega/doc/design/OmegaV1GoverningEqns.md b/components/omega/doc/design/OmegaV1GoverningEqns.md index ef1b34588420..8f3097e2a75c 100644 --- a/components/omega/doc/design/OmegaV1GoverningEqns.md +++ b/components/omega/doc/design/OmegaV1GoverningEqns.md @@ -89,7 +89,7 @@ If the control surface moves with the fluid in a Lagrangian fashion, then ${\bf The momentum equation is an expression of Newton's second law and includes two types of external forces. The first is the body force, represented here as ${\bf b}({\bf x}, t)$, which encompasses any volumetric force acting throughout the fluid, such as gravitational acceleration or the Coriolis force. In some contexts, body forces may be expressible as the gradient of a potential, ${\bf b} = -\nabla_{3D} \Phi$, but this is not assumed in general. -The second is the surface force ${\bf \tau}$, which acts on the boundary of the control volume and includes pressure and viscous stresses. These forces appear as surface integrals over the boundary and drive momentum exchange between adjacent fluid parcels or between the fluid and its environment. +The second is the surface force or traction vector ${\bf \tau} = \sigma \cdot {\bf n}$, which acts on the boundary of the control volume and includes pressure and viscous stresses. These forces appear as surface integrals over the boundary and drive momentum exchange between adjacent fluid parcels or between the fluid and its environment. The derivation of the momentum equation may also be found in [Leishman 2025](https://eaglepubs.erau.edu/introductiontoaerospaceflightvehicles/chapter/conservation-of-momentum-momentum-equation/#chapter-260-section-2), Chapter 21, equation 10. ## 4. Hydrostatic Approximation @@ -295,13 +295,15 @@ $$ &= \int_A \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} {\bf b}_{\perp} \, d\tilde{z} \, dA + -\int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \frac{{\bf \tau}}{\rho} \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ +\int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}^{\text{top}}} \frac{{\bf \tau}_\perp}{\rho} \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ &\quad -+ \int_A \left[ \frac{{\bf \tau}}{\rho} \right]_{\tilde{z} = \tilde{z}^{\text{top}}} \, dA -- \int_A \left[ \frac{{\bf \tau}}{\rho} \right]_{\tilde{z} = \tilde{z}^{\text{bot}}} \, dA ++ \int_A \left[ \frac{{\bf \tau}_\perp}{\rho} \right]_{\tilde{z} = \tilde{z}^{\text{top}}} \, dA +- \int_A \left[ \frac{{\bf \tau}_\perp}{\rho} \right]_{\tilde{z} = \tilde{z}^{\text{bot}}} \, dA $$ (vh-momentum-pseudo) -In [](#vh-momentum-pseudo), $\mathbf{b}_\perp$ is defined similarly to $\mathbf{n}_\perp$, it is the body force normal to $dl$. These equations express horizontal and vertical fluxes naturally in terms of pseudo-height, enabling fully consistent discretization in the vertical coordinate used for prognostic evolution. +Here, we have divided by $\rho_0$, since it occurs in every term. + +In [](#vh-momentum-pseudo), $\mathbf{b}_\perp$ is defined similarly to $\mathbf{n}_\perp$, it is the body force normal to $dl$, and ${\bf \tau}_\perp$ is the horizontal component of the traction vector. These equations express horizontal and vertical fluxes naturally in terms of pseudo-height, enabling fully consistent discretization in the vertical coordinate used for prognostic evolution. ## 7. Vertical Discretization for the Layered Equations @@ -496,15 +498,23 @@ We now derive the horizontal momentum equation in our non-Boussinesq, hydrostati We specify the body forces: $$ -\mathbf{b}_{\perp} = {\bf f} \times \mathbf{u} + \nabla \Phi +\mathbf{b}_{\perp} = - \left({\bf f} \times \mathbf{u} + \nabla \Phi \right). $$ (body-forces) -and surface force: +We neglect viscous stresses and take the stress tensor to be purely isotropic pressure, so the traction vector on any surface is $$ -\tau = p +{\bf \tau} = - p \mathbf{n}, +$$ (surface-forces-perp) + +where $\mathbf{n}$ is the outward unit normal. In the horizontally projected momentum equation, only the horizontal component appears: + +$$ +{\bf \tau}_\perp = - p \mathbf{n}_\perp, $$ (surface-forces) +where $\mathbf{n}_\perp$ is the horizontal projection of $\mathbf{n}$. On vertical side walls, $\mathbf{n}_\perp = \mathbf{n}$; on sloping top and bottom interfaces, $\mathbf{n}_\perp \approx - \nabla \tilde{z}$. + Substituting these into [](#vh-momentum-pseudo), we arrive at: $$ @@ -513,9 +523,9 @@ $$ & + \int_A \, {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA - \int_A \, {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ & = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left({\bf f} \times \mathbf{u} + \nabla \Phi \right) \, d\tilde{z} \, dA -- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \frac{p}{\rho} \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ -& - \int_A \left[ \frac{p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA -+ \int_A \left[ \frac{p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \frac{p}{\rho} \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ +& + \int_A \left[ \frac{p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA +- \int_A \left[ \frac{p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-forces) In this equation: From 32dfeaf6e8f8be83af32b0f8a8d753734c5c9a65 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 11 Dec 2025 18:44:07 +0100 Subject: [PATCH 010/152] Clarify the horizontal gradient of Phi --- components/omega/doc/design/OmegaV1GoverningEqns.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/omega/doc/design/OmegaV1GoverningEqns.md b/components/omega/doc/design/OmegaV1GoverningEqns.md index 8f3097e2a75c..6ef413ba0928 100644 --- a/components/omega/doc/design/OmegaV1GoverningEqns.md +++ b/components/omega/doc/design/OmegaV1GoverningEqns.md @@ -501,6 +501,12 @@ $$ \mathbf{b}_{\perp} = - \left({\bf f} \times \mathbf{u} + \nabla \Phi \right). $$ (body-forces) +The first term on the right hand side is the **Coriolis force**, where $ \mathbf{f} $ is the vector Coriolis vector (e.g., $ f \hat{\mathbf{z}} $ on the sphere). + +The second term on the right hand side represents the **gravitational force**, expressed in terms of the two-dimensional along-layer gradient of the gravitational potential $ \Phi(x, y, z, t) $, which may include effects such as tides and self-attraction and loading. Because layers may tilt, this gradient is not taken at constant $z$ or $\tilde{z}$; instead it is the projection of $\nabla_{3D}\Phi$ onto the tangent plane of the layer surface. + +Going forward, $\nabla$ without a subscript will be used to represent along-layer gradients. + We neglect viscous stresses and take the stress tensor to be purely isotropic pressure, so the traction vector on any surface is $$ @@ -530,8 +536,7 @@ $$ (vh-momentum-forces) In this equation: -- The first term on the right hand side is the **Coriolis force**, where $ \mathbf{f} $ is the vector Coriolis vector (e.g., $ f \hat{\mathbf{z}} $ on the sphere). -- The second term on the right hand side represents the **gravitational force**, expressed in terms of the two-dimensional gradient of the gravitational potential $ \Phi(x, y, z, t) $, which may include effects such as tides and self-attraction and loading. This gradient is along layer (e.g. constant $\tilde{z}$) such that the gradient of $\Phi$ doesn't disappear. +- The first two terms are the body forces explained above. - The final terms are the **pressure force**, which acts on the boundary surfaces and is naturally expressed as a surface integral. It gives rise to both horizontal pressure gradients and contributions from sloping surfaces. - A negative sign is included for the pressure terms derived from the surface force terms. According to [Leishman 2025](https://eaglepubs.erau.edu/introductiontoaerospaceflightvehicles/chapter/conservation-of-momentum-momentum-equation/#chapter-260-section-2), Chapter 21, equation 2, "the negative sign indicates that the force is inward and in the opposite direction to the unit normal vector area, which always points *outward* by convention". Also see discussion in [Kundu et al. 2016](https://doi.org/10.1016/C2012-0-00611-4) page 104 and equation 4.26. From 4d2feb247b997bd8888188b07b5caf36f2406c58 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 11 Dec 2025 20:36:38 +0100 Subject: [PATCH 011/152] Fix more in Layer Equations and Discrete Equations --- .../omega/doc/design/OmegaV1GoverningEqns.md | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/components/omega/doc/design/OmegaV1GoverningEqns.md b/components/omega/doc/design/OmegaV1GoverningEqns.md index 6ef413ba0928..073931873d22 100644 --- a/components/omega/doc/design/OmegaV1GoverningEqns.md +++ b/components/omega/doc/design/OmegaV1GoverningEqns.md @@ -405,8 +405,8 @@ where the last equality is true by definition. We first Reynolds' average [](#vh-tracer-pseudo) and given the definition of the operator we can move the averaging past the derivatives and integrals without correction terms to yield $$ -\frac{d}{dt} \int_A \int_{\tilde{z}_{k+1}^{\text{top}}}^{\tilde{z}_k^{\text{top}}} \left<\varphi\right> \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}_{k+1}^{\text{bot}}}^{\tilde{z}_k^{\text{bot}}} \left<\varphi \, {\bf u} \right> \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl & \\ +\frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left<\varphi\right> \, d\tilde{z} \, dA +& + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left<\varphi \, {\bf u} \right> \, d\tilde{z} \right) \cdot {\bf n}_\perp \, dl & \\ & + \int_A \left<\left\{ \varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}\right>_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA & \\ & - \int_A \left<\left\{ \varphi \left[\tilde{w}_{tr} - \tilde{ u} \right] \right\}\right>_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\, dA & = 0. @@ -544,52 +544,52 @@ As with the tracer derivation, we next Reynolds' average [](#vh-momentum-forces) $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \right> \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \otimes {\bf u} \right> \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ -& + \int_A \rho_0 \,\left< {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \right> \, dA -- \int_A \rho_0 \, \left< {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right> \, dA \\ -& = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \, \left<\left( {\bf f} \times \mathbf{u} + \nabla \Phi \right) \right> \, d\tilde{z} \, dA -- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \left< \frac{p}{\rho} \right> \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ -& - \int_A \rho_0 \left[ \left< \frac{p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA -+ \int_A \rho_0 \left[ \left< \frac{p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \otimes {\bf u} \right> \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ +& + \int_A \,\left< {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \right> \, dA +- \int_A \, \left< {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right> \, dA \\ +& = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \, \left< {\bf f} \times \mathbf{u} + \nabla \Phi \right> \, d\tilde{z} \, dA +- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< \frac{p}{\rho} \right> \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ +& + \int_A \left[ \left< \frac{p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA +- \int_A \left[ \left< \frac{p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-reynolds1) Here we have also moved the Reynolds' average through the spatial integrals given the properties of the averaging. Next we do a Reynolds' decomposition, this yields $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \right> \, d\tilde{z} \, dA -& + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}^{\text{top}}} \left( \left< {\bf u} \right> \otimes \left< {\bf u} \right> + \left< {\bf u}^\prime \otimes {\bf u}^\prime \right> \right) \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ -& + \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& - \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ -& = - \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \, \left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right) \, d\tilde{z} \, dA \\ -& - \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \rho_0 \left(\left< \alpha \right> \left

+ \left<\alpha^\prime p^\prime\right> \right) \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ -& - \int_A \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}}\right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& + \int_A \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}}\right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left( \left< {\bf u} \right> \otimes \left< {\bf u} \right> + \left< {\bf u}^\prime \otimes {\bf u}^\prime \right> \right) \, d\tilde{z} \right) \cdot \mathbf{n}_\perp dl \, dl \\ +& + \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ +& - \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ +& = - \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \, \left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right) \, d\tilde{z} \, dA \\ +& - \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left(\left< \alpha \right> \left

+ \left<\alpha^\prime p^\prime\right> \right) \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ +& + \int_A \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}}\right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ +& - \int_A \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}}\right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-reynolds2) In [](#vh-momentum-reynolds2) we have also used $\alpha = \frac{1}{\rho}$ for notation conciseness. The definition of the layer average [](#def-layer-average-reynolds) is now utilized on terms with vertical integrals to yield $$ \frac{d}{dt} \int_A \tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \, dA -& + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right> \otimes \left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot \mathbf{n}_\perp \, dl \\ -& + \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& - \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ -& = - \int_A \rho_0 \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA \\ -& - \int_{\partial A} \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right> \left

}^{\tilde{z}}_k + \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \mathbf{n}_\perp dl \\ -& - \int_A \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& + \int_A \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right> \otimes \left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot {\bf n}_\perp \, dl \\ +& + \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ +& - \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ +& = - \int_A \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA \\ +& - \int_{\partial A} \tilde{h}_k \left( \overline{\left< \alpha \right> \left

}^{\tilde{z}}_k + \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \mathbf{n}_\perp dl \\ +& + \int_A \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ +& - \int_A \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-reynolds-lay-avg) The next step is to decompose vertical averages of products. This yields $$ \frac{d}{dt} \int_{A} \tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \, dA -& + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot \mathbf{n}_\perp \, dl \\ -& + \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& - \int_A \rho_0 \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ -& = - \int_A \rho_0 \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA \\ -& - \int_{\partial A} \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \mathbf{n}_\perp dl \\ -& - \int_A \rho_0 \left[\left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& + \int_A \rho_0 \left[\left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot {\bf n}_\perp \, dl \\ +& + \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ +& - \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ +& = - \int_A \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA \\ +& - \int_{\partial A} \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \mathbf{n}_\perp dl \\ +& + \int_A \left[\left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ +& - \int_A \left[\left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. $$ (vh-momentum-reynolds-lay-avg2) We next use the Divergence theorem on the surface integrals and combine terms into fewer integrals on each side of the equation. @@ -597,12 +597,12 @@ We next use the Divergence theorem on the surface integrals and combine terms in $$ \int_A \bigl\{ \frac{\partial\tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k}{\partial t} & + \nabla \cdot \left[ \tilde{h}_k \left(\overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ -& + \rho_0 \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}^{\text{top}}} \\ -& - \rho_0 \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}^{\text{bot}}} \bigr\} \, dA \\ -& = - \int_A \bigl\{ \rho_0 \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ -& - \nabla \left[ \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& - \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& + \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\bigr\} \, dA. +& + \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ +& - \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \bigr\} \, dA \\ +& = - \int_A \bigl\{ \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ +& - \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ +& + \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ +& - \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\bigr\} \, dA. $$ (vh-momentum-reynolds-lay-avg3) Since the equation is fully inside the integral, the equation is true for any area and therefore we can write the layer averaged momentum equation as @@ -610,12 +610,12 @@ Since the equation is fully inside the integral, the equation is true for any ar $$ \frac{\partial\tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k}{\partial t} & + \nabla \cdot \left[ \tilde{h}_k \left( \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ -& + \rho_0 \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& - \rho_0 \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \\ -& = - \rho_0 \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ -& - \nabla \left[ \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& - \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \cdot \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& + \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \cdot \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. +& + \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ +& - \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \\ +& = - \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ +& - \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ +& + \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \cdot \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ +& - \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \cdot \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. $$ (vh-momentum-v1) The product rule is used on the first two terms of [](#vh-momentum-v1) and then we multiply [](#layer-mass) by $\overline{\mathbf{u}}^{\tilde{z}}_k$ and subtract it from [](#vh-momentum-v1). This yields @@ -623,12 +623,12 @@ The product rule is used on the first two terms of [](#vh-momentum-v1) and then $$ \tilde{h}_k \frac{\partial \overline{\left< {\bf u} \right>}^{\tilde{z}}_k}{\partial t} & + \tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \cdot \nabla \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \nabla \cdot \left[ \tilde{h}_k \left(\overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ -& + \rho_0 \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{ \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& + \rho_0 \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& = - \rho_0 \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ -& - \nabla \left[ \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& - \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& + \rho_0 \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. +& + \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{ \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ +& + \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ +& = - \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ +& - \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ +& + \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ +& - \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. $$ (vh-momentum-v2) The previous equation is divided by $\tilde{h}_k$, @@ -636,12 +636,12 @@ The previous equation is divided by $\tilde{h}_k$, $$ \frac{\partial \overline{\left< {\bf u} \right>}^{\tilde{z}}_k}{\partial t} & + \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \cdot \nabla \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \frac{1}{\tilde{h}_k} \nabla \cdot \left[ \tilde{h}_k \left(\overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ -& + \frac{\rho_0}{\tilde{h}_k} \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{ \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& + \frac{\rho_0}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& = - \rho_0 \, \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ -& - \frac{1}{\tilde{h}_k} \nabla \left[ \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& - \frac{\rho_0}{\tilde{h}_k} \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& + \frac{\rho_0}{\tilde{h}_k} \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. +& + \frac{1}{\tilde{h}_k} \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{ \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ +& + \frac{1}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ +& = - \, \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ +& - \frac{1}{\tilde{h}_k} \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ +& + \frac{1}{\tilde{h}_k} \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ +& - \frac{1}{\tilde{h}_k} \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. $$ (vh-momentum-v3) @@ -672,10 +672,10 @@ Defining the absolute vorticity $\zeta_a$ as $\zeta + f$, where $f$ is the Corio $$ \frac{\partial \overline{\left< {\bf u} \right>}^{\tilde{z}}_k}{\partial t} & + \zeta_a {\overline{\left<{\bf u}\right>}^{\tilde{z}}_k}^{\perp} + \nabla K + \frac{1}{\tilde{h}_k} \nabla \cdot \left[ \tilde{h}_k \left(\overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ -& + \frac{\rho_0}{\tilde{h}_k} \left\{ \left[\left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{\left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{\left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& + \frac{\rho_0}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& = - \rho_0 \, \overline{\nabla \left<\Phi\right>}^{\tilde{z}}_k - \frac{1}{\tilde{h}_k} \nabla \left[ \rho_0 \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& - \frac{\rho_0}{\tilde{h}_k} \left\{ \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} +& + \frac{1}{\tilde{h}_k} \left\{ \left[\left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{\left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{\left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ +& + \frac{1}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ +& = - \, \overline{\nabla \left<\Phi\right>}^{\tilde{z}}_k - \frac{1}{\tilde{h}_k} \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ +& + \frac{1}{\tilde{h}_k} \left\{ \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\}. $$ (layer-momentum-final) @@ -695,9 +695,9 @@ With these three simplifications, the final continuous equations become **Tracer:** $$ -\frac{\partial {\tilde h}_k \varphi_k}{\partial t} & + \nabla \cdot \left({\tilde h}_k \varphi_k {\bf u}_k\right) + \rho_0 \left\{ \left[ \varphi \tilde{W}_{tr} \right]_{{\tilde z}={\tilde z}_k^{\text{top}}} - \left[ \varphi \tilde{W}_{tr} \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}} \right\} \\ +\frac{\partial {\tilde h}_k \varphi_k}{\partial t} & + \nabla \cdot \left({\tilde h}_k \varphi_k {\bf u}_k\right) + \left\{ \left[ \varphi \tilde{W}_{tr} \right]_{{\tilde z}={\tilde z}_k^{\text{top}}} - \left[ \varphi \tilde{W}_{tr} \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}} \right\} \\ & = - \nabla \cdot \left({\tilde h}_k \left<\varphi^{\prime} {\bf u}^{\prime}\right>_k\right) \\ -& - \rho_0 \left\{ \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{top}}} - \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}}\right\}. +& - \left\{ \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{top}}} - \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}}\right\}. $$ (layer-tracer-final-simple) **Mass:** @@ -705,8 +705,8 @@ $$ (layer-tracer-final-simple) $$ \frac{\partial {\tilde h}_k }{\partial t} + \nabla \cdot \left({\tilde h}_k {\bf u}_k\right) -+ \rho_0 \left[ \tilde{W}_{tr} \right]_{{\tilde z}={\tilde z}_{k}^{\text{top}}} -- \rho_0 \left[ \tilde{W}_{tr} \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}} ++ \left[ \tilde{W}_{tr} \right]_{{\tilde z}={\tilde z}_{k}^{\text{top}}} +- \left[ \tilde{W}_{tr} \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}} = 0. $$ (layer-mass-final-simple) @@ -715,10 +715,10 @@ $$ (layer-mass-final-simple) $$ \frac{\partial {\bf u}_k}{\partial t} & + \zeta_a {{\bf u}_k}^{\perp} + \nabla K + \frac{1}{\tilde{h}_k} \nabla \cdot \left[ \tilde{h}_k \left< {\bf u}^\prime \otimes {\bf u}^\prime \right>_k \right] \\ -& + \frac{\rho_0}{\tilde{h}_k} \left\{ \left[\left(\mathbf{u} - \mathbf{u}_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left(\mathbf{u} - \mathbf{u}_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& + \frac{\rho_0}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ +& + \frac{1}{\tilde{h}_k} \left\{ \left[\left(\mathbf{u} - \mathbf{u}_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left(\mathbf{u} - \mathbf{u}_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ +& + \frac{1}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ & = - \left(\nabla \Phi \right)_k - \frac{1}{\tilde{h}_k} \nabla \left[ \tilde{h}_k \alpha_k p_k \right] \\ -& - \frac{1}{\tilde{h}_k} \left\{ \left[ \alpha p \nabla \tilde{z}_k^{\text{top}}\right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} +& + \frac{1}{\tilde{h}_k} \left\{ \left[ \alpha p \nabla \tilde{z}_k^{\text{top}}\right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \alpha p \nabla \tilde{z}_k^{\text{bot}}\right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\}. $$ (layer-momentum-final-simple) @@ -733,8 +733,8 @@ In the following equations, the subscripts $i$, $e$, and $v$ indicate cell, edge $$ \frac{\partial {\tilde h}_{i,k} }{\partial t} + \nabla \cdot \left(\left[{\tilde h}_k\right]_e {\bf u}_{e,k}\right) -+ \rho_0 \left[ \tilde{W}_{tr} \right]_k^\text{top} -- \rho_0 \left[ \tilde{W}_{tr} \right]_{k+1}^\text{top} ++ \left[ \tilde{W}_{tr} \right]_k^\text{top} +- \left[ \tilde{W}_{tr} \right]_{k+1}^\text{top} = 0. $$ (discrete-mass) @@ -743,8 +743,8 @@ In this equation, mass source term ($Q$), such as sources like river runoff, sea **Tracer:** $$ -\frac{\partial {\tilde h}_{i,k} \varphi_{i,k}}{\partial t} & + \nabla \cdot \left(\left[{\tilde h}_{i,k} \varphi_{i,k}\right]_e {\bf u}_{e,k}\right) + \rho_0 \left\{ \left[ \varphi \tilde{W}_{tr} \right]_k^\text{top} - \left[ \varphi \tilde{W}_{tr} \right]_{k+1}^\text{top} \right\} \\ -& = - \nabla \cdot \left({\tilde h}_k \left<\varphi^{\prime} {\bf u}^{\prime}\right>_k \right) - \rho_0 \left\{ \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_k^\text{top} - \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{k+1}^\text{top} \right\}. +\frac{\partial {\tilde h}_{i,k} \varphi_{i,k}}{\partial t} & + \nabla \cdot \left(\left[{\tilde h}_{i,k} \varphi_{i,k}\right]_e {\bf u}_{e,k}\right) + \left\{ \left[ \varphi \tilde{W}_{tr} \right]_k^\text{top} - \left[ \varphi \tilde{W}_{tr} \right]_{k+1}^\text{top} \right\} \\ +& = - \nabla \cdot \left({\tilde h}_k \left<\varphi^{\prime} {\bf u}^{\prime}\right>_k \right) - \left\{ \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_k^\text{top} - \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{k+1}^\text{top} \right\}. $$ (discrete-tracer) In the tracer equation, we note that surface fluxes (e.g. latent heat fluxes) will appear in the surface value of the vertical turbulent tracer flux (e.g., $\left[\left<\varphi^\prime \tilde{w}_{tr}^\prime\right>\right]_{k=0}^\text{top}$). The surface fluxes will also include effects from volumetric surface fluxes that carry a non zero tracer value. The terms on the second line represent the small scale turbulence and the form used in Omega to represent these terms will be discussed in the next section. @@ -756,9 +756,9 @@ Omega will only predict the layer average normal velocity, so we drop the bold f $$ \frac{\partial u_{e,k}}{\partial t} & + \left[ {\bf k} \cdot \nabla \times u_{e,k} +f_v\right]_e\left(u_{e,k}^{\perp}\right) + \left[\nabla K\right]_e \\ -& + \frac{\rho_0}{\left[\tilde{h}_{i,k}\right]_e} \left\{ \left[\left(u - u_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{e,k}^\text{top} - \left[ \left(u - u_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{e,k+1}^\text{top} \right\} \\ -& = - \left(\nabla \Phi \right)_{e,k} - \frac{1}{\left[\tilde{h}_k\right]_e} \nabla \left( \tilde{h}_k \alpha_k p_k \right) - \frac{1}{\left[\tilde{h}_k\right]_e} \left\{ \left[ \alpha p \nabla \tilde{z}^{\text{top}}\right]_{e,k}^\text{top} - \left[ \alpha p \nabla \tilde{z}^{\text{top}}\right]_{e,k+1}^\text{top} \right\} \\ -& - \frac{1}{\left[\tilde{h}_{i,k}\right]_e} \nabla \cdot \left( \tilde{h}_k \left< {\bf u}^\prime \otimes {\bf u}^\prime \right>_k \right) - \frac{\rho_0}{\left[\tilde{h}_{i,k}\right]_e^\text{top}} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{e,k}^\text{top} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{e,k+1}^\text{top} \right\}. +& + \frac{1}{\left[\tilde{h}_{i,k}\right]_e} \left\{ \left[\left(u - u_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{e,k}^\text{top} - \left[ \left(u - u_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{e,k+1}^\text{top} \right\} \\ +& = - \left(\nabla \Phi \right)_{e,k} - \frac{1}{\left[\tilde{h}_k\right]_e} \nabla \left( \tilde{h}_k \alpha_k p_k \right) + \frac{1}{\left[\tilde{h}_k\right]_e} \left\{ \left[ \alpha p \nabla \tilde{z}^{\text{top}}\right]_{e,k}^\text{top} - \left[ \alpha p \nabla \tilde{z}^{\text{bot}}\right]_{e,k+1}^\text{top} \right\} \\ +& - \frac{1}{\left[\tilde{h}_{i,k}\right]_e} \nabla \cdot \left( \tilde{h}_k \left< {\bf u}^\prime \otimes {\bf u}^\prime \right>_k \right) - \frac{1}{\left[\tilde{h}_{i,k}\right]_e^\text{top}} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{e,k}^\text{top} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{e,k+1}^\text{top} \right\}. $$ (discrete-momentum) **Diagnostic Relations:** From dbe9dcf4421eedbf386f5b828838a4e85c9dbad6 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Mon, 9 Feb 2026 10:17:14 -0700 Subject: [PATCH 012/152] Exchange tracer halos at every RK stage --- .../omega/src/timeStepping/RungeKutta2Stepper.cpp | 3 +++ .../omega/src/timeStepping/RungeKutta4Stepper.cpp | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp index 3ac9e00056b8..15373a6baae6 100644 --- a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp @@ -49,6 +49,9 @@ void RungeKutta2Stepper::doStep(OceanState *State, // model state updateTracersByTend(NextTracerArray, CurTracerArray, State, NextLevel, State, CurLevel, 0.5 * TimeStep); + // We need to exchange tracer halos at every stage for high-order advection + MeshHalo->exchangeFullArrayHalo(NextTracerArray, OnCell); + // R_q^{n+0.5} = RHS_q(u^{n+0.5}, h^{n+0.5}, phi^{n+0.5}, t^{n+0.5}) Tend->computeAllTendencies(State, AuxState, NextTracerArray, NextLevel, NextLevel, SimTime + 0.5 * TimeStep); diff --git a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp index d9db7e9b8331..b54b162db660 100644 --- a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp @@ -104,14 +104,16 @@ void RungeKutta4Stepper::doStep(OceanState *State, // model state updateTracersByTend(ProvisTracers, CurTracerArray, ProvisState, CurLevel, State, CurLevel, RKA[Stage] * TimeStep); + Pacer::timingBarrier("RK4:haloExchProvisBarrier", 3, Comm); + Pacer::start("RK4:haloExchProvis", 3); // TODO(mwarusz) this depends on halo width actually if (Stage == 2) { - Pacer::timingBarrier("RK4:haloExchProvisBarrier", 3, Comm); - Pacer::start("RK4:haloExchProvis", 3); ProvisState->exchangeHalo(CurLevel); - MeshHalo->exchangeFullArrayHalo(ProvisTracers, OnCell); - Pacer::stop("RK4:haloExchProvis", 3); } + // We need to exchange tracer halos at every stage for high-order + // advection + MeshHalo->exchangeFullArrayHalo(ProvisTracers, OnCell); + Pacer::stop("RK4:haloExchProvis", 3); Tend->computeAllTendencies(ProvisState, AuxState, ProvisTracers, CurLevel, CurLevel, StageTime); From 59b1666175ca4ba61919387b163444366babdd87 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Sun, 9 Nov 2025 14:16:38 -0700 Subject: [PATCH 013/152] Cleanup branch starting with new commit. --- components/omega/configs/Default.yml | 3 +- .../omega/doc/userGuide/TendencyTerms.md | 3 +- components/omega/src/infra/Config.cpp | 24 +- components/omega/src/infra/Config.h | 2 + .../omega/src/ocn/CustomTendencyTerms.h | 4 +- components/omega/src/ocn/HorzMesh.cpp | 18 +- components/omega/src/ocn/HorzMesh.h | 5 +- components/omega/src/ocn/HorzOperators.cpp | 41 ++ components/omega/src/ocn/HorzOperators.h | 450 ++++++++++++++++ components/omega/src/ocn/HorzUtil.h | 197 +++++++ components/omega/src/ocn/Tendencies.cpp | 50 +- components/omega/src/ocn/Tendencies.h | 3 +- components/omega/src/ocn/TendencyTerms.cpp | 44 ++ components/omega/src/ocn/TendencyTerms.h | 82 ++- components/omega/test/ocn/HorzMeshTest.cpp | 27 +- .../omega/test/ocn/HorzOperatorsTest.cpp | 495 ++++++++++++++++++ .../omega/test/ocn/TendencyTermsTest.cpp | 7 +- 17 files changed, 1403 insertions(+), 52 deletions(-) create mode 100644 components/omega/src/ocn/HorzUtil.h diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 93b4fe7f7bf3..9b4d3c617f2f 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -43,7 +43,8 @@ Omega: WindForcingTendencyEnable: false BottomDragTendencyEnable: false BottomDragCoeff: 0.0 - TracerHorzAdvTendencyEnable: true + TracerHorzAdvTendencyEnable: false + TracerHighOrderHorzAdvTendencyEnable: true TracerDiffTendencyEnable: true EddyDiff2: 10.0 TracerHyperDiffTendencyEnable: true diff --git a/components/omega/doc/userGuide/TendencyTerms.md b/components/omega/doc/userGuide/TendencyTerms.md index 69ad8437d210..d0bab3d52aca 100644 --- a/components/omega/doc/userGuide/TendencyTerms.md +++ b/components/omega/doc/userGuide/TendencyTerms.md @@ -42,7 +42,8 @@ the currently available tendency terms: | VelocityHyperDiffOnEdge | VelHyperDiffTendencyEnable | enable/disable term | | ViscDel4 | horizontal biharmonic mixing coefficient for normal velocity | | DivFactor | scale factor for the divergence term -| TracerHorzAdvOnCell | TracerHorzAdvTendencyEnable | enable/disable term +| TracerHorzAdvOnCell | TracerHorzAdvTendencyEnable +| | TracerHighOrderHorzAdvTendencyEnable | enable/disable term | TracerDiffOnCell | TracerDiffTendencyEnable | enable/disable term | | EddyDiff2 | horizontal diffusion coefficient | TracerHyperDiffOnCell | TracerHyperDiffTendencyEnable | enable/disable term diff --git a/components/omega/src/infra/Config.cpp b/components/omega/src/infra/Config.cpp index 6e7077a60062..493ddc6eb744 100644 --- a/components/omega/src/infra/Config.cpp +++ b/components/omega/src/infra/Config.cpp @@ -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(); @@ -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. diff --git a/components/omega/src/infra/Config.h b/components/omega/src/infra/Config.h index 54cd24dc878e..7dcb3b2f510e 100644 --- a/components/omega/src/infra/Config.h +++ b/components/omega/src/infra/Config.h @@ -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. diff --git a/components/omega/src/ocn/CustomTendencyTerms.h b/components/omega/src/ocn/CustomTendencyTerms.h index 7ae226d2a27a..0635e0483ed9 100644 --- a/components/omega/src/ocn/CustomTendencyTerms.h +++ b/components/omega/src/ocn/CustomTendencyTerms.h @@ -22,8 +22,8 @@ // //===----------------------------------------------------------------------===// -#include "HorzMesh.h" -#include "TendencyTerms.h" +#include "AuxiliaryState.h" +#include "OceanState.h" #include "TimeMgr.h" namespace OMEGA { diff --git a/components/omega/src/ocn/HorzMesh.cpp b/components/omega/src/ocn/HorzMesh.cpp index 0c626b49905e..c3fd1b414f08 100644 --- a/components/omega/src/ocn/HorzMesh.cpp +++ b/components/omega/src/ocn/HorzMesh.cpp @@ -17,7 +17,6 @@ #include "Error.h" #include "IO.h" #include "Logging.h" -#include "MachEnv.h" #include "OmegaKokkos.h" namespace OMEGA { @@ -46,19 +45,21 @@ void HorzMesh::init() { HorzMesh::HorzMesh(const std::string &Name, //< [in] Name for new mesh Decomp *MeshDecomp //< [in] Decomp for the new mesh -) { - + ) + : CellID(MeshDecomp->CellID) ///< global cell ID for each local cell +{ MeshName = Name; // Retrieve mesh files name from Decomp MeshFileName = MeshDecomp->MeshFileName; // Retrieve mesh cell/edge/vertex totals from Decomp - NCellsHalo = MeshDecomp->NCellsHalo; - NCellsHaloH = MeshDecomp->NCellsHaloH; - NCellsOwned = MeshDecomp->NCellsOwned; - NCellsAll = MeshDecomp->NCellsAll; - NCellsSize = MeshDecomp->NCellsSize; + NCellsHalo = MeshDecomp->NCellsHalo; + NCellsHaloH = MeshDecomp->NCellsHaloH; + NCellsOwned = MeshDecomp->NCellsOwned; + NCellsAll = MeshDecomp->NCellsAll; + NCellsSize = MeshDecomp->NCellsSize; + NCellsGlobal = MeshDecomp->NCellsGlobal; NEdgesHalo = MeshDecomp->NEdgesHalo; NEdgesHaloH = MeshDecomp->NEdgesHaloH; @@ -67,6 +68,7 @@ HorzMesh::HorzMesh(const std::string &Name, //< [in] Name for new mesh NEdgesSize = MeshDecomp->NEdgesSize; MaxCellsOnEdge = MeshDecomp->MaxCellsOnEdge; MaxEdges = MeshDecomp->MaxEdges; + NEdgesGlobal = MeshDecomp->NEdgesGlobal; NVerticesHalo = MeshDecomp->NVerticesHalo; NVerticesHaloH = MeshDecomp->NVerticesHaloH; diff --git a/components/omega/src/ocn/HorzMesh.h b/components/omega/src/ocn/HorzMesh.h index 0b244c0e17a9..ff824479cf3d 100644 --- a/components/omega/src/ocn/HorzMesh.h +++ b/components/omega/src/ocn/HorzMesh.h @@ -98,6 +98,7 @@ class HorzMesh { I4 NCellsOwned; ///< Number of cells owned by this task I4 NCellsAll; ///< Total number of local cells (owned + all halo) I4 NCellsSize; ///< Array size (incl padding, bndy cell) for cell arrays + I4 NCellsGlobal; Array1DI4 NEdgesHalo; ///< num cells owned+halo for halo layer HostArray1DI4 NEdgesHaloH; ///< num cells owned+halo for halo layer @@ -106,7 +107,8 @@ class HorzMesh { I4 NEdgesSize; ///< Array length (incl padding, bndy) for edge dim I4 MaxCellsOnEdge; ///< Max number of cells sharing an edge I4 MaxEdges; ///< Max number of edges around a cell - I4 MaxEdges2; ///< Max number of edges around a cell x2 + I4 NEdgesGlobal; + I4 MaxEdges2; ///< Max number of edges around a cell x2 Array1DI4 NVerticesHalo; ///< num cells owned+halo for halo layer HostArray1DI4 NVerticesHaloH; ///< num cells owned+halo for halo layer @@ -147,6 +149,7 @@ class HorzMesh { Array2DI4 EdgesOnVertex; ///< Indx of edges sharing vertex as endpoint HostArray2DI4 EdgesOnVertexH; ///< Indx of edges sharing vertex as endpoint + Array1DI4 CellID; ///< global cell ID for each local cell // Coordinates Array1DReal XCell; ///< X Coordinates of cell centers (m) diff --git a/components/omega/src/ocn/HorzOperators.cpp b/components/omega/src/ocn/HorzOperators.cpp index b0f13dbe8d59..6cb54101bd8a 100644 --- a/components/omega/src/ocn/HorzOperators.cpp +++ b/components/omega/src/ocn/HorzOperators.cpp @@ -27,4 +27,45 @@ InterpCellToEdge::InterpCellToEdge(const HorzMesh *Mesh) KiteAreasOnVertex(Mesh->KiteAreasOnVertex), VertexDegree(Mesh->VertexDegree) {} +SecondDerivativeOnCell::SecondDerivativeOnCell(HorzMesh const *Mesh) + : OnSphere(true), NCellsAll(Mesh->NCellsAll), MaxEdges(1 + Mesh->MaxEdges), + NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), + CellsOnCell(Mesh->CellsOnCell), CellsOnEdge(Mesh->CellsOnEdge), + VerticesOnEdge(Mesh->VerticesOnEdge), + + XCell(Mesh->XCell), YCell(Mesh->YCell), + ZCell(createDeviceMirrorCopy(Mesh->ZCellH)), DvEdge(Mesh->DvEdge), + DcEdge(Mesh->DcEdge), AngleEdge(Mesh->AngleEdge), + AreaCell(Mesh->AreaCell), EdgeSignOnCell(Mesh->EdgeSignOnCell), + XVertex(createDeviceMirrorCopy(Mesh->XVertexH)), + YVertex(createDeviceMirrorCopy(Mesh->YVertexH)), + ZVertex(createDeviceMirrorCopy(Mesh->ZVertexH)), + + ThetaAbs("SphereAngle", NCellsAll), XPCell("XP", NCellsAll, MaxEdges), + YPCell("YP", NCellsAll, MaxEdges), + Angle2DCell("Angle2D", NCellsAll, MaxEdges), + BCell("WorkSpaceForLeastSquares", NCellsAll, 6, MaxEdges), + CellListCell("CellList", NCellsAll, MaxEdges) { + if (MaxMaxEdges <= Mesh->MaxEdges) + LOG_CRITICAL( + "SecondDerivativeOnCell::SecondDerivativeOnCell Max Edges exceeded:" + "Max Allowed: {} Found in Mesh:{}", + MaxMaxEdges - 1, Mesh->MaxEdges); +} + +MasksAndCoefficients::MasksAndCoefficients( + HorzMesh const *Mesh, const Array3DReal DerivTwo, + Array1DI4 NAdvCellsForEdge, Array2DI4 AdvCellsForEdge, + Array1DI4 AdvMaskHighOrder, Array2DReal AdvCoefs, Array2DReal AdvCoefs3rd) + : NCellsGlobal(Mesh->NCellsGlobal), NCellsAll(Mesh->NCellsAll), + NAdvCellsMax(Mesh->MaxEdges2), // PatchCellLists("PatchCellLists", Mesh->NEdgesOwned, Mesh->NEdgesAll+1), + NAdvCellsForEdge(NAdvCellsForEdge), AdvCellsForEdge(AdvCellsForEdge), + NEdgesOnEdge(Mesh->NEdgesOnEdge), NEdgesOnCell(Mesh->NEdgesOnCell), + CellIndx("CellIndx", Mesh->NEdgesOwned, Mesh->MaxEdges2 + 2), + CellIndxSorted("CellIndxSorted", Mesh->NEdgesOwned, 2, Mesh->MaxEdges2 + 2), + CellID(Mesh->CellID), AdvMaskHighOrder(AdvMaskHighOrder), + EdgesOnEdge(Mesh->EdgesOnEdge), CellsOnCell(Mesh->CellsOnCell), + CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), + DvEdge(Mesh->DvEdge), AdvCoefs(AdvCoefs), AdvCoefs3rd(AdvCoefs3rd), + DerivTwo(DerivTwo) { } } // namespace OMEGA diff --git a/components/omega/src/ocn/HorzOperators.h b/components/omega/src/ocn/HorzOperators.h index 6e187a2976e4..2bfb54b34284 100644 --- a/components/omega/src/ocn/HorzOperators.h +++ b/components/omega/src/ocn/HorzOperators.h @@ -3,6 +3,7 @@ #include "DataTypes.h" #include "HorzMesh.h" +#include "HorzUtil.h" namespace OMEGA { @@ -186,5 +187,454 @@ class InterpCellToEdge { I4 VertexDegree; }; +class SecondDerivativeOnCell { + /* + \brief deriv two computation + \author Doug Jacobsen, Bill Skamarock + \date 03/09/12 + \details + This routine precomputes the second derivative values for tracer + advection. It computes cell coefficients for the polynomial fit + as described in: + Skamarock, W. C., & Gassmann, A. (2011). + Conservative Transport Schemes for Spherical Geodesic Meshs: + High-Order Flux Operators for ODE-Based Time Integration. + Monthly Weather Review, 139(9), 2962-2975. + doi:10.1175/MWR-D-10-05056.1 + This is performed during model initialization. + */ + public: + SecondDerivativeOnCell(HorzMesh const *Mesh); + KOKKOS_FUNCTION ~SecondDerivativeOnCell() {} + KOKKOS_FUNCTION void operator()(const Array3DReal &DerivTwo, + int ICell) const { + const int NEdges = NEdgesOnCell(ICell); + if (MaxMaxEdges < NEdges) + printf ("Error: Number of edges on cell:%d exceeds maximum " + "expected:%d for cell:%d", NEdges, MaxMaxEdges, ICell); + + // check to see if we are reaching outside the halo + auto CellList = Kokkos::subview(CellListCell, ICell, Kokkos::ALL); + bool doCell = true; + CellList[0] = ICell; + for (int J = 1; J <= NEdges; ++J) + CellList[J] = CellsOnCell(ICell, J - 1); + + for (int I = 0; I <= NEdges; ++I) + if (NCellsAll <= CellList[I]) + doCell = false; + if (!doCell) + return; + + auto XP = Kokkos::subview(XPCell, ICell, Kokkos::ALL); + auto YP = Kokkos::subview(YPCell, ICell, Kokkos::ALL); + auto Angle2D = Kokkos::subview(Angle2DCell, ICell, Kokkos::ALL); + auto B = Kokkos::subview(BCell, ICell, Kokkos::ALL, Kokkos::ALL); + if (OnSphere) { + const Array1DI4 edgesOnCell = + Kokkos::subview(EdgesOnCell, ICell, Kokkos::ALL); + DetermineSphericalPatchGeometry( + NEdges, edgesOnCell, VerticesOnEdge, XCell, YCell, ZCell, XVertex, + YVertex, ZVertex, CellList, XP, YP, Angle2D, ThetaAbs[ICell]); + } else { // On an x-y plane + const Array1DI4 edgesOnCell = + Kokkos::subview(EdgesOnCell, ICell, Kokkos::ALL); + DeterminePlanerPatchGeometry(ICell, NEdges, edgesOnCell, CellsOnEdge, + AngleEdge, DcEdge, XP, YP, Angle2D); + } + LeastSquaresFit(XP, YP, NEdges, B); + + // fill second derivative stencil for rk advection + for (int I = 0; I < NEdges; ++I) { + const I4 IEdge = EdgesOnCell(ICell, I); + const I4 Ind = (ICell == CellsOnEdge(IEdge, 0)) ? 0 : 1; + const Real Theta = Angle2D[I]; + const Real x = Kokkos::cos(Theta); + const Real y = Kokkos::sin(Theta); + const Real xx = x * x; + const Real xy = x * y; + const Real yy = y * y; + for (int J = 0; J <= NEdges; ++J) + DerivTwo(J, Ind, IEdge) = 2._Real * xx * B(3, J) + + 2._Real * xy * B(4, J) + + 2._Real * yy * B(5, J); + } + } + + private: + // MaxMaxEdges is used to dimention arrays that include ICell and the + // neighbor cells, so it is technically one more than MaxEdges. + static const I4 MaxMaxEdges = 10; + static constexpr R8 Pii = 3.141592653589793_Real; + + const bool OnSphere; + const I4 NCellsAll; + const I4 MaxEdges; + Array1DI4 NEdgesOnCell; + Array2DI4 EdgesOnCell; + Array2DI4 CellsOnCell; + Array2DI4 CellsOnEdge; + Array2DI4 VerticesOnEdge; + + Array1DReal XCell; + Array1DReal YCell; + Array1DReal ZCell; + Array1DReal DvEdge; + Array1DReal DcEdge; + Array1DReal AngleEdge; + Array1DReal AreaCell; + Array2DReal EdgeSignOnCell; + Array1DReal XVertex; + Array1DReal YVertex; + Array1DReal ZVertex; + + Array1DReal ThetaAbs; + Array2DReal XPCell; + Array2DReal YPCell; + Array2DReal Angle2DCell; + Array3DReal BCell; + Array2DI4 CellListCell; + + protected: + KOKKOS_INLINE_FUNCTION static void LeastSquaresFit(const Array1DReal XP, + const Array1DReal YP, + const int NEdges, + Array2DReal B) { + constexpr int NA = 6; + Real P[MaxMaxEdges][NA] = {}; + + // (polynomial_order == 2) is the only order supported + // The first row is for the origin (0,0) since the data + // is relative to the ICell centroid. + P[0][0] = 1._Real; + for (int I = 1; I <= NEdges; ++I) { + P[I][0] = 1._Real; + P[I][1] = XP[I - 1]; + P[I][2] = YP[I - 1]; + P[I][3] = XP[I - 1] * XP[I - 1]; + P[I][4] = XP[I - 1] * YP[I - 1]; + P[I][5] = YP[I - 1] * YP[I - 1]; + } + poly_fit_2(P, B, 1 + NEdges); + } + + KOKKOS_INLINE_FUNCTION static void DeterminePlanerPatchGeometry( + const int ICell, const int NEdges, const Array1DI4 EdgesOnCell, + const Array2DI4 CellsOnEdge, const Array1DReal AngleEdge, + const Array1DReal DcEdge, Array1DReal XP, Array1DReal YP, + Array1DReal Angle2D) { + for (int I = 0; I < NEdges; ++I) { + const auto IEdge = EdgesOnCell[I]; + Angle2D[I] = AngleEdge(IEdge); + if (ICell != CellsOnEdge(IEdge, 0)) + Angle2D[I] -= Pii; + XP[I] = DcEdge(IEdge) * Kokkos::cos(Angle2D[I]); + YP[I] = DcEdge(IEdge) * Kokkos::sin(Angle2D[I]); + } + } + KOKKOS_INLINE_FUNCTION static void DetermineSphericalPatchGeometry( + const int NEdges, const Array1DI4 EdgesOnCell, + const Array2DI4 VerticesOnEdge, const Array1DReal XCell, + const Array1DReal YCell, const Array1DReal ZCell, + const Array1DReal XVertex, const Array1DReal YVertex, + const Array1DReal ZVertex, const Array1DI4 CellList, Array1DReal XP, + Array1DReal YP, Array1DReal Angle2D, Real &ThetaAbs) { + const Real length_scale = 1._Real; + const Real sphereRadius = distance(XCell(0), YCell(0), ZCell(0)); + Real XC[MaxMaxEdges] = {}; + Real YC[MaxMaxEdges] = {}; + Real ZC[MaxMaxEdges] = {}; + + Real Thetat_prev = 0; + Real Thetav_prev = 0; + + for (int I = 0; I <= NEdges; ++I) { + const auto J = CellList[I]; + XC[I] = XCell(J) / sphereRadius; + YC[I] = YCell(J) / sphereRadius; + ZC[I] = ZCell(J) / sphereRadius; + } + if (ZC[0] == 1.0_Real) + ThetaAbs = Pii / 2._Real; + else if (1 - ZC[0] < 1.0e-6) + ThetaAbs = Pii / 2._Real - (1 - ZC[0]) * std::atan2(YC[0], XC[0]); + else + ThetaAbs = + Pii / 2._Real - sphere_angle(XC[0], YC[0], ZC[0], XC[1], YC[1], + ZC[1], 0._Real, 0._Real, 1._Real); + + for (int I = 0; I < NEdges; ++I) { + int Ip1 = I + 1, Ip2 = I + 2; + if (NEdges < Ip2) + Ip2 = 1; + // angles from cell center to neighbor centers (thetav) + const Real Thetav = sphere_angle(XC[0], YC[0], ZC[0], XC[Ip1], YC[Ip1], + ZC[Ip1], XC[Ip2], YC[Ip2], ZC[Ip2]); + Real Dl_sphere = sphereRadius * arc_length(XC[0], YC[0], ZC[0], + XC[Ip1], YC[Ip1], ZC[Ip1]); + + Dl_sphere /= length_scale; + // Thetat = 0. this defines the x direction, + // cell center 0 -> this defines the x direction, longitude line + const Real Thetat = I ? Thetat_prev + Thetav_prev : ThetaAbs; + Thetat_prev = Thetat; + Thetav_prev = Thetav; + + XP[I] = Kokkos::cos(Thetat) * Dl_sphere; + YP[I] = Kokkos::sin(Thetat) * Dl_sphere; + + const I4 IEdge = EdgesOnCell[I]; + Real XV[2] = {}, YV[2] = {}, ZV[2] = {}, EC[3] = {}; + for (int J = 0; J < 2; ++J) { + const I4 vert = VerticesOnEdge(IEdge, J); + XV[J] = XVertex(vert) / sphereRadius; + YV[J] = YVertex(vert) / sphereRadius; + ZV[J] = ZVertex(vert) / sphereRadius; + } + arc_bisect(XV[0], YV[0], ZV[0], XV[1], YV[1], ZV[1], EC[0], EC[1], + EC[2]); + Real ThTmp = sphere_angle(XC[0], YC[0], ZC[0], XC[Ip1], YC[Ip1], + ZC[Ip1], EC[0], EC[1], EC[2]); + ThTmp += Thetat; + Angle2D[I] = ThTmp; + } + } +}; + +class MasksAndCoefficients { + + KOKKOS_INLINE_FUNCTION static void swap(Array2DI4 &vec, const int m, const int n) { + for (int i : {0,1}) { + const I4 j = vec(i,m); + vec(i,m) = vec(i,n); + vec(i,n) = j; + } + } + // Sort the second dimension (values) of vec based on the first (keys): + //Array1DI4 keys = Kokkos::subview(vec, 0, Kokkos::ALL); + //Array1DI4 values = Kokkos::subview(vec, 1, Kokkos::ALL); + KOKKOS_INLINE_FUNCTION static int partition(Array2DI4 &vec, const int low, const int high) { + // Selecting last element as the pivot + const I4 pivot = vec(0,high); + int i = (low - 1); + for (int j = low; j < high; ++j) { + if (vec(0,j) <= pivot) { + i++; + swap(vec, i, j); + } + } + // Put pivot to its position + swap(vec, i + 1, high); + // Return the point of partition + return (i + 1); + } + + KOKKOS_INLINE_FUNCTION static void sort_by_key(Array2DI4 &vec, const I4 low, const I4 high) { + // Base case: This part will be executed till the starting + // index low is lesser than the ending index high + if (low < high) { + // pi is Partitioning Index, vec[pi] is now at + // right place + const I4 pi = partition(vec, low, high); + // Separately sort elements before and after the + // Partition Index pi + sort_by_key(vec, low, pi - 1); + sort_by_key(vec, pi + 1, high); + } + } + KOKKOS_INLINE_FUNCTION static bool is_sorted(Array2DI4 &vec) { + bool sorted = true; + const I4 N = vec.extent(1)-1; + for (I4 I=0; I 0 corresponds to the + // third-order flux function. The - sign in the derivTwo + // accumulation is for the i+1 part of term 3, while + // the + sign is for the i part. + + for (int I = 0; I < NAdvCellsMax; ++I) { + AdvCoefs(I, IEdge) = 0._Real; + AdvCoefs3rd(I, IEdge) = 0._Real; + } + // pull together third and fourth order contributions to the flux first + // from cell1 + Array1DI4 keys_edge = Kokkos::subview( + keys, Kokkos::make_pair(0, NAdvCellsForEdge(IEdge))); + if (const I4 I = search(keys_edge, CellID(Cell1)); -1 != I) { + AdvCoefs (I, IEdge) += DerivTwo(0, 0, IEdge); + AdvCoefs3rd(I, IEdge) += DerivTwo(0, 0, IEdge); + } + for (int ICell = 0; ICell < NEdgesOnCell(Cell1); ++ICell) { + if (const I4 I = search(keys_edge, CellID(CellsOnCell(Cell1, ICell))); -1 != I) { + AdvCoefs (I, IEdge) += DerivTwo(ICell + 1, 0, IEdge); + AdvCoefs3rd(I, IEdge) += DerivTwo(ICell + 1, 0, IEdge); + } + } + // pull together third and fourth order contributions to the flux first + // from cell2 + if (const I4 I = search(keys_edge, CellID(Cell2));-1 != I) { + AdvCoefs (I, IEdge) += DerivTwo(0, 1, IEdge); + AdvCoefs3rd(I, IEdge) -= DerivTwo(0, 1, IEdge); + } + for (int ICell = 0; ICell < NEdgesOnCell(Cell2); ++ICell) { + if (const I4 I = search(keys_edge, CellID(CellsOnCell(Cell2, ICell)));-1 != I) { + AdvCoefs (I, IEdge) += DerivTwo(ICell + 1, 1, IEdge); + AdvCoefs3rd(I, IEdge) -= DerivTwo(ICell + 1, 1, IEdge); + } + } + for (int ICell = 0; ICell < NAdvCellsForEdge(IEdge); ++ICell) { + AdvCoefs (ICell, IEdge) *= -DcEdge(IEdge) * DcEdge(IEdge) / 12._Real; + AdvCoefs3rd(ICell, IEdge) *= -DcEdge(IEdge) * DcEdge(IEdge) / 12._Real; + + } + // 2nd order centered contribution place this in the main flux weights + if (const I4 I = search(keys_edge, CellID(Cell1)); -1 != I) { + AdvCoefs(I, IEdge) += 0.5_Real; + } + if (const I4 I = search(keys_edge, CellID(Cell2)); -1 != I) { + AdvCoefs(I, IEdge) += 0.5_Real; + } + // multiply by edge length - thus the flux is just dt*ru times the + // results of the vector-vector multiply + for (int ICell = 0; ICell < NAdvCellsForEdge(IEdge); ++ICell) { + AdvCoefs (ICell, IEdge) *= DvEdge(IEdge); + AdvCoefs3rd(ICell, IEdge) *= DvEdge(IEdge); + } + } + } + + private: + const I4 MaxI4 = std::numeric_limits::max(); + const I4 NCellsGlobal; + const I4 NCellsAll; + const I4 NAdvCellsMax; + Array2DI4 PatchCellLists; + Array1DI4 NAdvCellsForEdge; + Array2DI4 AdvCellsForEdge; + Array1DI4 NEdgesOnEdge; + Array1DI4 NEdgesOnCell; + Array2DI4 CellIndx; + Array3DI4 CellIndxSorted; + Array1DI4 CellID; + Array1DI4 AdvMaskHighOrder; + Array2DI4 EdgesOnEdge; + Array2DI4 CellsOnCell; + Array2DI4 CellsOnEdge; + Array1DReal DcEdge; + Array1DReal DvEdge; + Array2DReal AdvCoefs; + Array2DReal AdvCoefs3rd; + Array3DReal DerivTwo; +}; } // namespace OMEGA #endif diff --git a/components/omega/src/ocn/HorzUtil.h b/components/omega/src/ocn/HorzUtil.h new file mode 100644 index 000000000000..cc27927f8834 --- /dev/null +++ b/components/omega/src/ocn/HorzUtil.h @@ -0,0 +1,197 @@ +#ifndef OMEGA_HORZUTIL_H +#define OMEGA_HORZUTIL_H + +#include "DataTypes.h" +#include "Logging.h" +namespace OMEGA { + +KOKKOS_INLINE_FUNCTION Real distance(const Real x, const Real y, const Real z) { + const Real dist = Kokkos::sqrt(x * x + y * y + z * z); + return dist; +} + +KOKKOS_INLINE_FUNCTION Real sphere_angle(const Real ax, const Real ay, + const Real az, const Real bx, + const Real by, const Real bz, + const Real cx, const Real cy, + const Real cz) { + // Computes the angle between arcs AB and AC, given points A, B, and C + // Equation numbers w.r.t. + // http://mathworld.wolfram.com/SphericalTrigonometry.html + const Real one = 1._Real; + const Real neg_one = -1._Real; + const auto a = + Kokkos::acos(Kokkos::max(Kokkos::min(bx * cx + by * cy + bz * cz, one), + neg_one)); // Eqn. (3) + const auto b = + Kokkos::acos(Kokkos::max(Kokkos::min(ax * cx + ay * cy + az * cz, one), + neg_one)); // Eqn. (2) + const auto c = + Kokkos::acos(Kokkos::max(Kokkos::min(ax * bx + ay * by + az * bz, one), + neg_one)); // Eqn. (1) + const auto ABx = bx - ax; + const auto ABy = by - ay; + const auto ABz = bz - az; + + const auto ACx = cx - ax; + const auto ACy = cy - ay; + const auto ACz = cz - az; + + const auto Dx = (ABy * ACz) - (ABz * ACy); + const auto Dy = -((ABx * ACz) - (ABz * ACx)); + const auto Dz = (ABx * ACy) - (ABy * ACx); + + const auto s = 0.5_Real * (a + b + c); + const auto sin_angle = Kokkos::sqrt(Kokkos::min( + one, Kokkos::max(0._Real, + (Kokkos::sin(s - b) * Kokkos::sin(s - c)) / + (Kokkos::sin(b) * Kokkos::sin(c))))); // Eqn. (28) + Real sa = 2._Real * + Kokkos::asin(Kokkos::max(Kokkos::min(sin_angle, one), neg_one)); + if ((Dx * ax + Dy * ay + Dz * az) < 0.0) + sa *= neg_one; + return sa; +} +KOKKOS_INLINE_FUNCTION Real arc_length(const Real ax, const Real ay, + const Real az, const Real bx, + const Real by, const Real bz) { + // Returns the length of the great circle arc from A=(ax, ay, az) to + // B=(bx, by, bz). It is assumed that both A and B lie on the surface of the + // same sphere centered at the origin. + const auto cx = bx - ax; + const auto cy = by - ay; + const auto cz = bz - az; + const auto r = Kokkos::sqrt(ax * ax + ay * ay + az * az); + const auto c = Kokkos::sqrt(cx * cx + cy * cy + cz * cz); + const auto al = r * 2.0 * Kokkos::asin(c / (2.0 * r)); + return al; +} +template +static KOKKOS_FUNCTION void mat_t_mul(Real AtA[NA][NA], const Real A[][NA], + const I4 Rows) { + // Compute AtA = A^t * A for the first N rows of A + const I4 Cols = NA; + for (int I = 0; I < Cols; ++I) + for (int J = 0; J < Cols; ++J) + for (int K = 0; K < Rows; ++K) + AtA[I][J] += A[K][I] * A[K][J]; // = A^t(I,K) * A(K,j) +} +template +KOKKOS_INLINE_FUNCTION void elgs(Real A[NA][NA], I4 Indx[NA]) { + // subroutine to perform the partial-pivoting gaussian elimination. + // a(n,n) is the original matrix in the input and transformed matrix + // plus the pivoting element ratios below the diagonal in the output. + // indx(n) records the pivoting order. tao pang 2001. + // + // A is a square matrix NA by NA but only the first N rows,columns are + // pivoted. + const I4 N = NA; + for (int I = 0; I < N; ++I) + Indx[I] = I; + // find the rescaling factors, one from each row + Real C[NA] = {}; + for (int I = 0; I < N; ++I) + for (int J = 0; J < N; ++J) + C[I] = Kokkos::max(C[I], Kokkos::abs(A[I][J])); + + // search the pivoting (largest) element from each column + for (int J = 0; J < N - 1; ++J) { + Real pi1 = 0._Real; + I4 K = 0; + for (int I = J; I < N; ++I) { + const Real pi = Kokkos::abs(A[Indx[I]][J]) / C[Indx[I]]; + if (pi1 < pi) { + pi1 = pi; + K = I; + } + } + // interchange the rows via indx(n) to record pivoting order + const I4 ITmp = Indx[J]; + Indx[J] = Indx[K]; + Indx[K] = ITmp; + for (int I = J + 1; I < N; ++I) { + const Real pj = A[Indx[I]][J] / A[Indx[J]][J]; + // record pivoting ratios below the diagonal + A[Indx[I]][J] = pj; + // modify other elements accordingly + for (int K = J + 1; K < N; ++K) + A[Indx[I]][K] -= pj * A[Indx[J]][K]; + } + } +} +template +KOKKOS_INLINE_FUNCTION void migs(Real A[NA][NA], Real X[NA][NA]) { + // subroutine to invert matrix a(n,n) with the inverse stored + // in x(n,n) in the output. tao pang 2001. + // + // Matrix sizes are NA by NA but only the first N rowx & columns are + // inverted. + I4 Indx[NA] = {}; + Real B[NA][NA] = {}; + for (int I = 0; I < NA; ++I) + B[I][I] = 1._Real; + elgs(A, Indx); + for (int I = 0; I < NA - 1; ++I) + for (int J = I + 1; J < NA; ++J) + for (int K = 0; K < NA; ++K) + B[Indx[J]][K] -= A[Indx[J]][I] * B[Indx[I]][K]; + for (int I = 0; I < NA; ++I) { + X[NA - 1][I] = B[Indx[NA - 1]][I] / A[Indx[NA - 1]][NA - 1]; + for (int J = NA - 2; 0 <= J; --J) { + X[J][I] = B[Indx[J]][I]; + for (int K = J + 1; K < NA; ++K) + X[J][I] -= A[Indx[J]][K] * X[K][I]; + X[J][I] /= A[Indx[J]][J]; + } + } +} +template +KOKKOS_INLINE_FUNCTION void mat_mul_t(Array2DReal C, const Real B[NA][NA], + const Real A[][NA], const I4 Rows) { + // Compute C = B * A^t for first N columns of C + // C assumed to be initialized to 0. + // The number of rows in C is inferred from the number of column in A and B. + const I4 Cols = NA; + for (int I = 0; I < Cols; ++I) + for (int J = 0; J < Rows; ++J) + C(I, J) = 0; + for (int I = 0; I < Cols; ++I) + for (int J = 0; J < Rows; ++J) + for (int K = 0; K < Cols; ++K) + C(I, J) += B[I][K] * A[J][K]; // Transpose A +} +template +KOKKOS_INLINE_FUNCTION void poly_fit_2(const Real A[][NA], Array2DReal B, + const I4 Rows) { + Real AtA[NA][NA] = {}; + Real AtAInv[NA][NA] = {}; + mat_t_mul(AtA, A, Rows); + migs(AtA, AtAInv); + mat_mul_t(B, AtAInv, A, Rows); +} + +KOKKOS_INLINE_FUNCTION void arc_bisect(const Real ax, const Real ay, + const Real az, const Real bx, + const Real by, const Real bz, Real &cx, + Real &cy, Real &cz) { + + // Returns the point C=(cx, cy, cz) that bisects the great circle arc from + // A=(ax, ay, az) to B=(bx, by, bz). It is assumed that A and B lie on the + // surface of a sphere centered at the origin. + + cx = 0.5_Real * (ax + bx); + cy = 0.5_Real * (ay + by); + cz = 0.5_Real * (az + bz); + if (cx == 0. && cy == 0. && cz == 0.) { + printf("arc_bisect: A and B are diametrically opposite"); + } else { + const Real d = Kokkos::sqrt(cx * cx + cy * cy + cz * cz); + const Real r = Kokkos::sqrt(ax * ax + ay * ay + az * az); + cx = r * cx / d; + cy = r * cy / d; + cz = r * cz / d; + } +} + +} // namespace OMEGA +#endif diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index ac95a8b8c4b2..135928462503 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -69,7 +69,8 @@ void Tendencies::init() { CustomThickTend, CustomVelTend); DefaultTendencies->readTendConfig(&TendConfig); - + if (DefaultTendencies->TracerHighOrderHorzAdv.Enabled) + DefaultTendencies->TracerHighOrderHorzAdv.init(); } // end init //------------------------------------------------------------------------------ @@ -164,10 +165,23 @@ void Tendencies::readTendConfig( CHECK_ERROR_ABORT(Err, "Tendencies: DivFactor not found in TendConfig"); } - Err += TendConfig->get("TracerHorzAdvTendencyEnable", - this->TracerHorzAdv.Enabled); - CHECK_ERROR_ABORT( - Err, "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); + if (TendConfig->existsVar("TracerHorzAdvTendencyEnable")) { + TendConfig->get("TracerHorzAdvTendencyEnable", + this->TracerHorzAdv.Enabled); + } + if (TendConfig->existsVar("TracerHighOrderHorzAdvTendencyEnable")) { + TendConfig->get("TracerHighOrderHorzAdvTendencyEnable", + this->TracerHighOrderHorzAdv.Enabled); + } + if (!TendConfig->existsVar("TracerHorzAdvTendencyEnable") && !TendConfig->existsVar("TracerHighOrderHorzAdvTendencyEnable")) { + Err += TendConfig->get("TracerHorzAdvTendencyEnable", + this->TracerHorzAdv.Enabled); + Err += TendConfig->get("TracerHighOrderHorzAdvTendencyEnable", + this->TracerHighOrderHorzAdv.Enabled); + CHECK_ERROR_ABORT( + Err, "Tendencies: Neither TracerHorzAdvTendencyEnable nor " + "TracerHighOrderHorzAdvTendencyEnable found in TendConfig"); + } Err += TendConfig->get("TracerDiffTendencyEnable", this->TracerDiffusion.Enabled); @@ -224,6 +238,7 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies VelocityHyperDiff(Mesh, VCoord), WindForcing(Mesh, VCoord), BottomDrag(Mesh, VCoord), TracerHorzAdv(Mesh, VCoord), TracerDiffusion(Mesh, VCoord), TracerHyperDiff(Mesh, VCoord), + TracerHighOrderHorzAdv(Mesh), CustomThicknessTend(InCustomThicknessTend), CustomVelocityTend(InCustomVelocityTend) { @@ -496,6 +511,7 @@ void Tendencies::computeTracerTendenciesOnly( ) { OMEGA_SCOPE(LocTracerTend, TracerTend); OMEGA_SCOPE(LocTracerHorzAdv, TracerHorzAdv); + OMEGA_SCOPE(LocTracerHighOrderHorzAdv, TracerHighOrderHorzAdv); OMEGA_SCOPE(LocTracerDiffusion, TracerDiffusion); OMEGA_SCOPE(LocTracerHyperDiff, TracerHyperDiff); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); @@ -517,8 +533,9 @@ void Tendencies::computeTracerTendenciesOnly( }); // compute tracer horizotal advection - Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); - const Array3DReal &HTracersEdge = AuxState->TracerAux.HTracersEdge; + const Array2DReal &NormalVelEdge = State->NormalVelocity[VelTimeLevel]; + const Array3DReal &HTracersEdge = AuxState->TracerAux.HTracersEdge; + const Array2DReal &FluxLayerThickEdge = AuxState->LayerThicknessAux.FluxLayerThickEdge; if (LocTracerHorzAdv.Enabled) { Pacer::start("Tend:tracerHorzAdv", 2); parallelForOuter( @@ -536,6 +553,19 @@ void Tendencies::computeTracerTendenciesOnly( }); Pacer::stop("Tend:tracerHorzAdv", 2); } + if (LocTracerHighOrderHorzAdv.Enabled) { + parallelFor( + {NTracers, NEdgesAll, NChunks}, + KOKKOS_LAMBDA(int L, int IEdge, int KChunk) { + LocTracerHighOrderHorzAdv(L, IEdge, KChunk, TracerArray, + FluxLayerThickEdge, NormalVelEdge); + }); + parallelFor( + {NTracers, NCellsAll, NChunks}, + KOKKOS_LAMBDA(int L, int ICell, int KChunk) { + LocTracerHighOrderHorzAdv(LocTracerTend, L, ICell, KChunk); + }); + } // compute tracer diffusion const Array2DReal &MeanLayerThickEdge = @@ -705,9 +735,6 @@ void Tendencies::computeAllTendencies( int VelTimeLevel, ///< [in] Time level TimeInstant Time ///< [in] Time ) { - - Pacer::start("Tend:computeAllTendencies", 1); - AuxState->computeAll(State, TracerArray, ThickTimeLevel, VelTimeLevel); computeThicknessTendenciesOnly(State, AuxState, ThickTimeLevel, VelTimeLevel, Time); @@ -715,9 +742,6 @@ void Tendencies::computeAllTendencies( Time); computeTracerTendenciesOnly(State, AuxState, TracerArray, ThickTimeLevel, VelTimeLevel, Time); - - Pacer::stop("Tend:computeAllTendencies", 1); - } // end all tendency compute } // end namespace OMEGA diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 851ce1fc646c..9804483b9b54 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -65,9 +65,10 @@ class Tendencies { VelocityHyperDiffOnEdge VelocityHyperDiff; WindForcingOnEdge WindForcing; BottomDragOnEdge BottomDrag; - TracerHorzAdvOnCell TracerHorzAdv; TracerDiffOnCell TracerDiffusion; TracerHyperDiffOnCell TracerHyperDiff; + TracerHorzAdvOnCell TracerHorzAdv; + TracerHighOrderHorzAdvOnCell TracerHighOrderHorzAdv; // Methods to compute tendency groups void computeThicknessTendencies(const OceanState *State, diff --git a/components/omega/src/ocn/TendencyTerms.cpp b/components/omega/src/ocn/TendencyTerms.cpp index 57631a856d08..c7ad81c4024c 100644 --- a/components/omega/src/ocn/TendencyTerms.cpp +++ b/components/omega/src/ocn/TendencyTerms.cpp @@ -12,6 +12,7 @@ #include "AuxiliaryState.h" #include "DataTypes.h" #include "HorzMesh.h" +#include "HorzOperators.h" #include "OceanState.h" #include "Tracers.h" @@ -80,6 +81,25 @@ TracerHorzAdvOnCell::TracerHorzAdvOnCell(const HorzMesh *Mesh, MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} +TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh) + : HorzontalMesh(Mesh), + NAdvCellsForEdge("NumberOfCellsContribToAdvectionAtEdge", + Mesh->NEdgesOwned), + AdvCellsForEdge("IndexOfCellsContributingToAdvection", Mesh->NEdgesOwned, + Mesh->MaxEdges2 + 2), + AdvMaskHighOrder("MaskForHighOrderAdvectionTerms", Mesh->NEdgesAll), + AdvCoefs("CommonAdvectionCoefficients", Mesh->MaxEdges2 + 2, + Mesh->NEdgesAll), + AdvCoefs3rd("CommonAdvectionCoeffsForHighOrder", Mesh->MaxEdges2 + 2, + Mesh->NEdgesAll), + HighOrderFlxHorz("HigherOrderHorizontalFlux", Tracers::getNumTracers(), + Mesh->NEdgesAll, Mesh->NVertLayers / VecLength), + NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), + CellsOnEdge(Mesh->CellsOnEdge), EdgeSignOnCell(Mesh->EdgeSignOnCell), + DvEdge(Mesh->DvEdge), AreaCell(Mesh->AreaCell) { + deepCopy(HighOrderFlxHorz, 0); +} + TracerDiffOnCell::TracerDiffOnCell(const HorzMesh *Mesh, const VertCoord *VCoord) : NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), @@ -100,6 +120,30 @@ TracerHyperDiffOnCell::TracerHyperDiffOnCell(const HorzMesh *Mesh, MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} +void TracerHighOrderHorzAdvOnCell::init() { + const HorzMesh *Mesh = this->HorzontalMesh; + const auto MaxEdges2 = Mesh->MaxEdges2; + const auto NEdgesAll = Mesh->NEdgesAll; + const auto NCellsOwned = Mesh->NCellsOwned; + const auto NEdgesOwned = Mesh->NEdgesOwned; + // Allocate Kokkos arrays in member data + + SecondDerivativeOnCell secondDerivativeOnCell(Mesh); + Array3DReal DerivTwo("DerivTwo", MaxEdges2 + 2, 2, NEdgesAll); + parallelFor( + {NCellsOwned}, + KOKKOS_LAMBDA(int ICell) { secondDerivativeOnCell(DerivTwo, ICell); }); + // Compute masks and coefficients + Kokkos::fence(); + MasksAndCoefficients masksAndCoefficients(Mesh, DerivTwo, NAdvCellsForEdge, + AdvCellsForEdge, AdvMaskHighOrder, + AdvCoefs, AdvCoefs3rd); + Kokkos::fence(); + parallelFor( + {NEdgesOwned}, + KOKKOS_LAMBDA(int IEdge) { masksAndCoefficients(IEdge); }); + Kokkos::fence(); +} } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index cc9aa841b60c..dad6c0cdd204 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -17,8 +17,7 @@ #include "OceanState.h" #include "VertCoord.h" -#include -#include +#include // for std::copysign namespace OMEGA { @@ -373,17 +372,16 @@ class TracerHorzAdvOnCell { TracerHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); - KOKKOS_FUNCTION void operator()(const Array3DReal &Tend, I4 L, I4 ICell, - I4 KChunk, const Array2DReal &NormVelEdge, + KOKKOS_FUNCTION void operator()(const Array3DReal &Tend, const I4 L, + const I4 ICell, const I4 KChunk, + const Array2DReal &NormVelEdge, const Array3DReal &HTracersOnEdge) const { const I4 KStartCell = chunkStart(KChunk, MinLayerCell(ICell)); const I4 KLenCell = chunkLength(KChunk, KStartCell, MaxLayerCell(ICell)); const I4 KEndCell = KStartCell + KLenCell - 1; const Real InvAreaCell = 1._Real / AreaCell(ICell); - Real HAdvTmp[VecLength] = {0}; - for (int J = 0; J < NEdgesOnCell(ICell); ++J) { const I4 JEdge = EdgesOnCell(ICell, J); const I4 KStartEdge = Kokkos::max(KStartCell, MinLayerEdgeBot(JEdge)); @@ -395,7 +393,7 @@ class TracerHorzAdvOnCell { EdgeSignOnCell(ICell, J) * HTracersOnEdge(L, JEdge, K) * NormVelEdge(JEdge, K) * InvAreaCell; - } + } } for (int KVec = 0; KVec < KLenCell; ++KVec) { const I4 K = KStartCell + KVec; @@ -417,6 +415,76 @@ class TracerHorzAdvOnCell { Array1DI4 MaxLayerEdgeTop; }; +// Tracer high order horizontal advection term +class TracerHighOrderHorzAdvOnCell { + public: + bool Enabled = false; + // coefficient for blending high-order terms + Real coef3rdOrder = 0.25; + + TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh); + void init(); + + KOKKOS_FUNCTION void operator()(const I4 L, const I4 IEdge, const I4 KChunk, + const Array3DReal &TracerCell, + const Array2DReal &FluxLayerThickEdge, + const Array2DReal &NormVelEdge) const { + const I4 KStart = KChunk * VecLength; + const I4 KEnd = KStart + VecLength; + for (int K = KStart; K < KEnd; ++K) + HighOrderFlxHorz(L, IEdge, K) = 0; + if (AdvMaskHighOrder(IEdge)) { + for (int I = 0; I < NAdvCellsForEdge(IEdge); ++I) { + const I4 ICell = AdvCellsForEdge(IEdge, I); + for (int K = KStart; K < KEnd; ++K) { + const Real normalThicknessFlux = FluxLayerThickEdge(IEdge, K) * NormVelEdge(IEdge, K); + const Real tracerWgt = (AdvCoefs(I, IEdge) + coef3rdOrder * + std::copysign(1._Real, normalThicknessFlux) * + AdvCoefs3rd(I, IEdge)) * normalThicknessFlux; + HighOrderFlxHorz(L, IEdge, K) += tracerWgt * TracerCell(L, ICell, K); + } + } + } else { + for (int K = KStart; K < KEnd; ++K) { + const I4 JCell0 = CellsOnEdge(IEdge, 0); + const I4 JCell1 = CellsOnEdge(IEdge, 1); + const Real normalThicknessFlux = FluxLayerThickEdge(IEdge, K) * NormVelEdge(IEdge, K); + const Real tracerWgt = DvEdge(IEdge) * 0.5_Real * normalThicknessFlux; + HighOrderFlxHorz(L, IEdge, K) += tracerWgt * (TracerCell(L, JCell1, K) + TracerCell(L, JCell0, K)); + } + } + } + + KOKKOS_FUNCTION void operator()(const Array3DReal &Tend, + const I4 L, const I4 ICell, const I4 KChunk) const { + const I4 KStart = KChunk * VecLength; + const I4 KEnd = KStart + VecLength; + const Real InvAreaCell = 1._Real / AreaCell(ICell); + for (int I=0; I < NEdgesOnCell(ICell); ++I) { + const I4 IEdge = EdgesOnCell(ICell, I); + for (int K = KStart; K < KEnd; ++K) { + Tend(L, ICell, K) += EdgeSignOnCell(ICell, I) * HighOrderFlxHorz(L, IEdge, K) * InvAreaCell; + } + } + } + + private: + const HorzMesh *HorzontalMesh; + Array1DI4 NAdvCellsForEdge; + Array2DI4 AdvCellsForEdge; + Array1DI4 AdvMaskHighOrder; + Array2DReal AdvCoefs; + Array2DReal AdvCoefs3rd; + Array3DReal HighOrderFlxHorz; + + Array1DI4 NEdgesOnCell; + Array2DI4 EdgesOnCell; + Array2DI4 CellsOnEdge; + Array2DReal EdgeSignOnCell; + Array1DReal DvEdge; + Array1DReal AreaCell; +}; + // Tracer horizontal diffusion term class TracerDiffOnCell { public: diff --git a/components/omega/test/ocn/HorzMeshTest.cpp b/components/omega/test/ocn/HorzMeshTest.cpp index 0c218325b100..386459b13706 100644 --- a/components/omega/test/ocn/HorzMeshTest.cpp +++ b/components/omega/test/ocn/HorzMeshTest.cpp @@ -180,12 +180,12 @@ int main(int argc, char *argv[]) { LocCells = Mesh->NCellsOwned; Err = MPI_Allreduce(&LocCells, &SumCells, 1, MPI_INT32_T, MPI_SUM, Comm); - if (SumCells == DefDecomp->NCellsGlobal) { + if (SumCells == Mesh->NCellsGlobal) { LOG_INFO("HorzMeshTest: Sum cell ID test PASS"); } else { RetVal += 1; LOG_INFO("HorzMeshTest: Sum cell ID test FAIL {} {}", SumCells, - DefDecomp->NCellsGlobal); + Mesh->NCellsGlobal); } // Test that cell centers are on sphere @@ -247,12 +247,12 @@ int main(int argc, char *argv[]) { LocEdges = Mesh->NEdgesOwned; Err = MPI_Allreduce(&LocEdges, &SumEdges, 1, MPI_INT32_T, MPI_SUM, Comm); - if (SumEdges == DefDecomp->NEdgesGlobal) { + if (SumEdges == Mesh->NEdgesGlobal) { LOG_INFO("HorzMeshTest: Sum edge ID test PASS"); } else { RetVal += 1; LOG_INFO("HorzMeshTest: Sum edge ID test FAIL {} {}", SumEdges, - DefDecomp->NEdgesGlobal); + Mesh->NEdgesGlobal); } // Test that edge coordinates are on sphere @@ -767,6 +767,25 @@ int main(int argc, char *argv[]) { RetVal += 1; LOG_INFO("HorzMeshTest: vertex halo exhange FAIL"); } + + // Test that all Cell IDs are accounted for by + // summing the list of owned values by all tasks. The result should + // be the sum of the integers from 1 to NCellsGlobal + OMEGA::I4 RefSumIDs = 0, LocSumIDs = 0, SumIDs = 0; + OMEGA::HostArray1DI4 CellIDH = OMEGA::createHostMirrorCopy(Mesh->CellID); + for (int n = 0; n < Mesh->NCellsGlobal; ++n) + RefSumIDs += n + 1; + for (int n = 0; n < Mesh->NCellsOwned; ++n) + LocSumIDs += CellIDH(n); + Err = MPI_Allreduce(&LocSumIDs, &SumIDs, 1, MPI_INT32_T, MPI_SUM, Comm); + + if (SumIDs == RefSumIDs) { + LOG_INFO("DecompTest: Sum cell ID test PASS"); + } else { + RetVal += 1; + LOG_INFO("DecompTest: Sum cell ID test FAIL {} {}", SumIDs, RefSumIDs); + } + // Finalize Omega objects HorzMesh::clear(); Dimension::clear(); diff --git a/components/omega/test/ocn/HorzOperatorsTest.cpp b/components/omega/test/ocn/HorzOperatorsTest.cpp index a9ec84331acf..191b459c7810 100644 --- a/components/omega/test/ocn/HorzOperatorsTest.cpp +++ b/components/omega/test/ocn/HorzOperatorsTest.cpp @@ -422,6 +422,497 @@ int testInterpCellToEdge(Real RTol) { return Err; } +int testsecondderivativeoncellDeterminePlanerPatchGeometry(Real RTol) { + int Err = 0; + const int C[38][2] = { + {2200000, 2000000}, {2800000, 2000000}, {1300000, 2519615}, + {1900000, 2519615}, {3100000, 2519615}, {3700000, 2519615}, + {1000000, 3039230}, {2200000, 3039230}, {2800000, 3039230}, + {4000000, 3039230}, {1300000, 3558846}, {1900000, 3558846}, + {3100000, 3558846}, {3700000, 3558846}, {1000000, 4078461}, + {2200000, 4078461}, {2800000, 4078461}, {4000000, 4078461}, + {1300000, 4598076}, {1900000, 4598076}, {3100000, 4598076}, + {3700000, 4598076}, {1000000, 5117691}, {2200000, 5117691}, + {2800000, 5117691}, {4000000, 5117691}, {1300000, 5637307}, + {1900000, 5637307}, {3100000, 5637307}, {3700000, 5637307}, + {1000000, 6156922}, {2200000, 6156922}, {2800000, 6156922}, + {4000000, 6156922}, {1300000, 6676537}, {1900000, 6676537}, + {3100000, 6676537}, {3700000, 6676537}}; + const int E[12][6] = {{0, 1, 4, 8, 7, 3}, {2, 3, 7, 11, 10, 6}, + {4, 5, 9, 13, 12, 8}, {7, 8, 12, 16, 15, 11}, + {10, 11, 15, 19, 18, 14}, {12, 13, 17, 21, 20, 16}, + {15, 16, 20, 24, 23, 19}, {18, 19, 23, 27, 26, 22}, + {20, 21, 25, 29, 28, 24}, {23, 24, 28, 32, 31, 27}, + {26, 27, 31, 35, 34, 30}, {28, 29, 33, 37, 36, 32}}; + const int CtoC = 1039230; + const R8 Pii = 3.141592653589793_Real; + + std::map, int> Edges; + for (int c = 0; c < 12; ++c) { + for (int i = 0; i < 6; ++i) { + const int j = (i + 1) % 6; + const std::pair edge(std::min(E[c][i], E[c][j]), + std::max(E[c][i], E[c][j])); + if (!Edges.count(edge)) + Edges[edge] = Edges.size(); + } + } + const int NEdges = Edges.size(); + std::vector> edgesOnCell(12); + std::vector> cellsOnEdge(NEdges); + std::vector angleEdge(NEdges); + std::vector dcEdge(NEdges, -1); + for (int c = 0; c < 12; ++c) { + for (int i = 0; i < 6; ++i) { + const int j = (i + 1) % 6; + const std::pair edge(std::min(E[c][i], E[c][j]), + std::max(E[c][i], E[c][j])); + const int e = Edges.at(edge); + edgesOnCell[c][i] = e; + if (cellsOnEdge[e].empty()) { + const int v0[2] = {C[E[c][i]][0], C[E[c][i]][1]}; + const int v1[2] = {C[E[c][j]][0], C[E[c][j]][1]}; + const int v[2] = {v1[0] - v0[0], v1[1] - v0[1]}; + const double theta = std::atan2(-v[0], v[1]); + angleEdge[e] = theta; + } + cellsOnEdge[e].push_back(c); + } + } + for (int e = 0; e < cellsOnEdge.size(); ++e) { + if (1 < cellsOnEdge[e].size()) + dcEdge[e] = CtoC; + } + Array2DI4 EdgesOnCell("EdgesOnCell", 12, 6); + auto EdgesOnCellH = createHostMirrorCopy(EdgesOnCell); + for (int c = 0; c < 12; ++c) + for (int i = 0; i < 6; ++i) + EdgesOnCellH(c, i) = edgesOnCell[c][i]; + OMEGA::deepCopy(EdgesOnCell, EdgesOnCellH); + + Array2DI4 CellsOnEdge("CellsOnEdge", cellsOnEdge.size(), 2); + auto CellsOnEdgeH = createHostMirrorCopy(CellsOnEdge); + for (int e = 0; e < cellsOnEdge.size(); ++e) + for (int i = 0; i < cellsOnEdge[e].size(); ++i) + CellsOnEdgeH(e, i) = cellsOnEdge[e][i]; + OMEGA::deepCopy(CellsOnEdge, CellsOnEdgeH); + + Array1DReal AngleEdge("AndleEdge", angleEdge.size()); + auto AngleEdgeH = createHostMirrorCopy(AngleEdge); + for (int e = 0; e < angleEdge.size(); ++e) + AngleEdgeH(e) = angleEdge[e]; + OMEGA::deepCopy(AngleEdge, AngleEdgeH); + + Array1DReal DcEdge("DcEdge", dcEdge.size()); + auto DcEdgeH = createHostMirrorCopy(DcEdge); + for (int e = 0; e < dcEdge.size(); ++e) + DcEdgeH(e) = dcEdge[e]; + OMEGA::deepCopy(DcEdge, DcEdgeH); + + class SecondDerivativeOnCellTest : public SecondDerivativeOnCell { + public: + virtual ~SecondDerivativeOnCellTest() {} + KOKKOS_INLINE_FUNCTION static void + Test(const int ICell, const int NEdges, const Array1DI4 EdgesOnCell, + const Array2DI4 CellsOnEdge, const Array1DReal AngleEdge, + const Array1DReal DcEdge, Array1DReal XP, Array1DReal YP, + Array1DReal Angle2D) { + SecondDerivativeOnCell::DeterminePlanerPatchGeometry( + ICell, NEdges, EdgesOnCell, CellsOnEdge, AngleEdge, DcEdge, XP, YP, + Angle2D); + } + }; + for (int ICell = 3; ICell < 7; ICell += 3) { + const I4 NEdges = 6; + const I4 MaxEdges = 7; + Array1DReal XP("XP", MaxEdges); + Array1DReal YP("YP", MaxEdges); + Array1DReal Angle2D("Array2D", MaxEdges); + const Array1DI4 edgesOnCell = + Kokkos::subview(EdgesOnCell, ICell, Kokkos::ALL); + Kokkos::parallel_for( + 1, KOKKOS_LAMBDA(int /* dummy */) { + SecondDerivativeOnCellTest::Test(ICell, NEdges, edgesOnCell, + CellsOnEdge, AngleEdge, DcEdge, + XP, YP, Angle2D); + }); + auto XPH = createHostMirrorCopy(XP); + auto YPH = createHostMirrorCopy(YP); + auto Angle2DH = createHostMirrorCopy(Angle2D); + OMEGA::deepCopy(XPH, XP); + OMEGA::deepCopy(YPH, YP); + OMEGA::deepCopy(Angle2DH, Angle2D); + for (int i = 0; i < 6; ++i) { + const Real RTol = sizeof(Real) == 4 ? 1e-3 : 1e-5; + const double theta = -Pii / 2 + (5 == i ? -Pii / 3 : i * Pii / 3); + const double x = CtoC * std::cos(theta); + const double y = CtoC * std::sin(theta); + if (!isApprox(XPH[i], x, RTol)) { + Err++; + LOG_ERROR( + "{}: FAIL, expected {}, got {}", + "testsecondderivativeoncellDeterminePlanerPatchGeometry:x", x, + XP[i]); + } + if (!isApprox(YPH[i], y, RTol)) { + Err++; + LOG_ERROR( + "{}: FAIL, expected {}, got {}", + "testsecondderivativeoncellDeterminePlanerPatchGeometry:y", y, + YP[i]); + } + if (!isApprox(Angle2DH[i], theta, RTol)) { + Err++; + LOG_ERROR( + "{}: FAIL, expected {}, got {}", + "testsecondderivativeoncellDeterminePlanerPatchGeometry:theta", + theta, Angle2D[i]); + } + } + } + if (Err == 0) + LOG_INFO("testsecondderivativeoncellDeterminePlanerPatchGeometry: " + "Successful completion"); + return Err; +} + +int testsecondderivativeoncellLeastSquaresFit(Real RTol) { + int Err = 0; + + const int CtoC = 7; + const R8 Pii = 3.141592653589793_Real; + const I4 MaxMaxEdges = 10; + const int NEdges = 6; + class SecondDerivativeOnCellTest : public SecondDerivativeOnCell { + public: + virtual ~SecondDerivativeOnCellTest() {} + KOKKOS_INLINE_FUNCTION static void Test(const Array1DReal XP, + const Array1DReal YP, + const int NEdges, Array2DReal B) { + SecondDerivativeOnCell::LeastSquaresFit(XP, YP, NEdges, B); + } + }; + Array1DReal XP("XP", NEdges); + Array1DReal YP("YP", NEdges); + auto XPH = createHostMirrorCopy(XP); + auto YPH = createHostMirrorCopy(YP); + for (int i = 0; i < NEdges; ++i) { + const double theta = -Pii / 2 + i * Pii / 3; + XPH[i] = CtoC * std::cos(theta); + YPH[i] = CtoC * std::sin(theta); + } + OMEGA::deepCopy(XP, XPH); + OMEGA::deepCopy(YP, YPH); + Array2DReal B("B", MaxMaxEdges, MaxMaxEdges); + SecondDerivativeOnCellTest::Test(XP, YP, NEdges, B); + + // From a Mathematica script. + const Real c0 = 0.0412393049421; + const Real c1 = 0.0238095238095; + const Real c2 = 0.00680272108844; + const Real c3 = 0.0117826585549; + const Real M[6][7] = { + {1., 0, 0, 0, 0, 0, 0}, + {0, 0, c0, c0, 0, -c0, -c0}, + {0, -0.047619047619, -c1, c1, 0.047619047619, c1, -c1}, + {-0.0204081632653, -0.00340136054422, c2, c2, -0.00340136054422, c2, c2}, + {0, 0, -c3, c3, 0, -c3, c3}, + {-0.0204081632653, 0.0102040816327, 0, 0, 0.0102040816327, 0, 0}}; + + auto BH = createHostMirrorCopy(B); + OMEGA::deepCopy(BH, B); + for (int i = 0; i < MaxMaxEdges; ++i) { + for (int j = 0; j < MaxMaxEdges; ++j) { + const Real RTol = sizeof(Real) == 4 ? 1e-3 : 1e-5; + const Real m = (i < 6 && j < 7) ? M[i][j] : 0; + if (i < 6 && j < 7 && !m) { + if (1e-12 < std::abs(BH(i, j))) { + Err++; + LOG_ERROR("{}: FAIL, expected {}, got {}", + "testsecondderivativeoncellLeastSquaresFit", m, + B(i, j)); + } + } else { + if (!isApprox(B(i, j), m, RTol)) { + Err++; + LOG_ERROR("{}: FAIL, expected {}, got {}", + "testsecondderivativeoncellLeastSquaresFit", m, + B(i, j)); + } + } + } + } + if (Err == 0) + LOG_INFO( + "testsecondderivativeoncellLeastSquaresFit: Successful completion"); + return Err; +} + +int testsecondderivativeoncellDetermineSphericalPatchGeometry(Real RTol) { + int Err = 0; + const int C[38][2] = { + {2200000, 2000000}, {2800000, 2000000}, {1300000, 2519615}, + {1900000, 2519615}, {3100000, 2519615}, {3700000, 2519615}, + {1000000, 3039230}, {2200000, 3039230}, {2800000, 3039230}, + {4000000, 3039230}, {1300000, 3558846}, {1900000, 3558846}, + {3100000, 3558846}, {3700000, 3558846}, {1000000, 4078461}, + {2200000, 4078461}, {2800000, 4078461}, {4000000, 4078461}, + {1300000, 4598076}, {1900000, 4598076}, {3100000, 4598076}, + {3700000, 4598076}, {1000000, 5117691}, {2200000, 5117691}, + {2800000, 5117691}, {4000000, 5117691}, {1300000, 5637307}, + {1900000, 5637307}, {3100000, 5637307}, {3700000, 5637307}, + {1000000, 6156922}, {2200000, 6156922}, {2800000, 6156922}, + {4000000, 6156922}, {1300000, 6676537}, {1900000, 6676537}, + {3100000, 6676537}, {3700000, 6676537}}; + const int E[12][6] = {{0, 1, 4, 8, 7, 3}, {2, 3, 7, 11, 10, 6}, + {4, 5, 9, 13, 12, 8}, {7, 8, 12, 16, 15, 11}, + {10, 11, 15, 19, 18, 14}, {12, 13, 17, 21, 20, 16}, + {15, 16, 20, 24, 23, 19}, {18, 19, 23, 27, 26, 22}, + {20, 21, 25, 29, 28, 24}, {23, 24, 28, 32, 31, 27}, + {26, 27, 31, 35, 34, 30}, {28, 29, 33, 37, 36, 32}}; + const int CtoC = 1039230; + const int R = 100 * CtoC; + const R8 Pii = 3.141592653589793_Real; + + // Project coordinates to sphere. + + std::map, int> Edges; + for (int c = 0; c < 12; ++c) { + for (int i = 0; i < 6; ++i) { + const int j = (i + 1) % 6; + const std::pair edge(std::min(E[c][i], E[c][j]), + std::max(E[c][i], E[c][j])); + if (!Edges.count(edge)) + Edges[edge] = Edges.size(); + } + } + const int NEdges = Edges.size(); + std::vector> edgesOnCell(12); + std::vector> cellsOnEdge(NEdges); + std::vector angleEdge(NEdges); + std::vector> verticesOnEdge(NEdges); + std::vector dcEdge(NEdges, -1); + for (int c = 0; c < 12; ++c) { + for (int i = 0; i < 6; ++i) { + const int j = (i + 1) % 6; + const std::pair edge(std::min(E[c][i], E[c][j]), + std::max(E[c][i], E[c][j])); + const int e = Edges.at(edge); + edgesOnCell[c][i] = e; + if (cellsOnEdge[e].empty()) { + const int v0[2] = {C[E[c][i]][0], C[E[c][i]][1]}; + const int v1[2] = {C[E[c][j]][0], C[E[c][j]][1]}; + const int v[2] = {v1[0] - v0[0], v1[1] - v0[1]}; + const double theta = std::atan2(-v[0], v[1]); + angleEdge[e] = theta; + verticesOnEdge[e][0] = E[c][i]; + verticesOnEdge[e][1] = E[c][j]; + } + cellsOnEdge[e].push_back(c); + } + } + for (int e = 0; e < cellsOnEdge.size(); ++e) { + if (1 < cellsOnEdge[e].size()) + dcEdge[e] = CtoC; + } + + double T[2] = {}; + for (int I = 0; I < 6; ++I) + for (int J = 0; J < 2; ++J) + T[J] += C[E[3][I]][J]; + + T[0] /= 6; + T[1] /= 6; + double X[38][2] = {}; + for (int I = 0; I < 38; ++I) + for (int J = 0; J < 2; ++J) + X[I][J] = C[I][J] - T[J]; + + Array1DReal XCell("XCell", 12); + Array1DReal YCell("YCell", 12); + Array1DReal ZCell("ZCell", 12); + auto XCellH = createHostMirrorCopy(XCell); + auto YCellH = createHostMirrorCopy(YCell); + auto ZCellH = createHostMirrorCopy(ZCell); + std::vector xcellh(12, 0); + std::vector ycellh(12, 0); + std::vector zcellh(12, 0); + + for (int I = 0; I < 12; ++I) { + T[0] = T[1] = 0; + for (int J = 0; J < 6; ++J) { + for (int K = 0; K < 2; ++K) + T[K] += X[E[I][J]][K]; + } + T[0] /= 6; + T[1] /= 6; + const double d = std::sqrt(T[0] * T[0] + T[1] * T[1]); + xcellh[I] = R * std::sin(d / R) * T[0] / d; + ycellh[I] = R * std::sin(d / R) * T[1] / d; + zcellh[I] = R * std::cos(d / R); + } + for (int I = 0; I < 12; ++I) { + XCellH(I) = xcellh[I]; + YCellH(I) = ycellh[I]; + ZCellH(I) = zcellh[I]; + } + OMEGA::deepCopy(XCell, XCellH); + OMEGA::deepCopy(YCell, YCellH); + OMEGA::deepCopy(ZCell, ZCellH); + + Array1DReal XVertex("XVertex", 38); + Array1DReal YVertex("YVertex", 38); + Array1DReal ZVertex("ZVertex", 38); + auto XVertexH = createHostMirrorCopy(XVertex); + auto YVertexH = createHostMirrorCopy(YVertex); + auto ZVertexH = createHostMirrorCopy(ZVertex); + + for (int I = 0; I < 38; ++I) { + const double x = X[I][0], y = X[I][1]; + const double d = std::sqrt(x * x + y * y); + XVertexH(I) = R * std::sin(d / R) * X[I][0] / d; + YVertexH(I) = R * std::sin(d / R) * X[I][1] / d; + ZVertexH(I) = R * std::cos(d / R); + } + OMEGA::deepCopy(XVertex, XVertexH); + OMEGA::deepCopy(YVertex, YVertexH); + OMEGA::deepCopy(ZVertex, ZVertexH); + + Array2DI4 EdgesOnCell("EdgesOnCell", 12, 6); + auto EdgesOnCellH = createHostMirrorCopy(EdgesOnCell); + for (int c = 0; c < 12; ++c) + for (int i = 0; i < 6; ++i) + EdgesOnCellH(c, i) = edgesOnCell[c][i]; + OMEGA::deepCopy(EdgesOnCell, EdgesOnCellH); + + Array2DI4 VerticesOnEdge("VerticesOnEdge", NEdges, 2); + auto VerticesOnEdgeH = createHostMirrorCopy(VerticesOnEdge); + for (int c = 0; c < NEdges; ++c) + for (int i = 0; i < 2; ++i) + VerticesOnEdgeH(c, i) = verticesOnEdge[c][i]; + OMEGA::deepCopy(VerticesOnEdge, VerticesOnEdgeH); + + Array2DI4 CellsOnEdge("CellsOnEdge", cellsOnEdge.size(), 2); + auto CellsOnEdgeH = createHostMirrorCopy(CellsOnEdge); + for (int e = 0; e < cellsOnEdge.size(); ++e) + for (int i = 0; i < cellsOnEdge[e].size(); ++i) + CellsOnEdgeH(e, i) = cellsOnEdge[e][i]; + OMEGA::deepCopy(CellsOnEdge, CellsOnEdgeH); + + Array1DReal AngleEdge("AndleEdge", angleEdge.size()); + auto AngleEdgeH = createHostMirrorCopy(AngleEdge); + for (int e = 0; e < angleEdge.size(); ++e) + AngleEdgeH(e) = angleEdge[e]; + OMEGA::deepCopy(AngleEdge, AngleEdgeH); + + Array1DReal DcEdge("DcEdge", dcEdge.size()); + auto DcEdgeH = createHostMirrorCopy(DcEdge); + for (int e = 0; e < dcEdge.size(); ++e) + DcEdgeH(e) = dcEdge[e]; + OMEGA::deepCopy(DcEdge, DcEdgeH); + + class SecondDerivativeOnCellTest : public SecondDerivativeOnCell { + public: + virtual ~SecondDerivativeOnCellTest() {} + KOKKOS_INLINE_FUNCTION static void + Test(const int NEdges, const Array1DI4 EdgesOnCell, + const Array2DI4 VerticiesOnEdge, const Array1DReal XCell, + const Array1DReal YCell, const Array1DReal ZCell, + const Array1DReal XVertex, const Array1DReal YVertex, + const Array1DReal ZVertex, const Array1DI4 CellList, Array1DReal XP, + Array1DReal YP, Array1DReal Angle2D, Real &ThetaAbs) { + SecondDerivativeOnCell::DetermineSphericalPatchGeometry( + NEdges, EdgesOnCell, VerticiesOnEdge, XCell, YCell, ZCell, XVertex, + YVertex, ZVertex, CellList, XP, YP, Angle2D, ThetaAbs); + } + }; + { + const I4 NEdges = 6; + Array1DReal XP("XP", NEdges); + Array1DReal YP("YP", NEdges); + Array1DReal Angle2D("Angle2D", NEdges); + ; + Array1DI4 CellList("CellList", NEdges); + { + const I4 List[NEdges + 1] = {3, 0, 2, 5, 6, 4, 1}; + auto CellListH = createHostMirrorCopy(CellList); + for (int I = 0; I < NEdges + 1; ++I) + CellListH[I] = List[I]; + OMEGA::deepCopy(CellList, CellListH); + } + Array1DI4 edgesOnCell = Kokkos::subview(EdgesOnCell, 3, Kokkos::ALL); + Kokkos::parallel_for( + 1, KOKKOS_LAMBDA(int /* dummy */) { + Real ThetaAbs = {}; + SecondDerivativeOnCellTest::Test( + NEdges, edgesOnCell, VerticesOnEdge, XCell, YCell, ZCell, + XVertex, YVertex, ZVertex, CellList, XP, YP, Angle2D, + ThetaAbs); + }); + auto XPH = createHostMirrorCopy(XP); + auto YPH = createHostMirrorCopy(YP); + auto Angle2DH = createHostMirrorCopy(Angle2D); + for (int i = 0; i < 6; ++i) { + const Real RTol = sizeof(Real) == 4 ? 1e-3 : 1e-5; + T[0] = T[1] = 0; + for (int J = 0; J < 6; ++J) + for (int K = 0; K < 2; ++K) + T[K] += X[E[CellList[i + 1]][J]][K]; + T[0] /= 6; + T[1] /= 6; + + const double phi = Pii / 2 + i * 2 * Pii / 6; + // Rotate the mesh by Pi. This is what is done in + // DetermineSphericalPatchGeometry + const double x = -T[0]; // same as CtoC*std::cos(phi); + const double y = -T[1]; // same as CtoC*std::sin(phi); + + if (!isApprox(1 + XPH[i], 1 + x, RTol)) { + Err++; + LOG_ERROR( + "{}: FAIL, expected {}, got {}", + "testsecondderivativeoncellDetermineSphericalPatchGeometry:x", + x, XPH[i]); + } + if (!isApprox(1 + YPH[i], 1 + y, RTol)) { + Err++; + LOG_ERROR( + "{}: FAIL, expected {}, got {}", + "testsecondderivativeoncellDetermineSphericalPatchGeometry:y", + y, YPH[i]); + } + if (!isApprox(Angle2DH[i], phi, RTol)) { + Err++; + LOG_ERROR( + "{}: FAIL, expected {}, got {}", + "testsecondderivativeoncellDetermineSphericalPatchGeometry:phi", + phi, Angle2DH[i]); + } + } + } + if (Err == 0) + LOG_INFO("testsecondderivativeoncellDetermineSphericalPatchGeometry: " + "Successful completion"); + return Err; +} + +int testsecondderivativeoncellconstructor(Real RTol) { + int Err = 0; + const auto &Mesh = HorzMesh::getDefault(); + // Compute numerical result + const auto MaxEdges2 = Mesh->MaxEdges2; + const auto NEdgesAll = Mesh->NEdgesAll; + const auto NCellsOwned = Mesh->NCellsOwned; + + Array3DReal DerivTwo("DerivTwo", MaxEdges2, 2, NEdgesAll); + SecondDerivativeOnCell DerivativeOnCell(Mesh); + parallelFor( + {NCellsOwned}, + KOKKOS_LAMBDA(int ICell) { DerivativeOnCell(DerivTwo, ICell); }); + if (Err == 0) + LOG_INFO("OperatorsTest: TestSecondDerivativeOnCell PASS"); + return Err; +} //------------------------------------------------------------------------------ // The initialization routine for Operators testing int initOperatorsTest(const std::string &MeshFile) { @@ -473,6 +964,10 @@ int operatorsTest(const std::string &MeshFile = DefaultMeshFile) { Err += testCurl(RTol); Err += testRecon(RTol); Err += testInterpCellToEdge(RTol); + Err += testsecondderivativeoncellconstructor(RTol); + Err += testsecondderivativeoncellDeterminePlanerPatchGeometry(RTol); + Err += testsecondderivativeoncellLeastSquaresFit(RTol); + Err += testsecondderivativeoncellDetermineSphericalPatchGeometry(RTol); if (Err == 0) { LOG_INFO("OperatorsTest: Successful completion"); diff --git a/components/omega/test/ocn/TendencyTermsTest.cpp b/components/omega/test/ocn/TendencyTermsTest.cpp index 3817ca584da9..4aac2a0bb1e2 100644 --- a/components/omega/test/ocn/TendencyTermsTest.cpp +++ b/components/omega/test/ocn/TendencyTermsTest.cpp @@ -15,6 +15,7 @@ // //===-----------------------------------------------------------------------===/ #include "TendencyTerms.h" +#include "Config.h" #include "DataTypes.h" #include "Decomp.h" #include "Dimension.h" @@ -285,12 +286,12 @@ struct TestSetupSphere { } KOKKOS_FUNCTION Real scalarC(Real Lon, Real Lat) const { - return -(Radius / 2) * std::sqrt(3 / 2 / Pi) * std::cos(Lat) * + return -(Radius / 2) * std::sqrt(3. / 2. / Pi) * std::cos(Lat) * std::cos(Lon); } KOKKOS_FUNCTION Real tracerHyperDiff(Real Lon, Real Lat) const { - return std::sqrt(3 / 2 / Pi) * std::cos(Lat) * std::cos(Lon) / Radius; + return std::sqrt(3. / 2. / Pi) * std::cos(Lat) * std::cos(Lon) / Radius; } KOKKOS_FUNCTION Real windForcingX(Real Lon, Real Lat, @@ -1013,7 +1014,7 @@ void initTendTest(const std::string &MeshFile, int NVertLayers) { initLogging(DefEnv); // Open config file - Config("Omega"); + Config::Initialize(); Config::readAll("omega.yml"); IO::init(DefComm); From aec04402177cbae618fd11c63210f4c1cbac3d70 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 18 Nov 2025 14:47:46 -0700 Subject: [PATCH 014/152] Rename flag used to trigger higher order advection. --- .../omega/doc/userGuide/TendencyTerms.md | 2 +- components/omega/src/ocn/Tendencies.cpp | 29 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/components/omega/doc/userGuide/TendencyTerms.md b/components/omega/doc/userGuide/TendencyTerms.md index d0bab3d52aca..d058864a7c20 100644 --- a/components/omega/doc/userGuide/TendencyTerms.md +++ b/components/omega/doc/userGuide/TendencyTerms.md @@ -43,7 +43,7 @@ the currently available tendency terms: | | ViscDel4 | horizontal biharmonic mixing coefficient for normal velocity | | DivFactor | scale factor for the divergence term | TracerHorzAdvOnCell | TracerHorzAdvTendencyEnable -| | TracerHighOrderHorzAdvTendencyEnable | enable/disable term +| | TracerHorzAdvTendencyOrder | enable/disable term | TracerDiffOnCell | TracerDiffTendencyEnable | enable/disable term | | EddyDiff2 | horizontal diffusion coefficient | TracerHyperDiffOnCell | TracerHyperDiffTendencyEnable | enable/disable term diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 135928462503..b3a1e8b45504 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -13,6 +13,7 @@ #include "Error.h" #include "Pacer.h" #include "Tracers.h" +#include namespace OMEGA { @@ -166,23 +167,19 @@ void Tendencies::readTendConfig( } if (TendConfig->existsVar("TracerHorzAdvTendencyEnable")) { - TendConfig->get("TracerHorzAdvTendencyEnable", - this->TracerHorzAdv.Enabled); + I4 Order = 1; + if (TendConfig->existsVar("TracerHorzAdvTendencyOrder")) + TendConfig->get("TracerHorzAdvTendencyOrder", Order); + if (Order == 1) + this->TracerHorzAdv.Enabled = true; + else if (Order == 2) + this->TracerHighOrderHorzAdv.Enabled = true; + else { + const std::string msg = + "TracerHorzAdvTendencyOrder: Only values are 1 and 2, found "+std::to_string(Order); + ABORT_ERROR(msg); + } } - if (TendConfig->existsVar("TracerHighOrderHorzAdvTendencyEnable")) { - TendConfig->get("TracerHighOrderHorzAdvTendencyEnable", - this->TracerHighOrderHorzAdv.Enabled); - } - if (!TendConfig->existsVar("TracerHorzAdvTendencyEnable") && !TendConfig->existsVar("TracerHighOrderHorzAdvTendencyEnable")) { - Err += TendConfig->get("TracerHorzAdvTendencyEnable", - this->TracerHorzAdv.Enabled); - Err += TendConfig->get("TracerHighOrderHorzAdvTendencyEnable", - this->TracerHighOrderHorzAdv.Enabled); - CHECK_ERROR_ABORT( - Err, "Tendencies: Neither TracerHorzAdvTendencyEnable nor " - "TracerHighOrderHorzAdvTendencyEnable found in TendConfig"); - } - Err += TendConfig->get("TracerDiffTendencyEnable", this->TracerDiffusion.Enabled); CHECK_ERROR_ABORT( From 9f07fd9792cb0497bfdec9d0af8ae58024a06f4b Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 18 Nov 2025 15:01:54 -0700 Subject: [PATCH 015/152] Rename flag used to trigger higher order advection. --- components/omega/configs/Default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 9b4d3c617f2f..dffe1e25ff7a 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -44,7 +44,7 @@ Omega: BottomDragTendencyEnable: false BottomDragCoeff: 0.0 TracerHorzAdvTendencyEnable: false - TracerHighOrderHorzAdvTendencyEnable: true + TracerHorzAdvTendencyOrder: 1 TracerDiffTendencyEnable: true EddyDiff2: 10.0 TracerHyperDiffTendencyEnable: true From fc80b1be9f1c7ec6fe0bd56dd0da37472b8c800d Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 18 Nov 2025 15:03:34 -0700 Subject: [PATCH 016/152] Rename flag used to trigger higher order advection. --- configs/Default.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configs/Default.yml b/configs/Default.yml index 4b9d791b9efd..546859849e97 100644 --- a/configs/Default.yml +++ b/configs/Default.yml @@ -26,7 +26,8 @@ Omega: VelHyperDiffTendencyEnable: true ViscDel4: 1.2e11 DivFactor: 1.0 - TracerHorzAdvTendencyEnable: true + TracerHorzAdvTendencyEnable: false + TracerHorzAdvTendencyOrder: 2 TracerDiffTendencyEnable: true EddyDiff2: 10.0 TracerHyperDiffTendencyEnable: true From b73ed2e0a8140209d5e23bb1c01a63b968191967 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 18 Nov 2025 15:17:28 -0700 Subject: [PATCH 017/152] Rename flag used to trigger higher order advection. --- components/omega/configs/Default.yml | 2 +- components/omega/doc/userGuide/TendencyTerms.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index dffe1e25ff7a..7d3ce7525308 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -43,7 +43,7 @@ Omega: WindForcingTendencyEnable: false BottomDragTendencyEnable: false BottomDragCoeff: 0.0 - TracerHorzAdvTendencyEnable: false + TracerHorzAdvTendencyEnable: true TracerHorzAdvTendencyOrder: 1 TracerDiffTendencyEnable: true EddyDiff2: 10.0 diff --git a/components/omega/doc/userGuide/TendencyTerms.md b/components/omega/doc/userGuide/TendencyTerms.md index d058864a7c20..cad251f8dd93 100644 --- a/components/omega/doc/userGuide/TendencyTerms.md +++ b/components/omega/doc/userGuide/TendencyTerms.md @@ -42,8 +42,8 @@ the currently available tendency terms: | VelocityHyperDiffOnEdge | VelHyperDiffTendencyEnable | enable/disable term | | ViscDel4 | horizontal biharmonic mixing coefficient for normal velocity | | DivFactor | scale factor for the divergence term -| TracerHorzAdvOnCell | TracerHorzAdvTendencyEnable -| | TracerHorzAdvTendencyOrder | enable/disable term +| TracerHorzAdvOnCell | TracerHorzAdvTendencyEnable | enable/disable term +| | TracerHorzAdvTendencyOrder | Either 1 for standard to 2 for high order | TracerDiffOnCell | TracerDiffTendencyEnable | enable/disable term | | EddyDiff2 | horizontal diffusion coefficient | TracerHyperDiffOnCell | TracerHyperDiffTendencyEnable | enable/disable term From 2da7e1d9d58246fc120397b8910d7ddb76b2ea92 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 18 Nov 2025 15:50:45 -0700 Subject: [PATCH 018/152] Clang format. --- components/omega/src/ocn/HorzOperators.cpp | 8 +- components/omega/src/ocn/HorzOperators.h | 117 ++++++++++-------- components/omega/src/ocn/Tendencies.cpp | 20 +-- components/omega/src/ocn/TendencyTerms.h | 47 ++++--- .../omega/test/ocn/HorzOperatorsTest.cpp | 2 +- 5 files changed, 107 insertions(+), 87 deletions(-) diff --git a/components/omega/src/ocn/HorzOperators.cpp b/components/omega/src/ocn/HorzOperators.cpp index 6cb54101bd8a..c6ce926f4bca 100644 --- a/components/omega/src/ocn/HorzOperators.cpp +++ b/components/omega/src/ocn/HorzOperators.cpp @@ -58,14 +58,16 @@ MasksAndCoefficients::MasksAndCoefficients( Array1DI4 NAdvCellsForEdge, Array2DI4 AdvCellsForEdge, Array1DI4 AdvMaskHighOrder, Array2DReal AdvCoefs, Array2DReal AdvCoefs3rd) : NCellsGlobal(Mesh->NCellsGlobal), NCellsAll(Mesh->NCellsAll), - NAdvCellsMax(Mesh->MaxEdges2), // PatchCellLists("PatchCellLists", Mesh->NEdgesOwned, Mesh->NEdgesAll+1), + NAdvCellsMax(Mesh->MaxEdges2), // PatchCellLists("PatchCellLists", + // Mesh->NEdgesOwned, Mesh->NEdgesAll+1), NAdvCellsForEdge(NAdvCellsForEdge), AdvCellsForEdge(AdvCellsForEdge), NEdgesOnEdge(Mesh->NEdgesOnEdge), NEdgesOnCell(Mesh->NEdgesOnCell), CellIndx("CellIndx", Mesh->NEdgesOwned, Mesh->MaxEdges2 + 2), - CellIndxSorted("CellIndxSorted", Mesh->NEdgesOwned, 2, Mesh->MaxEdges2 + 2), + CellIndxSorted("CellIndxSorted", Mesh->NEdgesOwned, 2, + Mesh->MaxEdges2 + 2), CellID(Mesh->CellID), AdvMaskHighOrder(AdvMaskHighOrder), EdgesOnEdge(Mesh->EdgesOnEdge), CellsOnCell(Mesh->CellsOnCell), CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), DvEdge(Mesh->DvEdge), AdvCoefs(AdvCoefs), AdvCoefs3rd(AdvCoefs3rd), - DerivTwo(DerivTwo) { } + DerivTwo(DerivTwo) {} } // namespace OMEGA diff --git a/components/omega/src/ocn/HorzOperators.h b/components/omega/src/ocn/HorzOperators.h index 2bfb54b34284..7314e3160f44 100644 --- a/components/omega/src/ocn/HorzOperators.h +++ b/components/omega/src/ocn/HorzOperators.h @@ -210,8 +210,9 @@ class SecondDerivativeOnCell { int ICell) const { const int NEdges = NEdgesOnCell(ICell); if (MaxMaxEdges < NEdges) - printf ("Error: Number of edges on cell:%d exceeds maximum " - "expected:%d for cell:%d", NEdges, MaxMaxEdges, ICell); + printf("Error: Number of edges on cell:%d exceeds maximum " + "expected:%d for cell:%d", + NEdges, MaxMaxEdges, ICell); // check to see if we are reaching outside the halo auto CellList = Kokkos::subview(CellListCell, ICell, Kokkos::ALL); @@ -403,22 +404,24 @@ class SecondDerivativeOnCell { class MasksAndCoefficients { - KOKKOS_INLINE_FUNCTION static void swap(Array2DI4 &vec, const int m, const int n) { - for (int i : {0,1}) { - const I4 j = vec(i,m); - vec(i,m) = vec(i,n); - vec(i,n) = j; + KOKKOS_INLINE_FUNCTION static void swap(Array2DI4 &vec, const int m, + const int n) { + for (int i : {0, 1}) { + const I4 j = vec(i, m); + vec(i, m) = vec(i, n); + vec(i, n) = j; } } // Sort the second dimension (values) of vec based on the first (keys): - //Array1DI4 keys = Kokkos::subview(vec, 0, Kokkos::ALL); - //Array1DI4 values = Kokkos::subview(vec, 1, Kokkos::ALL); - KOKKOS_INLINE_FUNCTION static int partition(Array2DI4 &vec, const int low, const int high) { + // Array1DI4 keys = Kokkos::subview(vec, 0, Kokkos::ALL); + // Array1DI4 values = Kokkos::subview(vec, 1, Kokkos::ALL); + KOKKOS_INLINE_FUNCTION static int partition(Array2DI4 &vec, const int low, + const int high) { // Selecting last element as the pivot - const I4 pivot = vec(0,high); - int i = (low - 1); + const I4 pivot = vec(0, high); + int i = (low - 1); for (int j = low; j < high; ++j) { - if (vec(0,j) <= pivot) { + if (vec(0, j) <= pivot) { i++; swap(vec, i, j); } @@ -429,7 +432,8 @@ class MasksAndCoefficients { return (i + 1); } - KOKKOS_INLINE_FUNCTION static void sort_by_key(Array2DI4 &vec, const I4 low, const I4 high) { + KOKKOS_INLINE_FUNCTION static void sort_by_key(Array2DI4 &vec, const I4 low, + const I4 high) { // Base case: This part will be executed till the starting // index low is lesser than the ending index high if (low < high) { @@ -444,15 +448,14 @@ class MasksAndCoefficients { } KOKKOS_INLINE_FUNCTION static bool is_sorted(Array2DI4 &vec) { bool sorted = true; - const I4 N = vec.extent(1)-1; - for (I4 I=0; IexistsVar("TracerHorzAdvTendencyEnable")) { I4 Order = 1; - if (TendConfig->existsVar("TracerHorzAdvTendencyOrder")) + if (TendConfig->existsVar("TracerHorzAdvTendencyOrder")) TendConfig->get("TracerHorzAdvTendencyOrder", Order); if (Order == 1) this->TracerHorzAdv.Enabled = true; else if (Order == 2) this->TracerHighOrderHorzAdv.Enabled = true; - else { - const std::string msg = - "TracerHorzAdvTendencyOrder: Only values are 1 and 2, found "+std::to_string(Order); + else { + const std::string msg = + "TracerHorzAdvTendencyOrder: Only values are 1 and 2, found " + + std::to_string(Order); ABORT_ERROR(msg); - } + } } Err += TendConfig->get("TracerDiffTendencyEnable", this->TracerDiffusion.Enabled); @@ -530,9 +531,10 @@ void Tendencies::computeTracerTendenciesOnly( }); // compute tracer horizotal advection - const Array2DReal &NormalVelEdge = State->NormalVelocity[VelTimeLevel]; - const Array3DReal &HTracersEdge = AuxState->TracerAux.HTracersEdge; - const Array2DReal &FluxLayerThickEdge = AuxState->LayerThicknessAux.FluxLayerThickEdge; + const Array2DReal &NormalVelEdge = State->NormalVelocity[VelTimeLevel]; + const Array3DReal &HTracersEdge = AuxState->TracerAux.HTracersEdge; + const Array2DReal &FluxLayerThickEdge = + AuxState->LayerThicknessAux.FluxLayerThickEdge; if (LocTracerHorzAdv.Enabled) { Pacer::start("Tend:tracerHorzAdv", 2); parallelForOuter( @@ -555,7 +557,7 @@ void Tendencies::computeTracerTendenciesOnly( {NTracers, NEdgesAll, NChunks}, KOKKOS_LAMBDA(int L, int IEdge, int KChunk) { LocTracerHighOrderHorzAdv(L, IEdge, KChunk, TracerArray, - FluxLayerThickEdge, NormalVelEdge); + FluxLayerThickEdge, NormalVelEdge); }); parallelFor( {NTracers, NCellsAll, NChunks}, diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index dad6c0cdd204..f664a5eea5e5 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -373,8 +373,8 @@ class TracerHorzAdvOnCell { TracerHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); KOKKOS_FUNCTION void operator()(const Array3DReal &Tend, const I4 L, - const I4 ICell, const I4 KChunk, - const Array2DReal &NormVelEdge, + const I4 ICell, const I4 KChunk, + const Array2DReal &NormVelEdge, const Array3DReal &HTracersOnEdge) const { const I4 KStartCell = chunkStart(KChunk, MinLayerCell(ICell)); @@ -393,7 +393,7 @@ class TracerHorzAdvOnCell { EdgeSignOnCell(ICell, J) * HTracersOnEdge(L, JEdge, K) * NormVelEdge(JEdge, K) * InvAreaCell; - } + } } for (int KVec = 0; KVec < KLenCell; ++KVec) { const I4 K = KStartCell + KVec; @@ -429,41 +429,50 @@ class TracerHighOrderHorzAdvOnCell { const Array3DReal &TracerCell, const Array2DReal &FluxLayerThickEdge, const Array2DReal &NormVelEdge) const { - const I4 KStart = KChunk * VecLength; - const I4 KEnd = KStart + VecLength; + const I4 KStart = KChunk * VecLength; + const I4 KEnd = KStart + VecLength; for (int K = KStart; K < KEnd; ++K) HighOrderFlxHorz(L, IEdge, K) = 0; if (AdvMaskHighOrder(IEdge)) { for (int I = 0; I < NAdvCellsForEdge(IEdge); ++I) { const I4 ICell = AdvCellsForEdge(IEdge, I); for (int K = KStart; K < KEnd; ++K) { - const Real normalThicknessFlux = FluxLayerThickEdge(IEdge, K) * NormVelEdge(IEdge, K); - const Real tracerWgt = (AdvCoefs(I, IEdge) + coef3rdOrder * - std::copysign(1._Real, normalThicknessFlux) * - AdvCoefs3rd(I, IEdge)) * normalThicknessFlux; - HighOrderFlxHorz(L, IEdge, K) += tracerWgt * TracerCell(L, ICell, K); + const Real normalThicknessFlux = + FluxLayerThickEdge(IEdge, K) * NormVelEdge(IEdge, K); + const Real tracerWgt = + (AdvCoefs(I, IEdge) + + coef3rdOrder * std::copysign(1._Real, normalThicknessFlux) * + AdvCoefs3rd(I, IEdge)) * + normalThicknessFlux; + HighOrderFlxHorz(L, IEdge, K) += + tracerWgt * TracerCell(L, ICell, K); } } } else { for (int K = KStart; K < KEnd; ++K) { - const I4 JCell0 = CellsOnEdge(IEdge, 0); - const I4 JCell1 = CellsOnEdge(IEdge, 1); - const Real normalThicknessFlux = FluxLayerThickEdge(IEdge, K) * NormVelEdge(IEdge, K); - const Real tracerWgt = DvEdge(IEdge) * 0.5_Real * normalThicknessFlux; - HighOrderFlxHorz(L, IEdge, K) += tracerWgt * (TracerCell(L, JCell1, K) + TracerCell(L, JCell0, K)); + const I4 JCell0 = CellsOnEdge(IEdge, 0); + const I4 JCell1 = CellsOnEdge(IEdge, 1); + const Real normalThicknessFlux = + FluxLayerThickEdge(IEdge, K) * NormVelEdge(IEdge, K); + const Real tracerWgt = + DvEdge(IEdge) * 0.5_Real * normalThicknessFlux; + HighOrderFlxHorz(L, IEdge, K) += + tracerWgt * + (TracerCell(L, JCell1, K) + TracerCell(L, JCell0, K)); } } } - KOKKOS_FUNCTION void operator()(const Array3DReal &Tend, - const I4 L, const I4 ICell, const I4 KChunk) const { + KOKKOS_FUNCTION void operator()(const Array3DReal &Tend, const I4 L, + const I4 ICell, const I4 KChunk) const { const I4 KStart = KChunk * VecLength; const I4 KEnd = KStart + VecLength; const Real InvAreaCell = 1._Real / AreaCell(ICell); - for (int I=0; I < NEdgesOnCell(ICell); ++I) { + for (int I = 0; I < NEdgesOnCell(ICell); ++I) { const I4 IEdge = EdgesOnCell(ICell, I); for (int K = KStart; K < KEnd; ++K) { - Tend(L, ICell, K) += EdgeSignOnCell(ICell, I) * HighOrderFlxHorz(L, IEdge, K) * InvAreaCell; + Tend(L, ICell, K) += EdgeSignOnCell(ICell, I) * + HighOrderFlxHorz(L, IEdge, K) * InvAreaCell; } } } diff --git a/components/omega/test/ocn/HorzOperatorsTest.cpp b/components/omega/test/ocn/HorzOperatorsTest.cpp index 191b459c7810..b4370ade2d35 100644 --- a/components/omega/test/ocn/HorzOperatorsTest.cpp +++ b/components/omega/test/ocn/HorzOperatorsTest.cpp @@ -175,7 +175,7 @@ constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc"; #if defined HORZOPERATORS_TEST_PLANE using TestSetup = TestSetupPlane; #elif defined HORZOPERATORS_TEST_SPHERE_1 -using TestSetup = TestSetupSphere1; +using TestSetup = TestSetupSphere1; #elif defined HORZOPERATORS_TEST_SPHERE_2 using TestSetup = TestSetupSphere2; #endif From 9bd0099b1a43c77d6181629cca5b6b81fcbe8704 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 18 Nov 2025 15:57:26 -0700 Subject: [PATCH 019/152] Clang format with newer version. --- components/omega/src/ocn/HorzOperators.h | 12 ++++++------ components/omega/test/ocn/HorzOperatorsTest.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/omega/src/ocn/HorzOperators.h b/components/omega/src/ocn/HorzOperators.h index 7314e3160f44..910c1971c980 100644 --- a/components/omega/src/ocn/HorzOperators.h +++ b/components/omega/src/ocn/HorzOperators.h @@ -573,28 +573,28 @@ class MasksAndCoefficients { // from cell1 Array1DI4 keys_edge = Kokkos::subview( keys, Kokkos::make_pair(0, NAdvCellsForEdge(IEdge))); - if (const I4 I = search(keys_edge, CellID(Cell1)); - 1 != I) { + if (const I4 I = search(keys_edge, CellID(Cell1)); -1 != I) { AdvCoefs(I, IEdge) += DerivTwo(0, 0, IEdge); AdvCoefs3rd(I, IEdge) += DerivTwo(0, 0, IEdge); } for (int ICell = 0; ICell < NEdgesOnCell(Cell1); ++ICell) { if (const I4 I = search(keys_edge, CellID(CellsOnCell(Cell1, ICell))); - - 1 != I) { + -1 != I) { AdvCoefs(I, IEdge) += DerivTwo(ICell + 1, 0, IEdge); AdvCoefs3rd(I, IEdge) += DerivTwo(ICell + 1, 0, IEdge); } } // pull together third and fourth order contributions to the flux first // from cell2 - if (const I4 I = search(keys_edge, CellID(Cell2)); - 1 != I) { + if (const I4 I = search(keys_edge, CellID(Cell2)); -1 != I) { AdvCoefs(I, IEdge) += DerivTwo(0, 1, IEdge); AdvCoefs3rd(I, IEdge) -= DerivTwo(0, 1, IEdge); } for (int ICell = 0; ICell < NEdgesOnCell(Cell2); ++ICell) { if (const I4 I = search(keys_edge, CellID(CellsOnCell(Cell2, ICell))); - - 1 != I) { + -1 != I) { AdvCoefs(I, IEdge) += DerivTwo(ICell + 1, 1, IEdge); AdvCoefs3rd(I, IEdge) -= DerivTwo(ICell + 1, 1, IEdge); } @@ -605,10 +605,10 @@ class MasksAndCoefficients { -DcEdge(IEdge) * DcEdge(IEdge) / 12._Real; } // 2nd order centered contribution place this in the main flux weights - if (const I4 I = search(keys_edge, CellID(Cell1)); - 1 != I) { + if (const I4 I = search(keys_edge, CellID(Cell1)); -1 != I) { AdvCoefs(I, IEdge) += 0.5_Real; } - if (const I4 I = search(keys_edge, CellID(Cell2)); - 1 != I) { + if (const I4 I = search(keys_edge, CellID(Cell2)); -1 != I) { AdvCoefs(I, IEdge) += 0.5_Real; } // multiply by edge length - thus the flux is just dt*ru times the diff --git a/components/omega/test/ocn/HorzOperatorsTest.cpp b/components/omega/test/ocn/HorzOperatorsTest.cpp index b4370ade2d35..191b459c7810 100644 --- a/components/omega/test/ocn/HorzOperatorsTest.cpp +++ b/components/omega/test/ocn/HorzOperatorsTest.cpp @@ -175,7 +175,7 @@ constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc"; #if defined HORZOPERATORS_TEST_PLANE using TestSetup = TestSetupPlane; #elif defined HORZOPERATORS_TEST_SPHERE_1 -using TestSetup = TestSetupSphere1; +using TestSetup = TestSetupSphere1; #elif defined HORZOPERATORS_TEST_SPHERE_2 using TestSetup = TestSetupSphere2; #endif From 84caf5095bb42552427beb141347d375e737f659 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 2 Dec 2025 10:29:30 -0700 Subject: [PATCH 020/152] Add documentation for the higher order horizontal flux calculation. --- components/omega/doc/design/Tendencies.md | 25 +++-- components/omega/doc/design/Tendency.md | 52 +++++----- .../omega/doc/devGuide/TendencyTerms.md | 7 +- .../omega/doc/userGuide/TendencyTerms.md | 90 +++++++++++++++++- ...er_order_tracer_convergence_on_sphere.jpeg | Bin 0 -> 51328 bytes components/omega/src/ocn/TendencyTerms.h | 22 ++--- 6 files changed, 152 insertions(+), 44 deletions(-) create mode 100644 components/omega/doc/userGuide/images/higher_order_tracer_convergence_on_sphere.jpeg diff --git a/components/omega/doc/design/Tendencies.md b/components/omega/doc/design/Tendencies.md index 9bceff5a0b38..3f392698c12b 100644 --- a/components/omega/doc/design/Tendencies.md +++ b/components/omega/doc/design/Tendencies.md @@ -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> AllTendencies; @@ -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); ``` @@ -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 diff --git a/components/omega/doc/design/Tendency.md b/components/omega/doc/design/Tendency.md index a4f077638ce9..da797e057cf4 100644 --- a/components/omega/doc/design/Tendency.md +++ b/components/omega/doc/design/Tendency.md @@ -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; }; ``` @@ -66,13 +67,10 @@ 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 @@ -80,17 +78,27 @@ The operator method implements the tendency computation for a chunk of vertical 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]; + } } ``` diff --git a/components/omega/doc/devGuide/TendencyTerms.md b/components/omega/doc/devGuide/TendencyTerms.md index 536efbffb60c..b4817f0e131d 100644 --- a/components/omega/doc/devGuide/TendencyTerms.md +++ b/components/omega/doc/devGuide/TendencyTerms.md @@ -35,8 +35,11 @@ implemented: - `SSHGradOnEdge` - `VelocityDiffusionOnEdge` - `VelocityHyperDiffOnEdge` +- `WindForcingOnEdge` +- `BottomDragOnEdge` - `TracerHorzAdvOnCell` +- `TracerHighOrderHorzAdvOnCell` - `TracerDiffOnCell` - `TracerHyperDiffOnCell` -- `WindForcingOnEdge` -- `BottomDragOnEdge` + + diff --git a/components/omega/doc/userGuide/TendencyTerms.md b/components/omega/doc/userGuide/TendencyTerms.md index cad251f8dd93..3b75a87da07c 100644 --- a/components/omega/doc/userGuide/TendencyTerms.md +++ b/components/omega/doc/userGuide/TendencyTerms.md @@ -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 @@ -43,7 +44,9 @@ 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 -| | TracerHorzAdvTendencyOrder | Either 1 for standard to 2 for high order +| | TracerHorzAdvTendencyOrder | 1 for standard linear advection +| TracerHighOrderHorzAdvOnCell | TracerHorzAdvTendencyEnable | enable/disable term +| | TracerHorzAdvTendencyOrder | 2 for second order advection algorithm | TracerDiffOnCell | TracerDiffTendencyEnable | enable/disable term | | EddyDiff2 | horizontal diffusion coefficient | TracerHyperDiffOnCell | TracerHyperDiffTendencyEnable | enable/disable term @@ -51,3 +54,88 @@ the currently available tendency terms: | 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 TracerHorzAdvTendencyOrder 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 TracerHorzAdvTendencyOrder 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 +``` + + + diff --git a/components/omega/doc/userGuide/images/higher_order_tracer_convergence_on_sphere.jpeg b/components/omega/doc/userGuide/images/higher_order_tracer_convergence_on_sphere.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..fdcd0424a4c233ce3afad988edcdeb075ea67daf GIT binary patch literal 51328 zcmeFZcU)85mNy=HCv@pSs#2AzfJ8-_h=7HvLh=4>1iHP*3pr9Zi0-^#&dX>(zB9i+exKQ4C+Bl=&f%TlbAOb7ny7-sO8!w~Qtq7C+&%)x*6gd6sV(l_>iW)x|~mF=#3CVEvGKyZIygExyLfndUGw(w^$Q6NyA_VSeJA>UOl(|yLSlNxqsN(9Po8GK zC@d;|S@P=jo64%{n%cVhhQ{`e&aUpB-oE~iW8;S)Zf6E1q(O<;^-~TGvzmiK3l#7{_m4%h#w_Hrj;b3PGWM$ibm|f_EJ%@X+u)KB@ zr^v~N1r=>v3OeUVqE~N@a*HYI5|qflMf;0ne@?J_|Bz&V6YO8)nuVA`{wftS6L`1G z%;2M90UIkj>+i&!lb!Q#-~H*0y*W_T zl=fyIyev$hGO-9k;1CAmz>7r4pYOQb;psHW+das=0mkf|lCCrn6wf$=2MFnqWdSmX z80M+jwHf|~FpRp3=GJC=*Vy z1x*CkJugApi8r<>D~W?$94*@n2t62P{kWk%O5n`oIhSsYfa#*_Wp|;fatZ;S^s00-_LLjxH)@a{KtE1+o}vZYx5R`p@!-NR`@mZ2ba<{dc}pr zhhXO?YdOa_bY$Z*4d}*f)=9wB{k0#ipI6Kf>UjBPuyI8h7XN*9R9ZQ715j+LW-%`Vp?>I_Ii{dArA) z5vD;m;{hX?7o1c0YzS38kv2>+povK_xO&J2J&pU?_aJfkf@3aZXnc*8uD>gfhT6Go zpH2Cu2%)+Y0k%`4ZxuqV0+a2n>Tc?&3?jT3qcB;96nX%|Omn81(cGK$O~grVSs|r< zI`W$teYGdMJT9ed9c5BD^X1uH!x4_kQAErF7upEZb`$Pr8)q1_v5$mpTi`O01YA37 zM^{~7vBuuY6&S3QyJ1`uHn7Y^EyDR)a_KLB)@8X_#4@YnJX)DvV+^BeGX`9=Hdyu` zY=Sw~fa$AC;_E-9nC^sG#ulxwzH`uBQ{qEEXW=KL-)h#CfAdw zL1yL9>5T3h$k@$}n-;R=B?AE=FIOFuubjCSq4qW4d!aJ~93$?jBP#4IXFlQZ%`lSH zps-ctWEd=IqSX7gE=l)w@#;;DLf1Jf&*dGj0;-S%;i2I7=0^`Sf2iL-455d)tq?o)v7)JOlUd*2lNuO04^w|dtv^WeLk zqjELs1l5`*M8a)w5f-M;J5SUi_aI#^7r)oN{C0TOR>vf_q*8IaPd&K$kbHAu;=9WwK{&n|K{}%q0~Za0M!OjSbtNwTlE=2bWj`C8ng8s2CvxqOt^DS> z*tvC$A_t?(*XmEk#kR4xMA3`(AVI@ZJCx&}j1Lx}Kjs-$kTQVzg>#LgN=;VT^Q#*2 zB6&**iz&pD6}@>by{lt7s;6XnT_9BUMA{M=N)yF0xPB5~cMWYf^oYZ-J%|@zff)ZX zT3y%tG3y(*4eD5&pz>OHmnR8j(hn5c|mmN1DzQ7$aIe=;N#^IkV zu(znLBG}RLDCe>DnsT9^p&JgCagq4R22kZ-a&~ce@csqOC^q%jJ(x!aHwfYv#Ozb^-)g+Hy_@J=FRhX{WRXPu8BjHu*33A+_~pTV z)9KRg79C8RCk~&z06`yEgRwJ|QMPq)ow70y%*xD4zRkpzvS;mI&8pMaADf-2+-~rG z$c_KbBEqyAY=**6&oCC?mC*Iu1RIWSM73?ohOQJsRDVv4lbFY#p4)GlUpYNvp*K#r z_^2XPnOi)t+x$-cbP%PBo(&JMWxhnSq(5nP^(U5Vc5kd}p^7r+mxopl@tB4L+<17& zwU>#*v!d`ybh0F5*DY~tAq63Y7D5jpcz$A1;4&yv4+4VQ=g_a6-MqpBvGXE|s$bF^ zuI(4RAa-m21u;3tf&9uIn#eXqj9%UBA3>U=gp$`?a0%+yy9<>wB0M6B5btvYUJ%tH z&F5#FpZ7rqgc^C1^UNTTYi1Y?I0S<)o!NtI41iPlG#TEGh{H(L;Fti8_B^@K>S2DQ z%W&&HoqXZ$+S0IBGPd^Px+{^oRw_?9LCiPfeh==-C3w!|QyD&>2j- z-OObmucxxb9u7nAy}Y@cj(iC=kx2vL2<@r^F}D~q57sP(Mx zCu&WvbS_kX*-X-tF=&_N%&@AP{K;q0;WGbB*_aJR>R1jfU1~~~rs9^cqYG^E}BLwTu-2YRz{zd)c=PBizE^`4`mea)? z(~3FEau}f{mLJ-QG%7tChkw2B&U+(_tZy3)V@BP{VyIg?w4_;%htAR^w}{gwKWfQ- z4ikenl0Ib%;Q7yT=#^6KF>N+*_u;9r?phe{9>fPBYgUZRIah1&&Zkz)v9Y_DVt4x2 zrf-Ss=V58Zd#si61#)2bHsq2kJ1U>H@L7~PYfUA*RsUd%$?q-?8$u~}@T&fuw)DP16wCOPl z8L%J?cQd2`{{T`ru_tYb6q~Em-1vAiA$#;nHbK)%seFB7E`9$NQ}DeTcdp%qBYpl8 z6Jl@Vzh-8r0h6F8LFgVh=?o^d4p$wNg7eQstedDI(q!SaPJ0l7;V0BlMuH5*1MXQl zz9D~9eW7`)&fsJZ3vAj9b(Dw!lvQZLi0&4}abvGVQ0EAF*fPHz?fC_S8(@1HG)f!l zuWk*fSGwx-DW-PiPGw$GOt*Rt?SlfU`EA(gz7`Hf2RNi?adpW(NJl)zXMw|crboFz zvw^Rx*NT+-eCM1fAxJSgU14oy^c9`i&6~z4NEjI%GLXxDGfU>L{rtmw;pS^Kc<8KMp8z zG4&|&JN>b~sWM7laP|^M-Zi1lSJdK=XI0)K#+OMwgt|^X+fx(Gw+S0MuFa&uujAFX zH#g)PvTWaTjwU5td+5}0$iiz|L3P$C8MH%aYT%Go`L_rH2vDvA^f-nwdL+cuos`kB zy9Y_SUL#{|w_o?YQKw64j-*f3Wh2H0^hpM%=S^1I%-%I8ESZCV>DdI1M7O%B(I4yi zLk08MQ7RMVCh~qWisU&LrJA3gZHk>Q4RiT?Acm!T#yqLF&5<(q-&yH#V`MtY1yo zJ}PGorT28q1hhQ8zV$F=L$o@$;CgVn&T&{Of)to_uUU(bKc$5r%f@+$*6hT$C?V&! z?VpNp)MsnB5vL`3za)0>`sj>s??y0qJgE9KEBadu{xMDx{8zGd5evd=hLXc2E@q6% z?*WEHsnNjbrXjh*a$lYA>g_6#b_i)5LHE%zCIY~DaGX0Nt?KhO*IjIi92`JEXna_l zjnEW5wV<9ntl*)!RH2yeU;i9BkU!1C;4Y_fpaSVxsAFUVL(DtYSe@c>NpLOOyylIo zZ(XCzMzg!7Mvz=Raw!V`(3$@WcWdCe3->Tb_aK3`GYWW)WH@MPzDwI^BHEybsCeE< z6P@ZxvPR!wpW1o3mg-auZQ^ZZ?nI?SA8M=M{o?x^SeD^+2!Y>tRYL4Z#Swf^=K(lA zO&FPT6Gv$&B~W>0HwH%NFMMRrB;N=RcAH$(iiPo+C?I7qv2X#D(M$h%$;N~$IacEV z@{JYgirwQrN)9QHZyYMVap(XG>HJCZPKQ<+h7@cbvluk(dZB_Wio0(D^^*+m)bKES zs(C%?{kVLjWWZ60Wa^kr$5z)O6pHLH2XBXo(F=l}9hA94V-J!HzjjQj-NZ1#V>zOne`3bS=IU_p zH<9d+-r-)N;9z7VC)Hp(ZT&A`Y;N5|i9ebI%LJ8P**9gv^gFHQGR&v_C^`cMJ1K+-@q|)^czWv$cta3X^ zhh9LmTWy6lMeQgTQcaL7ZJ25(O(+X*(~}fnq6vg|*dZILaYE|cnKMO(-m#3amvddP zoErt_`=bc*j@?~X=G1njfu~e4bPr4thc>T!m{C0vxo`;>NeoT^VwH^6;|~@z3713>|or^x!Y#p z7F*A-B22&FxpN1%zFlS-B8T|dK8vXKL4AnaRBs9xmJ{M|`BlZ> z6s1}*24GcLG~tJ69`p!9m$Y-2p$fn{Wfjth0ci<(Y5_*)E(iRSN>FTUqMCMZsgFEK zJbSY&QRMK{aYo`bz=JfiLezzc2O1xHX+>PSY+|D<6HqR1n0yCSsgAu~r-b?xwRG`T zyng$tc=BEUz7{2P_d?~qZjAH^ve6>69m@e!b=DS$@wqyWtYfi-g7lH9VV>TC;BKkZ zaVFXlde#vpO8c#wQ=~me=bYA}cWeuX&%7F77F2?i>T)|cvVT%Hr@>vO2c3bjxeMdLm(8ORocA)NBnR~i|8lXt;;+!v)?Q=t~kMca>UzCdaQ1bR5TXCGvru&`irb`s)#a6-nv z7tM4oFbnr@_#E*R?eShd*oD_QrKS1#*oHswq<6RCxJA+e68~$bg zLY+7L5fqB~7CX@h+A&5pIFNKm##D>G&EXlAM_xa3k)5azyDXs?Xry}ZY6QdvT?Tmk zUjA8uBsVf98q^+N$-2Zqg{BGY^>X>T`tiX1$!5}Z=gbRMLQ_tvSvuWkI?>t{X81De zrGK{9P*S<8*=_IJ!fnAphFtQQ2bml!99XsADGHomm6n7s!oU@z2_w|P#~6W=PQv5C zg{YinRSXnbHwCd+7-wHxay(rzhm1bmy_}E}_+>~ZHSg07NC0F|GRgTg;XMe0iylGV zM~Qg%QTO(bH9AMfM0jINMTP}wqxk2N7$z{AvmYuyD6ai#zbWg`1p#y!X=Po7DgYR@ zx$JZVanhtn`i}_~sfK0Wl($a{eHwF4co zopK~jwjri_7~F+aFW@|-V)6%$3uQ?TO3+OSIrW~*_QAp80_o0_qQl94-+r#EHKCbD z9rR6HNv(oC8!Gp3SBIxo2<0tLeMu_>gJ@mO>dR;RUGDfc@}<0)@jD@TIGi(4RPZzB z+=J6kBFoHgSia3)v{>(<>Z3o3Q5-PU@QwvufTE$7G@}y#Bj>Vxz;s21cth&(`*_c{ zhISm=J)mF>w~dI#ft1Ems?lneMLe{;uR3R7?*YpSAUV&YKG6OAT7j0 zQC^OltF^D^yc=kuvqjOLqnW1|)9{Mgw1~w>W+@ym%5A&m3G7^*v+mdKoY1E47y6z! zsUGz3Y%?=0c|flIABPWBRR8>EJe&}MZ~!&k@>L)^Qad-p)_8)xy_wE&bFNAAD(V5 z$JFp7?*PjxXrCeL+FOiBKe0*8g0E-%4KHqO*1oTJs;RMTXW4Hh;pzUs{Dg7hSG6P{ zoU}k#Xos9VU={A?o*9>mAtLr> z|8B?Zjs9DQSB<>G;8r3T94Gf|?57&eUP5MdHyX3SvryR(&+=vWDoFdgNXYq(tr zv?}Nqe47woG^i_LeP0}q8QRDFuVuJ(%UsK+n@jEkAKTij2{0l zi1@#^{jW9Xe@FY@%!B_`lK;OeNlPG5K-ENQQ7+O!fW2lDMA(Om_^mIbcI#e`AYq%*Vg3PS>egZ+6E=iR<|iYsK(%dkVXv*C=hjueYMNM-4(J;1786Pk&^h>jZdM#dRAj2A~=u_ zW18kzx3dL$%M>&nRr$5q&~F`AR5qISAS!B91u%+saUAoLm#hatr%c?c&@ASE7^a^h zIf#7FbVBNx+G&vuRthU#a`Z*^t#DyS2l(IuabG7a$#A7B*Oti9$#<59Y(@0xeHtGk z&vk@LJ-d5TQa>eD)Lj1x1R_e;Mt?w%TyuhA=Cw$h>ouU=;6i#^OtS8pd0!19r2uuV zb}iE6V@)rfe4+Z#KNU|qup^cC+Yj+59EwNhp@Qg1Cb9sI7>V83htG%lqyv+-GfRGA zhX(i-&2LKdok@rz^`9l&WxB+yi*hYO4`J$H9fOV(Kl*E$l*jAxj{)P&dT-k+@AdO5 zMWymP8Wm>BSE|gPJK{&xJU~o5S}rN_+HUE_j}3pd&^<_00onLq`{S*|pR1p+LnUkT zJK<}45RUU3QW5c*c-ny|(B zM&K7oayq#V!D(!dJX=@f8l5T0{sNzACNUczXM97*SLz1cjvfWazd=;OX(Hz>-d6$! zrM88@a<6 z-X#un-cXjy7!ACDDaD}k?RXpq5OrBJAsvbXz2zq#eDTz|dD=0cp-|O^um?$yh32dq zekpvx_}X0YK(l})CEfGBqB;4yGVWYWq{8XRAJf$Vf%8U9){hUWYrhH&I~H#&`Rn9L z8%z)+vvcAmOoYl>w4=_*ZASkbnX5dNaH1e6K$dygdho&Hn4*)eWf9`;4l_+ zH3B3hwv;fL$1f75Myq=5`khP^jxcOksN(K?(~r!S%@ZVf-~}-%^(A zNTrv&^V=l1`JK$f!hN^ji95!}NXu6rY?6bGmWu+VR1r*@Z;KEO2|R~;B4$=P!WXJ` zHdy>?kAiGcCYRWY6*;S-1$X!Ni6^0%B5{GI)m|+iYA!(R%|!mk`yZknI%V?T0mD%< z5vINIKhWmw49eUdB;zq1c5w3C28elyWf7>ViDPYT{QpLk+xfM*YK$6&R5S2j-mu1PQ*L}fha zJyd$rMAQgTzkLViB!k9!irw<(_Hgi=mnOugn$^)NGy!s#5PSgNV zR1c5>5;c)UodwFZHj=1Z$TypmaDXoM4MR{O5t&N-dCxA2VM~J9#%~__wcvB>h_CTn zfqxf+A3X0GT9wg{6RaMdc4Y8G;-^dmkuP^`D}8W+y6xM5aOau#5`j565>HTr(!fOt z??d-qABT+|6SX)q{$tMHrfx#lKmP58=A!fSynR9z>VYh3g$MUPj$#$Cl*j(h;`bkc z^M8ifpZ>g-|B>?Fg`0oq zn^HMdZ>!}KrSIU$j_AXveSkIfBt#7iY~R#oQUe<0hOy%de*!9*R|akaqa%erH0Um6MX9aAwf4{#SGr`du8p99Nk|6XAB< zWI~=KZnt4XLit8PV*}cJo@=bx^yMIIw>4m?EnDxHXVMKw3PF4WR)W8$Z750mK*wS5 z8JNm&nuxL*OpJE2vWZmE8Od@z4@TaMy#{hg)eSF}^;1`kB?O`JN5c>F9jr*jGwu4C zVYDaUb!jvaTLu?XBs2<#FEi9#5I5mDwHD+-E^XI8GkHs3#Shbd;-y7HVUN^X`Cem) z_rp|pf$hC~X$C)I6y!)k7m^W7DC-hrQ~Pcw+UsR;@lc(BrP13jD*mqtqwjjBd6~d$ zsW%{Y{*S=}Z;S=YRjnDBWQ{;X+$UG6S z@ca7pIW!eLOg5l+8?CpD5{H>foLU$KFP}3uZMBtTnxNnBTJ46nwdj$+kUzVY5O9)W zw05-7Q1-%(1IJ_c&jOrpU$(fD(^}^c9lJkpLMF_Ym6OVFgJD+}>CAf&X~M{j)QCep zCigR+LyNW6)x)eMst3=CG}P9cCps0|eUm@Zp?0uWF8Us4n<(uk${$o`E!ru1Hbcn| zQ4>cK)atgQ?IV}}$oHEt=DAaD5M^vRKofS>qTROa_~IPU%6Y^rzUmnI6YUW2r3ksq zu;oCefBFFxapAq>n`OZpOtY)A68Om)k{X;W{QVIV;7^qUXNx~ga4{_jj0#uaXd(*j z;fN}1Pw}jf&mCGc73O;NRzhfsHQlV;^0T7Xj+l{x@97Hc=QqC`DL8rp+zU55KseUZ zhMB2rn=sKF`SN)rviS7p=?@*UVdd?j+4FWs7NlM)L_b!9bszv!9KIAoGK2j{i@oW$ zP&2$f>OSC8cNRv3#;Q-&l-2XRZWH>ECPiX?)1G+gF6Qhb$1X$#lqRA>`AyQsqjcfI z7%z<2>{^9ufQ_|H1|4=`O!?T==4zBR4>Kq0y|jDwPj6#ud?28M_TSq7&=I6d{o(F^ z_75ljyq5n-2><)!;H_aW!plK}kzLx4)J)CutetQC$oTv#Tz}{&n{-I>cJxC4Kbhi- z5JiQMTu~4J&I+nC`h%<-4&b03clw?_*<&JNFZe#BFYRI#+tro?5^D}w z;Vz4j+!Wd!2mnoU47~v7o+kYbOwyw5GiVn;%KLwd$xI-Jbc{ALcqU;l=tVILC71NI z2juTlht7$&s_YuSg%NE#wBisF#(b}7hk=>`CuCMf_6Oy8>Y4G4kE7Ka%rBr%d~~EV(7(Bh{`qJ)rWe|dR=`DXS!uP?A8tD!#Z1QWw z<93RRv$NaQRs^asAKGc`jL`gW15gb;x@dHHEj--cVlZg?TLU9{+};f3yz}4Y)BYp& z&mZKesr}I`0j>N$+WiOi&)-j;-`i>{xcEH?v-@Ni4sgQoVQu4#9R+%u;?c`1^rZOR zBk=h)lZIB&A4(!j+i4wpkkm0-tX4-*62-KmMMc`Y3^N*Jah$Rx%a5r)8^L_`_^x4n zP&|x}CPRjw2;v9&2LlV>xZ%5CRlm=)+lhU)o*8~J^jNq#SwDlli9mg|ss7y&6>_TWVO$05)w;h+ z?o?UYvWeErB!e4VT3Edwqm01I3yxvoKKav(k@yXU8W1?1FX)rm5o7p0ujr}AiJiBF zS8o{TrdzfjrbEC8Z*@s4VrH0dEuUOP2_#?3UDV?7^Eia~R>fs^2vU7h3o7#Zf$Tw1 zPt%SAdK)*pM98|ruR?Et2ImQmR|@sm6)GhTMDl~6v%-r$xC#Ru9h%@mw?B!6m^-~) zbBs?x8>!Y=Y-Mth@vI=7{rUstD3ddpg1=IhS#@^VUf|d;UYmeMUcNioZGdtFywYnV z=wx}@rkqEw4pUFt%!`o)Sr25L9`8FWu#s%4kY3h)STWtpV$)JCIW_jK*l(WV|F!=C z1ycGW@DKL>6EOn)zhY2WIbe}EoGi;jVx5_6#d6F7E1hnk_8rA@eo2L`_Cu0Zn%NQ* zRenpS+_WqHcs?kDJ3=J^cV##lR)dQ}v1n{2p^f?qzcIu~p)Tn$k>?!~t)qAwquUGQ z>gr-|FXyi>#S^bqRJmJnto{`JwL^kaCV(h1%occv$4SvN#?@O6qEvD3swa#U3j8wf z#5YA>NKmP68r$5=v98PJlJ~yqP1Q{t>CfR|gN_3Oo%3KS3xf?`OD@yui zOHGHRm~>NarG?hlG>+)`H^rZR^2pM<-5KVWIdFw#qmi~mufr^T!FXb*s*P!kf;?qx z0p{vP#3i!rdKf9ElcDlH|J-388hJ3m&Aia9~HZ=_);}q&?mCE;dlA~%hE;&eka*PWOWQ!pdiDX zyn?iMI>z)hG>@GD!S_wZSltL;$tM#rnm4JW>%g_@N+I9f_gMqCUtZ?RI&=j8<*arC zV?Tf=yETEnxivm5u0;Wa_vI0ME>L<|J(pN!USTthwa9gy zZX6Q?AcAUoLN+(z;Iwy{Sy#;WW3Hg56CK6agwW4$UXeI^;;RqKO3+La1w;~ffY$vT z94p38i_@2(P5SiRs-`7PgZGJ?HR~G(CuO^{(^o@-^PqZwDw+jcZ_dNwb zB?wOhxpbURQm*ZpUwZlcs|P8^LM)%il*@d{$^Ck9D`ztFOl!=oHthS-Ee^^iI7Ri* z!};JmPr=iR(EI$dGU(_up|z6}VeOM7 zpB0w!IV;RAzUOEw{0f*XN&`*x&g}d+jJIuEFimG9Fl;l)0JJ!o;Q^#R_dGS9fM(}f zqNPexx)+ksT{}9b(J%Vpp^kj@^#OTE$O?ly7({)RN53-j-_><0Cp4HUzklBSCBn;Q zS~b$bkKTywh&1TVCk6MtyT6*pO3xqcZd(?e)LW8jBA|PL-Jqb?*w2ZOEo_R3F}bG$ z5jW|k354NOY>Bd^ES(iTca9CF6n|b*h`Ht*rj}!UMd94#x(`KBF>LSDe(lh6;ny&; z`KLg=h+qun@lUkyF8~ol_(yBPnLd$o zZZ_VE6F8sxyk)|N8w09}XeZ6VdT6tWJW3x3BS*AlE%2MDgGmO7#>d{M7B6R&&U8h1 z-FVeDZq3?Hu6Mi$&NgwDaqf)+&!NYF%cKl~ZR;l649N1S5mHpz7&4Wb6T5B+Ui|r9 zT#u6)Eagt6SRYoOvQM`%o?PDC$RV5J>BAd6)B`lBk2JHwzC|N=f@ExQNI_RP({MDkHN_C6e(hmje6cE0j7Hw{u*Po?-^MiRK~q z#Gv5;1YW25ihhOVR_lEW@Mec@yCLDA~pq9n5 z;y^O}DU}lhLobZ?=FE1F4w#=I`lH+wU)PhOvNX<+Mb<}V(t6pgr_QWCTsYY(W}eK& z&9{8${$Y9)Hu|?=rd+ZPCg`$Bkkn-b>BThdI+xg|+g};`#}?M_EI&MClxc0$DeLKV ziG6DJ#WhHyJ{u%*#SC*edNH3SYQ*4thpEE(OI^06yrMr-N2c|-7)v$#69()TE>?d= z&++>2ycJElvM>F_Ru`ffWPukpf~f}R864L-!vH;ycW8{}MQHlW6yl-mCUJ->5$UA< z?#%O)(bMsZ#;IVPk;Fe5i~2|GbTGE0MxF)hEl68yi&|~)m<2XnSV9I$iOi9NR{na+ z5E>s)%{I9b=a4AXRBA8oU#fGh-I6um&)JHJS4rZUJEB7G*L!d*ZRr_dL97Zy=73giB3o9}G(|X77bw0O9SkK$CM@RkKfBzaezWIG0AIyt0chqr&V3lr zs3|VH^^q~x7|>x}a3&q4@^PodD7P^&IsXb9nmD;tMdIni$e`iKA+2aw)h3v}XHLe% zUv+Q_oz>z(o~2O2;)S{o%@) zO%7^N^pgx2dN1=x?e(B%3qM(bC%lnsMR)&0C-X~hFGzcG(=TEB;j`8@Kv{kr$9ln= z1%H<_dHk3J$@{lvF5U6$d)*TO#tOm{bkr7dCSnMx;+$sbhZJGDcTNBA7{uRhs!Z`9X5xSlshElU`eUdfA- z7IsTH&Ln}K#P@7ifzFD}$BhF=V-f`JQWDy0q);(?f+EW%6mqp z>Z!+>SPumY#U~0od5I7PSSjF*1vCO{NTtNg{J)T09w>1fSdutbm+{hnV}C}QiIVE^ zZFM~{^#?kh!n~VXo@$ngLF$;}4+cd$t&MqQ4VWH3S`xS;U}5Lqh4Vp7H{szM?xZ?9 z;2_l!dHyUpE2hS*a~ECAnR%>Lpwb+PEI#u^DJAoiaVg6n2A+ZL#gWcy0Sc$eiMD`> zN%3u}DT=8^**gFJyi%h|r=&*xm)Pi6hmn(2K;mAXd`%O`0oIm8rg`!(?-bO7@oFhbJhV>VyoV|sC-4|(O=j^6Ke3$$y;`zFBPWwC{{YFV&s@&?23SSE z!z&GGQX|b$AcMvY+}c`kATg4tTE5|qJ&^;mX2c7(L1u3?{?bZ=D7Ei6n~42iJKCNx zgAikgEHFO9D&yKBV8Y*Yklj5*^Mcoxz8;=$pLwQl1BT5H)JNDcZnisrIu*nGU4fQbxIsGDIU&*E%K91;HL6xWT6CyMv;&AFlLUs7k4b_ua7Z}Frw3&w}iJu<_XJjnUsIN$v^oCgQKhf(Ey@`Cs-wS8wnQb z)tfs|1gTAF6iwm6*I&eCJ_Nis4xbE8|FS}^r zmHaRkQ*k-E4pk7-=ELAegT;%&a6ILW8+?CKMgF4q=$q(S+}2H8C7k~FGMEKrH!SSDtK z2SzT{wg(v8?$mv#3-m??(>DZX3qS328o{Kl7@2mK%xkir(PiVe%rS=QiCcd&sb3yo zICS-4pwQ(u51;Y&rH(t`4LT6)BPCiuxY>PKh;YMMi z5+<#V-!)LvWEkZ={TW(}@xd@|Ba3J&GV__HGZxx4;W=47IP@u?xck9TZRY4YH986PL8h6b4)>oqmy@id#gfw3NZ6COBXt_nl zesARSrpEj4%T~F#2NZ8pJJJuw{KOW$;VE@>{VF*@(aTm5_YC7LiiqxloKCGg5PFo= zGm_7+89}sYvr!}RW1)!wVV5mVO$4|LbcMU$7^U6Kvf=lb4#oB6&|*cln{8z|l;jX#k^yphUsJlG(EC zz#6ZudEamkuMfP2q~L}S+VMNy9ElWea#|vSwfQJn*)nYQw&y7Agx6pm7^NPTkYTx! z@@=)G_J+C5?akO0Js_*qa2y5&t5K026%CKpt0rEES2XwRq%j{ncA}MHpL{p+yqSDu zW31_e+pOnCn84pvn9V2I22HClxMfJg4AHoa^8~ekJ3NuYC4L8==pEC()&s)em{2?Z zt|f=ZU(!4_3=gpUN&-tbN*UY@pjC8^KHPcWCuU}i;`8oX>FfbayqkEH+m7IpW(lkH z#F@=j^+E0f=c#E_7-M#!a(Lq#DG!~t{C*FOHbvG(!`#DIK ziIz)VmMW_=46w8tUk#QJHN+lJ?oU>oK)%1-Xdf9npdHP-}ewFxTpAuVyCT`Mk z)DL5S=UPS4nw{Q6N*Leg(_K96Yx1!&CsZwaf0RoyIDh|iL@h@g&SMWU)6vdQ03I(I zf#FOwM1!G2mHz9S8r3`d1nc7luk9Djyj*91|oPvCZvd^8Jn5%iyw0`@URwvJqc4OPBCBL&8 zZdAqZxmoBg(Aa4~?jHKjD8xSu0sbeEK`{QUwq-;K0`_B({tWH{>ILK?(e}Qk%2 zFPbms1etBHAhUn*OwHKL(6){9^y!1CtIFAyI@<{y0>^vl!Ps0{7}br@hvR9!7=}kA zB(nsEm9pAefgCo z-B2b>n@+Pv*ditxF21f(Gtnac?d#TOz}vC$p_AVo-UAXTMMH*Ds4RcIKMca@4%=oGs--={M3T=CR{51ty;!duCU zdUXDgzr~Uek1(}aUAP#Kulnr~k=%MZchAfpt^gjs@^dug4S!c#!k2+G>P*y>9fv=A zml1XWe;o5);WMK6kD~TtPn6L#Cw^-2pR6j% znCd#1{n3+`M=MMDhLb0I-)tURq>32?sg(-;NXZ!zEI}!QA;|R;%g=VQ&gJ?&>!Kr4 zJI8NdmtJJ$<+t_wkqZ`mN>3WYOOPqWR9=_2Y|Y{I_Z&R(v;3vMx2muhhkR_3BpQQN zENw7h-QrPHa7hn_S*Q=E+S}=&+HZPrSMa#Zdr=|-3S5jJHJN@|Z{~S3w{WZzLDs7M zRN@=3;bk{_@__;8fM7lEGk!IWHjpqeQL`XgBREiJNuiXC%ZE>mhpy}sQ@ra`we&sy zigci2{%OdrWa3Kb!ak&{)2F~{4K4fH`uCMrGiG~Kt1I3ywpi-X7f1i|EGH)uWfCr~ z+3Uq1SdZpn5$Sc~5c#w=v_Pg!Cccg1q3gIEM~_A^&4!-sG)y1_6LBmhTG!4}=|_#8 zfMG6YNiY}Z-H$J69~qqFmHP|@A3=~fsso85D==@Ncka{pR+-T~oy(D^!9`5eoy;8U z09g(1HRIz#MNNuzVxBKV^*MV;v%?o;eyxVvag6CY-lJ&)D<#N|3m6uldz5yJSn5vQ zFkD*v&_iyeE1HgbJiC)>``(Vpi6KX#L=gwYRyxoJE9%Cp*QP!=iq+Yxj|@nKIf4a? zwl_|QR+#C|gu5mbeah?UGt62SrHZb>`1BT{8?o)fyq}TYoqbjWZN0(dQ{ORw z#m}SRx}KgJby;#-mU-q7jE(NaPo;+GA+MEZSLdchUY~q);b50yqmn1u6Wxj+IUuIt z3~qs}d43Y#mxcQ+NYlS?{-?T#)&9>t(Yii&EHv@Yb7RE6!?GK} z3?&u@wwDI-SvLa+<{-{#@+v2nwO$O+-V&a@|{;FbyvW0ykCC{=L!i@-C=t0K*APuO3FB| zaLT_o`HvmZf9n-t|3k+UOsG&J<|koDHaI|KxMw)tC9Vsb3S(`C5;sNs<{f9l0z6Ju zy?VGJ}o4#Y1RIU<*wR>-vO5GB!)ETc(T6OgA@h=pyP1A8%(n7R{L@5 z@3KYyJv{%-<8&Wz3z2)uf9vkouO?pVZ6|tMeq3hhY>_29eJPl|3OL zQ--l*--=LV$zEu(7czFC$R4uHNFvLO7_ZFI@9KMhzW4X@zVG+_`5yQE`|Ecczdt&T z(lIkHuen~=b)L`jc|OjQBTE?xFX6ARo=rlDe^Y(QO*!v>^(nzJ3CBr2M0nF8M0n?T zNSltb4dh#rKH61I!;3Mz|86e|+ul3M;hp0>6WCn)$SuL(tuw3JwU9_H_0#`lbKjrF zzk8`&^AGY4Kvg9rS@DNm-!|%0`CZdF=96FU`Q^^3WfsZ3z}|STc|LR~QFTv+6CFHQ zQfS(tXvwSpAR_dYu?Qm&#+Er(rAD1-G@fs_Bbnf}^*bb8fs~YTw z;t%C>gUW^%p2|nMaf?5C?HZ&T$+{4=dUPBe9YS0t2(_8NMH>=L^|1J57um^J_dAZd zTOU1_W#HsLf>+P!NqkwiA2Zc%wm_sev@#uX=A&?D`rXGz;y3!hku|7d%c zmCY?i>vQ!&rR^ehD$L8LMV>!p1Od$P{{AV>N;W2*$fN804AgZr$iE|1(HCi%t>9%*bot?d(;@J+0FVe*K^T%e zp$<_EB0{`5Zfa(6E#!uTIEg>pE4!1g&b;`ht^vqvzgV>Q_4l|WBa6~9 zaDLe6lb~C+qUDeq;p3sk)3t*o$JX4hr)rPj*$0?%^*lfO!WG@j_!)LG1gJfjYd~kW z!z?5qT8L4rzhU*u!1Fa}`7{cY|GKj=Lqhlm-*$q83q>G^}f$Xr(fxLT48u zwt@UHL)Y1xAZCBJ^!W{p{|m2LO=@*ZJ#c$PvWjJSFFba?>dCk^{a-0w|J@Dd|F!AF zpnm2>WuW=MOhEkMNE4flm!>ZtL3W)lBiw6a+!?bn7@OhNAJZ%+@(HQG1cTsvJ2{|Q zqFxpn>Q?Gt@#3b9?7MeOYBdAW`_G;gad+2+!TRLGk1(F3^I!n=>XY^)or;An0>u~? zgZX|4Y+zw_RK=≫f&`6Dov}?R-8jJ3CEI-Xn!A@w1<|*`LSD?6K!NLM6nZ&ijwE zHRI4mrJt`FrmAuzmKQz>hIJnO!1`V?iLAze`|F#66!!tL6R~}u{x=M#NEgTnBC)jH z)n<}7`fj4~`pAZptEQj7XuEVk$NLK-VK!~2Q%mGy!w#c3x_2#I@TYw{ZSvH9O6Zu! zeRDe>k#;bR*;J%}3jrBXK{u!V4PVAUP0T(MfiXhFiDM;iT{xm1bcUs$n7d#&%?s&^#X% zNl@9xEwWPUxJq1UShb~_n@P2V#GSu8G2F}Z@XN0>9n(Jcwh#D48=ofIqMpz<#`zVP9^T<+By<1E zW%LWg@sagm6QGO03IqW35AfQ_6o4ie{E6R80Hw~m<^(MD<6y1^79^jRoy4^Zw9#Jx^@S^WO5)yE!{c~=mG9j7j{ zTaZiY>lktq8Q~dQ>n#3*FqS`_Y`~7%V{!Zqo6i9G1mZ7WbJ(r0@p$0u=A1QtDbvAeLeH2mvbILOU+1ZU++;fKMXNSg8uB|ixD=-05za~U2| zdupsETiBu0KZY+x!cY`OVjKSXH;jA@@(3oy623JS^${HEJnN6BIU~*uOJ}VSwt&Ky zD{G(W@KgLmFF#gV@it6kK~D(NZnXnI3#XuE4^jm_dAV~l5do_cK^wk;G=A8`kU5&= z>#{cn2?I8fC&edrrC5^szQ)GrUtSz!fYG%47@!+-T@EXP8V$`(x22e*x1ZdjOGK>w2&C{V9xRaQLd?e8dJ8e!T6L%xSPC8N<-x4i5aKfG^Zh8}xZMrlz?>45g z*LE{M%OrUDplKwdAnCS|!uYd?#qEA5!~8SvhOxZ!JEo|3IV?v)z;=kB==7H*Z(H18 zoBqDmLbUeze)|*yiJhEamzSZ>U)52nN^na#No5jwk74uw;_H5~p)NOCRhUWFP?|^g zuw6#rZ7X*;P8)D$-eF;UrJY({z`6M^US2hQgpuPfSM(qE^1BHzAbbAv*2|Ci>yt5C z_%AoXM$ZCHmH*Zy*vJsEu@3B=9-K-h+`LRdde5_%;3a|lR?N21LF(jqkC58|GM|js zLZ=MW2{>N*eg`Pu4hcuGlUj5LZ&)@q3*a)J=^PKfuioc14!y&y__;;pH%w|U_c!b+ z@B)0_*XYUk4aYzYYK}#w;;}sy>O|1M$34KYOgjJtaxujwye_m&Kd0D^Oogf+Fk)6oo zynSPILC}z>q-%+P=g#LBT6**DN|3j|2wf;e|I*L*i@?=rgwbi*r1II3L39;)$vYS$ zfLTbAI1H8%4&Qu!_kLO1yzteQOqmnPhdlydnmc63Kyk&zKsjwHA*`e^GN#3{;xw!?tvkiEZP^I$h?b&_yW4?AC>+x@GNiGH~~^ zP7SSmu{hjk2KGz1`9Cu*nC?tcUUhv;Z)b1faqYr-5}RN=-WL+PR< z)tWq3$%UwPfv10o&6@Qk?vgV1Xit~o^sxoxn z<+oyCE218QyzuMRr~UMalUBKi{j*4jaMH!648-NbHSA z0}nCNZy2<^FvRS&tKC{%LDeIvXCx;-w)77O3h*!eA_G#7Yb|M}YXUq5?l)je*;Et_ z+q$3XOVXqsG+!`jpXY*fu8iqA7fn-TooEMUEZ<~V*K7L>KAF7X?PjRt{@M7OPrvT# zN~EwBJq`5G^coTy31C@8MX>bL;y;?xd1Pt%2jbK>s|RS$)cFVor)nv-^NQ=tlCoRD zn^Qks#3!S`8KbL7kwwjuBWknA5wvHm$5H9sq7oVW_qn?;(b++i*cmpF3(rx zlnr}oAr+JH2Mxn>tWG{Xr43~P1ulnS%)iqY8f~`#S#Ox1 zyuPT+`$K=s{A_u5cm1?Xj5LlzQp`REM)dxE6yO-e7T5E8{65>!+54)(=4|u1&^9C; zd~7#@0eE!Bk?oEF-r7C!m7bHTW{jsFm<*+j5ZG8%&%Cm|`16Rc{BkNMm5WxQcN&6& zIH0-ZGI9AT@jA3bq)|^S__3_sEkTK7DBBPg0hWuVQ z*o`<_rRd#oK2QKeK$=BHw79@#Gu_jR&Ng_KShY7w>N4J^B*z1PaF|(7;9c22@#*dp zF)Tmv_3Le;Fn17jocj&)nx6(y!B?<2_EtvKTxa$_8a7f`Ya0QUE@Q7r(DpirB z66r%#dEtoG8LL$_AV(adESGMyR5$@Qj`<7F+ zN$0)QTlAXYLyRM{et`mJRXxU)aRpk2i=@r?@ z^>trS4(Mw*WAt4STdI-osKe#4ig1H9PnfNp)%QEB@5^j2(=!BqDixZD)bxn$m7Cq7}g%M&EQlAvvKCVbmBduBIm(-Ork;j3|m=oVVtN6{Htn2 zD#GKQ^;9XaEgL)`^#2&^ea~aIlEhu`UN4K-xM&xnCl_B=Vy5=)&cIU3$VSgVoPAK6 z)|b{KmQsCrfwjwvNd8tf$eZ90YwsX467=KPtsN)JG=khA2Fc_VdY|4KX*GtmI=lWb zacGo~8teE$f`L{jWo}lY=bYJmuseCaAfPS5(&{#1R(NRK^oLaJ84-I|p7)ZU7|Z|B zmr7lvwE`Ixg!?s`RDL_7+PBbfv1KtdWH=)iv1qXm2^d*CUVO&E#^ha3O#$Oy53E^I zgFoI- zi)s)XNtM(>v*|SAY-J@WT**JSYiTub>thPM; zEKy6eXLQZJxOJZ$FGiGi7t_H)XL%c$4*X06U#lUHXv-^B7`)}uKT)m(e)Um&f5ZCv z>F>dFdpQVBGt{*}76tv?kM-e2f#9W>zr~gO7YFU%{Wx5Y{+Cm9wo-*3^S-wHH*B(U zevjd07-%woFiPjC$cOB3H|UeUbW_*4fc=Wn8TK2d@{RJeM`uyjip9nV4YUT4r@vv= z0IV|nfByQ3WsKi$0Hp0t^LOMiU)SOP`!BFi10(cOA*Rz2a|@u(jX;)NM#kjAr(1`} zIGw&PnrpLLn=`p@7f*YCpN1X5#(+gtawlx^L|6xsPlon@!7owT)DJR=j7w_udy*HN zCP$lkX7KHo4)pWx;*#yMhz+IjJU`u2 zruXL8Ms$T$Q`dfVlv{V7M}K13PBg;sz_~EDQsH59nRGQ;QL73nm0UjTi)}Hg{EX5Y zSlEmWvcdZZogejeZK$6k+m0B#bQVl{H+tn{>DCR_9MGGEq=TK8Xf>3AsPS04M^Q!X zEjvLcNCD&rB*7|Rl@&%sE}zrBQ14L!Is3}I&6^(CkW^Mt*FoLL6guIP`cY1;rvrv~ z>8QAMb8$ftJQK&m7^zOQ3N?5FoHtBv2C``v?Wvg-N@+2Y!`=M3qOpmpIWt<8&zZU; z{8=Va72C0o0sb(1w?k#drX5E#)4@PW`y1Dl7k}WAPoPw12+#I{ddlJLsxu#bvlCa4 zpReyPZ=@&+3ykvqD$u%pypDl(`y%qg?m9P#k^F#GgIIVzUxN*A6#~bvWe4C=+d$@( zP*K<2Fo{-Ad6c;QVEn<`#NIoim*bp;v+WDLbPH43v+8JhbS1PCXu|f!P5Y6ZNr)8c zMUqR~9OzIjqgoSJNGSz-uc}?U{iK^~G)K6#JxfB|JiBs|Tq62z=pzC!p2*}d0njGP zfLbIclE&Z7&c!cqbFYTUj7h#eo3`rYe019Ug|)z=MdOS#)q}cmC9gS-d~49-qF(%@ zr-eRD#P;N~b^802pl~MD6KjO#%jORn#}1n43+%}HF(*r3ew}Sr_eSkT4C|TCrZ-Ytr2^mX zv-s-9y$x146HrsqnV>_zVL-&~MC|!C7I42lf?bZT^GnJn+8`hWB-qR%ugXEyi^!OK zPMpUQksM#LqPK5+UcY5*$Ju>geNwZr(JD%(SijP$L#$uYS6U|ueF`j_CR!C0I1rc~ z(cXaV8%xlreTmuxjanQ~@EOulRj0jou;Zmo4}UkAUBOam@@GOuVYWS4NcjC1`-j|X z%&m5`ml$tc%zTYNR=7&5to>?m9|_(o3J(t)E;ZsWRW?UH21Y#kt!ke}V{13pxrNX% zm+mm>u&E}Kbp-g_AG8NS+|!z!7?Bni!e(+lKeDrxi-L&@g4dw}jK`=(_x$)98$|Vu z>Kg?6M_7-u^~|dmf6a%1-}e!{3w{vi>JvgX91k2yp3{Sp3vd3ZH8m>-SduodwEATv z$3&Xg%qbb85M$@G;)iEDSj-qcb6$JM{Ub7gejfA+VP>Rze!~u77Rum)IJbPURpg<% zRInlJzg5R!?!+D8Xk2vqTTz9#?G3tju8q~`^E8xnil%ARdS_ujU}u>6!X3d4@Tp<} zDj>ymB~Q8qdhxuI*dx3I`7eSl)8JM?lG|K*sJOLHnn%jC!z(2Z-i}GLm_S$MBw#mf zKZ5Ql0j=HCPNl#vvGdsoLpTBxgUSGkH6xrCB!qv6Ui;r zdf7*a;ITIG?MFwVKLN|6@P*77JnyygrNeUXeebTN!ODQ$=Q=BvLkoUz$edst0gp$E z-OtLbxqXiKZ{_0CtTe@HY&Pg&KDIZCOefg2i`9S91(O=^y?X-Cs;y@)EPpfN+0V&u zVn|N9wmk}K3)H5$VW3Sk`^N`TqP7N77bEse%S41_7RNY0v9$hg2esWtT)icjhoT-f zgJj#FvxSYda*tA7@B=NcBgu$40|h2FpKsPR?Z=rSWG6EDLd zDeN9*tux>SeLTN5C{-1uaychBMOV}FUEM$>kn6p7efl@rE~Ti8m0&;neYT;qn^Qne z-fpbBUo#owZpl}&^DXzm__o**MXtWsBDYFlDJNf5Rajt7^K93ll+W1a3eqz92Y4>j z(2TT7Y6$nbI?>y1OpgtHf8?iJ4vD2her+;UQh2IdzeKtkQ8x$N!`%}>T zo7TtwOGD(}5vl$z==iU?d;fJTQU$_*e}bQoQm2LR%U$^iI97GcJ~Xh>&39sW=R$MW zbWPm6{4nZ|A6km{(j-?tRwd;FDZ zBP;)_0~CF*YBV+xUa3>KHrxTdBWUZ%Qc%&Ev`y{GlYp{(e7t;KPXF*%dvL+$M!b*c zY?>iwX0&c&+a=tfz3?-K z1qKDZWRDL8s9|O9C8erMvwwWh#}sxC4uK`xQ!yXWF#kF$4&jx1f0yi0pQhu#F#3Gj zG;gQVKM=e6#ep-}E9-g^X^CUK_MP7i$0eqm$s3@}Nh&p^G3)sZt3%vbTY~ft! zLlw}Ona)QJ?x(JgeQk7obt=4)9m z5|W$jZXTL$>_Rm7t(_)iVxlBeN#Al@N}wK&WcK;4`xok(9G|Sca-8t0npS)xrDxq= zlW`)PJ#>A}zY=`8LR9G>aCFW<^%S^^kXwPLmg zH$IMddp8H`G?;S^7biZ8O0EJZjt|@*`e>H`k^yknF?M5E&`uNw`dDzQZu&x5N*%X| z=R`HicJf?(leT)tN68lRkit>t!8j@NLV}h+vD9q#8I#R!z_7KWoWZo8YL)c{OMIe? z;!5vE2)%0MK!aio7-xFk#IMY8Kw`UJ&`)D2_h~Bx zW2d|0ZgP2fm#*@#u&h-MpTUp4m_4YYHE0Z5 z&W7#mDSpen`;(kQw5<<*;gz`;oTPS7?w)jm4O#KMi_F7>-V?7c_FVXO%dQwoUu5YV zV)|iwRg~0Pb|3>2%9^eA?2*LDsHZ1hU;BE;*pqS8^CdYeGmG4J&*i8EXVe|tTO#)N zfyLs+zxi~2TE^$I0aS;RnX^t8kw=#$wEl~{TX*vgmspC4mewhjH(2w+a6QAbAzr!GI&XxZ z^^ovWx4)@dv(au5<-1h&p)V3Y;*#iMKQtW4=|TBNw>kpfM8~Z%K9`mxnpmOKsb!dU z43&{|3=U%KeBK>JGdE&=Pb^n-?)2f~y#f5Ay45-i;mauh^q&fgIv4bSY!(%wdx+#> zO2nj6ohm#1eHLie-m;@zZIuCYXJq-zfc{Z_@lY)1ZPjz)p(SC0MVu+8USjX*gfuzX zc^_+Z!kLuW;J^F3-G550i9PelZnwS_Oi?UToVs6iA3nVFH;n*)-yUGsoqropoPkm4 zKLh;U4~Ey0UxG|J5f(^6s=P5d5v;4{!;hw8We4+ytVWmrz!!6hx|Q4>jgj4CL?QYAj0^ma z`o?scDZzT{_2ol-ZI#G>RywdkAVqKkXKDkkE!mIkNh{elBp~*qlBRPFErsj^-CaEV z4~-#O>(dmnZVgMLU!6=6k2HYoVjDkWhCF}6HlNegLR&lOAtt|Jq1+^F8yt!*qq92y zhMl#xq$<>J((QpPaXn$31-0Opz#jiyhUL&IwdrgN=~Kk!52+t))32enB7mm zVWL!q->^7G%tG}1wvh}drRQ7)%)oXHvTtkdtN;Z3H_v-4^ep=>F?D2 zXoeqQVNa;n!4nIrsO?(xeW=N9I9Heg+BBuJD~uE8tZ6q=FtVdK^WDQ6wGnTsT;-PL znI=-?B_DlNc=u&uV$aNE_jQuW$Jb65Nu;xfQtqK>O3&$2`8{ExqjNv6{R|rrjAu#| z_I)1w{)4`jJkz!SFmL9d z9lz<{rfE8%I`8DvjS?_$A$z1R?Qi`qS3)boEL4H-YiLS`aG{8pxQI>7F((~J=0ADU zwjtZN>}6=APTYk3^s2d-dE9!ZasNVp-`2zX0mu}I6Oc2OJ`T7<@Rw*?@ZsU;qofN! zX+H!7bj|VAd}@&|`#e9tDwtj#IWlNpzv60hC*_m2jOA7}=YxHU)ui3^=cJ)U&2=w| zAxgeGzy3l5==le0@dE>`uW6 z{y(UJUh+j+b&CPP@<7-}2}KE|^o~3O+}nnl=9KQtr4rM*=`By`hGt_=SFv@s3}a?f z&bM?;T~*~U5c#&3&q)#+-k$DxUi^_{eOm8CkZ*%^&MB(5Z@1%|`q}WtCtY1}6=El> z3OMa{bBb0(Eo19)oEZ%o68d|3_!}D=BbfXR`OgXP@aVI@EZ7gb#t@+oAKIk~#?sld z?os5ZmXHNShI)cDzTAo7K~Ze^e$2@=5=)lj-A3wI7v6@FZ!1@lCpAx6vL+5(RC^@n zii*s*zsIp4w0>+(0>T%pT-Bo$)^o#GtkArA+HZ+FyOA>%W1H(Kg~ zU}Tc!#dWZb$(~E!H7}Vo^;50ZuzUYmFJ~L+tt>DRoxNLR4N4Fqw@AdP^Aa z>xecwB6qD-?q<_uoO0DIwIMF=%qAzVJ-6=Osa&k@waj7J5YvpWc1^ZF%`D=eq_ArG z*nAy43I|lJspei}9j*(8G$9^~4XL`)Q|A4l!Cx&>Mwhzvnx~9%UU7?q&h-n*A%U49ZybDn-dysO9io5sI(JAP@*W=)Ev;DYlq){w;8sgb&uNi1~M zZi>FA1eZzL7W_bI;p(&enw3T?=P&eqtgb`#uuuhx0@?+NAohZM3!gs?{M90hieiUu zhH|2cy7n>%PAe1I=Wfc(DnHqpP-Alwe{r*NrsQ#{L+*?mZVhk(_S*s}@)jlzNqm9x zz{TCZIVT%(vfODUt3hbm;^S=gPTsgWM~F+%LGi4p4_2oJPhkgf(6LuI{5*h8UH}eh zvF8n;Hh6*9E+hh52@GC@5k@S$ zo+n;FgwJV%QKG;c0V*aVLtXR(twnfYyDa_2fy3l zC}*>SpA9{{m|MXi6L}p{>>y#zOXb zNdLUZ7C4>=U=6kxS zP9-`Ih#p&bD$R&`&oTpAzTq>$T?k>^^?Bj2QD7nsPOg+2Z8^P&5T5SC&eAeF`sa;C zf-0bUQQs2Tt;3lQ*Y2>FiS=z1pHx(&eYoq7WZRip!>J`Z4dU*M!|& zp=qa^$H8VvRtfd-%}S`ql$$E<*f$@@hkTEVZc(p*D(GDKiB`}_&L;VYK)>Xlt9#og zy|CrVsnN>nyTRVMI)Og^TxA<_d0#U6&$cIo+lSRcIe3pa+)c~4d4?7Xx*b%17PrY>jOt z?N+#|K4^jpx6`5F{^y?-k}5b;xTRAasUY72Q%My&fT{$zdGF{_IY)9FeS+KeJe!Yl zR@`jjRDCku^s@Hh2Zm08E41M&EPw_{203wb8O7TBPkko_LE7vds&krBp~_4H?;&JV z>5G2Db5|~x*6gw*T)k8qnv3qSp1Sol9CLODI{m*sryfWkW$@QoQ9+n#l?C}&J<%^z z1>*LeK-CysD$BZQ(sy}$v!Es@a$>w{ebF>8NayPtneYppBduYyWQ5BmWCxPV%_yoH zK!$1C4x-LzOoivb`S!h~Ln9AN-~B=CeG-8hM`IozZ^5|ni zW}y}Mh6zEjoq0UO8>otIM{Lw~vSWXUSmG~E?a&m;$8$?eTM^_)&a=V{?VPA#+H0H- z22$uJye^leh>cNd0H70JtD zvAPK`njZMH6u>zlb|7lK!N|jHW>RpX`mBouvGKw7{wLsKZhB9Tb#Rz+79Wg%^Hk;* zU$vMxp%p>2TYpXo3mb&jBf%y(wMl0;C`7R@o`uF`zdglo@}QVj4|bRSXt*u*=z6GN z!IoNL0PmZ|H}UDm9;lr=V3G}%h<}SEeilP^gv2PWD2Y98UX;+nW)GGb1zc>n5gDiN z#_^?BhP;Ed)%JH*a@`i`U-s>|pz-VuFf4MXAek{f+%uNsle9d5#)1p1=xI%;;o=@q z&Bwld@{<~q8r|k%?^#gW<<%doHu0DJ;D6>y>IvvdHjGj@M9zS!iM4cgTmZ22+KprC zS_DXTpcI!5v9=uSm<}r`Xqs8wyN!BuH7HHt+B=h{Pai(D(wI#^SSb>R$TN|C!r=0K zF*j5WpgeXP=lDijWuQdcsR5^zV9_grJhiDC13^b6hk|yx!@hwd{{N!u+k=}C({h?n zb-E7AXWw4#q-p<3+Ck_?3`_p~nZj`0Fyw;AMnowGOL&~?mBtn=MqWw$2 z_=$e&T*UOA-7?eBde?fK?PnhqgNFRz0*4cCC<^N9LA_8aem8=O4A{RJUsvHYweIX1 zO99+0Y(YF|1g}8z$Lg=5l3K7)vkkEC)$IIy8g`5WBX2N>!Mm>jr z*Bd(9XmP@Q@kswwW<{a4`9mse^$=Z@>Q@KaZ{(LEuVFr6d-XrPM=|{`hC&MN$e!by zO>r;+9LcN3rmMcFDAQ7({!lE&_}{FLhz!_WHM?GgO6qx9_7f*m`4?10JL}z&z2x2L z`*-;m=r!u}-RmnIljB##Nyj1xG*Rw@(2Wh9ABqu8r*8Q4hrWyZZqUvWr7!mQA07gm zF|~DsZEYyxQlw7KJ!T1oOWMc9x5^~Phz9#B*?ev#iu;#eG7`Rgu?=IiZccPeUd%i4cxgfl%WJBs zwQ#Zre}7#aJQa)N30g&%ELh)a7*}WHdD3~?d;x^#%q+Lzn68~%zK^theBF}prf^+{D5Up|Vt6{hwbA%Zq1?)@-@SO`8RJM`op5j6Jls*C%orq3oU zycZqO8DPG%l3{&J$V=z-=XPp7WB?+CB4}y>{<#+qMhR3A=94l-W3YgrJG_I8)sr37 zRSNv2)|@P*;PV?cT2(r>$xAyXnP=~QDdbkK^!}rt-C#TOQ$+;JbuSDFzL>w}LQs+L zqDzr5oeI>`m;z|P8ThQ%we8}rdu5(J(`k`&KE^!m>h$Vi+2$EVh4_9q2jpixd8!c& zNH^}72>KDInp7&rofJ&o_sLURC<1->h2vHA?V4XV-3-dTt?LYCCCrMVEn3W^*B|1& z%W3HdHx*tg6Nx)jPZ{k*x&v(zqbx-&(%Q?Ms_Jt~avx@Y1@jcIKVix9 zk}#9G8}NpK91TPJQ}(0I4YbeK>ScKy`na=1E-U{bI#+R;o*aTFhuS%e9aGs%atwRU zDRkT^F(-b1)BC5#qh+6^trLTLHwEZZqLb^@6lE`*gf0i3`vA&TB|-G^|)T8oS0M%`42C|8#v>a7Lt=&YIa8TmNad zy7I2K-7hN3jxI3n+ago*kkf}yupASkJ!48(zYavuHgIQ>lwwiyQ0ZmC*q1SS53!sHy)K6d2t*JKcXdE zijv3sb*jWn3AoBy zA4Ow`xbXweInRw&qw}?n9jFQ@)JVS>xKhuk)ugH7vvkkm)SG=6wHFvQmO*^>F8h&! z1MsdtgjX@tOqU~fBGm&-?6wSslhV==Nv&eoq%e3@$C-i3RuRk;9*XDn^1grPb zfdp2k(hd%OlZD1Ogr{F>6OPOs)uQZstlq+K?dR-0o|Bc=TPi%4p3*%oJ}}nZ?he^s zGA1-pjdX1IUTPG0%jIaNgj6-DvM0Vzcvrc5 zez1l&vbGNY?D>#P8G34F(=PR{tV<96iRx5zLH(Y7TZE485J9-3F8tJAWrJcuK zlg4`5peo@A<5KFB3fXZO;f9<_G@w0Bw|h(Si$$OG(aR{)s}j{MGCc+!_}Z+O{S6aq z4gFpFNCjU`rrpOy<=5n|HB)qMipEq?5NHizV}x(LuSzMAoa$9-7BILuS6_GW>8a&! z^4V|yq`C{eNx(&4Ms^{7t0m`)qZBPicPuQooYQVU7aeEy`o}8Fl%zcN$k!qzK?u;f@0sq zgs=UacsG#%TWH}TV)3z^+}u|nb38U?8^`Tsm!+whKIN6^@8~oTfcj=0e^u+z#p0)* z>rxCnZkY30pf?n<7jTRDtWe1Lk$RkfIn&o7M_!4|`n>uNch^*yhL z$cd(f_Od@+E~i+04=!Mzg0XMM5^G2E&9HL{yd5UH6~t(k#KNoSSA z-dlB&{e@Yjrd~_VB>7*B1wIf|Kxdu=5ffXfD~%W92vs~cQbnk(si}>>*4WS(amIxI zVgc);LfFff`(B2_y3s`>X*wIIt}s!czC~bMiq98n-H$#^LdL7xIj96VyL5{B?CcB_ zKCsQaw?1cbSm2VNOEfG{fP9*tVOe4ybc&dWx9beSMCEf(6^kIh4#7tjOrjE5E?jvz z{C1o}M`i1dyngw9aBV@7(;>c3?yX7`rUsm!!xZg-@*#>IRfpIcfwa4}`w_n`HF;k1 zeDOnfjbW2-2hG}V?O+2zQOTTWvX+5Q<)DAQs!6NcnYCVYBq0*&oHaYO(E)|U+8T*v zwI9e0GhyyRn%1YgKJ+TJ^Iwx3ns|IsU-&=RV(b5dZT9~Fy#5pe@6b1V zd_62r!nbe3f(l=*hx;tJ)@;a{4RQrtvSJy`6M#HOv2A%$b%|!AC~rLpUWU^RSoqVx zN8sE)HMu{hK&^U*jlU3HRj>2TgDwcN0Q#E(bUZoP?* zwPxtq-oB>oBI*7vvn802)sEyuU$76-y!Xd}{H=Ce`2cVr2Yz+A1C+^7qQNOF7%tx9 zCqPvnUm(KQ`9z)SJ}ymp6O$aA@3U!jS>In~1dvgN9UuO&zzn8G5BobMQw*4M|G{bd zZ!@d@ZXn5@kmP^RZ32*we>*ggLg|KYDXk)QerVFez3;wm0U?XjAdn}n)?&UhH+sUb z$r!)+X-g+sI>rsjOZ6hYD-O=Ce;7I4Ew}%ej&C{sAhV^11Id`%$3Uq`0HzyEVf4naKGTE z+JA+4J?Oj?<7Jb5!Okx0r1>i>&Nm-GcodhoUul6d*ZmTD>;hMS(@62wtIBQ4Q#_h^ zM#jHZp`A&b=ToHOual4+#RLpIYjFd+-R)7tLX|eQ*Fu8Jvh%9o8`7mbE3 zAAUjgUYq(aHsUg!ZHUy^0VkfxNrmL(N?v;UEl#zH-E2J|M6$dcnA(Um(7pKHO@n}u zQA==W9cqEyj(rZpQFOzmF+zHpn09Md$V{U66IydTj0w!6A!|z;FnEVr+KqW6FKO;O ztOGx6tr}w}JoE5*y!y8+WP{(j0=x$U9m#qy+Vy0Vz8{4-+e*|$x&U!4{?cxqfX_Mi z*{>UKX74h+d8-<+tIxJIjs$$T)8OVsBE{$e^a*%%*xQ8=tcWLqy>2KvPi0v5+AhgU zv!ThQs!Q{3u*U6{A8G+l`PmM$lMWu3STSFar}D{@dx?P8lNzU|3k`MZ$&pmnHH#hE zOJ-MA0zCY@?q(}ZaPxm^u~yS)_<63{sm3foai&M&yy-bBmiE@;v?`$dy@mtlSqA(7 zZlSsr0o4|x3R%?>{oaH?1^~mX?gc#$s1(5!o!`$Eop$&Hm&I^pWv`nRnk!pJy==&zohduB=d^9G{j0Gi{=Omh`4UDPW zQ@d?`YZx~@XLuC-+1M`~=*6B`NN%VXk(f_B^O2FxmW+RAcP6U0$u6=*2>;e^!{d$f z;a4qMdm&ev`WuE$#djCyUDUwmD899khg)IV&){4w;}P|F6~uiM{73Rr*L?QYW8Wq8 z#$!s9s-M=)9-StO+-Te?%nIsM@8`GM`V>BOTc3%M6h*EDf>xV+91eK(fiz!3p^t@0 z&@x)&iD^#C%Ew-*Ir#)Qkt8boVR=g(^D+%&^4;dlgMufOux~amhHF)Y&^Zh!M$izQ z^*O~j>=PKi?qBo+;05$&5d$JR0$cEZdhlrY2!K}aXZ&I&Ig$Mw+bA!WrQ zhw}t3>Z-Q&I|s9fbd1wL#nlBYJNWuX@HoALvH*oYU{aTnyK7$brgmVH%l8-Mj-G-V zf|?UTSI^g;Z`8Cgr>E-ND-_@yky^7VDQE1&_--Ej^A&}uPPlImG<)7IzrfN!pM-;A___!91^OWm za2?~c6kW6H3veD(YI`H{AlhM3XIA61W9Ui4`aB)2qUzemRn1?nIJfqAMVO{POhRER zFbgj+HK|>eGpV2=L^32>LY5(AUUO2UhRjS`P3@MnZa48nUF&qkPX4W<>t|G}s|U5P zJT&ZY*mcCTSQn;Fs1q|&MwTZXi+YZ}LS&8(z?;v&`MPzRgyPi1Cd{smd^1OGKM@ss zz}K#H^Rw?EVSjyzbU(b`bQXw_pXkZ_$gjh!%a*KeO*_=jWi5hgpKq^4s(!Lnyi7~v zh=YOk+C} zQ`wcHZoZJ3o?n@+ysObrD|Ml|qFpAvwrA<#GeP7ya$1qBN^8O_6#U^9fn;ndgxo(t z<-j8kqDr=>T6F1LDRg?rpvBQuvG4ThU4r~uhq4G>BYPJP9xcuPxP1%(%dZEm(h7oU$6j*3tzF>ji~o4%NcD0q8TS0Y;<_W5U= zgOT&6a_BZ6Db|=N1Qbf$VW;7+gQ-#AF_7=dj|EK+Hrz?`4OeSJOU?0^xI7_ZLwxCU zr&1M0peN3^g`6uY8-KB7XRH6**M$v^9*oiV|Jyt#Mh+nUZYizI@BHAVhbw{;78rT{ z(ue$Cy#qrD&l4S(!+nWG^`ExY6pblIZ%qk#$ceyZ-q932R;Ak^!qr_@b86sZ=i$Tg zdJq{~0%CpvD4E1akPojnzgLH0A0j9V^)VAdPT8$r>`Q-Uko2a9Tlnil3Lt&N;)Gh^ z>hwmRMzePTK1!m+R}FZXXX;`RC{5T4%IpE}3GYl&Y9v~b#At}&f_nN?DvJ*%`I`QU zJ;VOc;82|8CzcO>K#c;e&)zvh`<3Vl#B;eLUX?SOxA@*hr0#XWXHO$Eu)t0RJiBn- zZC0w;!c13lLztMjuFtHq<bf)@D*)B_J!sYDfkJu(Z*opw8ad|1z4h_`#3%fB zBeed+W&g7g%YPu=FVMD_KO=8PuyxLI2o`xT^48qP`t${lv9G{n^FXKTY5W>jTF1j^ zrL!G7pC7V3W{}lKShbjm4d7>>!u?OHN?-@fWG!wweK81T|1zVMrYT};^Uz>hUEReT zKg50I-00AC-;G|SkGyG?Z@Xc)hVB0t6#pGklKPk4e0{MxTcN+;8;bsd2e4vZ7l{6s zR1toLW~1x>0TJ+5cm~Y(|GxxS^#2#`LOoY^m*B(tj{|Xw2AS?!Fm4-%zW{EE6G6`3 znBfuGF?;C3DM6d$>I>agUCpVCJ^FvB`sT^i5rc3Zvte|r1&a46qzZ>5sZ zq#Qy_IhRmT%4w3E(Yu#&o*5@O2dE3>&fx~P6X~f@u5CZ>+ zRcWS@O6HuvbBgdUe*&}~TcmvgOwb3RC2Jwwg^vKn{2J6MEx5M0fpOkxkgG(* zB9?QD{%{Xe(d82&a~iz8vP_VcT7|JeYkKXZ_%ESZA3Wr#OLY{a&(SLdVl8sNgxWP?G(b8mkPzwZ zpQGk{aphjr=nR(Ww&j-zR1vX{2Ez~Sx61`xdy(jR6MOY)8Bvgj#Rct*rv(n}-DQ0t zWzA}HpgS+hRsp4B-B|4A1~zE#E=ta0rQQW`ZvN2r{HCQ~P2482bnI11=F8r&!@s$0 z+aIb(#>DQ1j5_2) z%4x1+2&P!HPtVt#-}|sgR;m$ zN3?Fs;mdD#k3W{m_puiNWWW2Sb z)#tDkIA@1i3>cs7kBMcqkl3q+^lQ$4tl>X2>J2opdMkb;UQDS?)N^{{Rm8t$@$04* z|H^CrV?_DmRzkJ}ssEqWEd1|Fh_3)9lm2`B?*9@5|Ns2U2A~z?iKBXoo%KoKG(b|#g?mYZtF*2JGZK6ue=mc)bf*bG`uHxqV(f|Yl7x7AM>9tsz8w?Z}# zR(`1%J~;cWV)x<$Vq^RpJ&Rp8mE8wkr8k~A86ETgY)1+qY9BuZ7a}&b(d{kUms1Jd z_I;{TUe>+liaahhPvx;ywBdYTXUl=^zKo8;OR3Yx`h*8L^HNt&L7oR2)0AAE{)nhd8QM5a=J2MAWr*0 z>y9&ywf8nfR+{}`fm1$B0FmCQe?&kcLjp~kYa=K^5gjYwcdw**9UBa{3mkiE+`O-n zdOpD_V_WreCC>`m+wbLtmNov6OgMakvyfBr23;tjsB=&=`zKwb0|`0F*L5`(`x{OW z6Sbe&GrI_pkL6SieQtVP91C82yn%Gly<_vnwb8*(v4g0Ziks}KL!9X0mS|cpQfaD7 zy`J@crM&iP%fKyX{VT!S3+|L^>{$QuZ6q_X``o|Q%iONC zc&bm4Ju(<*7fH|Spgw#lWA{uYCD$%a_2JA3kq`fi+~W@{TK{;cUyg zX>_b99CDx&lBPD)MTY3|E*47V*vdxSKD+g8Mdq%6tLcRg2ftbBVqSf{kgz4>)T6Jx zpnTLSZ0!9MA`3))S#%S}cKMP7SzFgJ&2=RlH;)eDW@{a7L2sz(_BRPTW|vQ*Zwl=5 z6#>6NU=|B`BTACh-!_b02Za_nL%5?uA2KM&phhsjK&gY)y68Ggaq5_#V<5th>+o(Z2Ri@3R`}AV zdiz63H@yV}&CW* zE691sz!Z#S*R!jvR^7=ucy9gdbwsYau;UP@G)+@b@dt*+RjJa%L;<`cMaDqlBNV~) zLJBBb)*4%IV2FN+?o6_=(}dOo7filjLoS0LA?P&Z+@=Ng0)@+qt_w|Fx!y8G=g*hf zJTd$vD=SlFBktHM-S`4i6e(4^U3~dk;13lV>`N}darBdvKJe67vNS@xv1BnOR`^u) zJN-bD1C!hhC!!x3VTx?9mfp*)%QlJB;_|>-J%vfw^nmQQ&NYpK=81e7vbvNeqBsE!w?f zM(r={X7nOAoh|!paAnHp>}Ca{UqZ&6=P|=GDgHV^=)Ec(pJIg(p1?_UTTPGxQCcVi znKG$L!OCGwQR4p8*Z|g~J!su%*UJTOumR)RKQzAGK0l^G(EXZhy{+h)Q!2u1YE*>| z?_7^H0FXc$I&ObIXdK$hkW2ltoeUoZ3$5NqJb83z^^H?H@Q*@X#nEdcy}e71XfM)i z7#lN!Z<|XDJoi3$=%e#Gkt!h|rH2*9wHgTa3f_~Y$^MWhc&y9?d2$?f$0)`RalK{t z@GqeeK;QA8rz*PAxgka@m!9veTNUy}k^OilN@t(iJ__KvX$`uJPSoNmR;KLlwbu{i zOzJ_A(gic@PR=U;6pkZX`ky(pHS)&Z4BGL%!(@-*>gv1&t@#I5c|vW6(#DVu z_rw_$S}#cl!E9l51k)(I1SJ>*t#~!|#%M*=^fu%czwE0u)JL7iQbe1Q9k*V3>a1fB zEcXdWw<4*WpD(Wh-q>Ew84^^yKdn1fb&z-Ea-*)CQfG_9%>0z+$6rEMLQLEFMKd@V zt|UZBdT`B1uQb)Y3e*MP4$+1^z&cB028EBncO6-_`DRw3s{3e%dI9xA7= zE)_bl#&pAwvDrh&>e!&MQ>b^u6PGay={MBiBInVTr2BglCT1J_yk0GP=~`tCci99&*5zu^6xv>yTQ^JQ9&sZghg& z@J+?pYbiv77>T-IeY;}T$l*x3kkl$&W(HZL#efUPR0?F#Y5S3DFs}AP%Kj~OkEiSc zpPsb|Vdwjo&!+6Nx#a82YltCJRh5A%&XKfb05V)N1$A>z^3L!}XkfYwSsEaagc7}J z_XVq2H2@LqHQzLuxT}-%!jryU$2U3n9I3PUc|_OU;;4pKcY0ATs{9PM$=j1+*WMBJ z@>6!!9^Nl$xqW+=6S|8j$LSQObsu=Qivqs z_vGovsOq|(ojad}7|W^WFlvflV_tEz>3EIfTv=UiO(+M>gsVVRv&>l%7h-UC2wqRs zGKULrT-zvJR!+yE@0T--M2+nqo?Yk<$dulo9@i(nG9C9ceNglGEjKXJoLadkZ)WZw?d@0OeqIvbX9INZiFM= zX~fj_Z}KBa4(a2i$IrSioT(qM2s@DPH+%rCtSNq;7+E2|^_!{ln?dPqAtnK6QH)g^ zb`|R_K87b&O!aFzkh@`Qbj0~^_V6(4e%m7lOKC}|i_(WzH~k;Rm;W5DfB&qN2xU!& zgD(@Om*}#9tD6^|?{6dzRM;2r8$!z0Om9(G*(0aeSV3=uB`L|iZwa1Gb80g*Z7#)l zem+^BF{$lp`^|LX!q+|OvCKW=X0sZnm(JhT`(RB6WCww91)$_@M@gwNtgQ5&j;g8wm<>e8QTjAW~Ht?=U?;{n6DdsuMjwT(HR-)toA ziTMWmqNS8=F5K>EpS4p7$r_K}E-c1b$BO9ahea8(!K|?% zeS&Xl*$ZZ=42VwhI=MyjK+rZ#J3Uu=q^YLxt;~G+n~|DEgF)o(%%1cMPU9sDXlN&; z6G2s>A-j>H3m`>RXzrKhT_X+X45Aa44mG^fZ83o5O;P1jFS(gX$7NHjf(r&;Z_Bw< zRk!0^cI*!)8u1GSC}=9M&(ts~X-s?y28tz))_l9VP?v^ZiV;7dQ zJFMS@v$ne0j`IGblbkKVSHrJRnPrd` z-Vb-1a`Qk|1{A94$~@ao$s*Tke|pOU%o=ak(4eUIATwQf_)yDc0kCdJK;hyp1 zi@kfhPa+-PQ(jXIcv}`Z8(4E4-=PWGVvOmk=j=xji%-?bXj-|Uwgb8#rS4UeYv`Wb zd)fQYlg=5({e+iCpl7D?pr-i(=RX7r$sp%x`wk$eOJz7;G+@F8L@6{C5TeuJkR`UC z@I93u`vv6#5-6R~?$;#p0~0S!6f|Fo9dmJF8u>GV*TFZ;=Sff!$uuP#Y~1udhf?i2 z4jSK7^V8081iyMqbI$6A%CAo6Z-#k)0D4-Z6j$hlKi4eKV-@S;96m^8(-&sm-MqR0WPo-{OpFT%_@$?@A4TwK+`2BU?`0I6LMEKgYXGDs9y{1FKm|R=w>x0|A zSIPn5c)zJwR2{k1M3|GpFZ@A(tc?}uNgj-2?@jwnN(kF786S%(yGzgA^QnDfGN%8S z{P#r}+^e!Dfbj1TApEPGSrn{4Ux91&48~30fv*E#4_}0Mlmdx$ay|z)8U|&$4>R{$ z;h7Ypw2m`PZmgon{Oh7x5)G;|w}cRn0i?5*+^S1WJWMfmVClLD#;aJ9FG?X_hnz1% zW08gxSsGd4>&BAkKHsL&KFJ?gc-EfxGeDq?@B#0W%t__n0-6%IT6m}K>iXPbFgPPH z!DO~y`FSy@^9Kc4Y@uPwWmkmlJebt?KC5x97VyPQ&K2 zTi|U|O6A6&E-l#Dd|O|r}DeSuHsow1+B_GxsPLe`71#v-AmuX zx@k%TRwXBvUxS#ghSwoq7KAY(@GT0g%2s*A^^nY?tcp6F{5_=i&vfKW`!5>2_7w}t z{d$tp6L{13@$IBk;qT~|cKu0803jVy1}gB?6=ViZ${k~84S7$e*qds6_!{+jXQd}O zf&Oixov!fuNZE^yfNK5Zt@mzRhmBv=T2^^a_J&JGfr?yA;-{(V$}hU?MaG3DSX=OM z$|2 zp_?*k2nDj{rhUpwFy`%4`1SocEPiL-1MR#Cj{w=9d!A%RwIpY=x6H(OoKB}7Mm+4% zf<8j?Twm(s@3ty0XWx<<4GfuTj@kF>`s%=Hej`=`uxdAQJupiz z;bLH~JP9#6Or;K7xmr=cI?mC8!5~j&dC`UVnQT{0jmA*!vP!n=Gud6I503~T!pOl$ z>LS>-)wl;flk4D}y^2T&5VjI4^m_wSqLnlsxA=ZQmw$(BRR>$;#I$?#X3gTn4|}dA zJk^2*AU5|D&z)b1o2I~|c~;O;Xjn9z5ASqAB>Yh59Z{}tZVHBXWy=iohDwRE)!L8T zoJ$(bd@FuxI$6+LD)f+i7r;lXrg6+wT(p1+ugk)BqVaIa?{Fa=0=jp35$5Nyl`=ad z?x>*P@9os!z9e?a5uWA0#Y!k5tHAB~7+Vp~#4`|M*BBz50K!L>tLvxYYzBEo40qo) z(#9d|`cEzoKe=ED4|_S`g|iE7)Kf1=c^3XY=~poHSBLX23W;!XEpVEEd|(Ph3U>%V zy4E}gB@C3bA7M*vftM|NhKlIu#f$ruKY)G-IkM7H#vjRYWMnln+O>Q5S#vUpjeJ^>Mg8qMKE^D}V%Fg&3~q;v5mVhhiP)!XLy*(EW?bob=c+oygx*)kM8v%C8b zZYQN{_bs2JLz|BfMf)nZ+~Xx&y%c6f=|7KZ0H9cErhGirQ8ZQ?x@E$topk z*#)7}wNF$N95nB?^f;W6IeGu%jhBc%bZ37Yxjy0xXeO+A4)D=2_5SeM96SK=rCvoO zUM9mAR(#5y-fWRA?ZoV8Xd05WTwnigY%-+@wvlTK5}sqEsU&ddcUaFlt=CBJ&~n7o%Zp{g=DE!UKTqXETvYVt_uuUi75cKM21wmd0WEh z_%1>c)}WcK)XWZRchMGc9<8UFAV4eWy3q=YHqVt7@cH;2U9PgjwyJ%A^)c zn9~Pn>p=mu|>Mm_I00Wq3~>?p9aXM#ZM$WP|c2T(zCu$NQv@m z6BV=E5KFw_)|U^EE~!ANdkR1-nWC)TPJ#?Clr==Fd}&uM+wP*CHm!L6yI2|DR`ANh zeQL_B?XmxnZ0G5eh^*QRPmG8{xTCdB>>+kPr;A@{tOd-A;@1kcLM3#as%va9&P8?# zWNyv=nAYx3efu?+^TO2CX14HM#~GHHk-1Sf5d%MP_Aa3k5dgA}soFW-mZ2`35H5g= z#(NmE2}w~Xwt2gf1baBKJU|&OwjhI5zcV(WpuN+}dAEH_-2Nwtv!}OS3Cq3@n-3uQ zun)BtWVRIK9d8h+yQXE3J~ZxpJDAtc%^~S13OCLw6HI>3H536lD*DPnu@$ z4^W{%h;bc0F&Pk;onZdaQkeI4qyPX_cLe@)%*-40&C+iIrQd z<*<$fbLNWSy-k+$p_hiFT73Mn7Vnx+GH0K@D&KK&6@RN!vS5I$P)iuCoVH3q_`x>$ zHB}~EUKH`qT=0|1-+9BaDAI~#)Pg1A5lFwM}tISo| zwWjhN98y7dfLekS*%zOT5*8q!gwNeS$s(X}Or$*x%Ap0H7t?j7AKnh0lKBmpg+~4JWTQV_|+u3*5>caCpLcI z-uN%UBfo(q@Lzvmar|rc`Pb|I56=*F`eg-rxE#*iLhX@0gBZPZz0?&-7lCzLcZ4BO z?`f|O+!8(7B{bqA3jDD>h;$5o8Q~ktU0mu z}Q#;*dSE8C$RTzOnuKEIx1C>WVyfxv=mTc~3d5II&OJi?cL$bwlB zOpGq-!y%Y(zX|*_Evy!5%|M+ia8F#|4Tn z25HUd5*BAGR!>N0eDvOQC&|ZH)~Fg`>vtda5m@PrV3i9g_?1@*@^zJt1B}ZHenaTnj*xlwkn)H1%`mS;E;Mw*EJ+HvkP7a zQVVuHE=I5e$_?}qNqwdhg$SnGYdI2*rM3rNQ*2ps%@3?iS`PZGqI!KbJ2M04p?M$H zQ7MCKmF7u2!L{x+js&|Nn?=O$Utfp6+0+9OIwlW&D3?7!e*a@MTp{?#!nZ)o$ND2a z+a^;e%ZPgoghTQwug2oD!4jju4oFlfhp=!ZHB%`WjnG&Udm4e)*XuqE< z?Y9@Y+UryhZob+T8U7gM)^Olbzmy*`SE8*ftw&@7!UsHiYuXmU`pCyXzgH9i#BkLL zZ?boxs{%=ys7+%~`kbBKTlD6@?3ErfRSArEf6^L>y#;nMOSWJ-j6Vm;J5Vgg3ea27 zvQ5>aLLd+_MKw5!JRPL`YX+=_kN-j*o%Ak#Q91f^d2ku}X0V*^#T^;#LcVSSOj9bH z1jwG9*acd9h-m@>?h7Fv@@$9I+3=*Oy-zS9l#Ue?=fk_zvz!illV|n#i>HiVldP%8 ze7~*2980ibX4$@9%DBN3y!9+XjPY8o-NIEA{<}hNb2+jzVp4uc_rdVV`H7aB-`{Od z(0njn2D`0n_*9~=qssI&O9Qlk0R5~2J)#>n=0z7L%Yz7kYP&gY@8bBV^}c^7CMdG_ z%XBp{MxeW zqReXQhjqRe7+Pdgjx|$Ma^7SN>&dG`UKy|+D7ByVf)^`o=O>qk;~`C0%rBu3JH_ar z`>co?KM-rXY2T|AyzMO)CZ~_z3ZXAk6p0w?@^+(t5b_^TrkB+T*WZo88dLrQaF=q=N0EZLlQnW>H z!jyG4)#ag30rDRw#$}xhGN|2ZwV2S5+g6*cQ{>lvOj09_^P51S83_fW)<+>&l{{4f zD-G8s#ujeJ8ZKeGIsJ9GHbfW)U(H;4@Qt_FJo~Xed}Kn4xuPjl*@GG}nl|Vlk7{rp z^B-Wv`-|KR+R@uU155@TA0D@=oYBNy4S0|#+wt18mRP2w`nbN6oXD=jd=~(oSpOSI zx8@KlF$Qs-5KpebCz7^L_#>nR8{8H^t}f7=cez9jkPja&iVnqm8cs7VGr5<7xEo*U;D0z6brx ztQBqA{BUIX(YpS7p|L9xKtsojWzGQ*x_AR_a8x)SB+#k?Fsl;(A&_Q-{(`SsMvM|z zDPwM9AzZ6_0vy$JpjqjZhsSu-@+%fW2-}B~1cCS7R82o~2Vl%xq+)ksn(44KK=7(@ zbRg)9MQxD-<)m8u5XpaLv+AV|l6ULgnE1O7nR7qRfGq+g5G~@W*n^SOcEm=qkM_y= za0##W75{vf7RLs+&+>7Vk)^gW~JD*ylh literal 0 HcmV?d00001 diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index f664a5eea5e5..5ef14a7b91d0 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -25,7 +25,7 @@ namespace OMEGA { /// arrays class ThicknessFluxDivOnCell { public: - bool Enabled; + bool Enabled = false; /// constructor declaration ThicknessFluxDivOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); @@ -79,7 +79,7 @@ class ThicknessFluxDivOnCell { /// momentum equation class PotentialVortHAdvOnEdge { public: - bool Enabled; + bool Enabled = false; /// constructor declaration PotentialVortHAdvOnEdge(const HorzMesh *Mesh, const VertCoord *VCoord); @@ -130,7 +130,7 @@ class PotentialVortHAdvOnEdge { /// Gradient of kinetic energy defined on edges, for momentum equation class KEGradOnEdge { public: - bool Enabled; + bool Enabled = false; /// constructor declaration KEGradOnEdge(const HorzMesh *Mesh, const VertCoord *VCoord); @@ -165,7 +165,7 @@ class KEGradOnEdge { /// acceleration, for momentum equation class SSHGradOnEdge { public: - bool Enabled; + bool Enabled = false; /// constructor declaration SSHGradOnEdge(const HorzMesh *Mesh, const VertCoord *VCoord); @@ -200,7 +200,7 @@ class SSHGradOnEdge { /// Laplacian horizontal mixing, for momentum equation class VelocityDiffusionOnEdge { public: - bool Enabled; + bool Enabled = false; Real ViscDel2; @@ -251,7 +251,7 @@ class VelocityDiffusionOnEdge { /// Biharmonic horizontal mixing, for momentum equation class VelocityHyperDiffOnEdge { public: - bool Enabled; + bool Enabled = false; Real ViscDel4; Real DivFactor; @@ -304,7 +304,7 @@ class VelocityHyperDiffOnEdge { /// Wind forcing class WindForcingOnEdge { public: - bool Enabled; + bool Enabled = false; Real LocRhoSw; /// constructor declaration @@ -332,7 +332,7 @@ class WindForcingOnEdge { /// Bottom drag class BottomDragOnEdge { public: - bool Enabled; + bool Enabled = false; Real Coeff; /// constructor declaration @@ -368,7 +368,7 @@ class BottomDragOnEdge { // Tracer horizontal advection term class TracerHorzAdvOnCell { public: - bool Enabled; + bool Enabled = false; TracerHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); @@ -497,7 +497,7 @@ class TracerHighOrderHorzAdvOnCell { // Tracer horizontal diffusion term class TracerDiffOnCell { public: - bool Enabled; + bool Enabled = false; Real EddyDiff2; @@ -560,7 +560,7 @@ class TracerDiffOnCell { // Tracer biharmonic horizontal mixing term class TracerHyperDiffOnCell { public: - bool Enabled; + bool Enabled = false; Real EddyDiff4; From de4fcf1a94136b28bd6c8f6aff96731f50e1e075 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 2 Dec 2025 10:38:01 -0700 Subject: [PATCH 021/152] Remove whitespace the linter disapproves of. --- .../omega/doc/devGuide/TendencyTerms.md | 2 -- .../omega/doc/userGuide/TendencyTerms.md | 27 +++++++++---------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/components/omega/doc/devGuide/TendencyTerms.md b/components/omega/doc/devGuide/TendencyTerms.md index b4817f0e131d..e665fb03ed76 100644 --- a/components/omega/doc/devGuide/TendencyTerms.md +++ b/components/omega/doc/devGuide/TendencyTerms.md @@ -41,5 +41,3 @@ implemented: - `TracerHighOrderHorzAdvOnCell` - `TracerDiffOnCell` - `TracerHyperDiffOnCell` - - diff --git a/components/omega/doc/userGuide/TendencyTerms.md b/components/omega/doc/userGuide/TendencyTerms.md index 3b75a87da07c..369d5673fd2d 100644 --- a/components/omega/doc/userGuide/TendencyTerms.md +++ b/components/omega/doc/userGuide/TendencyTerms.md @@ -66,21 +66,21 @@ 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. +and only a summary of a few equations from that paper are reproduced here. -The conservative form of the scalar transport equation +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 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. +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 @@ -90,26 +90,26 @@ $$ \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. +of $\psi$ for the elements on each side of the edge being evaluated. This is used when the TracerHorzAdvOnCell user option is active and TracerHorzAdvTendencyOrder 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 +For higher order flux calculations, higher order derivatives of $\psi$ are needed and for the user option of TracerHorzAdvOnCell along with TracerHorzAdvTendencyOrder 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 +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, +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 @@ -117,8 +117,8 @@ second order derivatives needed for the higher order flux corrections to the low 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 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 @@ -127,7 +127,7 @@ 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 +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 @@ -136,6 +136,3 @@ is about order 1.7 as shown in {numref}`tracer-higher-order-convergence`: :width: 600 px Tracer higer order convergence example of a cosine bell advected on a sphere showing an order 1.71 convergence rate ``` - - - From 01659de178d343cea2d967c31e515a88fbb428a3 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 9 Dec 2025 08:11:17 -0800 Subject: [PATCH 022/152] Fix off-by-one array size in unit test. --- components/omega/test/ocn/HorzOperatorsTest.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/omega/test/ocn/HorzOperatorsTest.cpp b/components/omega/test/ocn/HorzOperatorsTest.cpp index 191b459c7810..6f1be1079851 100644 --- a/components/omega/test/ocn/HorzOperatorsTest.cpp +++ b/components/omega/test/ocn/HorzOperatorsTest.cpp @@ -831,8 +831,7 @@ int testsecondderivativeoncellDetermineSphericalPatchGeometry(Real RTol) { Array1DReal XP("XP", NEdges); Array1DReal YP("YP", NEdges); Array1DReal Angle2D("Angle2D", NEdges); - ; - Array1DI4 CellList("CellList", NEdges); + Array1DI4 CellList("CellList", NEdges + 1); { const I4 List[NEdges + 1] = {3, 0, 2, 5, 6, 4, 1}; auto CellListH = createHostMirrorCopy(CellList); From 58781e78d775b09ed50d9f8a79f38f35faa77334 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 9 Dec 2025 08:27:45 -0800 Subject: [PATCH 023/152] Fix a couple of signed/unsigned int comparison compiler warnings. --- components/omega/src/ocn/HorzOperators.h | 4 ++-- .../omega/test/ocn/HorzOperatorsTest.cpp | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/omega/src/ocn/HorzOperators.h b/components/omega/src/ocn/HorzOperators.h index 910c1971c980..081e91a84645 100644 --- a/components/omega/src/ocn/HorzOperators.h +++ b/components/omega/src/ocn/HorzOperators.h @@ -492,8 +492,8 @@ class MasksAndCoefficients { Array1DI4 CellIndex = Kokkos::subview(CellIndx, IEdge, Kokkos::ALL); Array2DI4 CellIndexSorted = Kokkos::subview(CellIndxSorted, IEdge, Kokkos::ALL, Kokkos::ALL); - for (int I = 0; I < CellIndexSorted.extent(0); ++I) - for (int K = 0; K < CellIndexSorted.extent(1); ++K) + for (unsigned I = 0; I < CellIndexSorted.extent(0); ++I) + for (unsigned K = 0; K < CellIndexSorted.extent(1); ++K) CellIndexSorted(I, K) = MaxI4; const int Cell1 = CellsOnEdge(IEdge, 0); const int Cell2 = CellsOnEdge(IEdge, 1); diff --git a/components/omega/test/ocn/HorzOperatorsTest.cpp b/components/omega/test/ocn/HorzOperatorsTest.cpp index 6f1be1079851..b056ab177f4b 100644 --- a/components/omega/test/ocn/HorzOperatorsTest.cpp +++ b/components/omega/test/ocn/HorzOperatorsTest.cpp @@ -479,7 +479,7 @@ int testsecondderivativeoncellDeterminePlanerPatchGeometry(Real RTol) { cellsOnEdge[e].push_back(c); } } - for (int e = 0; e < cellsOnEdge.size(); ++e) { + for (unsigned e = 0; e < cellsOnEdge.size(); ++e) { if (1 < cellsOnEdge[e].size()) dcEdge[e] = CtoC; } @@ -492,20 +492,20 @@ int testsecondderivativeoncellDeterminePlanerPatchGeometry(Real RTol) { Array2DI4 CellsOnEdge("CellsOnEdge", cellsOnEdge.size(), 2); auto CellsOnEdgeH = createHostMirrorCopy(CellsOnEdge); - for (int e = 0; e < cellsOnEdge.size(); ++e) - for (int i = 0; i < cellsOnEdge[e].size(); ++i) + for (unsigned e = 0; e < cellsOnEdge.size(); ++e) + for (unsigned i = 0; i < cellsOnEdge[e].size(); ++i) CellsOnEdgeH(e, i) = cellsOnEdge[e][i]; OMEGA::deepCopy(CellsOnEdge, CellsOnEdgeH); Array1DReal AngleEdge("AndleEdge", angleEdge.size()); auto AngleEdgeH = createHostMirrorCopy(AngleEdge); - for (int e = 0; e < angleEdge.size(); ++e) + for (unsigned e = 0; e < angleEdge.size(); ++e) AngleEdgeH(e) = angleEdge[e]; OMEGA::deepCopy(AngleEdge, AngleEdgeH); Array1DReal DcEdge("DcEdge", dcEdge.size()); auto DcEdgeH = createHostMirrorCopy(DcEdge); - for (int e = 0; e < dcEdge.size(); ++e) + for (unsigned e = 0; e < dcEdge.size(); ++e) DcEdgeH(e) = dcEdge[e]; OMEGA::deepCopy(DcEdge, DcEdgeH); @@ -711,7 +711,7 @@ int testsecondderivativeoncellDetermineSphericalPatchGeometry(Real RTol) { cellsOnEdge[e].push_back(c); } } - for (int e = 0; e < cellsOnEdge.size(); ++e) { + for (unsigned e = 0; e < cellsOnEdge.size(); ++e) { if (1 < cellsOnEdge[e].size()) dcEdge[e] = CtoC; } @@ -794,20 +794,20 @@ int testsecondderivativeoncellDetermineSphericalPatchGeometry(Real RTol) { Array2DI4 CellsOnEdge("CellsOnEdge", cellsOnEdge.size(), 2); auto CellsOnEdgeH = createHostMirrorCopy(CellsOnEdge); - for (int e = 0; e < cellsOnEdge.size(); ++e) - for (int i = 0; i < cellsOnEdge[e].size(); ++i) + for (unsigned e = 0; e < cellsOnEdge.size(); ++e) + for (unsigned i = 0; i < cellsOnEdge[e].size(); ++i) CellsOnEdgeH(e, i) = cellsOnEdge[e][i]; OMEGA::deepCopy(CellsOnEdge, CellsOnEdgeH); Array1DReal AngleEdge("AndleEdge", angleEdge.size()); auto AngleEdgeH = createHostMirrorCopy(AngleEdge); - for (int e = 0; e < angleEdge.size(); ++e) + for (unsigned e = 0; e < angleEdge.size(); ++e) AngleEdgeH(e) = angleEdge[e]; OMEGA::deepCopy(AngleEdge, AngleEdgeH); Array1DReal DcEdge("DcEdge", dcEdge.size()); auto DcEdgeH = createHostMirrorCopy(DcEdge); - for (int e = 0; e < dcEdge.size(); ++e) + for (unsigned e = 0; e < dcEdge.size(); ++e) DcEdgeH(e) = dcEdge[e]; OMEGA::deepCopy(DcEdge, DcEdgeH); From 24358e66f30d2620e1422c63ce2d18a315c0a2dc Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 9 Dec 2025 08:59:01 -0800 Subject: [PATCH 024/152] Fix Kokkos View host/device incompatibility errors. --- components/omega/test/ocn/HorzOperatorsTest.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/components/omega/test/ocn/HorzOperatorsTest.cpp b/components/omega/test/ocn/HorzOperatorsTest.cpp index b056ab177f4b..eb8b4f44b624 100644 --- a/components/omega/test/ocn/HorzOperatorsTest.cpp +++ b/components/omega/test/ocn/HorzOperatorsTest.cpp @@ -604,7 +604,10 @@ int testsecondderivativeoncellLeastSquaresFit(Real RTol) { OMEGA::deepCopy(XP, XPH); OMEGA::deepCopy(YP, YPH); Array2DReal B("B", MaxMaxEdges, MaxMaxEdges); - SecondDerivativeOnCellTest::Test(XP, YP, NEdges, B); + Kokkos::parallel_for( + 1, KOKKOS_LAMBDA(int /* dummy */) { + SecondDerivativeOnCellTest::Test(XP, YP, NEdges, B); + }); // From a Mathematica script. const Real c0 = 0.0412393049421; @@ -630,14 +633,14 @@ int testsecondderivativeoncellLeastSquaresFit(Real RTol) { Err++; LOG_ERROR("{}: FAIL, expected {}, got {}", "testsecondderivativeoncellLeastSquaresFit", m, - B(i, j)); + BH(i, j)); } } else { - if (!isApprox(B(i, j), m, RTol)) { + if (!isApprox(BH(i, j), m, RTol)) { Err++; LOG_ERROR("{}: FAIL, expected {}, got {}", "testsecondderivativeoncellLeastSquaresFit", m, - B(i, j)); + BH(i, j)); } } } @@ -832,9 +835,9 @@ int testsecondderivativeoncellDetermineSphericalPatchGeometry(Real RTol) { Array1DReal YP("YP", NEdges); Array1DReal Angle2D("Angle2D", NEdges); Array1DI4 CellList("CellList", NEdges + 1); + auto CellListH = createHostMirrorCopy(CellList); { const I4 List[NEdges + 1] = {3, 0, 2, 5, 6, 4, 1}; - auto CellListH = createHostMirrorCopy(CellList); for (int I = 0; I < NEdges + 1; ++I) CellListH[I] = List[I]; OMEGA::deepCopy(CellList, CellListH); @@ -856,7 +859,7 @@ int testsecondderivativeoncellDetermineSphericalPatchGeometry(Real RTol) { T[0] = T[1] = 0; for (int J = 0; J < 6; ++J) for (int K = 0; K < 2; ++K) - T[K] += X[E[CellList[i + 1]][J]][K]; + T[K] += X[E[CellListH[i + 1]][J]][K]; T[0] /= 6; T[1] /= 6; From 530f89f71476fbbb9c85d0523b9a5d33931e6717 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 9 Dec 2025 12:25:02 -0800 Subject: [PATCH 025/152] Fix up use of VCoord after rebase from develop. --- components/omega/src/ocn/Tendencies.cpp | 1 + components/omega/src/ocn/TendencyTerms.cpp | 5 +++-- components/omega/src/ocn/TendencyTerms.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 84d001db8bb9..c22815c65810 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -237,6 +237,7 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies BottomDrag(Mesh, VCoord), TracerHorzAdv(Mesh, VCoord), TracerDiffusion(Mesh, VCoord), TracerHyperDiff(Mesh, VCoord), TracerHighOrderHorzAdv(Mesh), + TracerHighOrderHorzAdv(Mesh, VCoord), CustomThicknessTend(InCustomThicknessTend), CustomVelocityTend(InCustomVelocityTend) { diff --git a/components/omega/src/ocn/TendencyTerms.cpp b/components/omega/src/ocn/TendencyTerms.cpp index c7ad81c4024c..f1767cd7bc33 100644 --- a/components/omega/src/ocn/TendencyTerms.cpp +++ b/components/omega/src/ocn/TendencyTerms.cpp @@ -81,7 +81,8 @@ TracerHorzAdvOnCell::TracerHorzAdvOnCell(const HorzMesh *Mesh, MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} -TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh) +TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh, + const VertCoord *VCoord) : HorzontalMesh(Mesh), NAdvCellsForEdge("NumberOfCellsContribToAdvectionAtEdge", Mesh->NEdgesOwned), @@ -93,7 +94,7 @@ TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh) AdvCoefs3rd("CommonAdvectionCoeffsForHighOrder", Mesh->MaxEdges2 + 2, Mesh->NEdgesAll), HighOrderFlxHorz("HigherOrderHorizontalFlux", Tracers::getNumTracers(), - Mesh->NEdgesAll, Mesh->NVertLayers / VecLength), + Mesh->NEdgesAll, VCoord->NVertLayers / VecLength), NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), CellsOnEdge(Mesh->CellsOnEdge), EdgeSignOnCell(Mesh->EdgeSignOnCell), DvEdge(Mesh->DvEdge), AreaCell(Mesh->AreaCell) { diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index 5ef14a7b91d0..ec62e34afb78 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -422,7 +422,7 @@ class TracerHighOrderHorzAdvOnCell { // coefficient for blending high-order terms Real coef3rdOrder = 0.25; - TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh); + TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); void init(); KOKKOS_FUNCTION void operator()(const I4 L, const I4 IEdge, const I4 KChunk, From 1312bac19842f548a3666ae67b1e70f0b89a51dc Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 9 Dec 2025 13:44:42 -0700 Subject: [PATCH 026/152] Clang format --- components/omega/src/ocn/TendencyTerms.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/omega/src/ocn/TendencyTerms.cpp b/components/omega/src/ocn/TendencyTerms.cpp index f1767cd7bc33..26a2daf410f3 100644 --- a/components/omega/src/ocn/TendencyTerms.cpp +++ b/components/omega/src/ocn/TendencyTerms.cpp @@ -81,8 +81,8 @@ TracerHorzAdvOnCell::TracerHorzAdvOnCell(const HorzMesh *Mesh, MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} -TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh, - const VertCoord *VCoord) +TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell( + const HorzMesh *Mesh, const VertCoord *VCoord) : HorzontalMesh(Mesh), NAdvCellsForEdge("NumberOfCellsContribToAdvectionAtEdge", Mesh->NEdgesOwned), From f4081c21c26a117099bf61d97194e14f57e52a67 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Sat, 3 Jan 2026 08:52:01 -0700 Subject: [PATCH 027/152] Clang Format. --- components/omega/src/ocn/Tendencies.cpp | 3 +-- components/omega/src/ocn/TendencyTerms.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index c22815c65810..0d05708d3916 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -236,8 +236,7 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies VelocityHyperDiff(Mesh, VCoord), WindForcing(Mesh, VCoord), BottomDrag(Mesh, VCoord), TracerHorzAdv(Mesh, VCoord), TracerDiffusion(Mesh, VCoord), TracerHyperDiff(Mesh, VCoord), - TracerHighOrderHorzAdv(Mesh), - TracerHighOrderHorzAdv(Mesh, VCoord), + TracerHighOrderHorzAdv(Mesh), TracerHighOrderHorzAdv(Mesh, VCoord), CustomThicknessTend(InCustomThicknessTend), CustomVelocityTend(InCustomVelocityTend) { diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index ec62e34afb78..782b62c853e6 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -380,7 +380,7 @@ class TracerHorzAdvOnCell { const I4 KStartCell = chunkStart(KChunk, MinLayerCell(ICell)); const I4 KLenCell = chunkLength(KChunk, KStartCell, MaxLayerCell(ICell)); const I4 KEndCell = KStartCell + KLenCell - 1; - const Real InvAreaCell = 1._Real / AreaCell(ICell); + const Real InvAreaCell = 1._Real / AreaCell(ICell); Real HAdvTmp[VecLength] = {0}; for (int J = 0; J < NEdgesOnCell(ICell); ++J) { const I4 JEdge = EdgesOnCell(ICell, J); From c9870a55c741aa24386b376efa5cffbee32948b5 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Sat, 3 Jan 2026 10:59:05 -0700 Subject: [PATCH 028/152] Fix compiler errors after merge from develop. --- components/omega/src/ocn/Tendencies.cpp | 37 ++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 0d05708d3916..b7bc76de1ba6 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -236,7 +236,7 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies VelocityHyperDiff(Mesh, VCoord), WindForcing(Mesh, VCoord), BottomDrag(Mesh, VCoord), TracerHorzAdv(Mesh, VCoord), TracerDiffusion(Mesh, VCoord), TracerHyperDiff(Mesh, VCoord), - TracerHighOrderHorzAdv(Mesh), TracerHighOrderHorzAdv(Mesh, VCoord), + TracerHighOrderHorzAdv(Mesh, VCoord), CustomThicknessTend(InCustomThicknessTend), CustomVelocityTend(InCustomVelocityTend) { @@ -514,6 +514,8 @@ void Tendencies::computeTracerTendenciesOnly( OMEGA_SCOPE(LocTracerHyperDiff, TracerHyperDiff); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); + OMEGA_SCOPE(MinLayerEdgeBot, VCoord->MinLayerEdgeBot); + OMEGA_SCOPE(MaxLayerEdgeTop, VCoord->MaxLayerEdgeTop); Pacer::start("Tend:computeTracerTendenciesOnly", 1); @@ -553,17 +555,32 @@ void Tendencies::computeTracerTendenciesOnly( Pacer::stop("Tend:tracerHorzAdv", 2); } if (LocTracerHighOrderHorzAdv.Enabled) { - parallelFor( - {NTracers, NEdgesAll, NChunks}, - KOKKOS_LAMBDA(int L, int IEdge, int KChunk) { - LocTracerHighOrderHorzAdv(L, IEdge, KChunk, TracerArray, - FluxLayerThickEdge, NormalVelEdge); + Pacer::start("Tend:TracerHighOrderHorzAdv", 2); + parallelForOuter( + {NTracers, Mesh->NEdgesAll}, + KOKKOS_LAMBDA(int L, 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) { + LocTracerHighOrderHorzAdv(L, IEdge, KChunk, TracerArray, + FluxLayerThickEdge, + NormalVelEdge); + }); }); - parallelFor( - {NTracers, NCellsAll, NChunks}, - KOKKOS_LAMBDA(int L, int ICell, int KChunk) { - LocTracerHighOrderHorzAdv(LocTracerTend, L, ICell, KChunk); + parallelForOuter( + {NTracers, Mesh->NCellsAll}, + KOKKOS_LAMBDA(int L, int ICell, const TeamMember &Team) { + const int KMin = MinLayerCell(ICell); + const int KMax = MaxLayerCell(ICell); + const int KRange = vertRangeChunked(KMin, KMax); + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + LocTracerHighOrderHorzAdv(LocTracerTend, L, ICell, KChunk); + }); }); + Pacer::stop("Tend:LocTracerHighOrderHorzAdv", 2); } // compute tracer diffusion From afe06521c7657861206889be31c9682fc84eb262 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Thu, 15 Jan 2026 16:01:47 -0800 Subject: [PATCH 029/152] Forgot to zero out tendecies before summing into values. --- components/omega/src/ocn/Tendencies.cpp | 2 +- components/omega/src/ocn/TendencyTerms.h | 3 +++ configs/Default.yml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index b7bc76de1ba6..78171a6f6a0b 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -580,7 +580,7 @@ void Tendencies::computeTracerTendenciesOnly( LocTracerHighOrderHorzAdv(LocTracerTend, L, ICell, KChunk); }); }); - Pacer::stop("Tend:LocTracerHighOrderHorzAdv", 2); + Pacer::stop("Tend:TracerHighOrderHorzAdv", 2); } // compute tracer diffusion diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index 782b62c853e6..c800442131e0 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -468,6 +468,9 @@ class TracerHighOrderHorzAdvOnCell { const I4 KStart = KChunk * VecLength; const I4 KEnd = KStart + VecLength; const Real InvAreaCell = 1._Real / AreaCell(ICell); + for (int K = KStart; K < KEnd; ++K) + Tend(L, ICell, K) = 0; + for (int I = 0; I < NEdgesOnCell(ICell); ++I) { const I4 IEdge = EdgesOnCell(ICell, I); for (int K = KStart; K < KEnd; ++K) { diff --git a/configs/Default.yml b/configs/Default.yml index 546859849e97..9e6cd313a519 100644 --- a/configs/Default.yml +++ b/configs/Default.yml @@ -26,7 +26,7 @@ Omega: VelHyperDiffTendencyEnable: true ViscDel4: 1.2e11 DivFactor: 1.0 - TracerHorzAdvTendencyEnable: false + TracerHorzAdvTendencyEnable: true TracerHorzAdvTendencyOrder: 2 TracerDiffTendencyEnable: true EddyDiff2: 10.0 From 27517c993149c49f2f5449fc8fd729bacb930d4b Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 27 Jan 2026 11:27:42 -0600 Subject: [PATCH 030/152] Fix for parallel: NCellsOwned -> NCellsAll/ --- components/omega/src/ocn/HorzOperators.h | 4 ++-- components/omega/src/ocn/TendencyTerms.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/omega/src/ocn/HorzOperators.h b/components/omega/src/ocn/HorzOperators.h index 081e91a84645..1ceec927f4d4 100644 --- a/components/omega/src/ocn/HorzOperators.h +++ b/components/omega/src/ocn/HorzOperators.h @@ -207,7 +207,7 @@ class SecondDerivativeOnCell { SecondDerivativeOnCell(HorzMesh const *Mesh); KOKKOS_FUNCTION ~SecondDerivativeOnCell() {} KOKKOS_FUNCTION void operator()(const Array3DReal &DerivTwo, - int ICell) const { + const int ICell) const { const int NEdges = NEdgesOnCell(ICell); if (MaxMaxEdges < NEdges) printf("Error: Number of edges on cell:%d exceeds maximum " @@ -255,7 +255,7 @@ class SecondDerivativeOnCell { const Real xx = x * x; const Real xy = x * y; const Real yy = y * y; - for (int J = 0; J <= NEdges; ++J) + for (int J = 0; J <= NEdges; ++J) DerivTwo(J, Ind, IEdge) = 2._Real * xx * B(3, J) + 2._Real * xy * B(4, J) + 2._Real * yy * B(5, J); diff --git a/components/omega/src/ocn/TendencyTerms.cpp b/components/omega/src/ocn/TendencyTerms.cpp index 26a2daf410f3..cbd9f2a0fd8e 100644 --- a/components/omega/src/ocn/TendencyTerms.cpp +++ b/components/omega/src/ocn/TendencyTerms.cpp @@ -125,14 +125,14 @@ void TracerHighOrderHorzAdvOnCell::init() { const HorzMesh *Mesh = this->HorzontalMesh; const auto MaxEdges2 = Mesh->MaxEdges2; const auto NEdgesAll = Mesh->NEdgesAll; - const auto NCellsOwned = Mesh->NCellsOwned; + const auto NCellsAll = Mesh->NCellsAll; const auto NEdgesOwned = Mesh->NEdgesOwned; // Allocate Kokkos arrays in member data SecondDerivativeOnCell secondDerivativeOnCell(Mesh); Array3DReal DerivTwo("DerivTwo", MaxEdges2 + 2, 2, NEdgesAll); parallelFor( - {NCellsOwned}, + {NCellsAll}, KOKKOS_LAMBDA(int ICell) { secondDerivativeOnCell(DerivTwo, ICell); }); // Compute masks and coefficients Kokkos::fence(); From 565206d9d773f2083acdaabb2ca488e0ce404dca Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Tue, 10 Feb 2026 09:05:19 -0700 Subject: [PATCH 031/152] Fix conservation issue in high order advection --- components/omega/src/ocn/HorzOperators.cpp | 5 ++--- components/omega/src/ocn/HorzOperators.h | 5 +++-- components/omega/src/ocn/TendencyTerms.cpp | 16 +++++++--------- .../src/timeStepping/RungeKutta2Stepper.cpp | 3 +++ .../src/timeStepping/RungeKutta4Stepper.cpp | 10 ++++++---- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/components/omega/src/ocn/HorzOperators.cpp b/components/omega/src/ocn/HorzOperators.cpp index c6ce926f4bca..e182dd4f77ed 100644 --- a/components/omega/src/ocn/HorzOperators.cpp +++ b/components/omega/src/ocn/HorzOperators.cpp @@ -62,9 +62,8 @@ MasksAndCoefficients::MasksAndCoefficients( // Mesh->NEdgesOwned, Mesh->NEdgesAll+1), NAdvCellsForEdge(NAdvCellsForEdge), AdvCellsForEdge(AdvCellsForEdge), NEdgesOnEdge(Mesh->NEdgesOnEdge), NEdgesOnCell(Mesh->NEdgesOnCell), - CellIndx("CellIndx", Mesh->NEdgesOwned, Mesh->MaxEdges2 + 2), - CellIndxSorted("CellIndxSorted", Mesh->NEdgesOwned, 2, - Mesh->MaxEdges2 + 2), + CellIndx("CellIndx", Mesh->NEdgesAll, Mesh->MaxEdges2 + 2), + CellIndxSorted("CellIndxSorted", Mesh->NEdgesAll, 2, Mesh->MaxEdges2 + 2), CellID(Mesh->CellID), AdvMaskHighOrder(AdvMaskHighOrder), EdgesOnEdge(Mesh->EdgesOnEdge), CellsOnCell(Mesh->CellsOnCell), CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), diff --git a/components/omega/src/ocn/HorzOperators.h b/components/omega/src/ocn/HorzOperators.h index 1ceec927f4d4..6838ad071146 100644 --- a/components/omega/src/ocn/HorzOperators.h +++ b/components/omega/src/ocn/HorzOperators.h @@ -2,6 +2,7 @@ #define OMEGA_HORZOPERATORS_H #include "DataTypes.h" +#include "GlobalConstants.h" #include "HorzMesh.h" #include "HorzUtil.h" @@ -255,7 +256,7 @@ class SecondDerivativeOnCell { const Real xx = x * x; const Real xy = x * y; const Real yy = y * y; - for (int J = 0; J <= NEdges; ++J) + for (int J = 0; J <= NEdges; ++J) DerivTwo(J, Ind, IEdge) = 2._Real * xx * B(3, J) + 2._Real * xy * B(4, J) + 2._Real * yy * B(5, J); @@ -341,7 +342,7 @@ class SecondDerivativeOnCell { const Array1DReal ZVertex, const Array1DI4 CellList, Array1DReal XP, Array1DReal YP, Array1DReal Angle2D, Real &ThetaAbs) { const Real length_scale = 1._Real; - const Real sphereRadius = distance(XCell(0), YCell(0), ZCell(0)); + const Real sphereRadius = REarth; Real XC[MaxMaxEdges] = {}; Real YC[MaxMaxEdges] = {}; Real ZC[MaxMaxEdges] = {}; diff --git a/components/omega/src/ocn/TendencyTerms.cpp b/components/omega/src/ocn/TendencyTerms.cpp index cbd9f2a0fd8e..d9503401f5e7 100644 --- a/components/omega/src/ocn/TendencyTerms.cpp +++ b/components/omega/src/ocn/TendencyTerms.cpp @@ -85,8 +85,8 @@ TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell( const HorzMesh *Mesh, const VertCoord *VCoord) : HorzontalMesh(Mesh), NAdvCellsForEdge("NumberOfCellsContribToAdvectionAtEdge", - Mesh->NEdgesOwned), - AdvCellsForEdge("IndexOfCellsContributingToAdvection", Mesh->NEdgesOwned, + Mesh->NEdgesAll), + AdvCellsForEdge("IndexOfCellsContributingToAdvection", Mesh->NEdgesAll, Mesh->MaxEdges2 + 2), AdvMaskHighOrder("MaskForHighOrderAdvectionTerms", Mesh->NEdgesAll), AdvCoefs("CommonAdvectionCoefficients", Mesh->MaxEdges2 + 2, @@ -122,11 +122,10 @@ TracerHyperDiffOnCell::TracerHyperDiffOnCell(const HorzMesh *Mesh, MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} void TracerHighOrderHorzAdvOnCell::init() { - const HorzMesh *Mesh = this->HorzontalMesh; - const auto MaxEdges2 = Mesh->MaxEdges2; - const auto NEdgesAll = Mesh->NEdgesAll; - const auto NCellsAll = Mesh->NCellsAll; - const auto NEdgesOwned = Mesh->NEdgesOwned; + const HorzMesh *Mesh = this->HorzontalMesh; + const auto MaxEdges2 = Mesh->MaxEdges2; + const auto NEdgesAll = Mesh->NEdgesAll; + const auto NCellsAll = Mesh->NCellsAll; // Allocate Kokkos arrays in member data SecondDerivativeOnCell secondDerivativeOnCell(Mesh); @@ -141,8 +140,7 @@ void TracerHighOrderHorzAdvOnCell::init() { AdvCoefs, AdvCoefs3rd); Kokkos::fence(); parallelFor( - {NEdgesOwned}, - KOKKOS_LAMBDA(int IEdge) { masksAndCoefficients(IEdge); }); + {NEdgesAll}, KOKKOS_LAMBDA(int IEdge) { masksAndCoefficients(IEdge); }); Kokkos::fence(); } } // end namespace OMEGA diff --git a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp index 0cabb62b32be..f4b697529db0 100644 --- a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp @@ -44,6 +44,9 @@ void RungeKutta2Stepper::doStep(OceanState *State, // model state updateTracersByTend(NextTracerArray, CurTracerArray, State, NextLevel, State, CurLevel, 0.5 * TimeStep); + // We need to exchange tracer halos at every stage for high-order advection + MeshHalo->exchangeFullArrayHalo(NextTracerArray, OnCell); + // R_q^{n+0.5} = RHS_q(u^{n+0.5}, h^{n+0.5}, phi^{n+0.5}, t^{n+0.5}) Tend->computeAllTendencies(State, AuxState, NextTracerArray, NextLevel, NextLevel, SimTime + 0.5 * TimeStep); diff --git a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp index 24b3c1c251be..1b882e7631b9 100644 --- a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp @@ -99,14 +99,16 @@ void RungeKutta4Stepper::doStep(OceanState *State, // model state updateTracersByTend(ProvisTracers, CurTracerArray, ProvisState, CurLevel, State, CurLevel, RKA[Stage] * TimeStep); + Pacer::timingBarrier("RK4:haloExchProvisBarrier", 3, Comm); + Pacer::start("RK4:haloExchProvis", 3); // TODO(mwarusz) this depends on halo width actually if (Stage == 2) { - Pacer::timingBarrier("RK4:haloExchProvisBarrier", 3, Comm); - Pacer::start("RK4:haloExchProvis", 3); ProvisState->exchangeHalo(CurLevel); - MeshHalo->exchangeFullArrayHalo(ProvisTracers, OnCell); - Pacer::stop("RK4:haloExchProvis", 3); } + // We need to exchange tracer halos at every stage for high-order + // advection + MeshHalo->exchangeFullArrayHalo(ProvisTracers, OnCell); + Pacer::stop("RK4:haloExchProvis", 3); Tend->computeAllTendencies(ProvisState, AuxState, ProvisTracers, CurLevel, CurLevel, StageTime); From ad8f892739ed4e5106aa2607b30941d133ce1ebd Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Thu, 19 Feb 2026 12:16:20 -0700 Subject: [PATCH 032/152] Clang format. --- components/omega/src/ocn/TendencyTerms.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index c800442131e0..879e8d64e190 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -469,7 +469,7 @@ class TracerHighOrderHorzAdvOnCell { const I4 KEnd = KStart + VecLength; const Real InvAreaCell = 1._Real / AreaCell(ICell); for (int K = KStart; K < KEnd; ++K) - Tend(L, ICell, K) = 0; + Tend(L, ICell, K) = 0; for (int I = 0; I < NEdgesOnCell(ICell); ++I) { const I4 IEdge = EdgesOnCell(ICell, I); From a776bc054e263186e31261e45a9ae4f66c94bf35 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Fri, 20 Feb 2026 13:17:26 -0800 Subject: [PATCH 033/152] Fix for unit test. Use actual earth radius: REarth. --- components/omega/test/ocn/HorzOperatorsTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/test/ocn/HorzOperatorsTest.cpp b/components/omega/test/ocn/HorzOperatorsTest.cpp index eb8b4f44b624..e4cfde75ac5e 100644 --- a/components/omega/test/ocn/HorzOperatorsTest.cpp +++ b/components/omega/test/ocn/HorzOperatorsTest.cpp @@ -674,7 +674,7 @@ int testsecondderivativeoncellDetermineSphericalPatchGeometry(Real RTol) { {20, 21, 25, 29, 28, 24}, {23, 24, 28, 32, 31, 27}, {26, 27, 31, 35, 34, 30}, {28, 29, 33, 37, 36, 32}}; const int CtoC = 1039230; - const int R = 100 * CtoC; + const double R = OMEGA::REarth; const R8 Pii = 3.141592653589793_Real; // Project coordinates to sphere. From 8b39eb87859431da8022b2a1cf706f0319329712 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Fri, 20 Feb 2026 17:40:52 -0600 Subject: [PATCH 034/152] Fix setting of TracerHorzAdvTendencyEnable flag based on code review. --- components/omega/src/ocn/Tendencies.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 78171a6f6a0b..7d3279312a41 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -165,16 +165,20 @@ void Tendencies::readTendConfig( Err += TendConfig->get("DivFactor", this->VelocityHyperDiff.DivFactor); CHECK_ERROR_ABORT(Err, "Tendencies: DivFactor not found in TendConfig"); } - - if (TendConfig->existsVar("TracerHorzAdvTendencyEnable")) { - I4 Order = 1; - if (TendConfig->existsVar("TracerHorzAdvTendencyOrder")) - TendConfig->get("TracerHorzAdvTendencyOrder", Order); - if (Order == 1) - this->TracerHorzAdv.Enabled = true; - else if (Order == 2) + Err += TendConfig->get("TracerHorzAdvTendencyEnable", + this->TracerHorzAdv.Enabled); + CHECK_ERROR_ABORT(Err, + "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); + if (this->TracerHorzAdv.Enabled) { + I4 Order = 0; + Err += TendConfig->get("TracerHorzAdvTendencyOrder", Order); + CHECK_ERROR_ABORT(Err, + "Tendencies: TracerHorzAdvTendencyOrder not found in TendConfig"); + if ( Order == 2 ) { this->TracerHighOrderHorzAdv.Enabled = true; - else { + this->TracerHorzAdv.Enabled = false; + } + if ( !(Order == 1 or Order == 2) ) { const std::string msg = "TracerHorzAdvTendencyOrder: Only values are 1 and 2, found " + std::to_string(Order); From 8f19d724885ecca4b540c0e971648b49280fc017 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Sat, 21 Feb 2026 06:26:23 -0700 Subject: [PATCH 035/152] Clang format. --- components/omega/src/ocn/Tendencies.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 7d3279312a41..1edd89172c17 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -167,18 +167,19 @@ void Tendencies::readTendConfig( } Err += TendConfig->get("TracerHorzAdvTendencyEnable", this->TracerHorzAdv.Enabled); - CHECK_ERROR_ABORT(Err, - "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); + CHECK_ERROR_ABORT( + Err, "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); if (this->TracerHorzAdv.Enabled) { I4 Order = 0; Err += TendConfig->get("TracerHorzAdvTendencyOrder", Order); - CHECK_ERROR_ABORT(Err, + CHECK_ERROR_ABORT( + Err, "Tendencies: TracerHorzAdvTendencyOrder not found in TendConfig"); - if ( Order == 2 ) { + if (Order == 2) { this->TracerHighOrderHorzAdv.Enabled = true; - this->TracerHorzAdv.Enabled = false; + this->TracerHorzAdv.Enabled = false; } - if ( !(Order == 1 or Order == 2) ) { + if (!(Order == 1 or Order == 2)) { const std::string msg = "TracerHorzAdvTendencyOrder: Only values are 1 and 2, found " + std::to_string(Order); From 9c1830bd2a5820dcb68ea9f9178cfe1a2a7f71a1 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Tue, 24 Feb 2026 13:39:22 -0700 Subject: [PATCH 036/152] Change parameter name from TracerHorzAdvTendencyOrder to HorzTracerFluxOrder for consistentsy. --- components/omega/configs/Default.yml | 2 +- components/omega/doc/userGuide/TendencyTerms.md | 8 ++++---- components/omega/src/ocn/Tendencies.cpp | 7 +++---- configs/Default.yml | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 7d3ce7525308..f87cea63b360 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -44,7 +44,7 @@ Omega: BottomDragTendencyEnable: false BottomDragCoeff: 0.0 TracerHorzAdvTendencyEnable: true - TracerHorzAdvTendencyOrder: 1 + HorzTracerFluxOrder: 1 TracerDiffTendencyEnable: true EddyDiff2: 10.0 TracerHyperDiffTendencyEnable: true diff --git a/components/omega/doc/userGuide/TendencyTerms.md b/components/omega/doc/userGuide/TendencyTerms.md index 369d5673fd2d..5757e98ebe0f 100644 --- a/components/omega/doc/userGuide/TendencyTerms.md +++ b/components/omega/doc/userGuide/TendencyTerms.md @@ -44,9 +44,9 @@ 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 -| | TracerHorzAdvTendencyOrder | 1 for standard linear advection +| | HorzTracerFluxOrder | 1 for standard linear advection | TracerHighOrderHorzAdvOnCell | TracerHorzAdvTendencyEnable | enable/disable term -| | TracerHorzAdvTendencyOrder | 2 for second order advection algorithm +| | HorzTracerFluxOrder | 2 for second order advection algorithm | TracerDiffOnCell | TracerDiffTendencyEnable | enable/disable term | | EddyDiff2 | horizontal diffusion coefficient | TracerHyperDiffOnCell | TracerHyperDiffTendencyEnable | enable/disable term @@ -91,13 +91,13 @@ $$ $$ 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 TracerHorzAdvTendencyOrder is 1. +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 TracerHorzAdvTendencyOrder set to 2, a quadratic approximation of $\psi$ is used, +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, $$ diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 1edd89172c17..e49a71209cc0 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -171,17 +171,16 @@ void Tendencies::readTendConfig( Err, "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); if (this->TracerHorzAdv.Enabled) { I4 Order = 0; - Err += TendConfig->get("TracerHorzAdvTendencyOrder", Order); + Err += TendConfig->get("HorzTracerFluxOrder", Order); CHECK_ERROR_ABORT( - Err, - "Tendencies: TracerHorzAdvTendencyOrder not found in TendConfig"); + Err, "Tendencies: HorzTracerFluxOrder not found in TendConfig"); if (Order == 2) { this->TracerHighOrderHorzAdv.Enabled = true; this->TracerHorzAdv.Enabled = false; } if (!(Order == 1 or Order == 2)) { const std::string msg = - "TracerHorzAdvTendencyOrder: Only values are 1 and 2, found " + + "HorzTracerFluxOrder: Only values are 1 and 2, found " + std::to_string(Order); ABORT_ERROR(msg); } diff --git a/configs/Default.yml b/configs/Default.yml index 9e6cd313a519..75befd8af590 100644 --- a/configs/Default.yml +++ b/configs/Default.yml @@ -27,7 +27,7 @@ Omega: ViscDel4: 1.2e11 DivFactor: 1.0 TracerHorzAdvTendencyEnable: true - TracerHorzAdvTendencyOrder: 2 + HorzTracerFluxOrder: 2 TracerDiffTendencyEnable: true EddyDiff2: 10.0 TracerHyperDiffTendencyEnable: true From 06fab60690d3f7682aceacfa97a003d79aa75182 Mon Sep 17 00:00:00 2001 From: James Overfelt Date: Wed, 25 Feb 2026 06:44:52 -0700 Subject: [PATCH 037/152] =?UTF-8?q?Changing=20the=20variable=20names=20to?= =?UTF-8?q?=20PascalCase=20to=20follow=20Omega=E2=80=99s=20naming=20conven?= =?UTF-8?q?tions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/omega/src/ocn/TendencyTerms.h | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index 879e8d64e190..b8be20a1d41e 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -420,11 +420,9 @@ class TracerHighOrderHorzAdvOnCell { public: bool Enabled = false; // coefficient for blending high-order terms - Real coef3rdOrder = 0.25; - + Real Coef3rdOrder = 0.25; TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); void init(); - KOKKOS_FUNCTION void operator()(const I4 L, const I4 IEdge, const I4 KChunk, const Array3DReal &TracerCell, const Array2DReal &FluxLayerThickEdge, @@ -437,27 +435,27 @@ class TracerHighOrderHorzAdvOnCell { for (int I = 0; I < NAdvCellsForEdge(IEdge); ++I) { const I4 ICell = AdvCellsForEdge(IEdge, I); for (int K = KStart; K < KEnd; ++K) { - const Real normalThicknessFlux = + const Real NormalThicknessFlux = FluxLayerThickEdge(IEdge, K) * NormVelEdge(IEdge, K); - const Real tracerWgt = + const Real TracerWgt = (AdvCoefs(I, IEdge) + - coef3rdOrder * std::copysign(1._Real, normalThicknessFlux) * + Coef3rdOrder * std::copysign(1._Real, NormalThicknessFlux) * AdvCoefs3rd(I, IEdge)) * - normalThicknessFlux; + NormalThicknessFlux; HighOrderFlxHorz(L, IEdge, K) += - tracerWgt * TracerCell(L, ICell, K); + TracerWgt * TracerCell(L, ICell, K); } } } else { for (int K = KStart; K < KEnd; ++K) { const I4 JCell0 = CellsOnEdge(IEdge, 0); const I4 JCell1 = CellsOnEdge(IEdge, 1); - const Real normalThicknessFlux = + const Real NormalThicknessFlux = FluxLayerThickEdge(IEdge, K) * NormVelEdge(IEdge, K); - const Real tracerWgt = - DvEdge(IEdge) * 0.5_Real * normalThicknessFlux; + const Real TracerWgt = + DvEdge(IEdge) * 0.5_Real * NormalThicknessFlux; HighOrderFlxHorz(L, IEdge, K) += - tracerWgt * + TracerWgt * (TracerCell(L, JCell1, K) + TracerCell(L, JCell0, K)); } } From 6a361f2be8c4de89e963c40a4afc3877d6c7bef7 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Wed, 25 Feb 2026 12:49:08 -0700 Subject: [PATCH 038/152] Add ForceLowOrder option to high-order tracer advection class --- components/omega/src/ocn/TendencyTerms.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index b8be20a1d41e..4c2a68eb7840 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -418,7 +418,8 @@ class TracerHorzAdvOnCell { // Tracer high order horizontal advection term class TracerHighOrderHorzAdvOnCell { public: - bool Enabled = false; + bool Enabled = false; + bool ForceLowOrder = false; // coefficient for blending high-order terms Real Coef3rdOrder = 0.25; TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); @@ -431,7 +432,7 @@ class TracerHighOrderHorzAdvOnCell { const I4 KEnd = KStart + VecLength; for (int K = KStart; K < KEnd; ++K) HighOrderFlxHorz(L, IEdge, K) = 0; - if (AdvMaskHighOrder(IEdge)) { + if (!ForceLowOrder && AdvMaskHighOrder(IEdge)) { for (int I = 0; I < NAdvCellsForEdge(IEdge); ++I) { const I4 ICell = AdvCellsForEdge(IEdge, I); for (int K = KStart; K < KEnd; ++K) { From faba8754c674ae0e9125c9c1dc0f26152b1514e7 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Wed, 25 Feb 2026 12:50:32 -0700 Subject: [PATCH 039/152] Use high-order tracer adv class in tendency terms test --- .../omega/test/ocn/TendencyTermsTest.cpp | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/components/omega/test/ocn/TendencyTermsTest.cpp b/components/omega/test/ocn/TendencyTermsTest.cpp index 4aac2a0bb1e2..f928d3ddbe48 100644 --- a/components/omega/test/ocn/TendencyTermsTest.cpp +++ b/components/omega/test/ocn/TendencyTermsTest.cpp @@ -20,6 +20,7 @@ #include "Decomp.h" #include "Dimension.h" #include "Error.h" +#include "Field.h" #include "GlobalConstants.h" #include "Halo.h" #include "HorzMesh.h" @@ -28,6 +29,8 @@ #include "OceanTestCommon.h" #include "OmegaKokkos.h" #include "Pacer.h" +#include "TimeStepper.h" +#include "Tracers.h" #include "VertCoord.h" #include "mpi.h" @@ -49,8 +52,8 @@ struct TestSetupPlane { 0.00134354611117262161}; ErrorMeasures ExpectedLaplaceErrors = {0.00113090174765822192, 0.00134324628763667899}; - ErrorMeasures ExpectedTrHAdvErrors = {0.00205864372747571571, - 0.00172418025417940784}; + ErrorMeasures ExpectedTrHAdvErrors = {0.0029211089892916243, + 0.0024583038518548855}; ErrorMeasures ExpectedTrDel2Errors = {0.00334357193650093847, 0.00290978146207349032}; ErrorMeasures ExpectedTrDel4Errors = {0.00508833446725232875, @@ -190,8 +193,8 @@ struct TestSetupSphere { 0.0015218320661105702}; ErrorMeasures ExpectedLaplaceErrors = {0.28193638497826856, 0.270546491554748}; - ErrorMeasures ExpectedTrHAdvErrors = {0.013227657020868148, - 0.0038723934782890863}; + ErrorMeasures ExpectedTrHAdvErrors = {0.013259410329645643, + 0.004094907022292395}; ErrorMeasures ExpectedTrDel2Errors = {0.04865718541236144, 0.005105510870642706}; ErrorMeasures ExpectedTrDel4Errors = {0.0008646345116716073, @@ -865,21 +868,34 @@ int testTracerHorzAdvOnCell(int NVertLayers, int NTracers, Real RTol) { }, NormalVelocity, EdgeComponent::Normal, Geom, Mesh); - Array3DReal HTrOnEdge("HTrOnEdge", NTracers, Mesh->NEdgesSize, NVertLayers); + Array3DReal TrCell("TrCell", NTracers, Mesh->NCellsSize, NVertLayers); + Array2DReal ThickEdge("ThickEdh", Mesh->NEdgesSize, NVertLayers); Err += setScalar( KOKKOS_LAMBDA(Real X, Real Y) { return -Setup.layerThick(X, Y); }, - HTrOnEdge, Geom, Mesh, OnEdge); + TrCell, Geom, Mesh, OnCell); + + Err += setScalar( + KOKKOS_LAMBDA(Real X, Real Y) { return 1; }, ThickEdge, Geom, Mesh, + OnEdge); // Compute numerical result Array3DReal NumTrFluxDiv("NumTrFluxDiv", NTracers, Mesh->NCellsOwned, NVertLayers); - TracerHorzAdvOnCell TrHorzAdvOnC(Mesh, VCoord); + TracerHighOrderHorzAdvOnCell TrHorzAdvOnC(Mesh, VCoord); + TrHorzAdvOnC.init(); + TrHorzAdvOnC.ForceLowOrder = true; + + parallelFor( + {NTracers, Mesh->NEdgesAll, NVertLayers}, + KOKKOS_LAMBDA(int L, int IEdge, int KLayer) { + TrHorzAdvOnC(L, IEdge, KLayer, TrCell, ThickEdge, NormalVelocity); + }); + parallelFor( {NTracers, Mesh->NCellsOwned, NVertLayers}, KOKKOS_LAMBDA(int L, int ICell, int KLayer) { - TrHorzAdvOnC(NumTrFluxDiv, L, ICell, KLayer, NormalVelocity, - HTrOnEdge); + TrHorzAdvOnC(NumTrFluxDiv, L, ICell, KLayer); }); ErrorMeasures TrHAdvErrors; @@ -1017,6 +1033,8 @@ void initTendTest(const std::string &MeshFile, int NVertLayers) { Config::Initialize(); Config::readAll("omega.yml"); + TimeStepper::init1(); + IO::init(DefComm); Decomp::init(MeshFile); @@ -1031,13 +1049,17 @@ void initTendTest(const std::string &MeshFile, int NVertLayers) { // initialize vertical coordinate, do not read stream and use local // NVertLayers value VertCoord::init(false, NVertLayers); + Tracers::init(); } // end initTendTest void finalizeTendTest() { + Tracers::clear(); HorzMesh::clear(); VertCoord::clear(); + Field::clear(); Dimension::clear(); + TimeStepper::clear(); Halo::clear(); Decomp::clear(); MachEnv::removeAll(); @@ -1050,7 +1072,7 @@ int tendencyTermsTest(const std::string &MeshFile = DefaultMeshFile) { initTendTest(MeshFile, NVertLayers); - int NTracers = 3; + int NTracers = Tracers::getNumTracers(); const Real RTol = sizeof(Real) == 4 ? 2e-2 : 1e-5; From 4b869b7943b2c3c74d5a1d3301de9b04ced6bf4a Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Wed, 25 Feb 2026 13:42:34 -0700 Subject: [PATCH 040/152] Only use high-order tracer adv in tendencies --- components/omega/configs/Default.yml | 3 +- components/omega/src/ocn/Tendencies.cpp | 56 +++++++------------ components/omega/src/ocn/Tendencies.h | 1 - .../test/timeStepping/TimeStepperTest.cpp | 22 ++++---- 4 files changed, 32 insertions(+), 50 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index f87cea63b360..284e10db905d 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -44,7 +44,8 @@ Omega: BottomDragTendencyEnable: false BottomDragCoeff: 0.0 TracerHorzAdvTendencyEnable: true - HorzTracerFluxOrder: 1 + HorzTracerFluxOrder: 2 + HorzTracerCoef3rdOrder: 0.25 TracerDiffTendencyEnable: true EddyDiff2: 10.0 TracerHyperDiffTendencyEnable: true diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index e49a71209cc0..c6da6d95a3ee 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -166,23 +166,30 @@ void Tendencies::readTendConfig( CHECK_ERROR_ABORT(Err, "Tendencies: DivFactor not found in TendConfig"); } Err += TendConfig->get("TracerHorzAdvTendencyEnable", - this->TracerHorzAdv.Enabled); + this->TracerHighOrderHorzAdv.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); - if (this->TracerHorzAdv.Enabled) { + if (this->TracerHighOrderHorzAdv.Enabled) { I4 Order = 0; Err += TendConfig->get("HorzTracerFluxOrder", Order); CHECK_ERROR_ABORT( Err, "Tendencies: HorzTracerFluxOrder not found in TendConfig"); + OMEGA_REQUIRE(Order >= 2 && Order <= 4, + "HorzTracerFluxOrder: Only values are 2, 3, 4, found {}", + Order); + if (Order == 2) { - this->TracerHighOrderHorzAdv.Enabled = true; - this->TracerHorzAdv.Enabled = false; + this->TracerHighOrderHorzAdv.ForceLowOrder = true; + this->TracerHighOrderHorzAdv.Coef3rdOrder = 0; + } + if (Order == 3) { + Err += TendConfig->get("HorzTracerCoef3rdOrder", + TracerHighOrderHorzAdv.Coef3rdOrder); + CHECK_ERROR_ABORT( + Err, "Tendencies: HorzTracerCoef3rdOrder not found in TendConfig"); } - if (!(Order == 1 or Order == 2)) { - const std::string msg = - "HorzTracerFluxOrder: Only values are 1 and 2, found " + - std::to_string(Order); - ABORT_ERROR(msg); + if (Order == 4) { + this->TracerHighOrderHorzAdv.Coef3rdOrder = 0; } } Err += TendConfig->get("TracerDiffTendencyEnable", @@ -203,11 +210,6 @@ void Tendencies::readTendConfig( CHECK_ERROR_ABORT(Err, "Tendencies: BottomDragCoeff not found in TendConfig"); - Err += TendConfig->get("TracerHorzAdvTendencyEnable", - this->TracerHorzAdv.Enabled); - CHECK_ERROR_ABORT( - Err, "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); - if (this->TracerDiffusion.Enabled) { Err += TendConfig->get("EddyDiff2", this->TracerDiffusion.EddyDiff2); CHECK_ERROR_ABORT(Err, "Tendencies: EddyDiff2 not found in TendConfig"); @@ -238,9 +240,8 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies PotientialVortHAdv(Mesh, VCoord), KEGrad(Mesh, VCoord), SSHGrad(Mesh, VCoord), VelocityDiffusion(Mesh, VCoord), VelocityHyperDiff(Mesh, VCoord), WindForcing(Mesh, VCoord), - BottomDrag(Mesh, VCoord), TracerHorzAdv(Mesh, VCoord), - TracerDiffusion(Mesh, VCoord), TracerHyperDiff(Mesh, VCoord), - TracerHighOrderHorzAdv(Mesh, VCoord), + BottomDrag(Mesh, VCoord), TracerDiffusion(Mesh, VCoord), + TracerHyperDiff(Mesh, VCoord), TracerHighOrderHorzAdv(Mesh, VCoord), CustomThicknessTend(InCustomThicknessTend), CustomVelocityTend(InCustomVelocityTend) { @@ -512,7 +513,6 @@ void Tendencies::computeTracerTendenciesOnly( TimeInstant Time ///< [in] Time ) { OMEGA_SCOPE(LocTracerTend, TracerTend); - OMEGA_SCOPE(LocTracerHorzAdv, TracerHorzAdv); OMEGA_SCOPE(LocTracerHighOrderHorzAdv, TracerHighOrderHorzAdv); OMEGA_SCOPE(LocTracerDiffusion, TracerDiffusion); OMEGA_SCOPE(LocTracerHyperDiff, TracerHyperDiff); @@ -537,27 +537,9 @@ void Tendencies::computeTracerTendenciesOnly( }); // compute tracer horizotal advection - const Array2DReal &NormalVelEdge = State->NormalVelocity[VelTimeLevel]; - const Array3DReal &HTracersEdge = AuxState->TracerAux.HTracersEdge; + Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); const Array2DReal &FluxLayerThickEdge = AuxState->LayerThicknessAux.FluxLayerThickEdge; - if (LocTracerHorzAdv.Enabled) { - Pacer::start("Tend:tracerHorzAdv", 2); - parallelForOuter( - {NTracers, Mesh->NCellsAll}, - KOKKOS_LAMBDA(int L, int ICell, const TeamMember &Team) { - const int KMin = MinLayerCell(ICell); - const int KMax = MaxLayerCell(ICell); - const int KRange = vertRangeChunked(KMin, KMax); - - parallelForInner( - Team, KRange, INNER_LAMBDA(int KChunk) { - LocTracerHorzAdv(LocTracerTend, L, ICell, KChunk, - NormalVelEdge, HTracersEdge); - }); - }); - Pacer::stop("Tend:tracerHorzAdv", 2); - } if (LocTracerHighOrderHorzAdv.Enabled) { Pacer::start("Tend:TracerHighOrderHorzAdv", 2); parallelForOuter( diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 9804483b9b54..f42516294c83 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -67,7 +67,6 @@ class Tendencies { BottomDragOnEdge BottomDrag; TracerDiffOnCell TracerDiffusion; TracerHyperDiffOnCell TracerHyperDiff; - TracerHorzAdvOnCell TracerHorzAdv; TracerHighOrderHorzAdvOnCell TracerHighOrderHorzAdv; // Methods to compute tendency groups diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 56b14d713a02..7139b8c8b993 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -218,17 +218,17 @@ int initTimeStepperTest(const std::string &mesh) { } // Disable all other tendencies - TestTendencies->ThicknessFluxDiv.Enabled = false; - TestTendencies->PotientialVortHAdv.Enabled = false; - TestTendencies->KEGrad.Enabled = false; - TestTendencies->SSHGrad.Enabled = false; - TestTendencies->VelocityDiffusion.Enabled = false; - TestTendencies->VelocityHyperDiff.Enabled = false; - TestTendencies->TracerHorzAdv.Enabled = false; - TestTendencies->TracerDiffusion.Enabled = false; - TestTendencies->TracerHyperDiff.Enabled = false; - TestTendencies->WindForcing.Enabled = false; - TestTendencies->BottomDrag.Enabled = false; + TestTendencies->ThicknessFluxDiv.Enabled = false; + TestTendencies->PotientialVortHAdv.Enabled = false; + TestTendencies->KEGrad.Enabled = false; + TestTendencies->SSHGrad.Enabled = false; + TestTendencies->VelocityDiffusion.Enabled = false; + TestTendencies->VelocityHyperDiff.Enabled = false; + TestTendencies->TracerHighOrderHorzAdv.Enabled = false; + TestTendencies->TracerDiffusion.Enabled = false; + TestTendencies->TracerHyperDiff.Enabled = false; + TestTendencies->WindForcing.Enabled = false; + TestTendencies->BottomDrag.Enabled = false; return Err; } From 8f991877dd28e3697aae1b7ad22c487cb829e267 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Wed, 25 Feb 2026 13:54:54 -0700 Subject: [PATCH 041/152] Rip out old tracer advection --- components/omega/configs/Default.yml | 1 - components/omega/src/ocn/AuxiliaryState.cpp | 29 ----------- components/omega/src/ocn/Tendencies.cpp | 16 ------ components/omega/src/ocn/TendencyTerms.cpp | 10 ---- components/omega/src/ocn/TendencyTerms.h | 50 ------------------- .../src/ocn/auxiliaryVars/TracerAuxVars.cpp | 26 ++-------- .../src/ocn/auxiliaryVars/TracerAuxVars.h | 43 ---------------- .../omega/test/ocn/AuxiliaryStateTest.cpp | 9 ---- .../omega/test/ocn/AuxiliaryVarsTest.cpp | 27 ---------- 9 files changed, 3 insertions(+), 208 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 284e10db905d..e2e84dabc688 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -24,7 +24,6 @@ Omega: NTimeLevels: 2 Advection: FluxThicknessType: Center - FluxTracerType: Center WindStress: InterpType: Isotropic VertCoord: diff --git a/components/omega/src/ocn/AuxiliaryState.cpp b/components/omega/src/ocn/AuxiliaryState.cpp index dedd070704cb..0aaa97753a60 100644 --- a/components/omega/src/ocn/AuxiliaryState.cpp +++ b/components/omega/src/ocn/AuxiliaryState.cpp @@ -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); @@ -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); diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index c6da6d95a3ee..ceb88b525c43 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -687,22 +687,6 @@ void Tendencies::computeTracerTendencies( Pacer::start("Tend:computeTracerTendencies", 1); - Pacer::start("Tend:computeTracerAuxEdge", 2); - parallelForOuter( - "computeTracerAuxEdge", {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) { - TracerAux.computeVarsOnEdge(LTracer, IEdge, KChunk, - NormalVelEdge, LayerThickCell, - TracerArray); - }); - }); - Pacer::stop("Tend:computeTracerAuxEdge", 2); - const auto &MeanLayerThickEdge = AuxState->LayerThicknessAux.MeanLayerThickEdge; Pacer::start("Tend:computeTracerAuxCell", 2); diff --git a/components/omega/src/ocn/TendencyTerms.cpp b/components/omega/src/ocn/TendencyTerms.cpp index d9503401f5e7..b08dbab75052 100644 --- a/components/omega/src/ocn/TendencyTerms.cpp +++ b/components/omega/src/ocn/TendencyTerms.cpp @@ -71,16 +71,6 @@ BottomDragOnEdge::BottomDragOnEdge(const HorzMesh *Mesh, NVertLayers(VCoord->NVertLayers), EdgeMask(VCoord->EdgeMask), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} -TracerHorzAdvOnCell::TracerHorzAdvOnCell(const HorzMesh *Mesh, - const VertCoord *VCoord) - : NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), - CellsOnEdge(Mesh->CellsOnEdge), EdgeSignOnCell(Mesh->EdgeSignOnCell), - DvEdge(Mesh->DvEdge), AreaCell(Mesh->AreaCell), - EdgeMask(VCoord->EdgeMask), MinLayerCell(VCoord->MinLayerCell), - MaxLayerCell(VCoord->MaxLayerCell), - MinLayerEdgeBot(VCoord->MinLayerEdgeBot), - MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} - TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell( const HorzMesh *Mesh, const VertCoord *VCoord) : HorzontalMesh(Mesh), diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index 4c2a68eb7840..107c109a3212 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -365,56 +365,6 @@ class BottomDragOnEdge { Array1DI4 MaxLayerEdgeTop; }; -// Tracer horizontal advection term -class TracerHorzAdvOnCell { - public: - bool Enabled = false; - - TracerHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); - - KOKKOS_FUNCTION void operator()(const Array3DReal &Tend, const I4 L, - const I4 ICell, const I4 KChunk, - const Array2DReal &NormVelEdge, - const Array3DReal &HTracersOnEdge) const { - - const I4 KStartCell = chunkStart(KChunk, MinLayerCell(ICell)); - const I4 KLenCell = chunkLength(KChunk, KStartCell, MaxLayerCell(ICell)); - const I4 KEndCell = KStartCell + KLenCell - 1; - const Real InvAreaCell = 1._Real / AreaCell(ICell); - Real HAdvTmp[VecLength] = {0}; - for (int J = 0; J < NEdgesOnCell(ICell); ++J) { - const I4 JEdge = EdgesOnCell(ICell, J); - const I4 KStartEdge = Kokkos::max(KStartCell, MinLayerEdgeBot(JEdge)); - const I4 KEndEdge = Kokkos::min(KEndCell, MaxLayerEdgeTop(JEdge)); - - for (int K = KStartEdge; K <= KEndEdge; ++K) { - const I4 KVec = K - KStartCell; - HAdvTmp[KVec] -= EdgeMask(JEdge, K) * DvEdge(JEdge) * - EdgeSignOnCell(ICell, J) * - HTracersOnEdge(L, JEdge, K) * - NormVelEdge(JEdge, K) * InvAreaCell; - } - } - for (int KVec = 0; KVec < KLenCell; ++KVec) { - const I4 K = KStartCell + KVec; - Tend(L, ICell, K) -= HAdvTmp[KVec]; - } - } - - private: - Array1DI4 NEdgesOnCell; - Array2DI4 EdgesOnCell; - Array2DI4 CellsOnEdge; - Array2DReal EdgeSignOnCell; - Array1DReal DvEdge; - Array1DReal AreaCell; - Array2DReal EdgeMask; - Array1DI4 MinLayerCell; - Array1DI4 MaxLayerCell; - Array1DI4 MinLayerEdgeBot; - Array1DI4 MaxLayerEdgeTop; -}; - // Tracer high order horizontal advection term class TracerHighOrderHorzAdvOnCell { public: diff --git a/components/omega/src/ocn/auxiliaryVars/TracerAuxVars.cpp b/components/omega/src/ocn/auxiliaryVars/TracerAuxVars.cpp index eea3d7d31982..9c2645441877 100644 --- a/components/omega/src/ocn/auxiliaryVars/TracerAuxVars.cpp +++ b/components/omega/src/ocn/auxiliaryVars/TracerAuxVars.cpp @@ -8,9 +8,7 @@ namespace OMEGA { TracerAuxVars::TracerAuxVars(const std::string &AuxStateSuffix, const HorzMesh *Mesh, const VertCoord *VCoord, const I4 NTracers) - : HTracersEdge("ThickTracersEdge" + AuxStateSuffix, NTracers, - Mesh->NEdgesSize, VCoord->NVertLayers), - Del2TracersCell("Del2TracerCell" + AuxStateSuffix, NTracers, + : Del2TracersCell("Del2TracerCell" + AuxStateSuffix, NTracers, Mesh->NCellsSize, VCoord->NVertLayers), NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), CellsOnEdge(Mesh->CellsOnEdge), EdgeSignOnCell(Mesh->EdgeSignOnCell), @@ -33,25 +31,10 @@ void TracerAuxVars::registerFields(const std::string &AuxGroupName, DimSuffix = MeshName; } - // Thickness-weighted tracers on Edge - DimNames[0] = "NTracers"; - DimNames[1] = "NEdges" + DimSuffix; - DimNames[2] = "NVertLayers"; - auto HTracersEdgeField = Field::create( - HTracersEdge.label(), // field name - "thickness-weighted tracers at edges. May be centered, upwinded, or a " - "combination of the two.", // long name or description - "", // units - "", // CF standard name - 0, // min valid value - std::numeric_limits::max(), // max valid value - FillValue, // scalar for undefined entries - NDims, // number of dimensions - DimNames // dimension names - ); - // Del2 tracers on Cell + DimNames[0] = "NTracers"; DimNames[1] = "NCells" + DimSuffix; + DimNames[2] = "NVertLayers"; auto Del2TracersCellField = Field::create( Del2TracersCell.label(), // field name "laplacian of thickness-weighted tracers at cell center", // long name or @@ -66,16 +49,13 @@ void TracerAuxVars::registerFields(const std::string &AuxGroupName, ); // Add fields to Aux Field group - FieldGroup::addFieldToGroup(HTracersEdge.label(), AuxGroupName); FieldGroup::addFieldToGroup(Del2TracersCell.label(), AuxGroupName); // Attach data to fields - HTracersEdgeField->attachData(HTracersEdge); Del2TracersCellField->attachData(Del2TracersCell); } void TracerAuxVars::unregisterFields() const { - Field::destroy(HTracersEdge.label()); Field::destroy(Del2TracersCell.label()); } diff --git a/components/omega/src/ocn/auxiliaryVars/TracerAuxVars.h b/components/omega/src/ocn/auxiliaryVars/TracerAuxVars.h index 1d12e55d9585..2d923e4b8a31 100644 --- a/components/omega/src/ocn/auxiliaryVars/TracerAuxVars.h +++ b/components/omega/src/ocn/auxiliaryVars/TracerAuxVars.h @@ -11,56 +11,13 @@ namespace OMEGA { -enum class FluxTracerEdgeOption { Center, Upwind }; - class TracerAuxVars { public: - Array3DReal HTracersEdge; Array3DReal Del2TracersCell; - FluxTracerEdgeOption TracersOnEdgeChoice; - TracerAuxVars(const std::string &AuxStateSuffix, const HorzMesh *Mesh, const VertCoord *VCoord, const I4 NTracers); - KOKKOS_FUNCTION void computeVarsOnEdge(int L, int IEdge, int KChunk, - const Array2DReal &NormalVelEdge, - const Array2DReal &HCell, - const Array3DReal &TrCell) const { - const int KStart = chunkStart(KChunk, MinLayerEdgeBot(IEdge)); - const int KLen = chunkLength(KChunk, KStart, MaxLayerEdgeTop(IEdge)); - - const int JCell0 = CellsOnEdge(IEdge, 0); - const int JCell1 = CellsOnEdge(IEdge, 1); - - switch (TracersOnEdgeChoice) { - case FluxTracerEdgeOption::Center: - for (int KVec = 0; KVec < KLen; ++KVec) { - const int K = KStart + KVec; - HTracersEdge(L, IEdge, K) = - 0.5_Real * (HCell(JCell0, K) * TrCell(L, JCell0, K) + - HCell(JCell1, K) * TrCell(L, JCell1, K)); - } - break; - case FluxTracerEdgeOption::Upwind: - for (int KVec = 0; KVec < KLen; ++KVec) { - const int K = KStart + KVec; - if (NormalVelEdge(IEdge, K) > 0) { - HTracersEdge(L, IEdge, K) = - HCell(JCell0, K) * TrCell(L, JCell0, K); - } else if (NormalVelEdge(IEdge, K) < 0) { - HTracersEdge(L, IEdge, K) = - HCell(JCell1, K) * TrCell(L, JCell1, K); - } else { - HTracersEdge(L, IEdge, K) = - Kokkos::max(HCell(JCell0, K) * TrCell(L, JCell0, K), - HCell(JCell1, K) * TrCell(L, JCell1, K)); - } - } - break; - } - } - KOKKOS_FUNCTION void computeVarsOnCells(int L, int ICell, int KChunk, const Array2DReal &LayerThickEdgeMean, diff --git a/components/omega/test/ocn/AuxiliaryStateTest.cpp b/components/omega/test/ocn/AuxiliaryStateTest.cpp index 1952f70744ae..952af2814741 100644 --- a/components/omega/test/ocn/AuxiliaryStateTest.cpp +++ b/components/omega/test/ocn/AuxiliaryStateTest.cpp @@ -185,7 +185,6 @@ int testAuxState() { deepCopy(DefAuxState->VelocityDel2Aux.Del2DivCell, NAN); deepCopy(DefAuxState->VelocityDel2Aux.Del2RelVortVertex, NAN); - deepCopy(DefAuxState->TracerAux.HTracersEdge, NAN); deepCopy(DefAuxState->TracerAux.Del2TracersCell, NAN); // compute auxiliary variables @@ -295,14 +294,6 @@ int testAuxState() { LOG_ERROR("AuxStateTest: Del2RelVortVertex FAIL"); } - const Real HTracersESum = - sum(DefAuxState->TracerAux.HTracersEdge, NTracers, NEdgesOwned, - VCoord->MinLayerEdgeBot, VCoord->MaxLayerEdgeTop); - if (!Kokkos::isfinite(HTracersESum)) { - Err++; - LOG_ERROR("AuxStateTest: HTracersOnEdge FAIL"); - } - const Real Del2TracersCSum = sum(DefAuxState->TracerAux.Del2TracersCell, NTracers, NCellsOwned, VCoord->MinLayerCell, VCoord->MaxLayerCell); diff --git a/components/omega/test/ocn/AuxiliaryVarsTest.cpp b/components/omega/test/ocn/AuxiliaryVarsTest.cpp index ae1ab8719f69..44957762f953 100644 --- a/components/omega/test/ocn/AuxiliaryVarsTest.cpp +++ b/components/omega/test/ocn/AuxiliaryVarsTest.cpp @@ -730,7 +730,6 @@ int testTracerAuxVars(const Array2DReal &LayerThickCell, const auto VCoord = VertCoord::getDefault(); TracerAuxVars TracerAux("", Mesh, VCoord, NTracers); - TracerAux.TracersOnEdgeChoice = FluxTracerEdgeOption::Upwind; // Set input arrays @@ -745,32 +744,6 @@ int testTracerAuxVars(const Array2DReal &LayerThickCell, KOKKOS_LAMBDA(Real X, Real Y) { return Setup.layerThickness(X, Y); }, LayerThickEdge, Geom, Mesh, OnEdge); - // Compute exact HTracerEdge - - Array3DReal ExactHTrEdge("ExactHTrEdge", NTracers, Mesh->NEdgesOwned, - NVertLayers); - Err += setScalar( - KOKKOS_LAMBDA(Real X, Real Y) { return Setup.thickTracer(X, Y); }, - ExactHTrEdge, Geom, Mesh, OnEdge, ExchangeHalos::No); - - // Compute numerical HTracersEdge - - parallelFor( - {NTracers, Mesh->NEdgesOwned, NVertLayers}, - KOKKOS_LAMBDA(int L, int IEdge, int KLayer) { - TracerAux.computeVarsOnEdge(L, IEdge, KLayer, NormalVelEdge, - LayerThickCell, TracersOnCell); - }); - - // Compute error measures and check errors for HTracersEdge - - const auto &NumHTrEdge = TracerAux.HTracersEdge; - - ErrorMeasures HTracerErrors; - Err += computeErrors(HTracerErrors, NumHTrEdge, ExactHTrEdge, Mesh, OnEdge); - Err += checkErrors("AuxVarsTest", "HTracers", HTracerErrors, - Setup.ExpectedHTracerErrors, RTol); - // Compute exact Del2TracerCell Array3DReal ExactDel2TrCell("ExactDel2TrCell", NTracers, Mesh->NCellsOwned, From f75d949ee77c0766aaeca1f0e84867490ecf2f2f Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Wed, 25 Feb 2026 14:03:04 -0700 Subject: [PATCH 042/152] Rename: TracerHighOrderHorzAdv -> TracerHorzAdv --- components/omega/src/ocn/Tendencies.cpp | 33 +++++++++---------- components/omega/src/ocn/Tendencies.h | 2 +- components/omega/src/ocn/TendencyTerms.cpp | 6 ++-- components/omega/src/ocn/TendencyTerms.h | 6 ++-- .../omega/test/ocn/TendencyTermsTest.cpp | 2 +- .../test/timeStepping/TimeStepperTest.cpp | 22 ++++++------- 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index ceb88b525c43..abe6e2195fc9 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -70,8 +70,8 @@ void Tendencies::init() { CustomThickTend, CustomVelTend); DefaultTendencies->readTendConfig(&TendConfig); - if (DefaultTendencies->TracerHighOrderHorzAdv.Enabled) - DefaultTendencies->TracerHighOrderHorzAdv.init(); + if (DefaultTendencies->TracerHorzAdv.Enabled) + DefaultTendencies->TracerHorzAdv.init(); } // end init //------------------------------------------------------------------------------ @@ -166,10 +166,10 @@ void Tendencies::readTendConfig( CHECK_ERROR_ABORT(Err, "Tendencies: DivFactor not found in TendConfig"); } Err += TendConfig->get("TracerHorzAdvTendencyEnable", - this->TracerHighOrderHorzAdv.Enabled); + this->TracerHorzAdv.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); - if (this->TracerHighOrderHorzAdv.Enabled) { + if (this->TracerHorzAdv.Enabled) { I4 Order = 0; Err += TendConfig->get("HorzTracerFluxOrder", Order); CHECK_ERROR_ABORT( @@ -179,17 +179,17 @@ void Tendencies::readTendConfig( Order); if (Order == 2) { - this->TracerHighOrderHorzAdv.ForceLowOrder = true; - this->TracerHighOrderHorzAdv.Coef3rdOrder = 0; + this->TracerHorzAdv.ForceLowOrder = true; + this->TracerHorzAdv.Coef3rdOrder = 0; } if (Order == 3) { Err += TendConfig->get("HorzTracerCoef3rdOrder", - TracerHighOrderHorzAdv.Coef3rdOrder); + TracerHorzAdv.Coef3rdOrder); CHECK_ERROR_ABORT( Err, "Tendencies: HorzTracerCoef3rdOrder not found in TendConfig"); } if (Order == 4) { - this->TracerHighOrderHorzAdv.Coef3rdOrder = 0; + this->TracerHorzAdv.Coef3rdOrder = 0; } } Err += TendConfig->get("TracerDiffTendencyEnable", @@ -241,7 +241,7 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies SSHGrad(Mesh, VCoord), VelocityDiffusion(Mesh, VCoord), VelocityHyperDiff(Mesh, VCoord), WindForcing(Mesh, VCoord), BottomDrag(Mesh, VCoord), TracerDiffusion(Mesh, VCoord), - TracerHyperDiff(Mesh, VCoord), TracerHighOrderHorzAdv(Mesh, VCoord), + TracerHyperDiff(Mesh, VCoord), TracerHorzAdv(Mesh, VCoord), CustomThicknessTend(InCustomThicknessTend), CustomVelocityTend(InCustomVelocityTend) { @@ -513,7 +513,7 @@ void Tendencies::computeTracerTendenciesOnly( TimeInstant Time ///< [in] Time ) { OMEGA_SCOPE(LocTracerTend, TracerTend); - OMEGA_SCOPE(LocTracerHighOrderHorzAdv, TracerHighOrderHorzAdv); + OMEGA_SCOPE(LocTracerHorzAdv, TracerHorzAdv); OMEGA_SCOPE(LocTracerDiffusion, TracerDiffusion); OMEGA_SCOPE(LocTracerHyperDiff, TracerHyperDiff); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); @@ -540,8 +540,8 @@ void Tendencies::computeTracerTendenciesOnly( Array2DReal NormalVelEdge = State->getNormalVelocity(VelTimeLevel); const Array2DReal &FluxLayerThickEdge = AuxState->LayerThicknessAux.FluxLayerThickEdge; - if (LocTracerHighOrderHorzAdv.Enabled) { - Pacer::start("Tend:TracerHighOrderHorzAdv", 2); + if (LocTracerHorzAdv.Enabled) { + Pacer::start("Tend:tracerHorzAdv", 2); parallelForOuter( {NTracers, Mesh->NEdgesAll}, KOKKOS_LAMBDA(int L, int IEdge, const TeamMember &Team) { @@ -550,9 +550,8 @@ void Tendencies::computeTracerTendenciesOnly( const int KRange = vertRangeChunked(KMin, KMax); parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { - LocTracerHighOrderHorzAdv(L, IEdge, KChunk, TracerArray, - FluxLayerThickEdge, - NormalVelEdge); + LocTracerHorzAdv(L, IEdge, KChunk, TracerArray, + FluxLayerThickEdge, NormalVelEdge); }); }); parallelForOuter( @@ -563,10 +562,10 @@ void Tendencies::computeTracerTendenciesOnly( const int KRange = vertRangeChunked(KMin, KMax); parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { - LocTracerHighOrderHorzAdv(LocTracerTend, L, ICell, KChunk); + LocTracerHorzAdv(LocTracerTend, L, ICell, KChunk); }); }); - Pacer::stop("Tend:TracerHighOrderHorzAdv", 2); + Pacer::stop("Tend:tracerHorzAdv", 2); } // compute tracer diffusion diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index f42516294c83..851ce1fc646c 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -65,9 +65,9 @@ class Tendencies { VelocityHyperDiffOnEdge VelocityHyperDiff; WindForcingOnEdge WindForcing; BottomDragOnEdge BottomDrag; + TracerHorzAdvOnCell TracerHorzAdv; TracerDiffOnCell TracerDiffusion; TracerHyperDiffOnCell TracerHyperDiff; - TracerHighOrderHorzAdvOnCell TracerHighOrderHorzAdv; // Methods to compute tendency groups void computeThicknessTendencies(const OceanState *State, diff --git a/components/omega/src/ocn/TendencyTerms.cpp b/components/omega/src/ocn/TendencyTerms.cpp index b08dbab75052..cc543ad69752 100644 --- a/components/omega/src/ocn/TendencyTerms.cpp +++ b/components/omega/src/ocn/TendencyTerms.cpp @@ -71,8 +71,8 @@ BottomDragOnEdge::BottomDragOnEdge(const HorzMesh *Mesh, NVertLayers(VCoord->NVertLayers), EdgeMask(VCoord->EdgeMask), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} -TracerHighOrderHorzAdvOnCell::TracerHighOrderHorzAdvOnCell( - const HorzMesh *Mesh, const VertCoord *VCoord) +TracerHorzAdvOnCell::TracerHorzAdvOnCell(const HorzMesh *Mesh, + const VertCoord *VCoord) : HorzontalMesh(Mesh), NAdvCellsForEdge("NumberOfCellsContribToAdvectionAtEdge", Mesh->NEdgesAll), @@ -111,7 +111,7 @@ TracerHyperDiffOnCell::TracerHyperDiffOnCell(const HorzMesh *Mesh, MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} -void TracerHighOrderHorzAdvOnCell::init() { +void TracerHorzAdvOnCell::init() { const HorzMesh *Mesh = this->HorzontalMesh; const auto MaxEdges2 = Mesh->MaxEdges2; const auto NEdgesAll = Mesh->NEdgesAll; diff --git a/components/omega/src/ocn/TendencyTerms.h b/components/omega/src/ocn/TendencyTerms.h index 107c109a3212..5ccb1ec79ed3 100644 --- a/components/omega/src/ocn/TendencyTerms.h +++ b/components/omega/src/ocn/TendencyTerms.h @@ -365,14 +365,14 @@ class BottomDragOnEdge { Array1DI4 MaxLayerEdgeTop; }; -// Tracer high order horizontal advection term -class TracerHighOrderHorzAdvOnCell { +// Tracer horizontal advection term +class TracerHorzAdvOnCell { public: bool Enabled = false; bool ForceLowOrder = false; // coefficient for blending high-order terms Real Coef3rdOrder = 0.25; - TracerHighOrderHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); + TracerHorzAdvOnCell(const HorzMesh *Mesh, const VertCoord *VCoord); void init(); KOKKOS_FUNCTION void operator()(const I4 L, const I4 IEdge, const I4 KChunk, const Array3DReal &TracerCell, diff --git a/components/omega/test/ocn/TendencyTermsTest.cpp b/components/omega/test/ocn/TendencyTermsTest.cpp index f928d3ddbe48..c34fe574d10c 100644 --- a/components/omega/test/ocn/TendencyTermsTest.cpp +++ b/components/omega/test/ocn/TendencyTermsTest.cpp @@ -882,7 +882,7 @@ int testTracerHorzAdvOnCell(int NVertLayers, int NTracers, Real RTol) { // Compute numerical result Array3DReal NumTrFluxDiv("NumTrFluxDiv", NTracers, Mesh->NCellsOwned, NVertLayers); - TracerHighOrderHorzAdvOnCell TrHorzAdvOnC(Mesh, VCoord); + TracerHorzAdvOnCell TrHorzAdvOnC(Mesh, VCoord); TrHorzAdvOnC.init(); TrHorzAdvOnC.ForceLowOrder = true; diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 7139b8c8b993..56b14d713a02 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -218,17 +218,17 @@ int initTimeStepperTest(const std::string &mesh) { } // Disable all other tendencies - TestTendencies->ThicknessFluxDiv.Enabled = false; - TestTendencies->PotientialVortHAdv.Enabled = false; - TestTendencies->KEGrad.Enabled = false; - TestTendencies->SSHGrad.Enabled = false; - TestTendencies->VelocityDiffusion.Enabled = false; - TestTendencies->VelocityHyperDiff.Enabled = false; - TestTendencies->TracerHighOrderHorzAdv.Enabled = false; - TestTendencies->TracerDiffusion.Enabled = false; - TestTendencies->TracerHyperDiff.Enabled = false; - TestTendencies->WindForcing.Enabled = false; - TestTendencies->BottomDrag.Enabled = false; + TestTendencies->ThicknessFluxDiv.Enabled = false; + TestTendencies->PotientialVortHAdv.Enabled = false; + TestTendencies->KEGrad.Enabled = false; + TestTendencies->SSHGrad.Enabled = false; + TestTendencies->VelocityDiffusion.Enabled = false; + TestTendencies->VelocityHyperDiff.Enabled = false; + TestTendencies->TracerHorzAdv.Enabled = false; + TestTendencies->TracerDiffusion.Enabled = false; + TestTendencies->TracerHyperDiff.Enabled = false; + TestTendencies->WindForcing.Enabled = false; + TestTendencies->BottomDrag.Enabled = false; return Err; } From 595bca4a0d65333ef4e77eba3c2bde2c6ff67033 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Wed, 25 Feb 2026 14:16:33 -0700 Subject: [PATCH 043/152] Move some options to the advection section --- components/omega/configs/Default.yml | 4 +- components/omega/src/ocn/Tendencies.cpp | 69 ++++++++++++++----------- components/omega/src/ocn/Tendencies.h | 2 +- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index e2e84dabc688..38c2f97057cf 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -24,6 +24,8 @@ Omega: NTimeLevels: 2 Advection: FluxThicknessType: Center + HorzTracerFluxOrder: 2 + Coef3rdOrder: 0.25 WindStress: InterpType: Isotropic VertCoord: @@ -43,8 +45,6 @@ Omega: BottomDragTendencyEnable: false BottomDragCoeff: 0.0 TracerHorzAdvTendencyEnable: true - HorzTracerFluxOrder: 2 - HorzTracerCoef3rdOrder: 0.25 TracerDiffTendencyEnable: true EddyDiff2: 10.0 TracerHyperDiffTendencyEnable: true diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index abe6e2195fc9..4f661579dbae 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -69,7 +69,8 @@ void Tendencies::init() { create("Default", DefHorzMesh, DefVertCoord, NTracers, &TendConfig, CustomThickTend, CustomVelTend); - DefaultTendencies->readTendConfig(&TendConfig); + DefaultTendencies->readConfig(OmegaConfig); + if (DefaultTendencies->TracerHorzAdv.Enabled) DefaultTendencies->TracerHorzAdv.init(); } // end init @@ -122,58 +123,65 @@ Tendencies *Tendencies::get(const std::string &Name ///< [in] Name of tendencies //------------------------------------------------------------------------------ // read and set config options -void Tendencies::readTendConfig( - Config *TendConfig ///< [in] Tendencies subconfig +void Tendencies::readConfig(Config *OmegaConfig ///< [in] Omega config ) { Error Err; // error code - Err += TendConfig->get("ThicknessFluxTendencyEnable", - this->ThicknessFluxDiv.Enabled); + Config TendConfig("Tendencies"); + Err += OmegaConfig->get(TendConfig); + CHECK_ERROR_ABORT(Err, "Tendencies: Tendencies group not found in Config"); + + Err += TendConfig.get("ThicknessFluxTendencyEnable", + this->ThicknessFluxDiv.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: ThicknessFluxTendencyEnable not found in TendConfig"); - Err += TendConfig->get("PVTendencyEnable", this->PotientialVortHAdv.Enabled); + Err += TendConfig.get("PVTendencyEnable", this->PotientialVortHAdv.Enabled); CHECK_ERROR_ABORT(Err, "Tendencies: PVTendencyEnable not found in TendConfig"); - Err += TendConfig->get("KETendencyEnable", this->KEGrad.Enabled); + Err += TendConfig.get("KETendencyEnable", this->KEGrad.Enabled); CHECK_ERROR_ABORT(Err, "Tendencies: KETendencyEnable not found in TendConfig"); - Err += TendConfig->get("SSHTendencyEnable", this->SSHGrad.Enabled); + Err += TendConfig.get("SSHTendencyEnable", this->SSHGrad.Enabled); CHECK_ERROR_ABORT(Err, "Tendencies: SSHTendencyEnable not found in TendConfig"); - Err += TendConfig->get("VelDiffTendencyEnable", - this->VelocityDiffusion.Enabled); + Err += + TendConfig.get("VelDiffTendencyEnable", this->VelocityDiffusion.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: VelDiffTendencyEnable not found in TendConfig"); - Err += TendConfig->get("VelHyperDiffTendencyEnable", - this->VelocityHyperDiff.Enabled); + Err += TendConfig.get("VelHyperDiffTendencyEnable", + this->VelocityHyperDiff.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: VelHyperDiffTendencyEnable not found in TendConfig"); if (this->VelocityDiffusion.Enabled) { - Err += TendConfig->get("ViscDel2", this->VelocityDiffusion.ViscDel2); + Err += TendConfig.get("ViscDel2", this->VelocityDiffusion.ViscDel2); CHECK_ERROR_ABORT(Err, "Tendencies: ViscDel2 not found in TendConfig"); } if (this->VelocityHyperDiff.Enabled) { - Err += TendConfig->get("ViscDel4", this->VelocityHyperDiff.ViscDel4); + Err += TendConfig.get("ViscDel4", this->VelocityHyperDiff.ViscDel4); CHECK_ERROR_ABORT(Err, "Tendencies: ViscDel4 not found in TendConfig"); - Err += TendConfig->get("DivFactor", this->VelocityHyperDiff.DivFactor); + Err += TendConfig.get("DivFactor", this->VelocityHyperDiff.DivFactor); CHECK_ERROR_ABORT(Err, "Tendencies: DivFactor not found in TendConfig"); } - Err += TendConfig->get("TracerHorzAdvTendencyEnable", - this->TracerHorzAdv.Enabled); + Err += TendConfig.get("TracerHorzAdvTendencyEnable", + this->TracerHorzAdv.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: TracerHorzAdvTendencyEnable not found in TendConfig"); if (this->TracerHorzAdv.Enabled) { + Config AdvectConfig("Advection"); + Err += OmegaConfig->get(AdvectConfig); + CHECK_ERROR_ABORT(Err, "Tendencies: Advection group not in Config"); + I4 Order = 0; - Err += TendConfig->get("HorzTracerFluxOrder", Order); + Err += AdvectConfig.get("HorzTracerFluxOrder", Order); CHECK_ERROR_ABORT( - Err, "Tendencies: HorzTracerFluxOrder not found in TendConfig"); + Err, "Tendencies: HorzTracerFluxOrder not found in AdvectConfig"); OMEGA_REQUIRE(Order >= 2 && Order <= 4, "HorzTracerFluxOrder: Only values are 2, 3, 4, found {}", Order); @@ -183,46 +191,45 @@ void Tendencies::readTendConfig( this->TracerHorzAdv.Coef3rdOrder = 0; } if (Order == 3) { - Err += TendConfig->get("HorzTracerCoef3rdOrder", - TracerHorzAdv.Coef3rdOrder); + Err += AdvectConfig.get("Coef3rdOrder", TracerHorzAdv.Coef3rdOrder); CHECK_ERROR_ABORT( - Err, "Tendencies: HorzTracerCoef3rdOrder not found in TendConfig"); + Err, "Tendencies: Coef3rdOrder not found in AdvectConfig"); } if (Order == 4) { this->TracerHorzAdv.Coef3rdOrder = 0; } } - Err += TendConfig->get("TracerDiffTendencyEnable", - this->TracerDiffusion.Enabled); + Err += TendConfig.get("TracerDiffTendencyEnable", + this->TracerDiffusion.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: TracerDiffTendencyEnable not found in TendConfig"); Err += - TendConfig->get("WindForcingTendencyEnable", this->WindForcing.Enabled); + TendConfig.get("WindForcingTendencyEnable", this->WindForcing.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: WindForcingTendencyEnable not found in TendConfig"); - Err += TendConfig->get("BottomDragTendencyEnable", this->BottomDrag.Enabled); + Err += TendConfig.get("BottomDragTendencyEnable", this->BottomDrag.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: BottomDragTendencyEnable not found in TendConfig"); - Err += TendConfig->get("BottomDragCoeff", this->BottomDrag.Coeff); + Err += TendConfig.get("BottomDragCoeff", this->BottomDrag.Coeff); CHECK_ERROR_ABORT(Err, "Tendencies: BottomDragCoeff not found in TendConfig"); if (this->TracerDiffusion.Enabled) { - Err += TendConfig->get("EddyDiff2", this->TracerDiffusion.EddyDiff2); + Err += TendConfig.get("EddyDiff2", this->TracerDiffusion.EddyDiff2); CHECK_ERROR_ABORT(Err, "Tendencies: EddyDiff2 not found in TendConfig"); } - Err += TendConfig->get("TracerHyperDiffTendencyEnable", - this->TracerHyperDiff.Enabled); + Err += TendConfig.get("TracerHyperDiffTendencyEnable", + this->TracerHyperDiff.Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: TracerHyperDiffTendencyEnable not found in TendConfig"); if (this->TracerHyperDiff.Enabled) { - Err += TendConfig->get("EddyDiff4", this->TracerHyperDiff.EddyDiff4); + Err += TendConfig.get("EddyDiff4", this->TracerHyperDiff.EddyDiff4); CHECK_ERROR_ABORT(Err, "Tendencies: EddyDiff4 not found in TendConfig"); } } diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 851ce1fc646c..ef2afdc0bc38 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -144,7 +144,7 @@ class Tendencies { ); // read and set config options - void readTendConfig(Config *TendConfig); + void readConfig(Config *OmegaConfig); private: // Construct a new tendency object From a5bd1cf76ca298d28ce3ea9e74c3d74a93078da5 Mon Sep 17 00:00:00 2001 From: Maciej Waruszewski Date: Wed, 25 Feb 2026 14:44:40 -0700 Subject: [PATCH 044/152] Fix size of HighOrderFlxHorz --- components/omega/src/ocn/TendencyTerms.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/src/ocn/TendencyTerms.cpp b/components/omega/src/ocn/TendencyTerms.cpp index cc543ad69752..9550bf01c0be 100644 --- a/components/omega/src/ocn/TendencyTerms.cpp +++ b/components/omega/src/ocn/TendencyTerms.cpp @@ -84,7 +84,7 @@ TracerHorzAdvOnCell::TracerHorzAdvOnCell(const HorzMesh *Mesh, AdvCoefs3rd("CommonAdvectionCoeffsForHighOrder", Mesh->MaxEdges2 + 2, Mesh->NEdgesAll), HighOrderFlxHorz("HigherOrderHorizontalFlux", Tracers::getNumTracers(), - Mesh->NEdgesAll, VCoord->NVertLayers / VecLength), + Mesh->NEdgesAll, VCoord->NVertLayers), NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), CellsOnEdge(Mesh->CellsOnEdge), EdgeSignOnCell(Mesh->EdgeSignOnCell), DvEdge(Mesh->DvEdge), AreaCell(Mesh->AreaCell) { From dc1569811469ace05adf374cac73db76f3a5f2bc Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Fri, 12 Dec 2025 14:29:16 +0100 Subject: [PATCH 045/152] Rewrite PGF throughout sections 9 and 10 --- .../omega/doc/design/OmegaV1GoverningEqns.md | 146 +++++++++--------- 1 file changed, 76 insertions(+), 70 deletions(-) diff --git a/components/omega/doc/design/OmegaV1GoverningEqns.md b/components/omega/doc/design/OmegaV1GoverningEqns.md index 073931873d22..c067a35214a0 100644 --- a/components/omega/doc/design/OmegaV1GoverningEqns.md +++ b/components/omega/doc/design/OmegaV1GoverningEqns.md @@ -491,55 +491,87 @@ $$ = 0. $$ (layer-mass) -### Momentum +### Pressure Gradient Force -We now derive the horizontal momentum equation in our non-Boussinesq, hydrostatic framework, following the same finite-volume approach used for mass and tracer conservation. +Before deriving the layered momentum equation, we collect the pressure and gravitational contributions into a single **pressure gradient force (PGF)** in our pseudo-height framework. This subsection establishes the continuous form of the horizontal PGF per unit mass and serves as the target for the subsequent finite-volume, layer-averaged derivation. + +Recall from [](vh-momentum-pseudo) that the horizontal momentum equation is written in terms of + +- a **body force per unit mass** $\mathbf{b}_\perp$, and +- a **traction** vector $\boldsymbol{\tau}$ that enters the weak form as $\boldsymbol{\tau}/\rho$, -We specify the body forces: +so that all terms in the equation have the units of acceleration. + +In the finite-volume formulation, pressure acts on the boundary of a control volume $\mathcal{R}$ through the traction +$\boldsymbol{\tau}_p = -p\,\mathbf{n}$, where $\mathbf{n}$ is the outward unit normal. The net pressure force on $\mathcal{R}$ is therefore $$ -\mathbf{b}_{\perp} = - \left({\bf f} \times \mathbf{u} + \nabla \Phi \right). -$$ (body-forces) +\mathbf{F}_p(\mathcal{R}) += +\int_{\partial\mathcal{R}} \boldsymbol{\tau}_p\,dS += +-\int_{\partial\mathcal{R}} p\,\mathbf{n}\,dS . +$$ (pgf-pressure-traction) -The first term on the right hand side is the **Coriolis force**, where $ \mathbf{f} $ is the vector Coriolis vector (e.g., $ f \hat{\mathbf{z}} $ on the sphere). +Using the divergence theorem, this surface integral may be written equivalently as a volume integral of a force per unit volume, + +$$ +-\int_{\partial\mathcal{R}} p\,\mathbf{n}\,dS += +-\int_{\mathcal{R}} \nabla p \, dV . +$$ (pgf-pressure-volume) -The second term on the right hand side represents the **gravitational force**, expressed in terms of the two-dimensional along-layer gradient of the gravitational potential $ \Phi(x, y, z, t) $, which may include effects such as tides and self-attraction and loading. Because layers may tilt, this gradient is not taken at constant $z$ or $\tilde{z}$; instead it is the projection of $\nabla_{3D}\Phi$ onto the tangent plane of the layer surface. +Interpreting $-\nabla p$ as a force per unit volume, the corresponding pressure acceleration (body force per unit mass) is + +$$ +\mathbf{b}_{p} += +-\frac{1}{\rho}\,\nabla p . +$$ (pgf-pressure-accel) -Going forward, $\nabla$ without a subscript will be used to represent along-layer gradients. +Note that one should **not** apply the divergence theorem directly to $\boldsymbol{\tau}_p/\rho$; the divergence theorem applies to the physical traction $\boldsymbol{\tau}_p$ (force per unit area). The division by $\rho$ to obtain an acceleration is performed after forming the pressure force. -We neglect viscous stresses and take the stress tensor to be purely isotropic pressure, so the traction vector on any surface is +Gravity acts as a body force per unit mass $-\nabla\Phi$, so the combined pressure–gravity acceleration is $$ -{\bf \tau} = - p \mathbf{n}, -$$ (surface-forces-perp) +\mathbf{b}_{\text{PGF},\perp} += +-\,\frac{1}{\rho}\,\nabla p \;-\; \nabla \Phi . +$$ (pgf-continuous) + +In Omega, the operator $\nabla$ denotes the **horizontal gradient along layers**, following the convention adopted throughout this section. It should not be interpreted as a gradient taken at constant geometric height $z$ or constant pseudo-height $\tilde{z}$. + +Under the hydrostatic approximation [](hydrostatic), the form of the horizontal pressure–gravity force in [](pgf-continuous) is valid for *any* choice of vertical coordinate, provided the horizontal gradient is taken consistently with that coordinate. In particular, although the horizontal gradient of $\Phi$ vanishes in $z$-level coordinates in the absence of tides, self-attraction, and loading, the combined expression in [](pgf-continuous) remains the correct horizontal pressure–gravity force when written using the appropriate along-layer gradient operator. + +When inserted into the weak, finite-volume form of the momentum equation, $\mathbf{b}_{\text{PGF},\perp}$ represents the combined effect of pressure traction and gravitational body force. In the next subsection, we will layer-average [](pgf-continuous) over the pseudo-height thickness of each layer and combine it with the advective and Coriolis terms to obtain the layered horizontal momentum equation. The resulting horizontal pressure-gradient force in Omega must be a consistent finite-volume discretization of [](pgf-continuous), together with additional metric terms associated with sloping layer interfaces. + +### Momentum + +We now derive the horizontal momentum equation in our non-Boussinesq, hydrostatic framework, following the same finite-volume approach used for mass and tracer conservation. -where $\mathbf{n}$ is the outward unit normal. In the horizontally projected momentum equation, only the horizontal component appears: +As established in the preceding **Pressure Gradient Force** subsection, the combined horizontal pressure–gravity acceleration is +$-\,\rho^{-1}\nabla p - \nabla \Phi$ when written using the appropriate along-layer horizontal gradient operator $\nabla$; in the finite-volume derivation below we retain the same physics by treating gravity as a body force and pressure as a surface traction. + +We specify the body forces, including the pressure term, as: $$ -{\bf \tau}_\perp = - p \mathbf{n}_\perp, -$$ (surface-forces) +\mathbf{b}_{\perp} = - \left({\bf f} \times \mathbf{u} + \frac{1}{\rho} \nabla p + \nabla \Phi \right). +$$ (body-forces) + +The first term on the right hand side is the **Coriolis force**, where $ \mathbf{f} $ is the vector Coriolis vector (e.g., $ f \hat{\mathbf{z}} $ on the sphere). -where $\mathbf{n}_\perp$ is the horizontal projection of $\mathbf{n}$. On vertical side walls, $\mathbf{n}_\perp = \mathbf{n}$; on sloping top and bottom interfaces, $\mathbf{n}_\perp \approx - \nabla \tilde{z}$. +The second and third terms are $\mathbf{b}_{\text{PGF},\perp}$ from the previous subsection. -Substituting these into [](#vh-momentum-pseudo), we arrive at: +Substituting (body-forces) and (surface-forces) into [](#vh-momentum-pseudo), we arrive at: $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} {\bf u} \, d\tilde{z} \, dA & + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} {\bf u} \otimes {\bf u} \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ & + \int_A \, {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA - \int_A \, {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ -& = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left({\bf f} \times \mathbf{u} + \nabla \Phi \right) \, d\tilde{z} \, dA -- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \frac{p}{\rho} \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ -& + \int_A \left[ \frac{p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA -- \int_A \left[ \frac{p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left({\bf f} \times \mathbf{u} + \frac{1}{\rho} \nabla p + \nabla \Phi \right) \, d\tilde{z} \, dA. $$ (vh-momentum-forces) -In this equation: - -- The first two terms are the body forces explained above. -- The final terms are the **pressure force**, which acts on the boundary surfaces and is naturally expressed as a surface integral. It gives rise to both horizontal pressure gradients and contributions from sloping surfaces. -- A negative sign is included for the pressure terms derived from the surface force terms. According to [Leishman 2025](https://eaglepubs.erau.edu/introductiontoaerospaceflightvehicles/chapter/conservation-of-momentum-momentum-equation/#chapter-260-section-2), Chapter 21, equation 2, "the negative sign indicates that the force is inward and in the opposite direction to the unit normal vector area, which always points *outward* by convention". Also see discussion in [Kundu et al. 2016](https://doi.org/10.1016/C2012-0-00611-4) page 104 and equation 4.26. - As with the tracer derivation, we next Reynolds' average [](#vh-momentum-forces), $$ @@ -547,10 +579,7 @@ $$ & + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \otimes {\bf u} \right> \, d\tilde{z} \right) \cdot \mathbf{n}_\perp \, dl \\ & + \int_A \,\left< {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \right> \, dA - \int_A \, \left< {\bf u} \left[ \tilde{w}_{tr} - \tilde{\bf u} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right> \, dA \\ -& = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \, \left< {\bf f} \times \mathbf{u} + \nabla \Phi \right> \, d\tilde{z} \, dA -- \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< \frac{p}{\rho} \right> \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ -& + \int_A \left[ \left< \frac{p}{\rho} \nabla \tilde{z}_k^{\text{top}} \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA -- \int_A \left[ \left< \frac{p}{\rho} \nabla \tilde{z}_k^{\text{bot}} \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \, \left< {\bf f} \times \mathbf{u} + \frac{1}{\rho} \nabla p + \nabla \Phi \right> \, d\tilde{z} \, dA. $$ (vh-momentum-reynolds1) Here we have also moved the Reynolds' average through the spatial integrals given the properties of the averaging. Next we do a Reynolds' decomposition, this yields @@ -560,49 +589,37 @@ $$ & + \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left( \left< {\bf u} \right> \otimes \left< {\bf u} \right> + \left< {\bf u}^\prime \otimes {\bf u}^\prime \right> \right) \, d\tilde{z} \right) \cdot \mathbf{n}_\perp dl \, dl \\ & + \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & - \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ -& = - \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \, \left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right) \, d\tilde{z} \, dA \\ -& - \int_{\partial A} \left( \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left(\left< \alpha \right> \left

+ \left<\alpha^\prime p^\prime\right> \right) \, d\tilde{z} \right) \mathbf{n}_\perp dl \\ -& + \int_A \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}}\right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& - \int_A \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}}\right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& = - \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \, \left( {\bf f} \times \left<\mathbf{u}\right> + \left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right> \right) \, d\tilde{z} \, dA. $$ (vh-momentum-reynolds2) -In [](#vh-momentum-reynolds2) we have also used $\alpha = \frac{1}{\rho}$ for notation conciseness. The definition of the layer average [](#def-layer-average-reynolds) is now utilized on terms with vertical integrals to yield +In [](#vh-momentum-reynolds2), we have also used $\alpha = \frac{1}{\rho}$ for notation conciseness. The definition of the layer average [](#def-layer-average-reynolds) is now utilized on terms with vertical integrals to yield $$ \frac{d}{dt} \int_A \tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \, dA & + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right> \otimes \left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot {\bf n}_\perp \, dl \\ & + \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & - \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ -& = - \int_A \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA \\ -& - \int_{\partial A} \tilde{h}_k \left( \overline{\left< \alpha \right> \left

}^{\tilde{z}}_k + \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \mathbf{n}_\perp dl \\ -& + \int_A \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& - \int_A \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& = - \int_A \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA. $$ (vh-momentum-reynolds-lay-avg) -The next step is to decompose vertical averages of products. This yields +The next step is to decompose vertical averages of products. However we retain the PGF terms as a product because approximating them as products of vertical averages can lead to large inaccuracies. The result is $$ \frac{d}{dt} \int_{A} \tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \, dA & + \int_{\partial A} \tilde{h}_k \left( \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \cdot {\bf n}_\perp \, dl \\ & + \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ & - \int_A \, \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA \\ -& = - \int_A \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA \\ -& - \int_{\partial A} \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \mathbf{n}_\perp dl \\ -& + \int_A \left[\left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \, dA \\ -& - \int_A \left[\left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \, dA. +& = - \int_A \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \, dA. $$ (vh-momentum-reynolds-lay-avg2) We next use the Divergence theorem on the surface integrals and combine terms into fewer integrals on each side of the equation. $$ -\int_A \bigl\{ \frac{\partial\tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k}{\partial t} +\int_A \Bigl\{ \frac{\partial\tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k}{\partial t} & + \nabla \cdot \left[ \tilde{h}_k \left(\overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ & + \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& - \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \bigr\} \, dA \\ -& = - \int_A \bigl\{ \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ -& - \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& + \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& - \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}\bigr\} \, dA. +& - \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \Bigr\} \, dA \\ +& = - \int_A \left\{ \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \right\} \, dA. $$ (vh-momentum-reynolds-lay-avg3) Since the equation is fully inside the integral, the equation is true for any area and therefore we can write the layer averaged momentum equation as @@ -612,10 +629,7 @@ $$ & + \nabla \cdot \left[ \tilde{h}_k \left( \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \otimes \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ & + \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ & - \left[ \left(\left< {\bf u} \right> \left< \tilde{w}_{tr} \right> + \left<{\bf u}^\prime \tilde{w}_{tr}^\prime \right> \right) - \left(\left<{\bf u}\right> \left<\tilde{\bf u}\right> + \left< {\bf u}^\prime \tilde{\bf u}^\prime \right> \right) \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \\ -& = - \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ -& - \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& + \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \cdot \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& - \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \cdot \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. +& = - \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k. $$ (vh-momentum-v1) The product rule is used on the first two terms of [](#vh-momentum-v1) and then we multiply [](#layer-mass) by $\overline{\mathbf{u}}^{\tilde{z}}_k$ and subtract it from [](#vh-momentum-v1). This yields @@ -625,10 +639,7 @@ $$ & + \tilde{h}_k \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \cdot \nabla \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \nabla \cdot \left[ \tilde{h}_k \left(\overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ & + \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{ \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ & + \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& = - \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ -& - \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& + \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& - \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. +& = - \, \tilde{h}_k \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k. $$ (vh-momentum-v2) The previous equation is divided by $\tilde{h}_k$, @@ -638,10 +649,7 @@ $$ & + \overline{\left< {\bf u} \right>}^{\tilde{z}}_k \cdot \nabla \overline{\left< {\bf u} \right>}^{\tilde{z}}_k + \frac{1}{\tilde{h}_k} \nabla \cdot \left[ \tilde{h}_k \left(\overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ & + \frac{1}{\tilde{h}_k} \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{ \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ & + \frac{1}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& = - \, \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k \\ -& - \frac{1}{\tilde{h}_k} \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& + \frac{1}{\tilde{h}_k} \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} \\ -& - \frac{1}{\tilde{h}_k} \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}}. +& = - \, \overline{\left( {\bf f} \times \left<\mathbf{u}\right> + \left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right> \right)}^{\tilde{z}}_k. $$ (vh-momentum-v3) @@ -674,9 +682,7 @@ $$ & + \zeta_a {\overline{\left<{\bf u}\right>}^{\tilde{z}}_k}^{\perp} + \nabla K + \frac{1}{\tilde{h}_k} \nabla \cdot \left[ \tilde{h}_k \left(\overline{\delta {\bf u} \otimes \delta {\bf u}}^{\tilde{z}}_k + \overline{\left< {\bf u}^\prime \otimes {\bf u}^\prime \right>}^{\tilde{z}}_k \right) \right] \\ & + \frac{1}{\tilde{h}_k} \left\{ \left[\left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{\left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left(\left<\mathbf{u}\right> - \overline{\left<\mathbf{u}\right>}^{\tilde{z}}_k\right) \left\{\left<\tilde{w}_{tr}\right> - \left<\tilde{\bf u}\right> \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ & + \frac{1}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{\bf u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& = - \, \overline{\nabla \left<\Phi\right>}^{\tilde{z}}_k - \frac{1}{\tilde{h}_k} \nabla \left[ \tilde{h}_k \left( \overline{\left< \alpha \right>}^{\tilde{z}}_k \overline{\left

}^{\tilde{z}}_k + \overline{\delta \alpha \delta p}^{\tilde{z}}_k+ \overline{\left<\alpha^\prime p^\prime\right>}^{\tilde{z}}_k \right) \right] \\ -& + \frac{1}{\tilde{h}_k} \left\{ \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{top}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} -- \left[ \left< \alpha \right> \left

+ \left<\alpha^\prime \left(p \nabla \tilde{z}_k^{\text{bot}} \right)^\prime\right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\}. +& = - \, \overline{\left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right>}^{\tilde{z}}_k. $$ (layer-momentum-final) The $\delta X$ terms in the layered equations are vertical deviations from the vertical layer average of a given quantity $X$. We will assume these deviations are small and products of these deviations are even smaller. Thus we will ignore most of these terms in Omega. The exception is the pressure gradient force. At this time we will assume piecewise constant approximations of the vertically continuous system, which is appropriate for the simple pressure gradient force targeted for early versions of Omega. This assumption will be revisited at a later date. We also note that these terms could potentially serve as a bridge to multiscale fluxes, as resolution is increased, these $\delta X$ terms would get larger, but likely only for significantly higher resolution (e.g. 10s of meters). However, these terms would have to be further analyzed and developed as these terms are only deviations from layer averages, not temporal averages as in the Reynolds' approach. @@ -717,11 +723,11 @@ $$ & + \zeta_a {{\bf u}_k}^{\perp} + \nabla K + \frac{1}{\tilde{h}_k} \nabla \cdot \left[ \tilde{h}_k \left< {\bf u}^\prime \otimes {\bf u}^\prime \right>_k \right] \\ & + \frac{1}{\tilde{h}_k} \left\{ \left[\left(\mathbf{u} - \mathbf{u}_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left(\mathbf{u} - \mathbf{u}_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ & + \frac{1}{\tilde{h}_k} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\} \\ -& = - \left(\nabla \Phi \right)_k - \frac{1}{\tilde{h}_k} \nabla \left[ \tilde{h}_k \alpha_k p_k \right] \\ -& + \frac{1}{\tilde{h}_k} \left\{ \left[ \alpha p \nabla \tilde{z}_k^{\text{top}}\right]_{\tilde{z} = \tilde{z}_k^{\text{top}}} -- \left[ \alpha p \nabla \tilde{z}_k^{\text{bot}}\right]_{\tilde{z} = \tilde{z}_k^{\text{bot}}} \right\}. +& = - \left(\alpha \nabla p + \nabla \Phi \right)_k. $$ (layer-momentum-final-simple) +**XSAD: why do we drop $\left< \alpha' \nabla p' \right>$??** + In [](#layer-tracer-final-simple) and [](#layer-momentum-final-simple) we have not combined turbulent flux terms (e.g., $\left<\varphi^\prime \tilde{W}_{tr}^\prime \right>$) as the two terms that comprise this term ($\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right>$ and $\left< \varphi^\prime \tilde{ u}^{\prime}\right>$) are fundamentally different processes that must be modeled separately. ## 10. Discrete Equations @@ -757,8 +763,8 @@ $$ \frac{\partial u_{e,k}}{\partial t} & + \left[ {\bf k} \cdot \nabla \times u_{e,k} +f_v\right]_e\left(u_{e,k}^{\perp}\right) + \left[\nabla K\right]_e \\ & + \frac{1}{\left[\tilde{h}_{i,k}\right]_e} \left\{ \left[\left(u - u_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{e,k}^\text{top} - \left[ \left(u - u_k\right) \left\{\tilde{W}_{tr} \right\} \right]_{e,k+1}^\text{top} \right\} \\ -& = - \left(\nabla \Phi \right)_{e,k} - \frac{1}{\left[\tilde{h}_k\right]_e} \nabla \left( \tilde{h}_k \alpha_k p_k \right) + \frac{1}{\left[\tilde{h}_k\right]_e} \left\{ \left[ \alpha p \nabla \tilde{z}^{\text{top}}\right]_{e,k}^\text{top} - \left[ \alpha p \nabla \tilde{z}^{\text{bot}}\right]_{e,k+1}^\text{top} \right\} \\ -& - \frac{1}{\left[\tilde{h}_{i,k}\right]_e} \nabla \cdot \left( \tilde{h}_k \left< {\bf u}^\prime \otimes {\bf u}^\prime \right>_k \right) - \frac{1}{\left[\tilde{h}_{i,k}\right]_e^\text{top}} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{e,k}^\text{top} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{e,k+1}^\text{top} \right\}. +& = - \left(\alpha \nabla p + \nabla \Phi \right)_{e,k} - \frac{1}{\left[\tilde{h}_{i,k}\right]_e} \nabla \cdot \left( \tilde{h}_k \left< {\bf u}^\prime \otimes {\bf u}^\prime \right>_k \right) \\ +& - \frac{1}{\left[\tilde{h}_{i,k}\right]_e^\text{top}} \left\{ \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{e,k}^\text{top} - \left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> - \left< \mathbf{u}^\prime \tilde{ u}^\prime \right> \right]_{e,k+1}^\text{top} \right\}. $$ (discrete-momentum) **Diagnostic Relations:** From 34de80f14427faeb2daf64b5fd5ed6e9b20cbe7a Mon Sep 17 00:00:00 2001 From: Luke Van Roekel Date: Wed, 25 Feb 2026 10:29:20 -0700 Subject: [PATCH 046/152] Updates related to turbulence quantities This removes confusing description related to Reynolds' averaging and discusses instead resolved and unresolved through projection operators this removes unecessary dependence on specific averaging. It also adds description as to why the alpha' grad p' term is ignored The description of the vertical viscosity sections is cleaned up --- .../omega/doc/design/OmegaV1GoverningEqns.md | 83 +++++++++++++------ 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/components/omega/doc/design/OmegaV1GoverningEqns.md b/components/omega/doc/design/OmegaV1GoverningEqns.md index c067a35214a0..abf59d03f835 100644 --- a/components/omega/doc/design/OmegaV1GoverningEqns.md +++ b/components/omega/doc/design/OmegaV1GoverningEqns.md @@ -355,45 +355,63 @@ This relation is frequently used in discretized fluxes and conservation equation ### Decomposition -In this document, two decompositions will be critical. The first decomposition is +In this document, two decompositions will be critical. + +The first decomposition is $$ \varphi(x,y,z,t) = \overline{\varphi}^{\tilde{z}}_k(x,y,t) + \delta \varphi(x,y,z,t) $$ (vertical-decomposition) -which is a density weighted vertical integral based upon [](#def-layer-average) and the deviation from this value within the layer. +which is a density-weighted vertical layer average based upon [](#def-layer-average) and the deviation from this value within the layer. -The second decomposition is +The second decomposition separates each field into a resolved and unresolved component, $$ -\varphi = \left<\varphi\right> + \varphi^\prime -$$ (reynolds-definition) +\varphi = \left<\varphi\right> + \varphi^\prime, +$$ (resolved-unresolved-definition) + +where: + +- $\left<\varphi\right>$ denotes the **resolved (layer-mean / grid-resolved) component**, obtained by projection onto the resolved space, +- $\varphi^\prime$ denotes the **unresolved component**, defined such that + $$ + \left<\varphi^\prime\right> = 0. + $$ -which is the traditional Reynolds' average and deviation from this value. +This decomposition is introduced to identify subgrid-scale (SGS) fluxes that require parameterization. It should be interpreted as a resolved/unresolved (coarse-grained) split consistent with the model’s prognostic layer-averaged variables, rather than strictly as a time or ensemble Reynolds average. -The fundamental ocean model equations are most often Reynolds' averaged to derive the sub gridscale stresses. Each variable is decomposed into an average over a sufficiently large sample of turbulent processes, which allows for a meaningful fluctuating component [[](#reynolds-definition)]. Commonly, the Reynolds' average is thought of as a time average, but an ensemble average is equally valid. In fact, the ensemble and time averaging can be thought of as equivalent, given that a sufficiently long time average will effectively average over variations in the flow that could be thought of as an ensemble. Given the functions are continuous, the averaging operator can be passed through derivatives and integrals without corrections and an average of products with a single perturbation quantity is zero. +The projection operator is linear and commutes with spatial derivatives and integrals in the same manner assumed for Reynolds averaging. Products satisfy -The Reynolds' average is most commonly denoted by an overbar. However, to disambiguate from the definition of the bar as the vertical density weighted average, the Reynolds' average herein is denoted by $< . >$. +$$ +\left< \varphi \psi \right> += +\left< \varphi \right>\left< \psi \right> ++ +\left< \varphi^\prime \psi^\prime \right>, +$$ -The Reynolds' approach is an attractive approach for Boussinesq ocean models since the fundamental equations do not include products of spatially variable density and tracer, pressure, or momentum. When the ocean model is non Boussinesq, products of spatially varying density and other fields (e.g., tracer) arise and create difficulties, producing a term like $\left<\rho^\prime {\mathbf u}^\prime\right>$ which is difficult to parameterize. However, given the definition of our chosen pseudo-height, the density terms are wrapped up in $\tilde{z}$ and once again a Reynolds' approach can be cleanly used. +which serves to expose SGS flux terms. ### Averaging -The quantity being averaged in [](#def-layer-average) is arbitrary. For example, this equation can apply equally to a Reynolds' averaged quantity, e.g., +The quantity being averaged in [](#def-layer-average) is arbitrary. For example, the layer average may be applied to resolved quantities, $$ \overline{\left<\varphi\right>}^{\tilde{z}}_k(x,y,t) = \frac{1}{\tilde{h}_k} \int_{\tilde{z}_{k}^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left<\varphi\right> d\tilde{z}. $$ (def-layer-average-reynolds) -Additionally, for the decomposition related to vertical averaging, we will encounter terms such as ${\overline \varphi}^{\tilde{z}}_k \delta\varphi$ that need to be vertical averaged. Using [](#def-layer-average) we have +Additionally, for the decomposition related to vertical averaging, we will encounter terms such as ${\overline \varphi}^{\tilde{z}}_k \delta\varphi$ that need to be vertically averaged. Using [](#def-layer-average) we have $$ -\overline{{\overline \varphi}^{\tilde{z}}_k \delta\varphi}^{\tilde{z}}_k &= \frac{\int_{{\tilde z}_{k}^{\text{bot}}}^{{\tilde z}_k^{\text{top}}} {\overline \varphi}^{\tilde{z}}_k \delta \varphi d{\tilde z}} - {\int_{{\tilde z}_{k}^{\text{bot}}}^{{\tilde z}_k^{\text{top}}} d{\tilde z}} \\ - &= {\overline \varphi}^{\tilde{z}}_k \frac{\int_{{\tilde z}_{k}^{\text{bot}}}^{{\tilde z}_k^{\text{top}}} \delta \varphi d{\tilde z}} - {\int_{{\tilde z}_{k}^{\text{bot}}}^{{\tilde z}_k^{\text{top}}} d{\tilde z}} \\ - &= 0, +\overline{{\overline \varphi}^{\tilde{z}}_k \delta\varphi}^{\tilde{z}}_k += +{\overline \varphi}^{\tilde{z}}_k +\frac{\int_{{\tilde z}_{k}^{\text{bot}}}^{{\tilde z}_k^{\text{top}}} \delta \varphi d{\tilde z}} + {\int_{{\tilde z}_{k}^{\text{bot}}}^{{\tilde z}_k^{\text{top}}} d{\tilde z}} += +0, $$ (delta-vert-average) where the last equality is true by definition. @@ -402,7 +420,8 @@ where the last equality is true by definition. ### Tracer & Mass -We first Reynolds' average [](#vh-tracer-pseudo) and given the definition of the operator we can move the averaging past the derivatives and integrals without correction terms to yield +We first apply the resolved/unresolved decomposition to [](#vh-tracer-pseudo). +Because the projection operator is linear and commutes with spatial derivatives and integrals, we may move it through the spatial integrals without correction terms to yield $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left<\varphi\right> \, d\tilde{z} \, dA @@ -572,7 +591,7 @@ $$ & = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left({\bf f} \times \mathbf{u} + \frac{1}{\rho} \nabla p + \nabla \Phi \right) \, d\tilde{z} \, dA. $$ (vh-momentum-forces) -As with the tracer derivation, we next Reynolds' average [](#vh-momentum-forces), +As with the tracer derivation, we next apply [](#resolved-unresolved-definition) to [](#vh-momentum-forces) to identify subgrid-scale (SGS) fluxes, $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \right> \, d\tilde{z} \, dA @@ -582,7 +601,7 @@ $$ & = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \, \left< {\bf f} \times \mathbf{u} + \frac{1}{\rho} \nabla p + \nabla \Phi \right> \, d\tilde{z} \, dA. $$ (vh-momentum-reynolds1) -Here we have also moved the Reynolds' average through the spatial integrals given the properties of the averaging. Next we do a Reynolds' decomposition, this yields +Here we have also moved the Reynolds' average through the spatial integrals given the properties of the averaging. Next we decompose the nonlinear products into resolved and unresolved components, this yields $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \right> \, d\tilde{z} \, dA @@ -685,6 +704,8 @@ $$ & = - \, \overline{\left< \alpha \right> \nabla \left< p \right> + \left< \alpha' \nabla p' \right> + \nabla \left<\Phi\right>}^{\tilde{z}}_k. $$ (layer-momentum-final) +The term $\left< \alpha' \nabla p' \right>$ arises from decomposing the nonlinear product $\alpha \nabla p$ into resolved and unresolved components. In the present formulation, we do not introduce a separate parameterization for this term. It is assumed to be small relative to the resolved pressure–gravity acceleration under weak density variability and small dynamic pressure fluctuations. The pressure gradient force is instead computed directly from the hydrostatic pressure and equation of state, consistent with the finite-volume discretization. + The $\delta X$ terms in the layered equations are vertical deviations from the vertical layer average of a given quantity $X$. We will assume these deviations are small and products of these deviations are even smaller. Thus we will ignore most of these terms in Omega. The exception is the pressure gradient force. At this time we will assume piecewise constant approximations of the vertically continuous system, which is appropriate for the simple pressure gradient force targeted for early versions of Omega. This assumption will be revisited at a later date. We also note that these terms could potentially serve as a bridge to multiscale fluxes, as resolution is increased, these $\delta X$ terms would get larger, but likely only for significantly higher resolution (e.g. 10s of meters). However, these terms would have to be further analyzed and developed as these terms are only deviations from layer averages, not temporal averages as in the Reynolds' approach. (notational-simplifications)= @@ -692,7 +713,7 @@ The $\delta X$ terms in the layered equations are vertical deviations from the v Throughout the rest of this document, we will -1. Drop the $< >$ notation around single variables and note that all variables are assumed to be Reynolds' averaged. Turbulent correlations will retain the angle bracket notation. +1. Drop the $< >$ notation around single variables and interpret all prognostic variables as resolved (layer-mean) quantities. Covariance terms of the form $\left< X' Y' \right>$ denote unresolved (subgrid-scale) fluxes that must be parameterized. 2. Change the vertical density weighted average notation from $\overline{\varphi}^{\tilde{z}}_k$ to the more simple $\varphi_k$. Thus, any subscript $k$ implies a layer average. 3. Define a total vertical velocity across the pseudo height surface as $\tilde{W}_{tr} \equiv \tilde{w}_{tr} - \tilde{u}$. As a reminder $\tilde{u}$ is the projection of the normal velocity onto the normal vector to the pseudo height surface, in many cases this can be a very small correction to $\tilde{w}_{tr}$. @@ -726,8 +747,6 @@ $$ & = - \left(\alpha \nabla p + \nabla \Phi \right)_k. $$ (layer-momentum-final-simple) -**XSAD: why do we drop $\left< \alpha' \nabla p' \right>$??** - In [](#layer-tracer-final-simple) and [](#layer-momentum-final-simple) we have not combined turbulent flux terms (e.g., $\left<\varphi^\prime \tilde{W}_{tr}^\prime \right>$) as the two terms that comprise this term ($\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right>$ and $\left< \varphi^\prime \tilde{ u}^{\prime}\right>$) are fundamentally different processes that must be modeled separately. ## 10. Discrete Equations @@ -845,18 +864,30 @@ $$ in this relation, we have moved the subscript $k$ off the variable itself to prevent confusion with the layer average. With this definition, [](#discrete-mom-flux-sloping) goes to zero for flat layer surfaces. #### Vertical velocity dissipation -The vertical turbulent momentum stress is most commonly parameterized as a down-gradient process, i.e., +The vertical turbulent momentum stress is most commonly parameterized as a down-gradient process, which for a z-coordinate model is given as, $$ -\left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> \right]_{e,k} = -\frac{\nu_v \rho}{\rho_0} \left[\frac{\partial u}{\partial \tilde{z}}\right]_{e,k} +\left = -\nu_v \frac{\partial u}{\partial z} $$ +where $\nu_v$ is the parameterized vertical viscosity. + +For our pseudo-height coordinate the momentum stress is, + +$$ +\left[ \left<\mathbf{u}^\prime \tilde{w}_{tr}^\prime \right> \right]_{e,k} = -\tilde{\nu_v} \left[\frac{\partial u}{\partial \tilde{z}}\right]_{e,k} +$$ + +where $\tilde{\nu}_v \equiv \frac{\nu_v \rho}{\rho_0}$. + Plugging this relation into the last part of [](#discrete-momentum) $$ -\frac{1}{\left[\tilde{h}_{i,k}\right]_{e,k}} \left\{ \left[ \nu_v \left[\frac{\partial u}{\partial \tilde{z}}\right]_{e,k} \right]_{e,k} - \left[ \nu_v \left[\frac{\partial u}{\partial \tilde{z}}\right]_{e,k} \right]_{e,k+1} \right\} +-\frac{1}{\left[\tilde{h}_k\right]_e} \left\{ \left[\left \right]_{e,k} - \left[\left \right]_{e,k+1} \right\} = \frac{1}{\left[\tilde{h}_{i,k}\right]_{e,k}} \left\{ \left[ \tilde{\nu_v} \right]_{e,k} \left[\frac{\partial u}{\partial \tilde{z}}\right]_{e,k} - \left[ \tilde{\nu_v} \right]_{e,k+1} \left[\frac{\partial u}{\partial \tilde{z}}\right]_{e,k} \right\} $$ (discrete-mom-vert-diff) +Here $\left[ \tilde{\nu_v} \right]_{e,k} $ is the vertical viscosity interpolated to a given edge. + ##### Vertical derivatives in a finite volume framework Since, Omega will predict layer average quantities (e.g., $u_k$), it's not immediately clear that a traditional discretization is appropriate as there is a discontinuity in the predicted variables at layer interfaces. To circumvent this problem, we turn to weak derivatives instead of traditional pointwise forms. A weak derivative of $\varphi$ is defined as @@ -900,7 +931,7 @@ where $0.5 \left(\tilde{h}_k + \tilde{h}_{k+1}\right)$ is the distance between $ With this, we can now fully discretize [](#discrete-mom-vert-diff) as $$ --\frac{1}{\left[\tilde{h}_k\right]_e} \left\{ \left[\left \right]_{e,k} - \left[\left \right]_{e,k+1} \right\} = -\frac{1}{\left[\tilde{h}_k\right]_e} \left\{ \frac{\left(u_{e,k-1} - u_{e,k}\right)}{0.5 \left(\tilde{h}_{k-1} + \tilde{h}_{k}\right)} - \frac{\left(u_{e,k} - u_{e,k+1}\right)}{0.5 \left(\tilde{h}_k + \tilde{h}_{k+1}\right)} \right\}. +-\frac{1}{\left[\tilde{h}_k\right]_e} \left\{ \left[\left \right]_{e,k} - \left[\left \right]_{e,k+1} \right\} = -\frac{1}{\left[\tilde{h}_k\right]_{e,k}} \left\{ \left[\tilde{\nu_v} \right]_{e,k} \frac{\left(u_{e,k-1} - u_{e,k}\right)}{0.5 \left(\tilde{h}_{k-1} + \tilde{h}_{k}\right)} - \left[\tilde{\nu_v} \right]_{e,k+1} \frac{\left(u_{e,k} - u_{e,k+1}\right)}{0.5 \left(\tilde{h}_k + \tilde{h}_{k+1}\right)} \right\}. $$ (final-vert-vel-dissipation) This form can be interfaced with the Omega [tridiagonal solver](TridiagonalSolver.md) routine. From 348ec17644d36038a3b81932cb389baba11fc1e5 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Sat, 27 Dec 2025 12:43:53 -0800 Subject: [PATCH 047/152] VertAdv module definition; creation, destruction, retrieval methods --- components/omega/src/ocn/VertAdv.cpp | 207 +++++++++++++++++++++++++++ components/omega/src/ocn/VertAdv.h | 138 ++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 components/omega/src/ocn/VertAdv.cpp create mode 100644 components/omega/src/ocn/VertAdv.h diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp new file mode 100644 index 000000000000..4ae65d1d94e3 --- /dev/null +++ b/components/omega/src/ocn/VertAdv.cpp @@ -0,0 +1,207 @@ +//===-- ocn/VertAdv.cpp - vertical advection --------------------*- C++ -*-===// +// +//===----------------------------------------------------------------------===// + +#include "Tracers.h" +#include "VertAdv.h" + +namespace OMEGA { + +// create static class members +VertAdv *VertAdv::DefaultVertAdv = nullptr; +std::map> VertAdv::AllVertAdvs; + +//------------------------------------------------------------------------------ +// create the default VertAdv, requires prior initialization of default +// HorzMesh and VertCoord +void VertAdv::init() { + + auto Mesh = HorzMesh::getDefault(); + auto VCoord = VertCoord::getDefault(); + + Config *OmegaConfig = Config::getOmegaConfig(); + + VertAdv::DefaultVertAdv = create("Default", Mesh, VCoord, OmegaConfig); + +} // end init + +//------------------------------------------------------------------------------ +// constructor +VertAdv::VertAdv(const std::string &Name_, ///< [in] name for new VertAdv + const HorzMesh *InMesh, ///< [in] associated HorzMesh + const VertCoord *InVCoord, ///< [in] associated VertCoord + Config *Options ///< [in] configuration options +) { + + // Read options from Config object + readConfigOptions(Options); + + // Store name suffix + Name = Name_; + + // Store pointers to Mesh and VertCoord objects + Mesh = InMesh; + VCoord = InVCoord; + + // Store dimension sizes + NVertLayers = VCoord->NVertLayers; + NVertLayersP1 = VCoord->NVertLayersP1; + NCellsOwned = Mesh->NCellsOwned; + NCellsAll = Mesh->NCellsAll; + NCellsSize = Mesh->NCellsSize; + NEdgesOwned = Mesh->NEdgesOwned; + NEdgesAll = Mesh->NEdgesAll; + NEdgesSize = Mesh->NEdgesSize; + NTracers = Tracers::getNumTracers(); + + // Allocate member arrays + VerticalVelocity = + Array2DReal("VerticalVelocity", NCellsSize, NVertLayersP1); + TotalVerticalVelocity = + Array2DReal("TotalVerticalVelocity", NCellsSize, NVertLayersP1); + VertFlux = Array3DReal("VertFlux", NTracers, NCellsSize, NVertLayersP1); + + // Low-order flux array only needed for flux-corrected transport + if (VertAdvChoice == VertAdvOption::FCT) { + LowOrderVertFlux = + Array3DReal("LowOrderVertFlux", NTracers, NCellsSize, NVertLayersP1); + } + +} // end constructor + +//------------------------------------------------------------------------------ +// create a new VertAdv instance +VertAdv *VertAdv::create(const std::string &Name, ///< [in] name for new VertAdv + const HorzMesh *Mesh, ///< [in] associated HorzMesh + const VertCoord *VCoord, ///< [in] associated VertCoord + Config *Options ///< [in] configuration options +) { + // Check to see if a VertAdv of the same name already exists and, if so, + // exit with an error + if (AllVertAdvs.find(Name) != AllVertAdvs.end()) { + LOG_ERROR("Attempted to create a VertAdv with name {} but a VertAdv " + "of that name already exists", + Name); + return nullptr; + } + + // create a new VertAdv on the heap and put it in a map of unique_ptrs, + // which will manage its lifetime + auto *NewVertAdv = new VertAdv(Name, Mesh, VCoord, Options); + AllVertAdvs.emplace(Name, NewVertAdv); + + return NewVertAdv; + +} // end create + +//------------------------------------------------------------------------------ +// destructor +VertAdv::~VertAdv() {} // end destructor + +//------------------------------------------------------------------------------ +// Removes all VertAdvs to clean up before exit +void VertAdv::clear() { + + AllVertAdvs.clear(); // removes all VertAdvs from the list and in the + // process, calls the destructors for each + +} // end clear + +//------------------------------------------------------------------------------ +// Removes a VertAdv from map by name +void VertAdv::erase(std::string Name) { + AllVertAdvs.erase(Name); // removes the VertAdv from the list and in the + // process, calls the destructor +} // end erase + +//------------------------------------------------------------------------------ +// Get default VertAdv +VertAdv *VertAdv::getDefault() { return VertAdv::DefaultVertAdv; } + +//------------------------------------------------------------------------------ +// Get VertAdv by name +VertAdv *VertAdv::get(const std::string Name ///< [in] Name of VertAdv +) { + + // look for an instance of this name + auto it = AllVertAdvs.find(Name); + + // if found, return the VertAdv pointer + if (it != AllVertAdvs.end()) { + return it->second.get(); + + // otherwise print error and return null pointer + } else { + LOG_ERROR("VertAdv::get: Attempt to retrieve non-existant VertAdv:"); + LOG_ERROR("{} has not been defined or has been removed", Name); + return nullptr; + } + +} // end get VertAdv + +// Read and set config options +void VertAdv::readConfigOptions(Config *OmegaConfig) { + + Error Err; // Error code + + Config TendConfig("Tendencies"); + Err += OmegaConfig->get(TendConfig); + CHECK_ERROR_ABORT(Err, "VertAdv: Tendencies group not in Config"); + + Err += TendConfig.get("ThicknessVertAdvTendencyEnable", + this->ThickVertAdvEnabled); + CHECK_ERROR_ABORT( + Err, "VertAdv: ThicknessVertAdvTendencyEnable not found in TendConfig"); + + Err += + TendConfig.get("VelocityVertAdvTendencyEnable", this->VelVertAdvEnabled); + CHECK_ERROR_ABORT( + Err, "VertAdv: ThicknessVertAdvTendencyEnable not found in TendConfig"); + + Err += TendConfig.get("TracerVertAdvTendencyEnable", + this->TracerVertAdvEnabled); + CHECK_ERROR_ABORT( + Err, "VertAdv: ThicknessVertAdvTendencyEnable not found in TendConfig"); + + Config AdvectConfig("Advection"); + Err += OmegaConfig->get(AdvectConfig); + CHECK_ERROR_ABORT(Err, "VertAdv: Advection group not in Config"); + + bool FluxLimiterOn; + Err += AdvectConfig.get("VerticalTracerFluxLimiterEnabled", FluxLimiterOn); + CHECK_ERROR_ABORT( + Err, + "VertAdv: VerticalTracerFluxLimiterEnabled not found in AdvectConfig"); + if (FluxLimiterOn) { + VertAdvChoice = VertAdvOption::FCT; + } else { + VertAdvChoice = VertAdvOption::Standard; + } + + I4 VertFluxOrder; + Err += AdvectConfig.get("VerticalTracerFluxOrder", VertFluxOrder); + CHECK_ERROR_ABORT( + Err, "VertAdv: VerticalTracerFluxOrder not found in AdvectConfig"); + + switch (VertFluxOrder) { + case (2): + VertFluxChoice = VertFluxOption::Second; + break; + case (3): + VertFluxChoice = VertFluxOption::Third; + break; + case (4): + VertFluxChoice = VertFluxOption::Fourth; + break; + default: + ABORT_ERROR("VertAdv: Invalid option for VerticalTracerFluxOrder found " + "in AdvectConfig. Must be 2, 3, or 4"); + } + + Err += AdvectConfig.get("Coef3rdOrder", Coef3rdOrder); + CHECK_ERROR_ABORT(Err, "VertAdv: Coef3rdOrder not found in AdvectConfig"); + +} // end readConfigOptions + +} // end namespace OMEGA +//===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h new file mode 100644 index 000000000000..04f780fca123 --- /dev/null +++ b/components/omega/src/ocn/VertAdv.h @@ -0,0 +1,138 @@ +#ifndef OMEGA_VERTADV_H +#define OMEGA_VERTADV_H +//===-- ocn/VertAdv.h - vertical advection ----------------------*- C++ -*-===// +// +//===----------------------------------------------------------------------===// + +#include "AuxiliaryState.h" +#include "Config.h" +#include "DataTypes.h" +#include "Error.h" +#include "HorzMesh.h" +#include "OceanState.h" +#include "OmegaKokkos.h" +#include "TimeMgr.h" +#include "VertCoord.h" + +#include +#include +#include + +namespace OMEGA { + +// Enum for flux types +enum class VertFluxOption { Second, Third, Fourth }; +// Enum for advection scheme type +enum class VertAdvOption { Standard, FCT }; + +class VertAdv { + + public: + // Logicals to enable/disable advection of specific fields + bool ThickVertAdvEnabled = false; + bool VelVertAdvEnabled = false; + bool TracerVertAdvEnabled = false; + + // enums storing options chosen by user + VertFluxOption VertFluxChoice; + VertAdvOption VertAdvChoice; + + // Mesh dimensions + I4 NVertLayers; + I4 NVertLayersP1; + I4 NCellsOwned; + I4 NCellsAll; + I4 NCellsSize; + I4 NEdgesOwned; + I4 NEdgesAll; + I4 NEdgesSize; + + // Number of tracers + I4 NTracers; + + // Coefficient for blending 3rd-order and 4th-order reconstruction of tracers + Real Coef3rdOrder; + + // VertAdv instance name + std::string Name; + + Array2DReal VerticalVelocity; // pseudovelocity through top of cell + Array2DReal TotalVerticalVelocity; // transport velocity through top of Cell + Array3DReal VertFlux; // fluxes at vertical interfaces + Array3DReal LowOrderVertFlux; // low-order fluxes for FCT + + // public methods + + // Initialize the default VertAdv instance + static void init(); + + /// Creates a new vertical advection object by calling the constructor and + /// puts it in the AllVertAdvs map. + static VertAdv *create(const std::string &Name, // [in] name for new VertAdv + const HorzMesh *Mesh, // [in] associated HorzMesh + const VertCoord *VCoord, // [in] associated VertCoord + Config *Options // [in] configuration options + ); + + /// Destructor - deallocates all memory and deletes a VertAdv + ~VertAdv(); + + /// Deallocates arrays + static void clear(); + + /// Remove a VertAdv by name + static void erase(std::string InName); + + /// Retrieve the default VertAdv + static VertAdv *getDefault(); + + /// Retreive a VertAdv by name + static VertAdv *get(std::string name); + + /// Read and set config options + void readConfigOptions(Config *Options); + + private: + // Vertical loop bounds from VertCoord + Array1DI4 MinLayerCell; + Array1DI4 MaxLayerCell; + Array1DI4 MinLayerEdgeBot; + Array1DI4 MaxLayerEdgeTop; + + // Arrays from HorzMesh + Array2DI4 CellsOnEdge; + Array2DI4 EdgesOnCell; + Array1DI4 NEdgesOnCell; + Array1DReal AreaCell; + Array1DReal DvEdge; + Array2DReal EdgeSignOnCell; + + // small number to avoid numerical difficulties in computing + // scale factors for FCT. + Real Eps = 1e-10; + + const HorzMesh *Mesh; + const VertCoord *VCoord; + + static VertAdv *DefaultVertAdv; + static std::map> AllVertAdvs; + + // private methods + + /// construct a new vertical coordinate object + VertAdv(const std::string &Name, // [in] Name for new VertAdv + const HorzMesh *Mesh, // [in] associated HorzMesh + const VertCoord *VCoord, // [in] associated VertCoord + Config *Options // [in] configuration options + ); + + // Forbid copy and move construction + VertAdv(const VertAdv &) = delete; + VertAdv(VertAdv &&) = delete; + +}; // end class VertAdv + +} // end namespace OMEGA + +//===----------------------------------------------------------------------===// +#endif // defined OMEGA_VERTICALADV_H From 9ddddce3d53e9cdeea090e1bd2515a0eba0f2ffb Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Sat, 27 Dec 2025 13:27:46 -0800 Subject: [PATCH 048/152] Add optional per-team scratch space to parallelForOuter --- components/omega/src/infra/OmegaKokkos.h | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/components/omega/src/infra/OmegaKokkos.h b/components/omega/src/infra/OmegaKokkos.h index 5a3eb0b82499..a7ab8bca89da 100644 --- a/components/omega/src/infra/OmegaKokkos.h +++ b/components/omega/src/infra/OmegaKokkos.h @@ -78,6 +78,8 @@ using ScratchMemSpace = ExecSpace::scratch_memory_space; using Kokkos::MemoryUnmanaged; using Kokkos::PerTeam; using Kokkos::TeamThreadRange; +using RealScratchArray = + Kokkos::View; /// team_size for hierarchical parallelism #ifdef OMEGA_TARGET_DEVICE @@ -321,7 +323,8 @@ KOKKOS_INLINE_FUNCTION void teamBarrier(const TeamMember &Team) { // parallelForOuter: with label template inline void parallelForOuter(const std::string &Label, - const int (&UpperBounds)[N], F &&Functor) { + const int (&UpperBounds)[N], F &&Functor, + int ScratchValsPerTeam = 0) { auto LinFunctor = LinearIdxWrapper{std::forward(Functor), UpperBounds}; int LinBound = 1; @@ -330,6 +333,12 @@ inline void parallelForOuter(const std::string &Label, } auto Policy = TeamPolicy(LinBound, OMEGA_TEAMSIZE); + + if (ScratchValsPerTeam > 0) { + Policy.set_scratch_size( + 0, Kokkos::PerTeam(ScratchValsPerTeam * sizeof(Real))); + } + Kokkos::parallel_for( Label, Policy, KOKKOS_LAMBDA(const TeamMember &Team) { const int TeamId = Team.league_rank(); @@ -339,8 +348,10 @@ inline void parallelForOuter(const std::string &Label, // parallelForOuter: without label template -inline void parallelForOuter(const int (&UpperBounds)[N], F &&Functor) { - parallelForOuter("", UpperBounds, std::forward(Functor)); +inline void parallelForOuter(const int (&UpperBounds)[N], F &&Functor, + int ScratchValsPerTeam = 0) { + parallelForOuter("", UpperBounds, std::forward(Functor), + ScratchValsPerTeam); } // parallelForInner From a70b3afdf04e1a16cbd1e2b4f6212d5a3943cca8 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Sat, 27 Dec 2025 14:46:39 -0800 Subject: [PATCH 049/152] Add computeVerticalVelocity --- components/omega/src/ocn/VertAdv.cpp | 91 +++++++++++++++++++++++++++- components/omega/src/ocn/VertAdv.h | 7 +++ 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 4ae65d1d94e3..426fc15ed288 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -2,8 +2,8 @@ // //===----------------------------------------------------------------------===// -#include "Tracers.h" #include "VertAdv.h" +#include "Tracers.h" namespace OMEGA { @@ -16,8 +16,8 @@ std::map> VertAdv::AllVertAdvs; // HorzMesh and VertCoord void VertAdv::init() { - auto Mesh = HorzMesh::getDefault(); - auto VCoord = VertCoord::getDefault(); + auto Mesh = HorzMesh::getDefault(); + auto VCoord = VertCoord::getDefault(); Config *OmegaConfig = Config::getOmegaConfig(); @@ -139,6 +139,7 @@ VertAdv *VertAdv::get(const std::string Name ///< [in] Name of VertAdv } // end get VertAdv +//------------------------------------------------------------------------------ // Read and set config options void VertAdv::readConfigOptions(Config *OmegaConfig) { @@ -203,5 +204,89 @@ void VertAdv::readConfigOptions(Config *OmegaConfig) { } // end readConfigOptions +//------------------------------------------------------------------------------ +// Compute VerticalVelocity and TotalVerticalVelocity from the horizontal +// velocity (NormalVelocity) and the layer thickness used for fluxes through +// edges (FluxLayerThickEdge) +void VertAdv::computeVerticalVelocity(const Array2DReal &NormalVelocity, + const Array2DReal &FluxLayerThickEdge) { + + OMEGA_SCOPE(LocNVertLayers, NVertLayers); + OMEGA_SCOPE(LocAreaCell, Mesh->AreaCell); + OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); + OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); + OMEGA_SCOPE(LocNEOnC, Mesh->NEdgesOnCell); + OMEGA_SCOPE(LocEOnC, Mesh->EdgesOnCell); + OMEGA_SCOPE(LocDvE, Mesh->DvEdge); + OMEGA_SCOPE(LocESOnC, Mesh->EdgeSignOnCell); + + // Loop over all cells owned by the task + parallelForOuter( + "computeVerticalVelocity", {NCellsOwned}, + KOKKOS_LAMBDA(int ICell, const TeamMember &Team) { + RealScratchArray DivHU(Team.team_scratch(0), LocNVertLayers); + + const Real InvAreaCell = 1._Real / LocAreaCell(ICell); + + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + I4 KRange = vertRangeChunked(KMin, KMax); + + // Compute thickness-weighted divergence of horizontal velocity + // in each layer + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + Real DivHUTmp[VecLength] = {0}; + const I4 KStart = chunkStart(KChunk, KMin); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + + for (int J = 0; J < LocNEOnC(ICell); ++J) { + const I4 JEdge = LocEOnC(ICell, J); + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + DivHUTmp[KVec] -= LocDvE(JEdge) * LocESOnC(ICell, J) * + FluxLayerThickEdge(JEdge, K) * + NormalVelocity(JEdge, K) * InvAreaCell; + } + } + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + DivHU(K) = DivHUTmp[KVec]; + } + }); + + Team.team_barrier(); + + // Set velocity through top and bottom interfaces to zero + Kokkos::single( + PerTeam(Team), INNER_LAMBDA() { + VerticalVelocity(ICell, KMin) = 0.; + VerticalVelocity(ICell, KMax + 1) = 0.; + }); + KRange = vertRange(KMin + 1, KMax); + + // Prefix sum of divergence to determine velocity through each + // interface + parallelScanInner( + Team, KRange, INNER_LAMBDA(int K, Real &Accum, bool IsFinal) { + const I4 KRev = KMax - K; + Accum -= DivHU(KRev); + + if (IsFinal) { + VerticalVelocity(ICell, KRev) = Accum; + } + }); + }, + NVertLayers); + + // TODO: currently assuming TotalVerticalVelocity = VerticalVelocity, i.e. + // purely from divergence of horizontal velocity. Need to add optional + // corrections to transport velocity from other contributions, e.g. + // movement of vertical interfaces, contribution of horizontal velocity + // through tilted interface. + deepCopy(TotalVerticalVelocity, VerticalVelocity); + +} // end computeVerticalVelocity + } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index 04f780fca123..74516aa6350e 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -92,6 +92,13 @@ class VertAdv { /// Read and set config options void readConfigOptions(Config *Options); + /// Determine transport due to vertical advection from divergence of + /// horizontal advection. + void computeVerticalVelocity( + const Array2DReal &NormalVelocity, + const Array2DReal &FluxLayerThickEdge + ); + private: // Vertical loop bounds from VertCoord Array1DI4 MinLayerCell; From cfa28fdb9a2dff0ccf72e7e8c69eac4f53ae2d80 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Mon, 29 Dec 2025 09:14:03 -0800 Subject: [PATCH 050/152] Add computeThicknessVAdvTend --- components/omega/src/ocn/VertAdv.cpp | 44 ++++++++++++++++++++++++++-- components/omega/src/ocn/VertAdv.h | 9 ++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 426fc15ed288..12d1a9e6d79c 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -208,8 +208,10 @@ void VertAdv::readConfigOptions(Config *OmegaConfig) { // Compute VerticalVelocity and TotalVerticalVelocity from the horizontal // velocity (NormalVelocity) and the layer thickness used for fluxes through // edges (FluxLayerThickEdge) -void VertAdv::computeVerticalVelocity(const Array2DReal &NormalVelocity, - const Array2DReal &FluxLayerThickEdge) { +void VertAdv::computeVerticalVelocity( + const Array2DReal &NormalVelocity, /// [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge /// [in] layer thickness at edges +) { OMEGA_SCOPE(LocNVertLayers, NVertLayers); OMEGA_SCOPE(LocAreaCell, Mesh->AreaCell); @@ -288,5 +290,43 @@ void VertAdv::computeVerticalVelocity(const Array2DReal &NormalVelocity, } // end computeVerticalVelocity +//------------------------------------------------------------------------------ +// Compute thickness tendency due to vertical advection +void VertAdv::computeThicknessVAdvTend( + const Array2DReal &ThickTend // [inout] thickness tendency +) { + + // Return if vertical advection thickness tendency not enabled + if (!ThickVertAdvEnabled) + return; + + OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); + OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); + OMEGA_SCOPE(LocTotVertVelocity, TotalVerticalVelocity); + + // Loop over every owned cell, pseudo thickness tendency is simply + // difference in pseudo velocity between bottom and top interface for + // each layer + parallelForOuter( + "computeThicknessVAdvTend", {NCellsOwned}, + KOKKOS_LAMBDA(int ICell, const TeamMember &Team) { + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + const I4 KRange = vertRangeChunked(KMin, KMax); + + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + ThickTend(ICell, K) += LocTotVertVelocity(ICell, K + 1) - + LocTotVertVelocity(ICell, K); + } + }); + }); + +} // end computeThicknessVAdvTend + } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index 74516aa6350e..f1be78443b5a 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -95,8 +95,13 @@ class VertAdv { /// Determine transport due to vertical advection from divergence of /// horizontal advection. void computeVerticalVelocity( - const Array2DReal &NormalVelocity, - const Array2DReal &FluxLayerThickEdge + const Array2DReal &NormalVelocity, // [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge // [in] layer thickness at edges + ); + + /// Compute pseudo thickness tendency due to vertical advection + void computeThicknessVAdvTend( + const Array2DReal &ThickTend // [inout] thickness tendency ); private: From 2671b0be6c9469b29637b076d290284e9d62ddf7 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Mon, 29 Dec 2025 09:56:15 -0800 Subject: [PATCH 051/152] Add computeVelocityVAdvTend --- components/omega/src/ocn/VertAdv.cpp | 87 +++++++++++++++++++++++++++- components/omega/src/ocn/VertAdv.h | 7 +++ 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 12d1a9e6d79c..b2345d7ae4c3 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -209,8 +209,8 @@ void VertAdv::readConfigOptions(Config *OmegaConfig) { // velocity (NormalVelocity) and the layer thickness used for fluxes through // edges (FluxLayerThickEdge) void VertAdv::computeVerticalVelocity( - const Array2DReal &NormalVelocity, /// [in] horizontal velocity - const Array2DReal &FluxLayerThickEdge /// [in] layer thickness at edges + const Array2DReal &NormalVelocity, ///< [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge ///< [in] layer thickness at edges ) { OMEGA_SCOPE(LocNVertLayers, NVertLayers); @@ -293,7 +293,7 @@ void VertAdv::computeVerticalVelocity( //------------------------------------------------------------------------------ // Compute thickness tendency due to vertical advection void VertAdv::computeThicknessVAdvTend( - const Array2DReal &ThickTend // [inout] thickness tendency + const Array2DReal &ThickTend ///< [inout] thickness tendency ) { // Return if vertical advection thickness tendency not enabled @@ -328,5 +328,86 @@ void VertAdv::computeThicknessVAdvTend( } // end computeThicknessVAdvTend +//------------------------------------------------------------------------------ +// Compute velocity tendency due to vertical advection +void VertAdv::computeVelocityVAdvTend( + const Array2DReal &VelTend, ///< [inout] horizontal velocity tendency + const Array2DReal &NormalVelocity, ///< [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge ///< [in] layer thickness at edges +) { + + // Return if vertical advection velocity tendency not enabled + if (!VelVertAdvEnabled) + return; + + OMEGA_SCOPE(LocCOnE, Mesh->CellsOnEdge); + OMEGA_SCOPE(MinLayerEdgeBot, VCoord->MinLayerEdgeBot); + OMEGA_SCOPE(MaxLayerEdgeTop, VCoord->MaxLayerEdgeTop); + OMEGA_SCOPE(LocTotVertVelocity, TotalVerticalVelocity); + OMEGA_SCOPE(EdgeMask, VCoord->EdgeMask); + OMEGA_SCOPE(LocNVertLayersP1, NVertLayersP1); + + // Loop over every owned edge + parallelForOuter( + "computeVelocityVAdvTend", {NEdgesOwned}, + KOKKOS_LAMBDA(int IEdge, const TeamMember &Team) { + const I4 Cell1 = LocCOnE(IEdge, 0); + const I4 Cell2 = LocCOnE(IEdge, 1); + const I4 KMin = MinLayerEdgeBot(IEdge); + const I4 KMax = MaxLayerEdgeTop(IEdge); + I4 KRange = vertRangeChunked(KMin + 1, KMax); + + // Allocate scratch space for W times Du/Dz at vertical interfaces + // between edges + RealScratchArray WDuDzEdge(Team.team_scratch(0), LocNVertLayersP1); + + // Flux is zero at top and bottom + Kokkos::single( + PerTeam(Team), INNER_LAMBDA() { + WDuDzEdge(Kmin) = 0._Real; + WDuDzEdge(KMax + 1) = 0._Real; + }); + + // Average vertical velocities from cell centers to edges and multiply + // by derivative of horizontal velocity to obtain flux of horizontal + // velocity at the vertical interfaces between edges + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin + 1); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + const Real WAvg = 0.5_Real * (LocTotVertVelocity(Cell1, K) + + LocTotVertVelocity(Cell2, K)); + WDuDzEdge(K) = + WAvg * + (NormalVelocity(IEdge, K - 1) - + NormalVelocity(IEdge, K)) / + (0.5_Real * (FluxLayerThickEdge(IEdge, K - 1) + + FluxLayerThickEdge(IEdge, K))); + } + }); + + Team.team_barrier(); + + KRange = vertRangeChunked(KMin, KMax); + // Average W*Du/Dz from interfaces to layer midpoints + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + VelTend(IEdge, K) -= EdgeMask(IEdge, K) * 0.5_Real * + (WDuDzEdge(K) + WDuDzEdge(K + 1)); + } + }); + }, + NVertLayersP1); + +} // end computeVelocityVAdvTend + } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index f1be78443b5a..9a56b1d398a6 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -104,6 +104,13 @@ class VertAdv { const Array2DReal &ThickTend // [inout] thickness tendency ); + /// Compute velocity tendency due to vertical advection + void computeVelocityVAdvTend( + const Array2DReal &VelTend, // [inout] horizontal velocity tendency + const Array2DReal &NormalVelocity, // [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge // [in] layer thickness at edges + ); + private: // Vertical loop bounds from VertCoord Array1DI4 MinLayerCell; From fe72127a52e3c3aae1cb96fe5963cf5cad6f417f Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Mon, 29 Dec 2025 14:06:08 -0800 Subject: [PATCH 052/152] Add computeTracerVAdvTend dispatch function --- components/omega/src/ocn/VertAdv.cpp | 26 ++++++++++++++++++++++++++ components/omega/src/ocn/VertAdv.h | 9 +++++++++ 2 files changed, 35 insertions(+) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index b2345d7ae4c3..130ef4824277 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -409,5 +409,31 @@ void VertAdv::computeVelocityVAdvTend( } // end computeVelocityVAdvTend +//------------------------------------------------------------------------------ +// Compute tracer tendency due to vertical advection, TimeStep is only needed +// as an arugement for flux-corrected transport +void VertAdv::computeTracerVAdvTend( + const Array3DReal &TracerTend, ///< [inout] tracer tendencies + const Array3DReal &Tracers, ///< [in] tracer array + const Array2DReal &LayerThickness, ///< [in] layer thickness + const TimeInterval TimeStep ///< [in] (optional) time step +) { + + computeVerticalFluxes(Tracers, LayerThickness); + + // dispatch to appropriate algorithm based on configuration settings + switch (VertAdvChoice) { + case VertAdvOption::Standard: + computeStdVAdvTend(TracerTend); + break; + case VertAdvOption::FCT: + R8 Dt; + TimeStep.get(Dt, TimeUnits::Seconds); + computeFCTVAdvTend(TracerTend, Tracers, LayerThickness, Dt); + break; + } + +} // end computeTracerVAdvTend + } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index 9a56b1d398a6..a310b13ad044 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -111,6 +111,15 @@ class VertAdv { const Array2DReal &FluxLayerThickEdge // [in] layer thickness at edges ); + /// Compute tracer tendency due to vertical advection, TimeStep is only + /// needed as an argument for flux-corrected transport + void computeTracerVAdvTend( + const Array3DReal &TracerTend, // [inout] tracer tendencies + const Array3DReal &Tracers, // [in] tracer array + const Array2DReal &LayerThickness, // [in] layer thickness + const TimeInterval TimeStep = TimeInterval() // [in] (optional) time step + ); + private: // Vertical loop bounds from VertCoord Array1DI4 MinLayerCell; From 554fffb8537e6a2054595252b79f030144215db3 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 1 Jan 2026 10:48:58 -0800 Subject: [PATCH 053/152] Add computeVerticalFluxes --- components/omega/src/ocn/VertAdv.cpp | 167 ++++++++++++++++++++++++++- components/omega/src/ocn/VertAdv.h | 6 + 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 130ef4824277..fefe32c31c1d 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -364,7 +364,7 @@ void VertAdv::computeVelocityVAdvTend( // Flux is zero at top and bottom Kokkos::single( PerTeam(Team), INNER_LAMBDA() { - WDuDzEdge(Kmin) = 0._Real; + WDuDzEdge(KMin) = 0._Real; WDuDzEdge(KMax + 1) = 0._Real; }); @@ -435,5 +435,170 @@ void VertAdv::computeTracerVAdvTend( } // end computeTracerVAdvTend +//------------------------------------------------------------------------------ +// Compute tracer fluxes due to vertical advection, the particular scheme used +// is chosen via configuration settings +void VertAdv::computeVerticalFluxes( + const Array3DReal &Tracers, ///< [in] tracer array + const Array2DReal &LayerThickness ///< [in] layer thickness +) { + + OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); + OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); + OMEGA_SCOPE(LocTotVertVel, TotalVerticalVelocity); + OMEGA_SCOPE(LocVertFlux, VertFlux); + OMEGA_SCOPE(LocLowOrderVertFlux, LowOrderVertFlux); + OMEGA_SCOPE(LocCoef3rdOrder, Coef3rdOrder); + + // Compute the fluxes used for the standard VAdv scheme, or the high-order + // fluxes used for the FCT VAdv scheme, store in VertFlux member array + switch (VertFluxChoice) { + // 2nd-order centered fluxes + case VertFluxOption::Second: + parallelForOuter( + "computeVerticalFluxes-Second", {NTracers, NCellsOwned}, + KOKKOS_LAMBDA(int L, int ICell, const TeamMember &Team) { + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + const I4 KRange = vertRangeChunked(KMin + 2, KMax - 1); + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin + 2); + const I4 KLen = chunkLength(KChunk, KStart, KMax - 1); + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + const Real InvLayerThickSum = + 1._Real / (LayerThickness(ICell, K - 1) + + LayerThickness(ICell, K)); + const Real VerticalWeightK = + LayerThickness(ICell, K - 1) * InvLayerThickSum; + const Real VerticalWeightKm1 = + LayerThickness(ICell, K) * InvLayerThickSum; + LocVertFlux(L, ICell, K) = + LocTotVertVel(ICell, K) * + (VerticalWeightK * Tracers(L, ICell, K) + + VerticalWeightKm1 * Tracers(L, ICell, K - 1)); + } + }); + }); + break; + // 3rd-order upwind fluxes + case VertFluxOption::Third: + parallelForOuter( + "computeVerticalFluxes-Third", {NTracers, NCellsOwned}, + KOKKOS_LAMBDA(int L, int ICell, const TeamMember &Team) { + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + const I4 KRange = vertRangeChunked(KMin + 2, KMax - 1); + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin + 2); + const I4 KLen = chunkLength(KChunk, KStart, KMax - 1); + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + LocVertFlux(L, ICell, K) = + (LocTotVertVel(ICell, K) * + (7._Real * (Tracers(L, ICell, K) + + Tracers(L, ICell, K - 1)) - + (Tracers(L, ICell, K + 1) + + Tracers(L, ICell, K - 2))) - + LocCoef3rdOrder * + std::abs(LocTotVertVel(ICell, K)) * + ((Tracers(L, ICell, K + 1) - + Tracers(L, ICell, K - 2)) - + 3._Real * (Tracers(L, ICell, K) - + Tracers(L, ICell, K - 1)))) / + 12._Real; + } + }); + }); + break; + // 4th-order centered fluxes + case VertFluxOption::Fourth: + parallelForOuter( + "computeVerticalFluxes-Fourth", {NTracers, NCellsOwned}, + KOKKOS_LAMBDA(int L, int ICell, const TeamMember &Team) { + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + const I4 KRange = vertRangeChunked(KMin + 2, KMax - 1); + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin + 2); + const I4 KLen = chunkLength(KChunk, KStart, KMax - 1); + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + LocVertFlux(L, ICell, K) = + LocTotVertVel(ICell, K) * + (7._Real * (Tracers(L, ICell, K) + + Tracers(L, ICell, K - 1)) - + (Tracers(L, ICell, K + 1) + + Tracers(L, ICell, K - 2))) / + 12._Real; + } + }); + }); + break; + } + + // Handle fluxes on interfaces near top and bottom layers. Fluxes are 0 on + // top-most (KMin) and bottom-most (KMax + 1) interfaces, use second order + // for fluxes on next-to-top (KMin + 1) and next-to-bottom (KMax) interfaces. + parallelFor( + "computeVerticalFluxes-TopBot", {NTracers, NCellsOwned}, + KOKKOS_LAMBDA(int L, int ICell) { + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + for (int K : {KMin, KMax + 1}) { + LocVertFlux(L, ICell, K) = 0._Real; + } + for (int K : {KMin + 1, KMax}) { + const Real InvLayerThickSum = + 1._Real / + (LayerThickness(ICell, K - 1) + LayerThickness(ICell, K)); + const Real VerticalWeightK = + LayerThickness(ICell, K - 1) * InvLayerThickSum; + const Real VerticalWeightKm1 = + LayerThickness(ICell, K) * InvLayerThickSum; + LocVertFlux(L, ICell, K) = + LocTotVertVel(ICell, K) * + (VerticalWeightK * Tracers(L, ICell, K) + + VerticalWeightKm1 * Tracers(L, ICell, K - 1)); + } + }); + + // If using FCT scheme, compute 1st-order upwind fluxes for low-order and + // remove low-order flux from high-order flux + if (VertAdvChoice == VertAdvOption::FCT) { + parallelForOuter( + "computeVerticalFluxes-LowOrder", {NTracers, NCellsOwned}, + KOKKOS_LAMBDA(int L, int ICell, const TeamMember &Team) { + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + const I4 KRange = vertRangeChunked(KMin + 1, KMax); + + for (int K : {KMin, KMax + 1}) { + LocLowOrderVertFlux(L, ICell, K) = 0._Real; + } + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin + 1); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + LocLowOrderVertFlux(L, ICell, K) = + Kokkos::min(0._Real, LocTotVertVel(ICell, K)) * + Tracers(L, ICell, K - 1) + + Kokkos::max(0._Real, LocTotVertVel(ICell, K)) * + Tracers(L, ICell, K); + + LocVertFlux(L, ICell, K) -= + LocLowOrderVertFlux(L, ICell, K); + } + }); + }); + } + +} // end computeVerticalFluxes + } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index a310b13ad044..3683a76bd05e 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -120,6 +120,12 @@ class VertAdv { const TimeInterval TimeStep = TimeInterval() // [in] (optional) time step ); + /// Compute tracer fluxes due to vertical advection + void computeVerticalFluxes( + const Array3DReal &Tracers, // [in] tracer array + const Array2DReal &LayerThickness // [in] layer thickness + ); + private: // Vertical loop bounds from VertCoord Array1DI4 MinLayerCell; From a91a47985674a8cef20f41b74a094a18d0e410af Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 1 Jan 2026 11:41:44 -0800 Subject: [PATCH 054/152] Add computeStdVAdvTend --- components/omega/src/ocn/VertAdv.cpp | 33 ++++++++++++++++++++++++++++ components/omega/src/ocn/VertAdv.h | 6 +++++ 2 files changed, 39 insertions(+) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index fefe32c31c1d..0f19d365add7 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -600,5 +600,38 @@ void VertAdv::computeVerticalFluxes( } // end computeVerticalFluxes +//------------------------------------------------------------------------------ +// Compute tracer tendencies due to vertical advection using standard advection +// scheme +void VertAdv::computeStdVAdvTend( + const Array3DReal &TracerTend ///< [inout] tracer tendencies +) { + + OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); + OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); + OMEGA_SCOPE(LocVertFlux, VertFlux); + + // Loop over owned cells, tracer tendency in each layer is computed from + // difference between fluxes through bottom and top interfaces + parallelForOuter( + "computeStdVAdvTend", {NTracers, NCellsOwned}, + KOKKOS_LAMBDA(int L, int ICell, const TeamMember &Team) { + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + const I4 KRange = vertRangeChunked(KMin, KMax); + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + TracerTend(L, ICell, K) += + LocVertFlux(L, ICell, K + 1) - LocVertFlux(L, ICell, K); + } + }); + }); + +} // end computeStdVAdvTend + } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index 3683a76bd05e..ee7b198ab672 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -126,6 +126,12 @@ class VertAdv { const Array2DReal &LayerThickness // [in] layer thickness ); + /// Compute tracer tendencies due to vertical advection using standard + /// advection scheme + void + computeStdVAdvTend(const Array3DReal &TracerTend // [inout] tracer tendencies + ); + private: // Vertical loop bounds from VertCoord Array1DI4 MinLayerCell; From 2e47453c822a69b02f5257ec8eefd5a78db13e71 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 1 Jan 2026 16:19:09 -0800 Subject: [PATCH 055/152] Add computeFCTVAdvTend --- components/omega/src/ocn/VertAdv.cpp | 153 +++++++++++++++++++++++++++ components/omega/src/ocn/VertAdv.h | 9 ++ 2 files changed, 162 insertions(+) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 0f19d365add7..e7735ac90181 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -633,5 +633,158 @@ void VertAdv::computeStdVAdvTend( } // end computeStdVAdvTend +//------------------------------------------------------------------------------ +// Compute tracer tendencies due to vertical advection using flux-corrected +// transport scheme. ProvThickness input is provisional layer thickness after +// horizontal thickness flux +void VertAdv::computeFCTVAdvTend( + const Array3DReal &TracerTend, ///< [inout] tracer tendencies + const Array3DReal &Tracers, ///< [in] tracer array + const Array2DReal &ProvThickness, ///< [in] provisional layer thickness + const Real Dt ///< [in] time step +) { + + OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); + OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); + OMEGA_SCOPE(LocTotVertVel, TotalVerticalVelocity); + OMEGA_SCOPE(LocVertFlux, VertFlux); + OMEGA_SCOPE(LocLowOrderVertFlux, LowOrderVertFlux); + OMEGA_SCOPE(LocNVertLayers, NVertLayers); + OMEGA_SCOPE(LocEps, Eps); + + parallelForOuter( + "computeFCTVAdvTend", {NTracers, NCellsOwned}, + KOKKOS_LAMBDA(int L, int ICell, const TeamMember &Team) { + const I4 KMin = MinLayerCell(ICell); + const I4 KMax = MaxLayerCell(ICell); + I4 KRange = vertRangeChunked(KMin, KMax); + + RealScratchArray InvNewProvThick(Team.team_scratch(0), + LocNVertLayers); + RealScratchArray WorkTend(Team.team_scratch(0), LocNVertLayers); + RealScratchArray FlxIn(Team.team_scratch(0), LocNVertLayers); + RealScratchArray FlxOut(Team.team_scratch(0), LocNVertLayers); + RealScratchArray RescaledFlux(Team.team_scratch(0), + LocNVertLayers + 1); + + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + InvNewProvThick(K) = + 1._Real / (ProvThickness(ICell, K) + + Dt * (LocTotVertVel(ICell, K + 1) - + LocTotVertVel(ICell, K))); + Real TracerMax; + Real TracerMin; + // Determine bounds on tracer from neighbor values for + // limiting + if (K == KMin) { + TracerMax = Kokkos::max(Tracers(L, ICell, K), + Tracers(L, ICell, K + 1)); + TracerMin = Kokkos::min(Tracers(L, ICell, K), + Tracers(L, ICell, K + 1)); + } else if (K == KMax) { + TracerMax = Kokkos::max(Tracers(L, ICell, K - 1), + Tracers(L, ICell, K)); + TracerMin = Kokkos::min(Tracers(L, ICell, K - 1), + Tracers(L, ICell, K)); + } else { + TracerMax = + Kokkos::max(Tracers(L, ICell, K - 1), + Kokkos::max(Tracers(L, ICell, K), + Tracers(L, ICell, K + 1))); + TracerMin = + Kokkos::min(Tracers(L, ICell, K - 1), + Kokkos::min(Tracers(L, ICell, K), + Tracers(L, ICell, K + 1))); + } + + // Accumulate upwind flux in WorkTend + WorkTend(K) = LocLowOrderVertFlux(L, ICell, K + 1) - + LocLowOrderVertFlux(L, ICell, K); + // Accumulate remaining high-order flux into layer + FlxIn(K) = + Kokkos::max(0._Real, LocVertFlux(L, ICell, K + 1)) - + Kokkos::min(0._Real, LocVertFlux(L, ICell, K)); + // Accumulate remaining high-order flux out of layer + FlxOut(K) = + Kokkos::min(0._Real, LocVertFlux(L, ICell, K + 1)) - + Kokkos::max(0._Real, LocVertFlux(L, ICell, K)); + // Build scale factors to limit flux for FCT using the + // bounds determined above and bounds on newly updated + // values. Factors are stored in FlxIn and FlxOut + // scratch space. + Real TracerMinNew = + (Tracers(L, ICell, K) * ProvThickness(ICell, K) + + Dt * (WorkTend(K) + FlxOut(K))) * + InvNewProvThick(K); + Real TracerMaxNew = + (Tracers(L, ICell, K) * ProvThickness(ICell, K) + + Dt * (WorkTend(K) + FlxIn(K))) * + InvNewProvThick(K); + Real TracerUpwindNew = + (Tracers(L, ICell, K) * ProvThickness(ICell, K) + + Dt * WorkTend(K)) * + InvNewProvThick(K); + Real ScaleFactor = + (TracerMax - TracerUpwindNew) / + (TracerMaxNew - TracerUpwindNew + LocEps); + FlxIn(K) = + Kokkos::min(1._Real, Kokkos::max(0._Real, ScaleFactor)); + ScaleFactor = (TracerUpwindNew - TracerMin) / + (TracerUpwindNew - TracerMinNew + LocEps); + FlxOut(K) = + Kokkos::min(1._Real, Kokkos::max(0._Real, ScaleFactor)); + } + }); + + Team.team_barrier(); + + KRange = vertRangeChunked(KMin + 1, KMax); + + // Rescale the high-order vertical flux + RescaledFlux(KMin) = 0._Real; + RescaledFlux(KMax + 1) = 0._Real; + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin + 1); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + + RescaledFlux(K) = + Kokkos::max(0._Real, LocVertFlux(L, ICell, K)) * + Kokkos::min(FlxOut(K), FlxIn(K - 1)) + + Kokkos::min(0._Real, LocVertFlux(L, ICell, K)) * + Kokkos::min(FlxOut(K - 1), FlxIn(K)); + } + }); + + Team.team_barrier(); + + // Accumulate total FCT vertical advection tendency + KRange = vertRangeChunked(KMin, KMax); + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin); + const I4 KLen = chunkLength(KChunk, KStart, KMax); + + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + WorkTend(K) += RescaledFlux(K + 1) - RescaledFlux(K); + TracerTend(L, ICell, K) += WorkTend(K); + } + }); + // TODO: Monotonicity and diagnostic checks + }, + 5 * NVertLayers + 1); + +} // end computeFTCVAdvTend + } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index ee7b198ab672..cbe808720e8c 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -132,6 +132,15 @@ class VertAdv { computeStdVAdvTend(const Array3DReal &TracerTend // [inout] tracer tendencies ); + /// Compute tracer tendencies due to vertical advection using flux-corrected + /// transport scheme + void computeFCTVAdvTend( + const Array3DReal &TracerTend, // [inout] tracer tendencies + const Array3DReal &Tracers, // [in] tracer array + const Array2DReal &ProvThickness, // [in] provisional layer thickness + const Real Dt // [in] time step + ); + private: // Vertical loop bounds from VertCoord Array1DI4 MinLayerCell; From 686640c83a8c5b29f8ac4163dfcc4e0f64170a20 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 6 Jan 2026 11:24:16 -0800 Subject: [PATCH 056/152] Add ProvThickness to auxvars (follow-up restructuring needed) --- components/omega/src/ocn/AuxiliaryState.cpp | 5 +-- .../auxiliaryVars/LayerThicknessAuxVars.cpp | 7 ++-- .../ocn/auxiliaryVars/LayerThicknessAuxVars.h | 32 +++++++++++++++++-- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/components/omega/src/ocn/AuxiliaryState.cpp b/components/omega/src/ocn/AuxiliaryState.cpp index 0aaa97753a60..423c3506346f 100644 --- a/components/omega/src/ocn/AuxiliaryState.cpp +++ b/components/omega/src/ocn/AuxiliaryState.cpp @@ -194,8 +194,9 @@ void AuxiliaryState::computeMomAux(const OceanState *State, int ThickTimeLevel, parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { - LocLayerThicknessAux.computeVarsOnCells(ICell, KChunk, - LayerThickCell); + LocLayerThicknessAux.computeVarsOnCells( + ICell, KChunk, LayerThickCell, NormalVelEdge, 0._Real); + // TODO: make timestep available to this call }); }); Pacer::stop("AuxState:cellAuxState3", 2); diff --git a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp index cb937863528c..872b68a82b9d 100644 --- a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp +++ b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp @@ -14,8 +14,11 @@ LayerThicknessAuxVars::LayerThicknessAuxVars(const std::string &AuxStateSuffix, Mesh->NEdgesSize, VCoord->NVertLayers), SshCell("SshCell" + AuxStateSuffix, Mesh->NCellsSize, VCoord->NVertLayers), - CellsOnEdge(Mesh->CellsOnEdge), BottomDepth(Mesh->BottomDepth), - MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + ProvThickness("ProvThickness" + AuxStateSuffix, Mesh->NCellsSize, + VCoord->NVertLayers), + AreaCell(Mesh->AreaCell), DvEdge(Mesh->AreaCell), + EdgeSignOnCell(Mesh->EdgeSignOnCell), CellsOnEdge(Mesh->CellsOnEdge), + BottomDepth(Mesh->BottomDepth), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), MinLayerCell(VCoord->MinLayerCell), MaxLayerCell(VCoord->MaxLayerCell) {} diff --git a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h index 0620d546fd8f..a415bfa02ba3 100644 --- a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h +++ b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h @@ -17,6 +17,7 @@ class LayerThicknessAuxVars { Array2DReal FluxLayerThickEdge; Array2DReal MeanLayerThickEdge; Array2DReal SshCell; + Array2DReal ProvThickness; FluxThickEdgeOption FluxThickEdgeChoice; @@ -63,9 +64,10 @@ class LayerThicknessAuxVars { } } - KOKKOS_FUNCTION void - computeVarsOnCells(int ICell, int KChunk, - const Array2DReal &LayerThickCell) const { + KOKKOS_FUNCTION void computeVarsOnCells(int ICell, int KChunk, + const Array2DReal &LayerThickCell, + const Array2DReal &NormalVelEdge, + const Real Dt) const { // Temporary for stacked shallow water const int KStart = chunkStart(KChunk, MinLayerCell(ICell)); @@ -76,6 +78,25 @@ class LayerThicknessAuxVars { SshCell(ICell, K) = LayerThickCell(ICell, K) - BottomDepth(ICell); } + Real TmpProv[VecLength] = {0.}; + + Real DtInvAreaCell = Dt / AreaCell(ICell); + for (int J = 0; J < NEdgesOnCell(ICell); ++J) { + const I4 JEdge = EdgesOnCell(ICell, J); + const Real Factor = + DtInvAreaCell * DvEdge(JEdge) * EdgeSignOnCell(ICell, J); + for (int KVec = 0; KVec < KLen; ++KVec) { + const int K = KStart + KVec; + TmpProv[KVec] += + Factor * FluxLayerThickEdge(JEdge, K) * NormalVelEdge(JEdge, K); + } + } + + for (int KVec = 0; KVec < KLen; ++KVec) { + const int K = KStart + KVec; + ProvThickness(ICell, K) = LayerThickCell(ICell, K) + TmpProv[KVec]; + } + /* Real TotalThickness = 0.0; for (int K = 0; K < NVertLayers; K++) { @@ -91,6 +112,11 @@ class LayerThicknessAuxVars { void unregisterFields() const; private: + Array1DReal AreaCell; + Array1DReal DvEdge; + Array1DI4 NEdgesOnCell; + Array2DI4 EdgesOnCell; + Array2DReal EdgeSignOnCell; Array2DI4 CellsOnEdge; Array1DReal BottomDepth; Array1DI4 MinLayerEdgeBot; From cc92f706c5aed446ee4836502e11f8d6c72b49de Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 8 Jan 2026 15:54:07 -0800 Subject: [PATCH 057/152] Add new config variables to default config --- components/omega/configs/Default.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 38c2f97057cf..6cc0cf7877fd 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -23,9 +23,12 @@ Omega: State: NTimeLevels: 2 Advection: + Coef3rdOrder: 0.25 FluxThicknessType: Center HorzTracerFluxOrder: 2 - Coef3rdOrder: 0.25 + FluxTracerType: Center + VerticalTracerFluxLimiterEnabled: true + VerticalTracerFluxOrder: 3 WindStress: InterpType: Isotropic VertCoord: @@ -51,6 +54,9 @@ Omega: EddyDiff4: 0.0 UseCustomTendency: false ManufacturedSolutionTendency: false + ThicknessVertAdvTendencyEnable: true + VelocityVertAdvTendencyEnable: true + TracerVertAdvTendencyEnable: true Tracers: Base: [Temperature, Salinity] Debug: [Debug1, Debug2, Debug3] From 5587339ef1f9b27866c0cc7bd74da64c16237c3e Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 8 Jan 2026 15:58:15 -0800 Subject: [PATCH 058/152] Add VertAdv unit tests --- components/omega/test/CMakeLists.txt | 11 + components/omega/test/ocn/VertAdvTest.cpp | 437 ++++++++++++++++++++++ 2 files changed, 448 insertions(+) create mode 100644 components/omega/test/ocn/VertAdvTest.cpp diff --git a/components/omega/test/CMakeLists.txt b/components/omega/test/CMakeLists.txt index 8d2e539b24bb..271af1c40d46 100644 --- a/components/omega/test/CMakeLists.txt +++ b/components/omega/test/CMakeLists.txt @@ -494,3 +494,14 @@ add_omega_test( ocn/VertMixTest.cpp "-n;4" ) + +################## +# VAdv test +################## + +add_omega_test( + VERTADV_TEST + testVertAdv.exe + ocn/VertAdvTest.cpp + "-n;1" +) diff --git a/components/omega/test/ocn/VertAdvTest.cpp b/components/omega/test/ocn/VertAdvTest.cpp new file mode 100644 index 000000000000..71f7e448f2e7 --- /dev/null +++ b/components/omega/test/ocn/VertAdvTest.cpp @@ -0,0 +1,437 @@ +//===-- Test driver for OMEGA Vertical Advection ----------------*- C++ -*-===/ +// +/// \file +/// \brief Test driver for OMEGA VertAdv class +/// +/// +// +//===-----------------------------------------------------------------------===/ + +#include "VertAdv.h" +#include "Config.h" +#include "DataTypes.h" +#include "Decomp.h" +#include "GlobalConstants.h" +#include "HorzMesh.h" +#include "IO.h" +#include "IOStream.h" +#include "Logging.h" +#include "OceanState.h" +#include "OmegaKokkos.h" +#include "TimeStepper.h" +#include "VertCoord.h" + +#include "Pacer.h" +#include +#include +#include + +using namespace OMEGA; + +constexpr Real Alpha = 0.197_Real; // random phase angle +constexpr Real Length = 1024._Real; + +// Test function used for tendTest +KOKKOS_FUNCTION Real testFunc(const Real X) { + Real Arg = Pi * (X + Alpha) / Length; + return std::sin(Arg) * std::sin(Arg) * std::cos(Arg) * std::cos(Arg); +} + +// Function used for analytical derivative in tendTest +KOKKOS_FUNCTION Real testDeriv(const Real X) { + Real C = Pi / Length; + return (C / 2._Real) * std::sin(4._Real * C * (X + Alpha)); +} + +// For a given resolution, use computeStdVAdvTend to compute tracer +// tendencies, then calculate L2 error +Real tendTest(const int NLayers, VertAdv *VAdv) { + + // Setup test for this resolution + Real Delta = Length / NLayers; + + VAdv->NVertLayers = NLayers; + VAdv->TotalVerticalVelocity = + Array2DReal("TotalVerticaVelocity", 1, NLayers + 1); + Array1DReal XLayer("XLayer", NLayers); + OMEGA_SCOPE(LocTotVertVel, VAdv->TotalVerticalVelocity); + Array2DReal LayerThick("LayerThickness", 1, NLayers); + Array3DReal TracerArray("TracerArray", 1, 1, NLayers); + Array3DReal Tend("TracerArray", 1, 1, NLayers); + + // Set uniform velocity throughout domain + deepCopy(VAdv->TotalVerticalVelocity, 1._Real); + // Set velocities at top and bottom interfaces to 0. + parallelFor( + {1}, KOKKOS_LAMBDA(const int &) { + LocTotVertVel(0, 0) = 0._Real; + LocTotVertVel(0, NLayers) = 0._Real; + }); + // Set X values, LayerThickness, and Tracer values throughout domain + parallelFor( + {NLayers}, KOKKOS_LAMBDA(int K) { + XLayer(K) = Delta * (K + 0.5_Real); + LayerThick(0, K) = Delta; + TracerArray(0, 0, K) = testFunc(XLayer(K)); + }); + + // Compute tracer tendencies + VAdv->computeTracerVAdvTend(Tend, TracerArray, LayerThick); + + HostArray3DReal TendH = createHostMirrorCopy(Tend); + HostArray1DReal XLayerH = createHostMirrorCopy(XLayer); + Real SumSq = 0._Real; + int Count = 0; + + // Compute errors at interior of range, ignoring layers near top and bottom + // where lower order is used + for (int K = 2; K < NLayers - 2; ++K) { + Real Expected = testDeriv(XLayerH(K)); + Real Diff = TendH(0, 0, K) / Delta - Expected; + SumSq += Diff * Diff; + Count++; + } + Real L2 = std::sqrt(SumSq / Count); + + return L2; +} + +// Initialize needed modules +void initVertAdvTest() { + + I4 Err; + + MachEnv::init(MPI_COMM_WORLD); + MachEnv *DefEnv = MachEnv::getDefault(); + MPI_Comm DefComm = DefEnv->getComm(); + + // Initialize the Logging system + initLogging(DefEnv); + + // Open config file + Config("Omega"); + Config::readAll("omega.yml"); + + // First step of time stepper initialization needed for IOstream + TimeStepper::init1(); + + // Initialize the IO system + IO::init(DefComm); + + // Create the default decomposition (initializes the decomposition) + Decomp::init(); + + // Initialize streams + IOStream::init(); + + // Initialize the default halo + Err = Halo::init(); + if (Err != 0) + ABORT_ERROR("VertAdvTest: error initializing default halo"); + + // Initialize the default mesh + HorzMesh::init(); + + // Initialize the default vertical coordinate + VertCoord::init(false); + + // Initialize tracers + Tracers::init(); + + // Initialize the default vertical advection + VertAdv::init(); +} + +// Clean-up modules +void finalizeVertAdvTest() { + + IOStream::finalize(); + Tracers::clear(); + VertAdv::clear(); + VertCoord::clear(); + TimeStepper::clear(); + HorzMesh::clear(); + Field::clear(); + Dimension::clear(); + Halo::clear(); + Decomp::clear(); + MachEnv::removeAll(); +} + +int main(int argc, char *argv[]) { + + // Initialize error code + Error ErrAll; + + // Initialize the global MPI environment + MPI_Init(&argc, &argv); + Kokkos::initialize(); + Pacer::initialize(MPI_COMM_WORLD); + Pacer::setPrefix("Omega:"); + { + initVertAdvTest(); + + auto DefMesh = HorzMesh::getDefault(); + auto DefDecomp = Decomp::getDefault(); + auto DefVertAdv = VertAdv::getDefault(); + auto DefVCoord = VertCoord::getDefault(); + + // Only need to test within one column or along one edge and with one + // tracer + DefVertAdv->NCellsOwned = 1; + DefVertAdv->NCellsAll = 1; + DefVertAdv->NEdgesOwned = 1; + DefVertAdv->NEdgesAll = 1; + DefVertAdv->NTracers = 1; + + I4 NVertLayers = DefVertAdv->NVertLayers; + I4 NVertLayersP1 = DefVertAdv->NVertLayersP1; + I4 NTracers = DefVertAdv->NTracers; + + Array2DReal FluxLayerThickEdge("FluxLayerThickEdge", + DefDecomp->NEdgesSize, NVertLayers); + Array2DReal NormalVelEdge("NormalVelEdge", DefDecomp->NEdgesSize, + NVertLayers); + + const I4 ICell0 = 0; + const I4 NEdgesOnCell0 = DefDecomp->NEdgesOnCell(ICell0); + + // Test for computeVerticalVelocity + I4 Err = 0; + + OMEGA_SCOPE(LocEOnC, DefDecomp->EdgesOnCell); + OMEGA_SCOPE(LocESOnC, DefMesh->EdgeSignOnCell); + + // Set unifrom values for layer thickness and velocity on edges of column + parallelFor( + {NVertLayers}, KOKKOS_LAMBDA(int K) { + for (int J = 0; J < NEdgesOnCell0; ++J) { + const I4 JEdge = LocEOnC(ICell0, J); + FluxLayerThickEdge(JEdge, K) = 1._Real; + NormalVelEdge(JEdge, K) = 1._Real * LocESOnC(ICell0, J); + } + }); + + // Compute vertical velocity + DefVertAdv->computeVerticalVelocity(NormalVelEdge, FluxLayerThickEdge); + + HostArray2DReal VertVelH = + createHostMirrorCopy(DefVertAdv->VerticalVelocity); + + Real Tol = 1e-10; + + // Divergence is equal in each layer. Expected vertical velocity through + // each interface is sum over negative divergence starting from bottom + // layer + Real Perim0 = 0.; + for (int J = 0; J < NEdgesOnCell0; ++J) { + Perim0 += DefMesh->DvEdge(DefMesh->EdgesOnCell(ICell0, J)); + } + Real InvAreaCell0 = 1._Real / DefMesh->AreaCell(ICell0); + + for (int K = 1; K < NVertLayersP1; ++K) { + Real Expected = (NVertLayers - K) * Perim0 * InvAreaCell0; + Real Diff = std::abs(VertVelH(ICell0, K) - Expected); + if (Diff > Tol) { + ++Err; + } + } + + if (Err != 0) { + ErrAll += Error(ErrorCode::Fail, + "VertAdvTest: computeVerticalVelocity FAIL"); + } + + // Test for computeThicknessVAdvTend + Err = 0; + + Array2DReal TendCell2D("TendCell2D", DefMesh->NCellsSize, NVertLayers); + // Compute thickness tendencies for each layer using velocities from + // previous tests + DefVertAdv->computeThicknessVAdvTend(TendCell2D); + HostArray2DReal TendCell2DH = createHostMirrorCopy(TendCell2D); + + // Expected thickness tendency for each layer is difference between + // velocity at bottom and top interface, i.e. the divergence + for (int K = 1; K < NVertLayers; ++K) { + Real Expected = -Perim0 * InvAreaCell0; + Real Diff = std::abs(TendCell2DH(ICell0, K) - Expected); + if (Diff > Tol) { + ++Err; + } + } + + if (Err != 0) { + ErrAll += Error(ErrorCode::Fail, + "VertAdvTest: computeThicknessVAdvTend FAIL"); + } + + // Test for computeVelocityVAdvTend + Err = 0; + + // Set uniform vertical velocity in each column along the edge and + // uniform layer thickness at the edge. Horizontal velocity is set + // to a periodic function + Array2DReal TendEdge2D("TendEdge2D", DefMesh->NEdgesSize, NVertLayers); + const I4 IEdge0 = 0; + OMEGA_SCOPE(LocCOnE, DefDecomp->CellsOnEdge); + OMEGA_SCOPE(LocVertVel, DefVertAdv->TotalVerticalVelocity); + parallelFor( + {NVertLayers}, KOKKOS_LAMBDA(int K) { + NormalVelEdge(IEdge0, K) = sin(Pi * K / NVertLayers); + FluxLayerThickEdge(IEdge0, K) = 1._Real; + const I4 Cell1 = LocCOnE(IEdge0, 0); + const I4 Cell2 = LocCOnE(IEdge0, 1); + LocVertVel(Cell1, K) = 1._Real; + LocVertVel(Cell2, K) = 1._Real; + }); + + // Compute velocity tendencies for each layer + DefVertAdv->computeVelocityVAdvTend(TendEdge2D, NormalVelEdge, + FluxLayerThickEdge); + + Tol = 1._Real / (NVertLayers * NVertLayers); + + HostArray2DReal TendEdge2DH = createHostMirrorCopy(TendEdge2D); + + // Expected velocity tendency is derivative of initial distribution + for (int K = 0; K < NVertLayers; ++K) { + Real Expected = (Pi / NVertLayers) * cos(Pi * K / NVertLayers); + if (K == 0 or K == NVertLayers - 1) + Expected /= 2._Real; + Real Diff = std::abs(TendEdge2DH(IEdge0, K) - Expected); + if (Diff > Tol) { + ++Err; + } + } + + if (Err != 0) { + ErrAll += Error(ErrorCode::Fail, + "VertAdvTest: computeVelocityVAdvTend FAIL"); + } + + // Tests for computeTracerVAdvTend + + DefVertAdv->VertAdvChoice = VertAdvOption::Standard; + + // Setup for convergence tests with computeStdVAdvTend + constexpr I4 NLayersArray[] = {128, 256, 512, 1024}; + constexpr VertFluxOption AllOrders[] = {VertFluxOption::Second, + VertFluxOption::Third, + VertFluxOption::Fourth}; + static const std::map OrderOfAcc = { + {VertFluxOption::Second, 2}, + {VertFluxOption::Third, 3}, + {VertFluxOption::Fourth, 4}}; + static const std::map OrderStr = { + {VertFluxOption::Second, "2nd"}, + {VertFluxOption::Third, "3rd"}, + {VertFluxOption::Fourth, "4th"}}; + + // Set Coef3rdOrder to 1 so VertFluxOption::Third is calculated with + // purely 3rd-order and not a blend of 3rd- and 4th-order fluxes + DefVertAdv->Coef3rdOrder = 1.; + + Tol = 0.1_Real; + + // Compute L2 error for computeStdVAdvTend with each VertFluxOption over a + // set of resolutions. Compare successive errors for each Order to confirm + // expected order of accuracy. + for (VertFluxOption Order : AllOrders) { + Err = 0; + DefVertAdv->VertFluxChoice = Order; + std::vector L2Errors; + for (I4 NLayers : NLayersArray) { + DefVCoord->MaxLayerCell(0) = NLayers - 1; + Real L2Err = tendTest(NLayers, DefVertAdv); + L2Errors.push_back(L2Err); + } + Real ExpectedRat = std::pow(2._Real, OrderOfAcc.at(Order)); + for (I4 I = 0; I < L2Errors.size() - 1; ++I) { + Real Rat = L2Errors[I] / L2Errors[I + 1]; + Real RelErr = std::abs(Rat - ExpectedRat) / ExpectedRat; + if (RelErr > Tol) { + ++Err; + } + } + if (Err != 0) { + ErrAll += + Error(ErrorCode::Fail, + "VertAdvTest: computeStdVAdvTend with {} order FAIL", + OrderStr.at(Order)); + } + } + + // Monotonicity check for computeFCTVAdvTend + + NVertLayers = 256; + DefVertAdv->VertAdvChoice = VertAdvOption::FCT; + DefVertAdv->VertFluxChoice = VertFluxOption::Third; + DefVertAdv->Coef3rdOrder = 0.25; + std::vector TestVel = {1._Real, -1._Real}; + std::vector StartIdx = {NVertLayers / 2, NVertLayers / 4}; + + // Test a top-hat function for a uniform velocity in both directions + for (int IMono = 0; IMono < 2; ++IMono) { + Err = 0; + Array3DReal TracerArray("TracerArray", 1, 1, NVertLayers); + Array2DReal LayerThick("LayerThick", 1, NVertLayers); + Array3DReal TendCell3D("TendCell3D", 1, 1, NVertLayers); + deepCopy(LayerThick, 1._Real); + DefVCoord->MaxLayerCell(0) = NVertLayers - 1; + DefVertAdv->NVertLayers = NVertLayers; + LocVertVel = Array2DReal("TotalVerticaVelocity", 1, NVertLayers + 1); + deepCopy(LocVertVel, TestVel[IMono]); + parallelFor( + {1}, KOKKOS_LAMBDA(const int &) { + LocVertVel(0, 0) = 0._Real; + LocVertVel(0, NVertLayers) = 1._Real; + }); + deepCopy(TracerArray, 0._Real); + const I4 KStart = StartIdx[IMono]; + const I4 KRange = NVertLayers / 4; + parallelFor( + {KRange}, KOKKOS_LAMBDA(const int K) { + TracerArray(0, 0, KStart + K) = 1._Real; + }); + Real Dt = 0.5_Real; + TimeInterval TimeStep(Dt, TimeUnits::Seconds); + + const I4 NTSteps = 100; + + // Integrate 100 time steps and confirm monotonicity + for (int N = 0; N < NTSteps; ++N) { + deepCopy(TendCell3D, 0._Real); + DefVertAdv->computeTracerVAdvTend(TendCell3D, TracerArray, + LayerThick, TimeStep); + parallelFor( + {NVertLayers}, KOKKOS_LAMBDA(const int K) { + TracerArray(0, 0, K) += Dt * TendCell3D(0, 0, K); + }); + } + HostArray3DReal TracerH = createHostMirrorCopy(TracerArray); + HostArray3DReal TendH = createHostMirrorCopy(TendCell3D); + for (int K = 0; K < NVertLayers; ++K) { + if (TracerH(0, 0, K) < 0._Real or TracerH(0, 0, K) > 1._Real) { + ++Err; + } + } + if (Err != 0) { + ErrAll += + Error(ErrorCode::Fail, + "VertAdvTest: computeFCTVAdvTend with velocity = {} " + "monotonicity FAIL", + TestVel[IMono]); + } + } + + finalizeVertAdvTest(); + } + Pacer::finalize(); + Kokkos::finalize(); + MPI_Finalize(); + + CHECK_ERROR_ABORT(ErrAll, "VertAdv unit tests FAIL"); + + return 0; +} From fd0ea9ff0f91a3b9964e3623ed3a4e2208ac6a60 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Fri, 9 Jan 2026 14:11:45 -0800 Subject: [PATCH 059/152] Init some previously missed pointers and add Field for ProvThickness --- .../ocn/auxiliaryVars/LayerThicknessAuxVars.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp index 872b68a82b9d..506a328749e0 100644 --- a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp +++ b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp @@ -17,6 +17,7 @@ LayerThicknessAuxVars::LayerThicknessAuxVars(const std::string &AuxStateSuffix, ProvThickness("ProvThickness" + AuxStateSuffix, Mesh->NCellsSize, VCoord->NVertLayers), AreaCell(Mesh->AreaCell), DvEdge(Mesh->AreaCell), + NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), EdgeSignOnCell(Mesh->EdgeSignOnCell), CellsOnEdge(Mesh->CellsOnEdge), BottomDepth(Mesh->BottomDepth), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), @@ -81,21 +82,37 @@ void LayerThicknessAuxVars::registerFields(const std::string &AuxGroupName, DimNames // dimension names ); + // Provisional Thickness + auto ProvThicknessField = Field::create( + ProvThickness.label(), // field name + "layer thickness after horizontal flux", // long Name or description + "m", // units + "", // CF standard Name + 0, // min valid value + std::numeric_limits::max(), // max valid value + FillValue, // scalar for undefined entries + NDims, // number of dimensions + DimNames // dimension names + ); + // Add fields to Aux field group FieldGroup::addFieldToGroup(FluxLayerThickEdge.label(), AuxGroupName); FieldGroup::addFieldToGroup(MeanLayerThickEdge.label(), AuxGroupName); FieldGroup::addFieldToGroup(SshCell.label(), AuxGroupName); + FieldGroup::addFieldToGroup(ProvThickness.label(), AuxGroupName); // Attach field data FluxLayerThickEdgeField->attachData(FluxLayerThickEdge); MeanLayerThickEdgeField->attachData(MeanLayerThickEdge); SshCellField->attachData(SshCell); + ProvThicknessField->attachData(ProvThickness); } void LayerThicknessAuxVars::unregisterFields() const { Field::destroy(FluxLayerThickEdge.label()); Field::destroy(MeanLayerThickEdge.label()); Field::destroy(SshCell.label()); + Field::destroy(ProvThickness.label()); } } // namespace OMEGA From fd3fc35be73e79dc3d3ffe017446f651d2e35c31 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Fri, 9 Jan 2026 14:14:50 -0800 Subject: [PATCH 060/152] Comments, bug fix, and add Fields for VertAdv arrays --- components/omega/src/ocn/VertAdv.cpp | 184 ++++++++++++++++++++++----- components/omega/src/ocn/VertAdv.h | 95 +++++++++----- 2 files changed, 212 insertions(+), 67 deletions(-) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index e7735ac90181..97b8ab813e67 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -1,10 +1,18 @@ //===-- ocn/VertAdv.cpp - vertical advection --------------------*- C++ -*-===// // +// The VertAdv class contains methods for calculating the vertical velocity and +// the tendencies of thickness, horizontal velocity, and tracers due to vertical +// advective transport, and member variables for storing the vertical velocity +// and the fluxes of tracers at vertical interfaces in each column. +// //===----------------------------------------------------------------------===// #include "VertAdv.h" +#include "Field.h" #include "Tracers.h" +#include + namespace OMEGA { // create static class members @@ -13,7 +21,7 @@ std::map> VertAdv::AllVertAdvs; //------------------------------------------------------------------------------ // create the default VertAdv, requires prior initialization of default -// HorzMesh and VertCoord +// HorzMesh, VertCoord, and Tracers void VertAdv::init() { auto Mesh = HorzMesh::getDefault(); @@ -27,10 +35,10 @@ void VertAdv::init() { //------------------------------------------------------------------------------ // constructor -VertAdv::VertAdv(const std::string &Name_, ///< [in] name for new VertAdv - const HorzMesh *InMesh, ///< [in] associated HorzMesh - const VertCoord *InVCoord, ///< [in] associated VertCoord - Config *Options ///< [in] configuration options +VertAdv::VertAdv(const std::string &Name_, //< [in] name for new VertAdv + const HorzMesh *InMesh, //< [in] associated HorzMesh + const VertCoord *InVCoord, //< [in] associated VertCoord + Config *Options //< [in] configuration options ) { // Read options from Config object @@ -67,14 +75,16 @@ VertAdv::VertAdv(const std::string &Name_, ///< [in] name for new VertAdv Array3DReal("LowOrderVertFlux", NTracers, NCellsSize, NVertLayersP1); } + defineFields(); + } // end constructor //------------------------------------------------------------------------------ // create a new VertAdv instance -VertAdv *VertAdv::create(const std::string &Name, ///< [in] name for new VertAdv - const HorzMesh *Mesh, ///< [in] associated HorzMesh - const VertCoord *VCoord, ///< [in] associated VertCoord - Config *Options ///< [in] configuration options +VertAdv *VertAdv::create(const std::string &Name, //< [in] name for new VertAdv + const HorzMesh *Mesh, //< [in] associated HorzMesh + const VertCoord *VCoord, //< [in] associated VertCoord + Config *Options //< [in] configuration options ) { // Check to see if a VertAdv of the same name already exists and, if so, // exit with an error @@ -94,9 +104,117 @@ VertAdv *VertAdv::create(const std::string &Name, ///< [in] name for new VertAdv } // end create +void VertAdv::defineFields() { + + // set field names (append Name if not default) + VerticalVelocityFldName = "VerticalVelocity"; + TotalVertVelocityFldName = "TotalVerticalVelocity"; + VertFluxFldName = "VertFlux"; + LowOrderVertFluxFldName = "LowOrderVertFlux"; + + if (Name != "Default") { + VerticalVelocityFldName.append(Name); + TotalVertVelocityFldName.append(Name); + VertFluxFldName.append(Name); + LowOrderVertFluxFldName.append(Name); + } + + const Real FillValueReal = -9.99e30; + I4 NDims = 2; + std::vector DimNames(NDims); + DimNames[0] = "NCells"; + DimNames[1] = "NVertLayersP1"; + + auto VerticalVelocityField = Field::create( + VerticalVelocityFldName, // field name + "Vertical pseudovelocity across a pseudoheight surface", // long name or + // description + "m s^-1", // units + "", // CF standard Name + std::numeric_limits::min(), // min valid value + std::numeric_limits::max(), // max valid value + FillValueReal, // scalar for undefined entries + NDims, // number of dimensions + DimNames // dimension names + ); + + auto TotalVertVelocityField = + Field::create(TotalVertVelocityFldName, // field name + "Total vertical pseudovelocity across a moving, tilted " + "pseudoheight surface", // long name or description + "m s^-1", // units + "", // CF standard Name + std::numeric_limits::min(), // min valid value + std::numeric_limits::max(), // max valid value + FillValueReal, // scalar for undefined entries + NDims, // number of dimensions + DimNames // dimension names + ); + + NDims = 3; + DimNames.resize(NDims); + DimNames[2] = "NTracers"; + + auto VertFluxField = Field::create( + VertFluxFldName, // field name + "Vertical flux of tracers across a pseudoheight surface", // long name or + // description + "", // units + "", // CF standard Name + std::numeric_limits::min(), // min valid value + std::numeric_limits::max(), // max valid value + FillValueReal, // scalar for undefined entries + NDims, // number of dimensions + DimNames // dimension names + ); + + auto LowOrderVertFluxField = + Field::create(LowOrderVertFluxFldName, // field name + "Low-order vertical flux of tracers across a pseudoheight " + "surface", // long name or description + "", // units + "", // CF standard Name + std::numeric_limits::min(), // min valid value + std::numeric_limits::max(), // max valid value + FillValueReal, // scalar for undefined entries + NDims, // number of dimensions + DimNames // dimension names + ); + + GroupName = "VertAdv"; + if (Name != "Default") { + GroupName.append(Name); + } + + // Create a field group for VertAdv fields + auto VertAdvGroup = FieldGroup::create(GroupName); + + VertAdvGroup->addField(VerticalVelocityFldName); + VertAdvGroup->addField(TotalVertVelocityFldName); + VertAdvGroup->addField(VertFluxFldName); + VertAdvGroup->addField(LowOrderVertFluxFldName); + + // Associate Fields with data + VerticalVelocityField->attachData(VerticalVelocity); + TotalVertVelocityField->attachData(TotalVerticalVelocity); + VertFluxField->attachData(VertFlux); + LowOrderVertFluxField->attachData(LowOrderVertFlux); + +} // end defineFields + //------------------------------------------------------------------------------ // destructor -VertAdv::~VertAdv() {} // end destructor +VertAdv::~VertAdv() { + + if (FieldGroup::exists(GroupName)) { + Field::destroy(VerticalVelocityFldName); + Field::destroy(TotalVertVelocityFldName); + Field::destroy(VertFluxFldName); + Field::destroy(LowOrderVertFluxFldName); + FieldGroup::destroy(GroupName); + } + +} // end destructor //------------------------------------------------------------------------------ // Removes all VertAdvs to clean up before exit @@ -120,7 +238,7 @@ VertAdv *VertAdv::getDefault() { return VertAdv::DefaultVertAdv; } //------------------------------------------------------------------------------ // Get VertAdv by name -VertAdv *VertAdv::get(const std::string Name ///< [in] Name of VertAdv +VertAdv *VertAdv::get(const std::string Name //< [in] Name of VertAdv ) { // look for an instance of this name @@ -209,10 +327,11 @@ void VertAdv::readConfigOptions(Config *OmegaConfig) { // velocity (NormalVelocity) and the layer thickness used for fluxes through // edges (FluxLayerThickEdge) void VertAdv::computeVerticalVelocity( - const Array2DReal &NormalVelocity, ///< [in] horizontal velocity - const Array2DReal &FluxLayerThickEdge ///< [in] layer thickness at edges + const Array2DReal &NormalVelocity, //< [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge //< [in] layer thickness at edges ) { + OMEGA_SCOPE(LocVertVel, VerticalVelocity); OMEGA_SCOPE(LocNVertLayers, NVertLayers); OMEGA_SCOPE(LocAreaCell, Mesh->AreaCell); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); @@ -262,8 +381,8 @@ void VertAdv::computeVerticalVelocity( // Set velocity through top and bottom interfaces to zero Kokkos::single( PerTeam(Team), INNER_LAMBDA() { - VerticalVelocity(ICell, KMin) = 0.; - VerticalVelocity(ICell, KMax + 1) = 0.; + LocVertVel(ICell, KMin) = 0.; + LocVertVel(ICell, KMax + 1) = 0.; }); KRange = vertRange(KMin + 1, KMax); @@ -275,7 +394,7 @@ void VertAdv::computeVerticalVelocity( Accum -= DivHU(KRev); if (IsFinal) { - VerticalVelocity(ICell, KRev) = Accum; + LocVertVel(ICell, KRev) = Accum; } }); }, @@ -293,7 +412,7 @@ void VertAdv::computeVerticalVelocity( //------------------------------------------------------------------------------ // Compute thickness tendency due to vertical advection void VertAdv::computeThicknessVAdvTend( - const Array2DReal &ThickTend ///< [inout] thickness tendency + const Array2DReal &ThickTend //< [inout] thickness tendency ) { // Return if vertical advection thickness tendency not enabled @@ -331,9 +450,9 @@ void VertAdv::computeThicknessVAdvTend( //------------------------------------------------------------------------------ // Compute velocity tendency due to vertical advection void VertAdv::computeVelocityVAdvTend( - const Array2DReal &VelTend, ///< [inout] horizontal velocity tendency - const Array2DReal &NormalVelocity, ///< [in] horizontal velocity - const Array2DReal &FluxLayerThickEdge ///< [in] layer thickness at edges + const Array2DReal &VelTend, //< [inout] horizontal velocity tendency + const Array2DReal &NormalVelocity, //< [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge //< [in] layer thickness at edges ) { // Return if vertical advection velocity tendency not enabled @@ -413,15 +532,16 @@ void VertAdv::computeVelocityVAdvTend( // Compute tracer tendency due to vertical advection, TimeStep is only needed // as an arugement for flux-corrected transport void VertAdv::computeTracerVAdvTend( - const Array3DReal &TracerTend, ///< [inout] tracer tendencies - const Array3DReal &Tracers, ///< [in] tracer array - const Array2DReal &LayerThickness, ///< [in] layer thickness - const TimeInterval TimeStep ///< [in] (optional) time step + const Array3DReal &TracerTend, //< [inout] tracer tendencies + const Array3DReal &Tracers, //< [in] tracer array + const Array2DReal &LayerThickness, //< [in] layer thickness + const TimeInterval TimeStep //< [in] (optional) time step ) { + // Compute tracer fluxes at the interfaces computeVerticalFluxes(Tracers, LayerThickness); - // dispatch to appropriate algorithm based on configuration settings + // Dispatch to appropriate algorithm based on configuration settings switch (VertAdvChoice) { case VertAdvOption::Standard: computeStdVAdvTend(TracerTend); @@ -439,8 +559,8 @@ void VertAdv::computeTracerVAdvTend( // Compute tracer fluxes due to vertical advection, the particular scheme used // is chosen via configuration settings void VertAdv::computeVerticalFluxes( - const Array3DReal &Tracers, ///< [in] tracer array - const Array2DReal &LayerThickness ///< [in] layer thickness + const Array3DReal &Tracers, //< [in] tracer array + const Array2DReal &LayerThickness //< [in] layer thickness ) { OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); @@ -604,7 +724,7 @@ void VertAdv::computeVerticalFluxes( // Compute tracer tendencies due to vertical advection using standard advection // scheme void VertAdv::computeStdVAdvTend( - const Array3DReal &TracerTend ///< [inout] tracer tendencies + const Array3DReal &TracerTend //< [inout] tracer tendencies ) { OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); @@ -638,10 +758,10 @@ void VertAdv::computeStdVAdvTend( // transport scheme. ProvThickness input is provisional layer thickness after // horizontal thickness flux void VertAdv::computeFCTVAdvTend( - const Array3DReal &TracerTend, ///< [inout] tracer tendencies - const Array3DReal &Tracers, ///< [in] tracer array - const Array2DReal &ProvThickness, ///< [in] provisional layer thickness - const Real Dt ///< [in] time step + const Array3DReal &TracerTend, //< [inout] tracer tendencies + const Array3DReal &Tracers, //< [in] tracer array + const Array2DReal &ProvThickness, //< [in] provisional layer thickness + const Real Dt //< [in] time step ) { OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index cbe808720e8c..5a173fdd7fb7 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -1,6 +1,13 @@ #ifndef OMEGA_VERTADV_H #define OMEGA_VERTADV_H -//===-- ocn/VertAdv.h - vertical advection ----------------------*- C++ -*-===// +//===-- ocn/VertAdv.h - vertical advection definitions ----------*- C++ -*-===// +// +/// \file +/// \brief Contains methods and variables for vertical advection +/// +/// This header defines the VertAdv class which contains methods and variables +/// for calculating the vertical velocity and for vertical transport of +/// thickness, velocity, and tracers. // //===----------------------------------------------------------------------===// @@ -50,16 +57,26 @@ class VertAdv { // Number of tracers I4 NTracers; - // Coefficient for blending 3rd-order and 4th-order reconstruction of tracers + // Coefficient for blending 3rd-order and 4th-order reconstruction of + // tracers with VertFluxOption::Third. Coef3rdOrder = 1 gives purely + // 3rd-order, Coef3rdOrder = 0 gives purely 4th-order. Real Coef3rdOrder; - // VertAdv instance name + Array2DReal VerticalVelocity; ///< pseudovelocity through top of cell + Array2DReal + TotalVerticalVelocity; ///< transport velocity through top of Cell + Array3DReal VertFlux; ///< fluxes at vertical interfaces + Array3DReal LowOrderVertFlux; ///< low-order fluxes for FCT + + // VertAdv instance name and group name std::string Name; + std::string GroupName; - Array2DReal VerticalVelocity; // pseudovelocity through top of cell - Array2DReal TotalVerticalVelocity; // transport velocity through top of Cell - Array3DReal VertFlux; // fluxes at vertical interfaces - Array3DReal LowOrderVertFlux; // low-order fluxes for FCT + // Field names + std::string VerticalVelocityFldName; + std::string TotalVertVelocityFldName; + std::string VertFluxFldName; + std::string LowOrderVertFluxFldName; // public methods @@ -68,10 +85,11 @@ class VertAdv { /// Creates a new vertical advection object by calling the constructor and /// puts it in the AllVertAdvs map. - static VertAdv *create(const std::string &Name, // [in] name for new VertAdv - const HorzMesh *Mesh, // [in] associated HorzMesh - const VertCoord *VCoord, // [in] associated VertCoord - Config *Options // [in] configuration options + static VertAdv * + create(const std::string &Name, ///< [in] name for new VertAdv + const HorzMesh *Mesh, ///< [in] associated HorzMesh + const VertCoord *VCoord, ///< [in] associated VertCoord + Config *Options ///< [in] configuration options ); /// Destructor - deallocates all memory and deletes a VertAdv @@ -95,50 +113,54 @@ class VertAdv { /// Determine transport due to vertical advection from divergence of /// horizontal advection. void computeVerticalVelocity( - const Array2DReal &NormalVelocity, // [in] horizontal velocity - const Array2DReal &FluxLayerThickEdge // [in] layer thickness at edges + const Array2DReal &NormalVelocity, ///< [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge ///< [in] layer thickness at edges ); /// Compute pseudo thickness tendency due to vertical advection void computeThicknessVAdvTend( - const Array2DReal &ThickTend // [inout] thickness tendency + const Array2DReal &ThickTend ///< [inout] thickness tendency ); /// Compute velocity tendency due to vertical advection void computeVelocityVAdvTend( - const Array2DReal &VelTend, // [inout] horizontal velocity tendency - const Array2DReal &NormalVelocity, // [in] horizontal velocity - const Array2DReal &FluxLayerThickEdge // [in] layer thickness at edges + const Array2DReal &VelTend, ///< [inout] horizontal velocity tendency + const Array2DReal &NormalVelocity, ///< [in] horizontal velocity + const Array2DReal &FluxLayerThickEdge ///< [in] layer thickness at edges ); - /// Compute tracer tendency due to vertical advection, TimeStep is only - /// needed as an argument for flux-corrected transport + /// Compute tracer tendency due to vertical advection, The LayerThickness at + /// the beginning of the time step is passed for standard advection, while + /// the provisional thickness including horizontal thickness flux is passed + /// for flux-corrected transport. The TimeStep is only needed as an argument + /// for flux-corrected transport void computeTracerVAdvTend( - const Array3DReal &TracerTend, // [inout] tracer tendencies - const Array3DReal &Tracers, // [in] tracer array - const Array2DReal &LayerThickness, // [in] layer thickness - const TimeInterval TimeStep = TimeInterval() // [in] (optional) time step + const Array3DReal &TracerTend, ///< [inout] tracer tendencies + const Array3DReal &Tracers, ///< [in] tracer array + const Array2DReal &LayerThickness, ///< [in] layer thickness + const TimeInterval TimeStep = + TimeInterval() ///< [in] (optional) time step ); /// Compute tracer fluxes due to vertical advection void computeVerticalFluxes( - const Array3DReal &Tracers, // [in] tracer array - const Array2DReal &LayerThickness // [in] layer thickness + const Array3DReal &Tracers, ///< [in] tracer array + const Array2DReal &LayerThickness ///< [in] layer thickness ); /// Compute tracer tendencies due to vertical advection using standard /// advection scheme - void - computeStdVAdvTend(const Array3DReal &TracerTend // [inout] tracer tendencies + void computeStdVAdvTend( + const Array3DReal &TracerTend ///< [inout] tracer tendencies ); /// Compute tracer tendencies due to vertical advection using flux-corrected /// transport scheme void computeFCTVAdvTend( - const Array3DReal &TracerTend, // [inout] tracer tendencies - const Array3DReal &Tracers, // [in] tracer array - const Array2DReal &ProvThickness, // [in] provisional layer thickness - const Real Dt // [in] time step + const Array3DReal &TracerTend, ///< [inout] tracer tendencies + const Array3DReal &Tracers, ///< [in] tracer array + const Array2DReal &ProvThickness, ///< [in] provisional layer thickness + const Real Dt ///< [in] time step ); private: @@ -169,12 +191,15 @@ class VertAdv { // private methods /// construct a new vertical coordinate object - VertAdv(const std::string &Name, // [in] Name for new VertAdv - const HorzMesh *Mesh, // [in] associated HorzMesh - const VertCoord *VCoord, // [in] associated VertCoord - Config *Options // [in] configuration options + VertAdv(const std::string &Name, ///< [in] Name for new VertAdv + const HorzMesh *Mesh, ///< [in] associated HorzMesh + const VertCoord *VCoord, ///< [in] associated VertCoord + Config *Options ///< [in] configuration options ); + /// define field metadata + void defineFields(); + // Forbid copy and move construction VertAdv(const VertAdv &) = delete; VertAdv(VertAdv &&) = delete; From 17da3e485f71931c492cd94e9315de2bf828bf7d Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Fri, 9 Jan 2026 14:25:52 -0800 Subject: [PATCH 061/152] Some bug fixes for running on GPU --- components/omega/test/ocn/VertAdvTest.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/components/omega/test/ocn/VertAdvTest.cpp b/components/omega/test/ocn/VertAdvTest.cpp index 71f7e448f2e7..18b1bea12f9f 100644 --- a/components/omega/test/ocn/VertAdvTest.cpp +++ b/components/omega/test/ocn/VertAdvTest.cpp @@ -186,7 +186,6 @@ int main(int argc, char *argv[]) { I4 NVertLayers = DefVertAdv->NVertLayers; I4 NVertLayersP1 = DefVertAdv->NVertLayersP1; - I4 NTracers = DefVertAdv->NTracers; Array2DReal FluxLayerThickEdge("FluxLayerThickEdge", DefDecomp->NEdgesSize, NVertLayers); @@ -194,7 +193,7 @@ int main(int argc, char *argv[]) { NVertLayers); const I4 ICell0 = 0; - const I4 NEdgesOnCell0 = DefDecomp->NEdgesOnCell(ICell0); + const I4 NEdgesOnCell0 = DefDecomp->NEdgesOnCellH(ICell0); // Test for computeVerticalVelocity I4 Err = 0; @@ -225,9 +224,9 @@ int main(int argc, char *argv[]) { // layer Real Perim0 = 0.; for (int J = 0; J < NEdgesOnCell0; ++J) { - Perim0 += DefMesh->DvEdge(DefMesh->EdgesOnCell(ICell0, J)); + Perim0 += DefMesh->DvEdgeH(DefMesh->EdgesOnCellH(ICell0, J)); } - Real InvAreaCell0 = 1._Real / DefMesh->AreaCell(ICell0); + Real InvAreaCell0 = 1._Real / DefMesh->AreaCellH(ICell0); for (int K = 1; K < NVertLayersP1; ++K) { Real Expected = (NVertLayers - K) * Perim0 * InvAreaCell0; @@ -333,6 +332,7 @@ int main(int argc, char *argv[]) { DefVertAdv->Coef3rdOrder = 1.; Tol = 0.1_Real; + OMEGA_SCOPE(LocMaxLyrCell, DefVCoord->MaxLayerCell); // Compute L2 error for computeStdVAdvTend with each VertFluxOption over a // set of resolutions. Compare successive errors for each Order to confirm @@ -342,8 +342,10 @@ int main(int argc, char *argv[]) { DefVertAdv->VertFluxChoice = Order; std::vector L2Errors; for (I4 NLayers : NLayersArray) { - DefVCoord->MaxLayerCell(0) = NLayers - 1; - Real L2Err = tendTest(NLayers, DefVertAdv); + parallelFor( + {1}, + KOKKOS_LAMBDA(const int &) { LocMaxLyrCell(0) = NLayers - 1; }); + Real L2Err = tendTest(NLayers, DefVertAdv); L2Errors.push_back(L2Err); } Real ExpectedRat = std::pow(2._Real, OrderOfAcc.at(Order)); @@ -370,6 +372,9 @@ int main(int argc, char *argv[]) { DefVertAdv->Coef3rdOrder = 0.25; std::vector TestVel = {1._Real, -1._Real}; std::vector StartIdx = {NVertLayers / 2, NVertLayers / 4}; + parallelFor( + {1}, + KOKKOS_LAMBDA(const int &) { LocMaxLyrCell(0) = NVertLayers - 1; }); // Test a top-hat function for a uniform velocity in both directions for (int IMono = 0; IMono < 2; ++IMono) { @@ -378,8 +383,7 @@ int main(int argc, char *argv[]) { Array2DReal LayerThick("LayerThick", 1, NVertLayers); Array3DReal TendCell3D("TendCell3D", 1, 1, NVertLayers); deepCopy(LayerThick, 1._Real); - DefVCoord->MaxLayerCell(0) = NVertLayers - 1; - DefVertAdv->NVertLayers = NVertLayers; + DefVertAdv->NVertLayers = NVertLayers; LocVertVel = Array2DReal("TotalVerticaVelocity", 1, NVertLayers + 1); deepCopy(LocVertVel, TestVel[IMono]); parallelFor( From becd4f27878915e1d781a1a4b9cb4c75df47c5de Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Sat, 10 Jan 2026 15:24:00 -0500 Subject: [PATCH 062/152] Bug fixes: BottomDepth no longer in HorzMesh and typo fixes --- .../omega/src/ocn/CustomTendencyTerms.cpp | 5 +++-- components/omega/src/ocn/HorzMesh.cpp | 10 --------- components/omega/src/ocn/HorzMesh.h | 9 -------- .../auxiliaryVars/LayerThicknessAuxVars.cpp | 4 ++-- components/omega/test/ocn/HorzMeshTest.cpp | 22 ------------------- 5 files changed, 5 insertions(+), 45 deletions(-) diff --git a/components/omega/src/ocn/CustomTendencyTerms.cpp b/components/omega/src/ocn/CustomTendencyTerms.cpp index ff9c17da6435..79ab03630912 100644 --- a/components/omega/src/ocn/CustomTendencyTerms.cpp +++ b/components/omega/src/ocn/CustomTendencyTerms.cpp @@ -10,6 +10,7 @@ #include "Config.h" #include "GlobalConstants.h" #include "TimeStepper.h" +#include "VertCoord.h" namespace OMEGA { @@ -76,8 +77,8 @@ void ManufacturedSolution::init() { /// This test case assumes that the restingThickness is horizontally uniform /// and that only one vertical level is used so only one set of indices is /// used here. - HorzMesh *DefHorzMesh = HorzMesh::getDefault(); - R8 H0 = DefHorzMesh->BottomDepthH(0); + VertCoord *DefVCoord = VertCoord::getDefault(); + R8 H0 = DefVCoord->BottomDepthH(0); // Define and compute common constants R8 Kx = TwoPi / WavelengthX; // Wave in X-dir diff --git a/components/omega/src/ocn/HorzMesh.cpp b/components/omega/src/ocn/HorzMesh.cpp index c3fd1b414f08..f97d6379bae5 100644 --- a/components/omega/src/ocn/HorzMesh.cpp +++ b/components/omega/src/ocn/HorzMesh.cpp @@ -113,9 +113,6 @@ HorzMesh::HorzMesh(const std::string &Name, //< [in] Name for new mesh // Read x/y/z and lon/lat coordinates for cells, edges, and vertices readCoordinates(); - // Read the cell-centered bottom depth - readBottomDepth(); - // Read the mesh areas, lengths, and angles readMeasurements(); @@ -441,12 +438,6 @@ void HorzMesh::readCoordinates() { } // end readCoordinates -//------------------------------------------------------------------------------ -// Read the cell-centered bottom depth -void HorzMesh::readBottomDepth() { - readCellArray(BottomDepthH, "bottomDepth"); -} // end readDepth - //------------------------------------------------------------------------------ // Read the mesh areas (cell, triangle, and kite), // lengths (between centers and vertices), and edge angles @@ -602,7 +593,6 @@ void HorzMesh::copyToDevice() { AngleEdge = createDeviceMirrorCopy(AngleEdgeH); WeightsOnEdge = createDeviceMirrorCopy(WeightsOnEdgeH); FVertex = createDeviceMirrorCopy(FVertexH); - BottomDepth = createDeviceMirrorCopy(BottomDepthH); FEdge = createDeviceMirrorCopy(FEdgeH); XCell = createDeviceMirrorCopy(XCellH); YCell = createDeviceMirrorCopy(YCellH); diff --git a/components/omega/src/ocn/HorzMesh.h b/components/omega/src/ocn/HorzMesh.h index ff824479cf3d..e36762730e57 100644 --- a/components/omega/src/ocn/HorzMesh.h +++ b/components/omega/src/ocn/HorzMesh.h @@ -42,16 +42,12 @@ class HorzMesh { void readCoordinates(); - void readBottomDepth(); - void readMeasurements(); void readWeights(); void readCoriolis(); - // void computeEdgeSign(); - void copyToDevice(); // int computeMesh(); @@ -235,11 +231,6 @@ class HorzMesh { Array1DReal FVertex; ///< Coriolis parameter at vertices (radians s^-1) HostArray1DReal FVertexH; ///< Coriolis parameter at vertices (radians s^-1) - // Depth - - Array1DReal BottomDepth; ///< Depth of the bottom of the ocean (m) - HostArray1DReal BottomDepthH; ///< Depth of the bottom of the ocean (m) - // Edge sign Array2DReal EdgeSignOnCell; ///< Sign of vector connecting cells diff --git a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp index 506a328749e0..ba1a0cdde4a8 100644 --- a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp +++ b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp @@ -16,10 +16,10 @@ LayerThicknessAuxVars::LayerThicknessAuxVars(const std::string &AuxStateSuffix, VCoord->NVertLayers), ProvThickness("ProvThickness" + AuxStateSuffix, Mesh->NCellsSize, VCoord->NVertLayers), - AreaCell(Mesh->AreaCell), DvEdge(Mesh->AreaCell), + AreaCell(Mesh->AreaCell), DvEdge(Mesh->DvEdge), NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), EdgeSignOnCell(Mesh->EdgeSignOnCell), CellsOnEdge(Mesh->CellsOnEdge), - BottomDepth(Mesh->BottomDepth), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + BottomDepth(VCoord->BottomDepth), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), MinLayerCell(VCoord->MinLayerCell), MaxLayerCell(VCoord->MaxLayerCell) {} diff --git a/components/omega/test/ocn/HorzMeshTest.cpp b/components/omega/test/ocn/HorzMeshTest.cpp index 386459b13706..0a0f96b710f7 100644 --- a/components/omega/test/ocn/HorzMeshTest.cpp +++ b/components/omega/test/ocn/HorzMeshTest.cpp @@ -368,28 +368,6 @@ int main(int argc, char *argv[]) { LOG_INFO("HorzMeshTest: Vertex lon/lat test PASS"); } - // Test bounds of bathymetry - // Find minimum and maximum values of the bottom depth - // and compares to reasonable values - // Tests that the bottom depth has been read in correctly - R8 MaxBathy = -1e10; - R8 MinBathy = 1e10; - for (int Cell = 0; Cell < LocCells; Cell++) { - if (Mesh->BottomDepthH(Cell) < MinBathy) { - MinBathy = Mesh->BottomDepthH(Cell); - } - if (Mesh->BottomDepthH(Cell) > MaxBathy) { - MaxBathy = Mesh->BottomDepthH(Cell); - } - } - - if ((MinBathy > 0) && (MaxBathy < 11000.0)) { - LOG_INFO("HorzMeshTest: Bathy min/max test PASS"); - } else { - RetVal += 1; - LOG_INFO("HorzMeshTest: Bathy min/max test FAIL"); - } - // Test cell areas // Find the global sum of all the local cell areas // and compares to reasonable value for Earth's ocean area From 0516858f765b9e9a65c30bb47ac745c20b79f99c Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Sun, 11 Jan 2026 08:37:50 -0800 Subject: [PATCH 063/152] Make host copies of VertAdv arrays --- components/omega/src/ocn/VertAdv.cpp | 30 ++++++++++++++++++++++++++++ components/omega/src/ocn/VertAdv.h | 12 +++++++++++ 2 files changed, 42 insertions(+) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 97b8ab813e67..886b839025e1 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -69,10 +69,16 @@ VertAdv::VertAdv(const std::string &Name_, //< [in] name for new VertAdv Array2DReal("TotalVerticalVelocity", NCellsSize, NVertLayersP1); VertFlux = Array3DReal("VertFlux", NTracers, NCellsSize, NVertLayersP1); + // Allocate host copies + VerticalVelocityH = createHostMirrorCopy(VerticalVelocity); + TotalVerticalVelocityH = createHostMirrorCopy(TotalVerticalVelocity); + VertFluxH = createHostMirrorCopy(VertFlux); + // Low-order flux array only needed for flux-corrected transport if (VertAdvChoice == VertAdvOption::FCT) { LowOrderVertFlux = Array3DReal("LowOrderVertFlux", NTracers, NCellsSize, NVertLayersP1); + LowOrderVertFluxH = createHostMirrorCopy(LowOrderVertFlux); } defineFields(); @@ -232,6 +238,30 @@ void VertAdv::erase(std::string Name) { // process, calls the destructor } // end erase +//------------------------------------------------------------------------------ +// Perform deepCopy for each variable array from device to host +void VertAdv::copyToHost() { + + deepCopy(VerticalVelocityH, VerticalVelocity); + deepCopy(TotalVerticalVelocityH, TotalVerticalVelocity); + deepCopy(VertFluxH, VertFlux); + if (VertAdvChoice == VertAdvOption::FCT) { + deepCopy(LowOrderVertFluxH, LowOrderVertFlux); + } +} + +//------------------------------------------------------------------------------ +// Perform deepCopy for each variable array from host to device +void VertAdv::copyToDevice() { + + deepCopy(VerticalVelocity, VerticalVelocityH); + deepCopy(TotalVerticalVelocity, TotalVerticalVelocityH); + deepCopy(VertFlux, VertFluxH); + if (VertAdvChoice == VertAdvOption::FCT) { + deepCopy(LowOrderVertFlux, LowOrderVertFluxH); + } +} + //------------------------------------------------------------------------------ // Get default VertAdv VertAdv *VertAdv::getDefault() { return VertAdv::DefaultVertAdv; } diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index 5a173fdd7fb7..95bd30968913 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -68,6 +68,12 @@ class VertAdv { Array3DReal VertFlux; ///< fluxes at vertical interfaces Array3DReal LowOrderVertFlux; ///< low-order fluxes for FCT + // Arrays on host + HostArray2DReal VerticalVelocityH; + HostArray2DReal TotalVerticalVelocityH; + HostArray3DReal VertFluxH; + HostArray3DReal LowOrderVertFluxH; + // VertAdv instance name and group name std::string Name; std::string GroupName; @@ -92,6 +98,12 @@ class VertAdv { Config *Options ///< [in] configuration options ); + /// Copy member arrays from device to host + void copyToHost(); + + /// Copy member arrays from host to device + void copyToDevice(); + /// Destructor - deallocates all memory and deletes a VertAdv ~VertAdv(); From ece1a8cd14c6c27cc6c3dfed0dd520f67e3aa84b Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Sun, 11 Jan 2026 09:46:39 -0800 Subject: [PATCH 064/152] Linting fix --- .../omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp index ba1a0cdde4a8..8a69aa7c8269 100644 --- a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp +++ b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.cpp @@ -19,7 +19,8 @@ LayerThicknessAuxVars::LayerThicknessAuxVars(const std::string &AuxStateSuffix, AreaCell(Mesh->AreaCell), DvEdge(Mesh->DvEdge), NEdgesOnCell(Mesh->NEdgesOnCell), EdgesOnCell(Mesh->EdgesOnCell), EdgeSignOnCell(Mesh->EdgeSignOnCell), CellsOnEdge(Mesh->CellsOnEdge), - BottomDepth(VCoord->BottomDepth), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + BottomDepth(VCoord->BottomDepth), + MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), MinLayerCell(VCoord->MinLayerCell), MaxLayerCell(VCoord->MaxLayerCell) {} From e4d7484c7890c5ed1342ab643b716a3ecd2fd395 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 3 Feb 2026 12:01:18 -0800 Subject: [PATCH 065/152] Resize flux arrays in VertAdvTest --- components/omega/test/ocn/VertAdvTest.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/omega/test/ocn/VertAdvTest.cpp b/components/omega/test/ocn/VertAdvTest.cpp index 18b1bea12f9f..816ea9183192 100644 --- a/components/omega/test/ocn/VertAdvTest.cpp +++ b/components/omega/test/ocn/VertAdvTest.cpp @@ -53,6 +53,7 @@ Real tendTest(const int NLayers, VertAdv *VAdv) { VAdv->NVertLayers = NLayers; VAdv->TotalVerticalVelocity = Array2DReal("TotalVerticaVelocity", 1, NLayers + 1); + VAdv->VertFlux = Array3DReal("VertFlux", 1, 1, NLayers + 1); Array1DReal XLayer("XLayer", NLayers); OMEGA_SCOPE(LocTotVertVel, VAdv->TotalVerticalVelocity); Array2DReal LayerThick("LayerThickness", 1, NLayers); @@ -366,7 +367,9 @@ int main(int argc, char *argv[]) { // Monotonicity check for computeFCTVAdvTend - NVertLayers = 256; + NVertLayers = 256; + DefVertAdv->LowOrderVertFlux = + Array3DReal("LowOrderVertFlux", 1, 1, NVertLayers + 1); DefVertAdv->VertAdvChoice = VertAdvOption::FCT; DefVertAdv->VertFluxChoice = VertFluxOption::Third; DefVertAdv->Coef3rdOrder = 0.25; From 116f9d3dc8e2854ba81396c1e3fda00d0b15c38f Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 5 Feb 2026 08:32:23 -0800 Subject: [PATCH 066/152] Rename config option from Enabled to Enable --- components/omega/configs/Default.yml | 2 +- components/omega/src/ocn/VertAdv.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 6cc0cf7877fd..d46de8c24e95 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -27,7 +27,7 @@ Omega: FluxThicknessType: Center HorzTracerFluxOrder: 2 FluxTracerType: Center - VerticalTracerFluxLimiterEnabled: true + VerticalTracerFluxLimiterEnable: true VerticalTracerFluxOrder: 3 WindStress: InterpType: Isotropic diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 886b839025e1..72bad626b94f 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -317,7 +317,7 @@ void VertAdv::readConfigOptions(Config *OmegaConfig) { CHECK_ERROR_ABORT(Err, "VertAdv: Advection group not in Config"); bool FluxLimiterOn; - Err += AdvectConfig.get("VerticalTracerFluxLimiterEnabled", FluxLimiterOn); + Err += AdvectConfig.get("VerticalTracerFluxLimiterEnable", FluxLimiterOn); CHECK_ERROR_ABORT( Err, "VertAdv: VerticalTracerFluxLimiterEnabled not found in AdvectConfig"); From 690433340d32e3e9e90b998810e9ebd6e5172da6 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 5 Feb 2026 08:52:38 -0800 Subject: [PATCH 067/152] Remove unnecessary header --- components/omega/src/ocn/VertAdv.h | 1 - 1 file changed, 1 deletion(-) diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index 95bd30968913..cae279ffe1fe 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -11,7 +11,6 @@ // //===----------------------------------------------------------------------===// -#include "AuxiliaryState.h" #include "Config.h" #include "DataTypes.h" #include "Error.h" From c32fc97e68a8ebe8fb664bef60e113ac7795f885 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 5 Feb 2026 08:56:24 -0800 Subject: [PATCH 068/152] Skip vertical tracer advection when not enabled --- components/omega/src/ocn/VertAdv.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 72bad626b94f..d97f9da6bdcb 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -568,6 +568,10 @@ void VertAdv::computeTracerVAdvTend( const TimeInterval TimeStep //< [in] (optional) time step ) { + // Return if vertical advection tracer tendency not enabled + if (!TracerVertAdvEnabled) + return; + // Compute tracer fluxes at the interfaces computeVerticalFluxes(Tracers, LayerThickness); From 39b87832eaca228756323b8b8b8dd8b0d4af2cc0 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 10 Feb 2026 15:33:20 -0800 Subject: [PATCH 069/152] Add TimeStep to AuxState for ProvThickness --- components/omega/src/ocn/AuxiliaryState.cpp | 20 +++++++++++++------ components/omega/src/ocn/AuxiliaryState.h | 7 +++++-- components/omega/src/ocn/VertAdv.cpp | 2 +- .../omega/test/ocn/AuxiliaryStateTest.cpp | 3 ++- .../test/timeStepping/TimeStepperTest.cpp | 4 +++- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/components/omega/src/ocn/AuxiliaryState.cpp b/components/omega/src/ocn/AuxiliaryState.cpp index 423c3506346f..a364445413ec 100644 --- a/components/omega/src/ocn/AuxiliaryState.cpp +++ b/components/omega/src/ocn/AuxiliaryState.cpp @@ -3,6 +3,7 @@ #include "Field.h" #include "Logging.h" #include "Pacer.h" +#include "TimeStepper.h" namespace OMEGA { @@ -19,7 +20,7 @@ static std::string stripDefault(const std::string &Name) { // fields with IOStreams AuxiliaryState::AuxiliaryState(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, const VertCoord *VCoord, - int NTracers) + int NTracers, TimeInterval TimeStep) : Mesh(Mesh), MeshHalo(MeshHalo), VCoord(VCoord), Name(stripDefault(Name)), KineticAux(stripDefault(Name), Mesh, VCoord), LayerThicknessAux(stripDefault(Name), Mesh, VCoord), @@ -80,6 +81,9 @@ void AuxiliaryState::computeMomAux(const OceanState *State, int ThickTimeLevel, OMEGA_SCOPE(MaxLayerEdgeBot, VCoord->MaxLayerEdgeBot); OMEGA_SCOPE(MaxLayerEdgeTop, VCoord->MaxLayerEdgeTop); + R8 TimeStepSeconds; + TimeStep.get(TimeStepSeconds, TimeUnits::Seconds); + Pacer::start("AuxState:computeMomAux", 1); Pacer::start("AuxState:vertexAuxState1", 2); @@ -195,8 +199,8 @@ void AuxiliaryState::computeMomAux(const OceanState *State, int ThickTimeLevel, parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { LocLayerThicknessAux.computeVarsOnCells( - ICell, KChunk, LayerThickCell, NormalVelEdge, 0._Real); - // TODO: make timestep available to this call + ICell, KChunk, LayerThickCell, NormalVelEdge, + TimeStepSeconds); }); }); Pacer::stop("AuxState:cellAuxState3", 2); @@ -254,7 +258,8 @@ void AuxiliaryState::computeAll(const OceanState *State, AuxiliaryState *AuxiliaryState::create(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, const VertCoord *VCoord, - const int NTracers) { + const int NTracers, + TimeInterval TimeStep) { if (AllAuxStates.find(Name) != AllAuxStates.end()) { LOG_ERROR("Attempted to create a new AuxiliaryState with name {} but it " "already exists", @@ -263,7 +268,7 @@ AuxiliaryState *AuxiliaryState::create(const std::string &Name, } auto *NewAuxState = - new AuxiliaryState(Name, Mesh, MeshHalo, VCoord, NTracers); + new AuxiliaryState(Name, Mesh, MeshHalo, VCoord, NTracers, TimeStep); AllAuxStates.emplace(Name, NewAuxState); return NewAuxState; @@ -275,11 +280,14 @@ void AuxiliaryState::init() { const HorzMesh *DefMesh = HorzMesh::getDefault(); Halo *DefHalo = Halo::getDefault(); const VertCoord *DefVCoord = VertCoord::getDefault(); + const TimeStepper *DefTimeStepper = TimeStepper::getDefault(); int NTracers = Tracers::getNumTracers(); + TimeInterval TimeStep = DefTimeStepper->getTimeStep(); AuxiliaryState::DefaultAuxState = - AuxiliaryState::create("Default", DefMesh, DefHalo, DefVCoord, NTracers); + AuxiliaryState::create("Default", DefMesh, DefHalo, DefVCoord, NTracers, + TimeStep); Config *OmegaConfig = Config::getOmegaConfig(); DefaultAuxState->readConfigOptions(OmegaConfig); diff --git a/components/omega/src/ocn/AuxiliaryState.h b/components/omega/src/ocn/AuxiliaryState.h index 313cc157f0f3..5b997d93e3c7 100644 --- a/components/omega/src/ocn/AuxiliaryState.h +++ b/components/omega/src/ocn/AuxiliaryState.h @@ -6,6 +6,7 @@ #include "Halo.h" #include "HorzMesh.h" #include "OceanState.h" +#include "TimeMgr.h" #include "Tracers.h" #include "VertCoord.h" #include "auxiliaryVars/KineticAuxVars.h" @@ -51,7 +52,7 @@ class AuxiliaryState { // Create a non-default auxiliary state static AuxiliaryState *create(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, const VertCoord *VCoord, - int NTracers); + int NTracers, TimeInterval TimeStep); /// Get the default auxiliary state static AuxiliaryState *getDefault(); @@ -84,7 +85,7 @@ class AuxiliaryState { private: AuxiliaryState(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, - const VertCoord *VCoord, int NTracers); + const VertCoord *VCoord, int NTracers, TimeInterval TimeStep); AuxiliaryState(const AuxiliaryState &) = delete; AuxiliaryState(AuxiliaryState &&) = delete; @@ -92,6 +93,8 @@ class AuxiliaryState { const HorzMesh *Mesh; Halo *MeshHalo; const VertCoord *VCoord; + TimeInterval TimeStep; + static AuxiliaryState *DefaultAuxState; static std::map> AllAuxStates; }; diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index d97f9da6bdcb..c59b77505ab6 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -320,7 +320,7 @@ void VertAdv::readConfigOptions(Config *OmegaConfig) { Err += AdvectConfig.get("VerticalTracerFluxLimiterEnable", FluxLimiterOn); CHECK_ERROR_ABORT( Err, - "VertAdv: VerticalTracerFluxLimiterEnabled not found in AdvectConfig"); + "VertAdv: VerticalTracerFluxLimiterEnable not found in AdvectConfig"); if (FluxLimiterOn) { VertAdvChoice = VertAdvOption::FCT; } else { diff --git a/components/omega/test/ocn/AuxiliaryStateTest.cpp b/components/omega/test/ocn/AuxiliaryStateTest.cpp index 952af2814741..f53bdb332838 100644 --- a/components/omega/test/ocn/AuxiliaryStateTest.cpp +++ b/components/omega/test/ocn/AuxiliaryStateTest.cpp @@ -147,8 +147,9 @@ int testAuxState() { const auto *Mesh = HorzMesh::getDefault(); auto *MeshHalo = Halo::getDefault(); const auto *VCoord = VertCoord::getDefault(); + TimeInterval TimeStep; // test creation of another auxiliary state - AuxiliaryState::create("AnotherAuxState", Mesh, MeshHalo, VCoord, 3); + AuxiliaryState::create("AnotherAuxState", Mesh, MeshHalo, VCoord, 3, TimeStep); // test retrievel of another if (AuxiliaryState::get("AnotherAuxState")) { diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 56b14d713a02..a7cd64a98fb2 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -195,8 +195,10 @@ int initTimeStepperTest(const std::string &mesh) { LOG_ERROR("TimeStepperTest: error creating test state"); } + TimeInterval ZeroTimeStep; auto *TestAuxState = AuxiliaryState::create("TestAuxState", DefMesh, DefHalo, - DefVertCoord, NTracers); + DefVertCoord, NTracers, + ZeroTimeStep); Config *OmegaConfig = Config::getOmegaConfig(); TestAuxState->readConfigOptions(OmegaConfig); From e97befa961b26ddf07bfae05b8229da752d6220d Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 10 Feb 2026 17:33:42 -0800 Subject: [PATCH 070/152] Remove commented out ssh calculation --- .../omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h index a415bfa02ba3..68bb7da2296c 100644 --- a/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h +++ b/components/omega/src/ocn/auxiliaryVars/LayerThicknessAuxVars.h @@ -96,15 +96,6 @@ class LayerThicknessAuxVars { const int K = KStart + KVec; ProvThickness(ICell, K) = LayerThickCell(ICell, K) + TmpProv[KVec]; } - - /* - Real TotalThickness = 0.0; - for (int K = 0; K < NVertLayers; K++) { - TotalThickness += LayerThickCell(ICell, K); - } - - SshCell(ICell) = TotalThickness - BottomDepth(ICell); - */ } void registerFields(const std::string &AuxGroupName, From 1372e299b1aff3d0ba10373eccabe75a33e1552d Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 10 Feb 2026 20:19:06 -0800 Subject: [PATCH 071/152] Add LayerThicknessAux.computeVarsOnCells to AuxState::computeAll This PR adds ProvThickness to LayerThicknessAux, which is needed by the tracer equations. PR #335 removes SSH from LayerThicknessAux and removes computeVarsOnCells from computeMomAux. --- components/omega/src/ocn/AuxiliaryState.cpp | 50 ++++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/components/omega/src/ocn/AuxiliaryState.cpp b/components/omega/src/ocn/AuxiliaryState.cpp index a364445413ec..4cb340d41740 100644 --- a/components/omega/src/ocn/AuxiliaryState.cpp +++ b/components/omega/src/ocn/AuxiliaryState.cpp @@ -217,16 +217,53 @@ void AuxiliaryState::computeAll(const OceanState *State, const int NTracers = TracerArray.extent_int(0); + OMEGA_SCOPE(LocLayerThicknessAux, LayerThicknessAux); OMEGA_SCOPE(LocTracerAux, TracerAux); OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); OMEGA_SCOPE(MinLayerEdgeBot, VCoord->MinLayerEdgeBot); OMEGA_SCOPE(MaxLayerEdgeTop, VCoord->MaxLayerEdgeTop); + R8 TimeStepSeconds; + TimeStep.get(TimeStepSeconds, TimeUnits::Seconds); + Pacer::start("AuxState:computeAll", 1); computeMomAux(State, ThickTimeLevel, VelTimeLevel); + Pacer::start("AuxState:cellAuxState3", 2); + parallelForOuter( + "cellAuxState3", {Mesh->NCellsAll}, + KOKKOS_LAMBDA(int ICell, const TeamMember &Team) { + const int KMin = MinLayerCell(ICell); + const int KMax = MaxLayerCell(ICell); + const int KRange = vertRangeChunked(KMin, KMax); + + parallelForInner( + Team, KRange, INNER_LAMBDA(int KChunk) { + LocLayerThicknessAux.computeVarsOnCells( + ICell, KChunk, LayerThickCell, NormalVelEdge, + TimeStepSeconds); + }); + }); + Pacer::stop("AuxState:cellAuxState3", 2); + + 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); @@ -277,17 +314,16 @@ AuxiliaryState *AuxiliaryState::create(const std::string &Name, // Create the default auxiliary state. Assumes that HorzMesh, VertCoord and // Halo have been initialized. void AuxiliaryState::init() { - const HorzMesh *DefMesh = HorzMesh::getDefault(); - Halo *DefHalo = Halo::getDefault(); - const VertCoord *DefVCoord = VertCoord::getDefault(); + const HorzMesh *DefMesh = HorzMesh::getDefault(); + Halo *DefHalo = Halo::getDefault(); + const VertCoord *DefVCoord = VertCoord::getDefault(); const TimeStepper *DefTimeStepper = TimeStepper::getDefault(); - int NTracers = Tracers::getNumTracers(); + int NTracers = Tracers::getNumTracers(); TimeInterval TimeStep = DefTimeStepper->getTimeStep(); - AuxiliaryState::DefaultAuxState = - AuxiliaryState::create("Default", DefMesh, DefHalo, DefVCoord, NTracers, - TimeStep); + AuxiliaryState::DefaultAuxState = AuxiliaryState::create( + "Default", DefMesh, DefHalo, DefVCoord, NTracers, TimeStep); Config *OmegaConfig = Config::getOmegaConfig(); DefaultAuxState->readConfigOptions(OmegaConfig); From da4107d7ec10fd30bf2d806027d4858a15b16f53 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 07:23:23 -0800 Subject: [PATCH 072/152] Add VertAdv to Tendencies --- components/omega/src/ocn/Tendencies.cpp | 14 +++++++++----- components/omega/src/ocn/Tendencies.h | 5 ++++- components/omega/test/ocn/TendenciesTest.cpp | 4 +++- .../omega/test/timeStepping/TimeStepperTest.cpp | 11 +++++++---- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 4f661579dbae..b0f54e1c0758 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -13,6 +13,7 @@ #include "Error.h" #include "Pacer.h" #include "Tracers.h" +#include "VertAdv.h" #include namespace OMEGA { @@ -28,6 +29,7 @@ void Tendencies::init() { HorzMesh *DefHorzMesh = HorzMesh::getDefault(); VertCoord *DefVertCoord = VertCoord::getDefault(); + VertAdv *DefVertAdv = VertAdv::getDefault(); I4 NTracers = Tracers::getNumTracers(); @@ -66,8 +68,8 @@ void Tendencies::init() { // Ceate default tendencies Tendencies::DefaultTendencies = - create("Default", DefHorzMesh, DefVertCoord, NTracers, &TendConfig, - CustomThickTend, CustomVelTend); + create("Default", DefHorzMesh, DefVertCoord, DefVertAdv, NTracers, + &TendConfig, CustomThickTend, CustomVelTend); DefaultTendencies->readConfig(OmegaConfig); @@ -239,11 +241,12 @@ void Tendencies::readConfig(Config *OmegaConfig ///< [in] Omega config Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection int NTracersIn, ///< [in] Number of tracers Config *Options, ///< [in] Configuration options CustomTendencyType InCustomThicknessTend, CustomTendencyType InCustomVelocityTend) - : Mesh(Mesh), VCoord(VCoord), ThicknessFluxDiv(Mesh, VCoord), + : Mesh(Mesh), VCoord(VCoord), VAdv(VAdv), ThicknessFluxDiv(Mesh, VCoord), PotientialVortHAdv(Mesh, VCoord), KEGrad(Mesh, VCoord), SSHGrad(Mesh, VCoord), VelocityDiffusion(Mesh, VCoord), VelocityHyperDiff(Mesh, VCoord), WindForcing(Mesh, VCoord), @@ -267,10 +270,11 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection int NTracersIn, ///< [in] Number of tracers Config *Options) ///< [in] Configuration options - : Tendencies(Name, Mesh, VCoord, NTracersIn, Options, CustomTendencyType{}, - CustomTendencyType{}) {} + : Tendencies(Name, Mesh, VCoord, VAdv, NTracersIn, Options, + CustomTendencyType{}, CustomTendencyType{}) {} //------------------------------------------------------------------------------ // Compute tendencies for layer thickness equation diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index ef2afdc0bc38..457bd2767b76 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -37,7 +37,7 @@ #include "OceanState.h" #include "TendencyTerms.h" #include "TimeMgr.h" -#include "VertCoord.h" +#include "VertAdv.h" #include #include @@ -151,6 +151,7 @@ class Tendencies { Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection int NTracersIn, ///< [in] Number of tracers Config *Options, ///< [in] Configuration options CustomTendencyType InCustomThicknessTend, @@ -159,6 +160,7 @@ class Tendencies { Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection int NTracersIn, ///< [in] Number of tracers Config *Options ///< [in] Configuration options ); @@ -169,6 +171,7 @@ class Tendencies { const HorzMesh *Mesh; ///< Pointer to horizontal mesh const VertCoord *VCoord; ///< Pointer to vertical coordinate + VertAdv *VAdv; ///< Pointer to vertical advection I4 NTracers; ///< Number of tracers // Pointer to default tendencies diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 8ef4caa84f0e..e7b7a7d76a44 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -123,6 +123,7 @@ int initTendenciesTest(const std::string &mesh) { HorzMesh::init(); VertCoord::init(); Tracers::init(); + VertAdv::init(); // VertCoord::init2(); @@ -156,10 +157,11 @@ int testTendencies() { const auto Mesh = HorzMesh::getDefault(); const auto VCoord = VertCoord::getDefault(); + const auto VAdv = VertAdv::getDefault(); VCoord->NVertLayers = 12; // test creation of another tendencies Config *Options = Config::getOmegaConfig(); - Tendencies::create("TestTendencies", Mesh, VCoord, 3, Options); + Tendencies::create("TestTendencies", Mesh, VCoord, VAdv, 3, Options); // test retrievel of another tendencies if (Tendencies::get("TestTendencies")) { diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index a7cd64a98fb2..176bfa862f13 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -168,6 +168,10 @@ int initTimeStepperTest(const std::string &mesh) { auto *DefVertCoord = VertCoord::getDefault(); Tracers::init(); + + VertAdv::init(); + auto *DefVertAdv = VertAdv::getDefault(); + AuxiliaryState::init(); Tendencies::init(); @@ -196,9 +200,8 @@ int initTimeStepperTest(const std::string &mesh) { } TimeInterval ZeroTimeStep; - auto *TestAuxState = AuxiliaryState::create("TestAuxState", DefMesh, DefHalo, - DefVertCoord, NTracers, - ZeroTimeStep); + auto *TestAuxState = AuxiliaryState::create( + "TestAuxState", DefMesh, DefHalo, DefVertCoord, NTracers, ZeroTimeStep); Config *OmegaConfig = Config::getOmegaConfig(); TestAuxState->readConfigOptions(OmegaConfig); @@ -212,7 +215,7 @@ int initTimeStepperTest(const std::string &mesh) { // Creating non-default tendencies with custom velocity tendencies auto *TestTendencies = Tendencies::create( - "TestTendencies", DefMesh, DefVertCoord, NTracers, &Options, + "TestTendencies", DefMesh, DefVertCoord, DefVertAdv, NTracers, &Options, Tendencies::CustomTendencyType{}, DecayVelocityTendency{}); if (!TestTendencies) { Err++; From 16597a2aee44e2f20723b6720cff7e4f7c7f0425 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 07:36:27 -0800 Subject: [PATCH 073/152] Disable VertAdv tends in TimeStepperTest --- components/omega/test/timeStepping/TimeStepperTest.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 176bfa862f13..b7cc3e541189 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -234,6 +234,9 @@ int initTimeStepperTest(const std::string &mesh) { TestTendencies->TracerHyperDiff.Enabled = false; TestTendencies->WindForcing.Enabled = false; TestTendencies->BottomDrag.Enabled = false; + DefVertAdv->ThickVertAdvEnabled = false; + DefVertAdv->VelVertAdvEnabled = false; + DefVertAdv->TracerVertAdvEnabled = false; return Err; } @@ -274,6 +277,7 @@ void finalizeTimeStepperTest() { Tendencies::clear(); AuxiliaryState::clear(); OceanState::clear(); + VertAdv::clear(); VertCoord::clear(); HorzMesh::clear(); Dimension::clear(); From a8853d1167dc90f2ff988cf2adf081d3db6f3f86 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 08:39:03 -0800 Subject: [PATCH 074/152] Add VertAdv to AuxState --- components/omega/src/ocn/AuxiliaryState.cpp | 20 ++++++++++--------- components/omega/src/ocn/AuxiliaryState.h | 8 ++++++-- .../omega/test/ocn/AuxiliaryStateTest.cpp | 7 ++++++- components/omega/test/ocn/TendenciesTest.cpp | 1 + .../test/timeStepping/TimeStepperTest.cpp | 17 ++++++++-------- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/components/omega/src/ocn/AuxiliaryState.cpp b/components/omega/src/ocn/AuxiliaryState.cpp index 4cb340d41740..d2dff8ed6a41 100644 --- a/components/omega/src/ocn/AuxiliaryState.cpp +++ b/components/omega/src/ocn/AuxiliaryState.cpp @@ -20,9 +20,10 @@ static std::string stripDefault(const std::string &Name) { // fields with IOStreams AuxiliaryState::AuxiliaryState(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, const VertCoord *VCoord, - int NTracers, TimeInterval TimeStep) - : Mesh(Mesh), MeshHalo(MeshHalo), VCoord(VCoord), Name(stripDefault(Name)), - KineticAux(stripDefault(Name), Mesh, VCoord), + VertAdv *VAdv, int NTracers, + TimeInterval TimeStep) + : Mesh(Mesh), MeshHalo(MeshHalo), VCoord(VCoord), VAdv(VAdv), + Name(stripDefault(Name)), KineticAux(stripDefault(Name), Mesh, VCoord), LayerThicknessAux(stripDefault(Name), Mesh, VCoord), VorticityAux(stripDefault(Name), Mesh, VCoord), VelocityDel2Aux(stripDefault(Name), Mesh, VCoord), @@ -294,7 +295,7 @@ void AuxiliaryState::computeAll(const OceanState *State, // Create a non-default auxiliary state AuxiliaryState *AuxiliaryState::create(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, - const VertCoord *VCoord, + const VertCoord *VCoord, VertAdv *VAdv, const int NTracers, TimeInterval TimeStep) { if (AllAuxStates.find(Name) != AllAuxStates.end()) { @@ -304,26 +305,27 @@ AuxiliaryState *AuxiliaryState::create(const std::string &Name, return nullptr; } - auto *NewAuxState = - new AuxiliaryState(Name, Mesh, MeshHalo, VCoord, NTracers, TimeStep); + auto *NewAuxState = new AuxiliaryState(Name, Mesh, MeshHalo, VCoord, VAdv, + NTracers, TimeStep); AllAuxStates.emplace(Name, NewAuxState); return NewAuxState; } -// Create the default auxiliary state. Assumes that HorzMesh, VertCoord and -// Halo have been initialized. +// Create the default auxiliary state. Assumes that HorzMesh, VertCoord, +// VertAdv, and Halo have been initialized. void AuxiliaryState::init() { const HorzMesh *DefMesh = HorzMesh::getDefault(); Halo *DefHalo = Halo::getDefault(); const VertCoord *DefVCoord = VertCoord::getDefault(); + VertAdv *DefVAdv = VertAdv::getDefault(); const TimeStepper *DefTimeStepper = TimeStepper::getDefault(); int NTracers = Tracers::getNumTracers(); TimeInterval TimeStep = DefTimeStepper->getTimeStep(); AuxiliaryState::DefaultAuxState = AuxiliaryState::create( - "Default", DefMesh, DefHalo, DefVCoord, NTracers, TimeStep); + "Default", DefMesh, DefHalo, DefVCoord, DefVAdv, NTracers, TimeStep); Config *OmegaConfig = Config::getOmegaConfig(); DefaultAuxState->readConfigOptions(OmegaConfig); diff --git a/components/omega/src/ocn/AuxiliaryState.h b/components/omega/src/ocn/AuxiliaryState.h index 5b997d93e3c7..60a414d39d35 100644 --- a/components/omega/src/ocn/AuxiliaryState.h +++ b/components/omega/src/ocn/AuxiliaryState.h @@ -8,6 +8,7 @@ #include "OceanState.h" #include "TimeMgr.h" #include "Tracers.h" +#include "VertAdv.h" #include "VertCoord.h" #include "auxiliaryVars/KineticAuxVars.h" #include "auxiliaryVars/LayerThicknessAuxVars.h" @@ -52,7 +53,8 @@ class AuxiliaryState { // Create a non-default auxiliary state static AuxiliaryState *create(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, const VertCoord *VCoord, - int NTracers, TimeInterval TimeStep); + VertAdv *Vadv, int NTracers, + TimeInterval TimeStep); /// Get the default auxiliary state static AuxiliaryState *getDefault(); @@ -85,7 +87,8 @@ class AuxiliaryState { private: AuxiliaryState(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, - const VertCoord *VCoord, int NTracers, TimeInterval TimeStep); + const VertCoord *VCoord, VertAdv *Vadv, int NTracers, + TimeInterval TimeStep); AuxiliaryState(const AuxiliaryState &) = delete; AuxiliaryState(AuxiliaryState &&) = delete; @@ -93,6 +96,7 @@ class AuxiliaryState { const HorzMesh *Mesh; Halo *MeshHalo; const VertCoord *VCoord; + VertAdv *VAdv; TimeInterval TimeStep; static AuxiliaryState *DefaultAuxState; diff --git a/components/omega/test/ocn/AuxiliaryStateTest.cpp b/components/omega/test/ocn/AuxiliaryStateTest.cpp index f53bdb332838..a646f31dfce4 100644 --- a/components/omega/test/ocn/AuxiliaryStateTest.cpp +++ b/components/omega/test/ocn/AuxiliaryStateTest.cpp @@ -124,6 +124,8 @@ int initAuxStateTest(const std::string &mesh) { LOG_ERROR("AuxStateTest: error initializing default state"); } + VertAdv::init(); + return Err; } @@ -147,9 +149,11 @@ int testAuxState() { const auto *Mesh = HorzMesh::getDefault(); auto *MeshHalo = Halo::getDefault(); const auto *VCoord = VertCoord::getDefault(); + auto *VAdv = VertAdv::getDefault(); TimeInterval TimeStep; // test creation of another auxiliary state - AuxiliaryState::create("AnotherAuxState", Mesh, MeshHalo, VCoord, 3, TimeStep); + AuxiliaryState::create("AnotherAuxState", Mesh, MeshHalo, VCoord, VAdv, 3, + TimeStep); // test retrievel of another if (AuxiliaryState::get("AnotherAuxState")) { @@ -311,6 +315,7 @@ int testAuxState() { void finalizeAuxStateTest() { Tracers::clear(); OceanState::clear(); + VertAdv::clear(); VertCoord::clear(); HorzMesh::clear(); Field::clear(); diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index e7b7a7d76a44..d3d812216e56 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -236,6 +236,7 @@ void finalizeTendenciesTest() { Tracers::clear(); AuxiliaryState::clear(); OceanState::clear(); + VertAdv::clear(); VertCoord::clear(); HorzMesh::clear(); Field::clear(); diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index b7cc3e541189..ed31f8ae5aa9 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -170,7 +170,7 @@ int initTimeStepperTest(const std::string &mesh) { Tracers::init(); VertAdv::init(); - auto *DefVertAdv = VertAdv::getDefault(); + auto *DefVAdv = VertAdv::getDefault(); AuxiliaryState::init(); Tendencies::init(); @@ -199,9 +199,10 @@ int initTimeStepperTest(const std::string &mesh) { LOG_ERROR("TimeStepperTest: error creating test state"); } - TimeInterval ZeroTimeStep; - auto *TestAuxState = AuxiliaryState::create( - "TestAuxState", DefMesh, DefHalo, DefVertCoord, NTracers, ZeroTimeStep); + TimeInterval ZeroTimeStep; // Zero-length time step placeholder + auto *TestAuxState = + AuxiliaryState::create("TestAuxState", DefMesh, DefHalo, DefVertCoord, + DefVAdv, NTracers, ZeroTimeStep); Config *OmegaConfig = Config::getOmegaConfig(); TestAuxState->readConfigOptions(OmegaConfig); @@ -215,7 +216,7 @@ int initTimeStepperTest(const std::string &mesh) { // Creating non-default tendencies with custom velocity tendencies auto *TestTendencies = Tendencies::create( - "TestTendencies", DefMesh, DefVertCoord, DefVertAdv, NTracers, &Options, + "TestTendencies", DefMesh, DefVertCoord, DefVAdv, NTracers, &Options, Tendencies::CustomTendencyType{}, DecayVelocityTendency{}); if (!TestTendencies) { Err++; @@ -234,9 +235,9 @@ int initTimeStepperTest(const std::string &mesh) { TestTendencies->TracerHyperDiff.Enabled = false; TestTendencies->WindForcing.Enabled = false; TestTendencies->BottomDrag.Enabled = false; - DefVertAdv->ThickVertAdvEnabled = false; - DefVertAdv->VelVertAdvEnabled = false; - DefVertAdv->TracerVertAdvEnabled = false; + DefVAdv->ThickVertAdvEnabled = false; + DefVAdv->VelVertAdvEnabled = false; + DefVAdv->TracerVertAdvEnabled = false; return Err; } From d27bc7998e2f8ef53ee972befe2f6ef35f9d4452 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 11:32:13 -0800 Subject: [PATCH 075/152] Add computeVerticalVelocity to computeMomAux --- components/omega/src/ocn/AuxiliaryState.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/omega/src/ocn/AuxiliaryState.cpp b/components/omega/src/ocn/AuxiliaryState.cpp index d2dff8ed6a41..60acf223b8b0 100644 --- a/components/omega/src/ocn/AuxiliaryState.cpp +++ b/components/omega/src/ocn/AuxiliaryState.cpp @@ -206,6 +206,13 @@ void AuxiliaryState::computeMomAux(const OceanState *State, int ThickTimeLevel, }); Pacer::stop("AuxState:cellAuxState3", 2); + Pacer::start("AuxState:computeVerticalVelocity", 2); + + const auto &FluxLayerThickEdge = LayerThicknessAux.FluxLayerThickEdge; + VAdv->computeVerticalVelocity(NormalVelEdge, FluxLayerThickEdge); + + Pacer::stop("AuxState:computeVerticalVelocity", 2); + Pacer::stop("AuxState:computeMomAux", 1); } From 7584bfbe4089cee708db83668f71762e86265282 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 11:34:09 -0800 Subject: [PATCH 076/152] Add VertAdv init and clear to OceanDriver methods --- components/omega/src/ocn/OceanFinal.cpp | 1 + components/omega/src/ocn/OceanInit.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/components/omega/src/ocn/OceanFinal.cpp b/components/omega/src/ocn/OceanFinal.cpp index bcadc327a324..3715e95baef6 100644 --- a/components/omega/src/ocn/OceanFinal.cpp +++ b/components/omega/src/ocn/OceanFinal.cpp @@ -36,6 +36,7 @@ int ocnFinalize(const TimeInstant &CurrTime ///< [in] current sim time Tendencies::clear(); AuxiliaryState::clear(); OceanState::clear(); + VertAdv::clear(); VertCoord::clear(); Dimension::clear(); Field::clear(); diff --git a/components/omega/src/ocn/OceanInit.cpp b/components/omega/src/ocn/OceanInit.cpp index db422f315c2c..b5c21aa1508f 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -26,6 +26,7 @@ #include "TimeMgr.h" #include "TimeStepper.h" #include "Tracers.h" +#include "VertAdv.h" #include "VertCoord.h" #include "mpi.h" @@ -131,6 +132,7 @@ int initOmegaModules(MPI_Comm Comm) { HorzMesh::init(); VertCoord::init(); Tracers::init(); + VertAdv::init(); AuxiliaryState::init(); Tendencies::init(); TimeStepper::init2(); From 4828a49b9aad8e9d90254af7122483874d22327a Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 11:59:28 -0800 Subject: [PATCH 077/152] Add computeThicknessVAdvTend to computeThicknessTendenciesOnly --- components/omega/src/ocn/Tendencies.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index b0f54e1c0758..cdd9477f051b 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -329,6 +329,11 @@ void Tendencies::computeThicknessTendenciesOnly( Pacer::stop("Tend:thicknessFluxDiv", 2); } + Pacer::start("Tend:computeThicknessVAdvTend", 2); + // Compute thickness tendency from vertical advection + VAdv->computeThicknessVAdvTend(LayerThicknessTend); + Pacer::stop("Tend:computeThicknessVAdvTend", 2); + if (CustomThicknessTend) { Pacer::start("Tend:customThicknessTend", 2); CustomThicknessTend(LocLayerThicknessTend, State, AuxState, From 005b96fc83dee09e14c673997665c47da79fa427 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 12:58:13 -0800 Subject: [PATCH 078/152] Add computeVelocityVAdvTend to computeVelocityTendenciesOnly --- components/omega/src/ocn/Tendencies.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index cdd9477f051b..091361f0f4f1 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -478,6 +478,12 @@ void Tendencies::computeVelocityTendenciesOnly( Pacer::stop("Tend:velocityHyperDiff", 2); } + Pacer::start("Tend:computeVelocityVAdvTend", 2); + // Compute velocity tendency from vertical advection + VAdv->computeVelocityVAdvTend(NormalVelocityTend, NormalVelEdge, + FluxLayerThickEdge); + Pacer::stop("Tend:computeVelocityVAdvTend", 2); + // Compute wind forcing const auto &NormalStressEdge = AuxState->WindForcingAux.NormalStressEdge; const auto &MeanLayerThickEdge = From a92f9a6f5dd60021d2393a8bb69bbc081beb73fc Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 15:55:39 -0800 Subject: [PATCH 079/152] Add computeTracerVAdvTend to computeTracerTendenciesOnly Also add TimeStep to Tendencies --- components/omega/src/ocn/Tendencies.cpp | 33 +++++++++++++++---- components/omega/src/ocn/Tendencies.h | 3 ++ components/omega/test/ocn/TendenciesTest.cpp | 5 ++- .../test/timeStepping/TimeStepperTest.cpp | 4 +-- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 091361f0f4f1..99a827734ff7 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -12,6 +12,7 @@ #include "CustomTendencyTerms.h" #include "Error.h" #include "Pacer.h" +#include "TimeStepper.h" #include "Tracers.h" #include "VertAdv.h" #include @@ -22,14 +23,15 @@ Tendencies *Tendencies::DefaultTendencies = nullptr; std::map> Tendencies::AllTendencies; //------------------------------------------------------------------------------ -// Initialize the tendencies. Assumes that HorzMesh and VertCoord has alread -// been initialized. +// Initialize the tendencies. Assumes that HorzMesh, VertCoord, VertAdv, and +// TimeStepper has already been initialized. void Tendencies::init() { Error Err; // error code - HorzMesh *DefHorzMesh = HorzMesh::getDefault(); - VertCoord *DefVertCoord = VertCoord::getDefault(); - VertAdv *DefVertAdv = VertAdv::getDefault(); + HorzMesh *DefHorzMesh = HorzMesh::getDefault(); + VertCoord *DefVertCoord = VertCoord::getDefault(); + VertAdv *DefVertAdv = VertAdv::getDefault(); + TimeStepper *DefTimeStepper = TimeStepper::getDefault(); I4 NTracers = Tracers::getNumTracers(); @@ -66,10 +68,12 @@ void Tendencies::init() { } // end if UseCustomTendency + TimeInterval TimeStep = DefTimeStepper->getTimeStep(); + // Ceate default tendencies Tendencies::DefaultTendencies = create("Default", DefHorzMesh, DefVertCoord, DefVertAdv, NTracers, - &TendConfig, CustomThickTend, CustomVelTend); + TimeStep, &TendConfig, CustomThickTend, CustomVelTend); DefaultTendencies->readConfig(OmegaConfig); @@ -243,6 +247,7 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection int NTracersIn, ///< [in] Number of tracers + TimeInterval TimeStepIn, ///< [in] Time step Config *Options, ///< [in] Configuration options CustomTendencyType InCustomThicknessTend, CustomTendencyType InCustomVelocityTend) @@ -264,6 +269,7 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies VCoord->NVertLayers); NTracers = NTracersIn; + TimeStep = TimeStepIn; } // end constructor @@ -272,8 +278,9 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection int NTracersIn, ///< [in] Number of tracers + TimeInterval TimeStepIn, ///< [in] Time step Config *Options) ///< [in] Configuration options - : Tendencies(Name, Mesh, VCoord, VAdv, NTracersIn, Options, + : Tendencies(Name, Mesh, VCoord, VAdv, NTracersIn, TimeStepIn, Options, CustomTendencyType{}, CustomTendencyType{}) {} //------------------------------------------------------------------------------ @@ -631,6 +638,18 @@ void Tendencies::computeTracerTendenciesOnly( Pacer::stop("Tend:tracerHyperDiff", 2); } + Pacer::start("Tend:computeTracerVAdvTend", 2); + // compute tracer tendencies from vertical advection + Array2DReal ThicknessForVAdv; + if (VAdv->VertAdvChoice == VertAdvOption::Standard) { + State->getLayerThickness(ThicknessForVAdv, ThickTimeLevel); + } else if (VAdv->VertAdvChoice == VertAdvOption::FCT) { + ThicknessForVAdv = AuxState->LayerThicknessAux.ProvThickness; + } + VAdv->computeTracerVAdvTend(TracerTend, TracerArray, ThicknessForVAdv, + TimeStep); + Pacer::stop("Tend:computeTracerVAdvTend", 2); + Pacer::stop("Tend:computeTracerTendenciesOnly", 1); } // end tracer tendency compute diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 457bd2767b76..9bc4f0584b5a 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -153,6 +153,7 @@ class Tendencies { const VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection int NTracersIn, ///< [in] Number of tracers + TimeInterval TimeStep, ///< [in] Time step Config *Options, ///< [in] Configuration options CustomTendencyType InCustomThicknessTend, CustomTendencyType InCustomVelocityTend); @@ -162,6 +163,7 @@ class Tendencies { const VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection int NTracersIn, ///< [in] Number of tracers + TimeInterval TimeStep, ///< [in] Time step Config *Options ///< [in] Configuration options ); @@ -173,6 +175,7 @@ class Tendencies { const VertCoord *VCoord; ///< Pointer to vertical coordinate VertAdv *VAdv; ///< Pointer to vertical advection I4 NTracers; ///< Number of tracers + TimeInterval TimeStep; ///< Time step // Pointer to default tendencies static Tendencies *DefaultTendencies; diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index d3d812216e56..f686a9bf9af9 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -160,8 +160,11 @@ int testTendencies() { const auto VAdv = VertAdv::getDefault(); VCoord->NVertLayers = 12; // test creation of another tendencies + + TimeInterval ZeroTimeStep; // Zero-length time step placeholder Config *Options = Config::getOmegaConfig(); - Tendencies::create("TestTendencies", Mesh, VCoord, VAdv, 3, Options); + Tendencies::create("TestTendencies", Mesh, VCoord, VAdv, 3, ZeroTimeStep, + Options); // test retrievel of another tendencies if (Tendencies::get("TestTendencies")) { diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index ed31f8ae5aa9..95aca280fa42 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -216,8 +216,8 @@ int initTimeStepperTest(const std::string &mesh) { // Creating non-default tendencies with custom velocity tendencies auto *TestTendencies = Tendencies::create( - "TestTendencies", DefMesh, DefVertCoord, DefVAdv, NTracers, &Options, - Tendencies::CustomTendencyType{}, DecayVelocityTendency{}); + "TestTendencies", DefMesh, DefVertCoord, DefVAdv, NTracers, ZeroTimeStep, + &Options, Tendencies::CustomTendencyType{}, DecayVelocityTendency{}); if (!TestTendencies) { Err++; LOG_ERROR("TimeStepperTest: error creating test tendencies"); From 36ec79213ebb80136566ad793fceb8abf425e14f Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Feb 2026 17:16:15 -0800 Subject: [PATCH 080/152] Make TimeStep nonoptional for computeTracerVAdvTend --- components/omega/src/ocn/VertAdv.cpp | 2 +- components/omega/src/ocn/VertAdv.h | 3 +-- components/omega/test/ocn/VertAdvTest.cpp | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index c59b77505ab6..30ea70cf2c8e 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -565,7 +565,7 @@ void VertAdv::computeTracerVAdvTend( const Array3DReal &TracerTend, //< [inout] tracer tendencies const Array3DReal &Tracers, //< [in] tracer array const Array2DReal &LayerThickness, //< [in] layer thickness - const TimeInterval TimeStep //< [in] (optional) time step + const TimeInterval TimeStep //< [in] time step ) { // Return if vertical advection tracer tendency not enabled diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index cae279ffe1fe..f8daaa26d38f 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -149,8 +149,7 @@ class VertAdv { const Array3DReal &TracerTend, ///< [inout] tracer tendencies const Array3DReal &Tracers, ///< [in] tracer array const Array2DReal &LayerThickness, ///< [in] layer thickness - const TimeInterval TimeStep = - TimeInterval() ///< [in] (optional) time step + const TimeInterval TimeStep ///< [in] time step ); /// Compute tracer fluxes due to vertical advection diff --git a/components/omega/test/ocn/VertAdvTest.cpp b/components/omega/test/ocn/VertAdvTest.cpp index 816ea9183192..b8bcc0e22863 100644 --- a/components/omega/test/ocn/VertAdvTest.cpp +++ b/components/omega/test/ocn/VertAdvTest.cpp @@ -59,6 +59,7 @@ Real tendTest(const int NLayers, VertAdv *VAdv) { Array2DReal LayerThick("LayerThickness", 1, NLayers); Array3DReal TracerArray("TracerArray", 1, 1, NLayers); Array3DReal Tend("TracerArray", 1, 1, NLayers); + TimeInterval ZeroTimeStep; // Set uniform velocity throughout domain deepCopy(VAdv->TotalVerticalVelocity, 1._Real); @@ -77,7 +78,7 @@ Real tendTest(const int NLayers, VertAdv *VAdv) { }); // Compute tracer tendencies - VAdv->computeTracerVAdvTend(Tend, TracerArray, LayerThick); + VAdv->computeTracerVAdvTend(Tend, TracerArray, LayerThick, ZeroTimeStep); HostArray3DReal TendH = createHostMirrorCopy(Tend); HostArray1DReal XLayerH = createHostMirrorCopy(XLayer); From 6fa3aa03cb2ff9db264ec405f8d98e98b79876e4 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 12 Feb 2026 08:47:13 -0800 Subject: [PATCH 081/152] Add VertAdv devGuide and userGuide --- components/omega/doc/devGuide/VertAdv.md | 103 ++++++++++++++++++++++ components/omega/doc/index.md | 2 + components/omega/doc/userGuide/VertAdv.md | 47 ++++++++++ 3 files changed, 152 insertions(+) create mode 100644 components/omega/doc/devGuide/VertAdv.md create mode 100644 components/omega/doc/userGuide/VertAdv.md diff --git a/components/omega/doc/devGuide/VertAdv.md b/components/omega/doc/devGuide/VertAdv.md new file mode 100644 index 000000000000..822c40ee13b6 --- /dev/null +++ b/components/omega/doc/devGuide/VertAdv.md @@ -0,0 +1,103 @@ +(omega-dev-vert-adv)= + +## Vertical Advection + +The `VertAdv` class contains methods and arrays for calculating vertical +velocities and the tendencies of thickness, horizontal velocity, and tracers due +to vertical advection. + +### Initialization + +The default `VertAdv` instance is created by calling `VertAdv::init()` method. +The default `HorzMesh`, `VertCoord`, and `Tracers` objects must be initialized +first. The default instance is retrieved by: +``` +VertAdv *DefVertAdv = VertAdv::getDefault(); +``` + +Additional instances can be created with the `create` method, which call the +constructor and places the new instance in a map identified with the label +`Name`: +``` +VertAdv *VertAdv::create(const std::string &Name, ///< [in] name for new VertAdv + const HorzMesh *Mesh, ///< [in] associated HorzMesh + const VertCoord *VCoord, ///< [in] associated VertCoord + Config *Options ///< [in] configuration options +) +``` +This instance is retrieved with the get method: +``` +VertAdv *NewVertAdv = VertAdv::get("Name"); +``` +The constructor reads configuration options, stores some info from the +`HorzMesh`, `VertCoord`, and `Tracers` objects, allocates needed arrays, +and defines `Field` metadata. + +### Variables + +The following arrays are stored as members of the `VertAdv` class. + +| Variable Name | Type | Dimensions | +| ------------- | ---- | ---------- | +| VerticalVelocity | Real | NCellsSize, NVertLayersP1 | +| TotalVerticalVelocity | Real | NCellsSize, NVertLayersP1 | +| VertFlux | Real | NTracers, NCellsSize, NVertLayersP1 | +| LowOrderVertFlux | Real | NTracers, NCellsSize, NVertLayersP1 | + +The `VerticalVelocity` represents the raw vertical pseudovelocity through the +top interfaces of cell layers, computed from the divergence of the horizontal +velocity. The `TotalVerticalVelocity` is the transport velocity and includes +corrections applied to the `VerticalVelocity`. The `VertFlux` and +`LowOrderVertFlux` store tracer fluxes at layer interfaces. The specific +numerical algorithms to compute these fluxes are chosen by the user through +configuration options. + +### Methods + +The `VerticalVelocity` and `TotalVerticalVelocity` arrays are computed by +calling the `computeVerticalVelocity` method: +``` +VertAdv::computeVerticalVelocity(NormalVelocity, FluxLayerThickEdge); +``` +This method takes as input the `NormalVelocity` field from the `OceanState` +object, and the `FluxLayerThickEdge` field from the `AuxiliaryState`. At +present, `TotalVerticalVelocity` is equivalent to `VerticalVelocity`; +additional corrections will be added in subsequent updates. The +`TotalVerticalVelocity` array is used by each of the tendency methods, +therefore `computeVerticalVelocity` must be called before any tendency +computations. + +There are three methods for computing vertical advection tendencies of +thickness, horizontal velocity and tracers that are called from the time +stepper: `computeThicknessVAdvTend`, `computeVelocityVAdvTend`, and +`computeTracerVAdvTend`. Each method updates the corresponding tendency array +in place (inout). + +Only the thickness tendency array is passed to the `computeThicknessVAdvTend` +method: +``` +VertAdv::computeThicknessVAdvTend(ThickTend); +``` + +The `computeVelocityVAdvTend` method requires the `NormalVelocity` and +`FluxLayerThickEdge` fields, in addition to the velocity tendency array: +``` +VertAdv::computeVelocityVAdvTend(VelTend, NormalVelocity, FluxLayerThickEdge); +``` + +The tracer vertical advection tendency depends on the configured settings. +The `computeTracerVAdvTend` method takes as arguments the full 3D array of +tracers, a thickness array (selected based on the configured advection +algorithm) and a `TimeInterval` representing the time step, along with the +tracer tendency array: +``` +VertAdv::computeTracerVAdvTend(TracerTend, TracerArray, Thickness, TimeStep); +``` +For the standard advection algorithm, `LayerThickness` from the `OceanState` is +passed as the thickness argument; for the FCT algorithm, the `ProvThickness` +from the `AuxiliaryState` is used instead. + +This method first calls `computeVerticalFluxes` to compute the tracer fluxes at +layer interfaces, using the order of accuracy specified in the configuration +file. It then dispatches to the selected advection algorithm (standard or FCT) +to compute the tracer tendencies. diff --git a/components/omega/doc/index.md b/components/omega/doc/index.md index bac7b4633a60..1dd11e400a43 100644 --- a/components/omega/doc/index.md +++ b/components/omega/doc/index.md @@ -51,6 +51,7 @@ userGuide/TridiagonalSolvers userGuide/VertCoord userGuide/Timing userGuide/VerticalMixingCoeff +userGuide/VertAdv ``` ```{toctree} @@ -94,6 +95,7 @@ devGuide/TridiagonalSolvers devGuide/VertCoord devGuide/Timing devGuide/VerticalMixingCoeff +devGuide/VertAdv ``` ```{toctree} diff --git a/components/omega/doc/userGuide/VertAdv.md b/components/omega/doc/userGuide/VertAdv.md new file mode 100644 index 000000000000..38511a201956 --- /dev/null +++ b/components/omega/doc/userGuide/VertAdv.md @@ -0,0 +1,47 @@ +(omega-user-vert-adv)= + +## Vertical Advection + +### Overview + +In Omega, vertical advection represents the transport of mass, momentum, and +tracer quantities along the vertical coordinate due to vertical motion within a +water column. In the context of Omega's +[governing equations](omega-design-governing-eqns-omega1), vertical advection +contributes to the tendencies of pseudo-thickness, horizontal velocity, and +tracer fields. Vertical motion is inferred from the divergence of horizontal +flow through the continuity equation and is necessary for accurately +representing vertical transport in three-dimensional ocean simulations. + +The `VertAdv` class contains variables and functions for computing the vertical +velocity and the tendencies of `LayerThickness`, `NormalVelocity`, and +`Tracers`. The algorithms used are determined by options specified in the +configuration file. + +### Configuration Options + +Within the `Tendencies` group of the configuration file, flags are used to +enable or disable the contribution of vertical advection to the tendencies of +thickness, velocity, and tracers: +| Configuration name | Options | +| ------------------ | ------- | +| ThicknessVertAdvTendencyEnable | true / false | +| VelocityVertAdvTendencyEnable | true / false | +| TracerVertAdvTendencyEnable | true / false | + +Within the `Advection` group, options are provided to configure the algorithms +used to compute the tracer tendencies: +| Configuration name | Description | Options | +| ------------------ | ----------- | ------- | +| VerticalTracerFluxOrder | order of accuracy used for the tracer flux calculation | 2, 3, or 4 | +| VerticalTracerFluxLimiterEnable | selects the tracer tendency algorithm | true / false | +| Coef3rdOrder | coefficient used for blending 3rd- and 4th-order fluxes when `VerticalTracerFluxOrder == 3` | 0.0 - 1.0 | + +For `VerticalTracerFluxLimiterEnable`, `true` enables flux-corrected transport, +and `false` selects the standard algorithm. For `Coef3rdOrder`, `1` corresponds +to purely 3rd-order, `0` purely 4th-order. + +The `VertAdv` class provides four fields that can be written to output by +adding them to the contents of an output stream in the configuration file: +`VerticalVelocity`, `TotalVerticalVelocity`, `VertFlux`, and `LowOrderVertFlux`. +These fields collectively make up the `VertAdv` output group. From 0a01a4cae630d9ff7e8d6610f041193f592085f7 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Fri, 13 Feb 2026 18:47:30 -0600 Subject: [PATCH 082/152] Add early return to VertAdv methods when NVertLayers == 1 --- components/omega/src/ocn/VertAdv.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 30ea70cf2c8e..21e1c94456bc 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -361,6 +361,10 @@ void VertAdv::computeVerticalVelocity( const Array2DReal &FluxLayerThickEdge //< [in] layer thickness at edges ) { + // Return if mesh only has a single vertical layer + if (NVertLayers == 1) + return; + OMEGA_SCOPE(LocVertVel, VerticalVelocity); OMEGA_SCOPE(LocNVertLayers, NVertLayers); OMEGA_SCOPE(LocAreaCell, Mesh->AreaCell); @@ -449,6 +453,10 @@ void VertAdv::computeThicknessVAdvTend( if (!ThickVertAdvEnabled) return; + // Return if mesh only has a single vertical layer + if (NVertLayers == 1) + return; + OMEGA_SCOPE(MinLayerCell, VCoord->MinLayerCell); OMEGA_SCOPE(MaxLayerCell, VCoord->MaxLayerCell); OMEGA_SCOPE(LocTotVertVelocity, TotalVerticalVelocity); @@ -489,6 +497,10 @@ void VertAdv::computeVelocityVAdvTend( if (!VelVertAdvEnabled) return; + // Return if mesh only has a single vertical layer + if (NVertLayers == 1) + return; + OMEGA_SCOPE(LocCOnE, Mesh->CellsOnEdge); OMEGA_SCOPE(MinLayerEdgeBot, VCoord->MinLayerEdgeBot); OMEGA_SCOPE(MaxLayerEdgeTop, VCoord->MaxLayerEdgeTop); @@ -572,6 +584,10 @@ void VertAdv::computeTracerVAdvTend( if (!TracerVertAdvEnabled) return; + // Return if mesh only has a single vertical layer + if (NVertLayers == 1) + return; + // Compute tracer fluxes at the interfaces computeVerticalFluxes(Tracers, LayerThickness); From 9461d88094abe9e620de4f663f1bf28a9e65ea5a Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 19 Feb 2026 17:20:58 -0600 Subject: [PATCH 083/152] Fix typos in comments and docs --- components/omega/doc/devGuide/VertAdv.md | 2 +- components/omega/src/ocn/AuxiliaryState.h | 4 ++-- components/omega/src/ocn/VertAdv.cpp | 2 +- components/omega/src/ocn/VertAdv.h | 2 +- components/omega/test/ocn/VertAdvTest.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/omega/doc/devGuide/VertAdv.md b/components/omega/doc/devGuide/VertAdv.md index 822c40ee13b6..672a25ef550e 100644 --- a/components/omega/doc/devGuide/VertAdv.md +++ b/components/omega/doc/devGuide/VertAdv.md @@ -15,7 +15,7 @@ first. The default instance is retrieved by: VertAdv *DefVertAdv = VertAdv::getDefault(); ``` -Additional instances can be created with the `create` method, which call the +Additional instances can be created with the `create` method, which calls the constructor and places the new instance in a map identified with the label `Name`: ``` diff --git a/components/omega/src/ocn/AuxiliaryState.h b/components/omega/src/ocn/AuxiliaryState.h index 60a414d39d35..f3c14be20ac3 100644 --- a/components/omega/src/ocn/AuxiliaryState.h +++ b/components/omega/src/ocn/AuxiliaryState.h @@ -53,7 +53,7 @@ class AuxiliaryState { // Create a non-default auxiliary state static AuxiliaryState *create(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, const VertCoord *VCoord, - VertAdv *Vadv, int NTracers, + VertAdv *VAdv, int NTracers, TimeInterval TimeStep); /// Get the default auxiliary state @@ -87,7 +87,7 @@ class AuxiliaryState { private: AuxiliaryState(const std::string &Name, const HorzMesh *Mesh, Halo *MeshHalo, - const VertCoord *VCoord, VertAdv *Vadv, int NTracers, + const VertCoord *VCoord, VertAdv *VAdv, int NTracers, TimeInterval TimeStep); AuxiliaryState(const AuxiliaryState &) = delete; diff --git a/components/omega/src/ocn/VertAdv.cpp b/components/omega/src/ocn/VertAdv.cpp index 21e1c94456bc..343136b10d34 100644 --- a/components/omega/src/ocn/VertAdv.cpp +++ b/components/omega/src/ocn/VertAdv.cpp @@ -572,7 +572,7 @@ void VertAdv::computeVelocityVAdvTend( //------------------------------------------------------------------------------ // Compute tracer tendency due to vertical advection, TimeStep is only needed -// as an arugement for flux-corrected transport +// as an argument for flux-corrected transport void VertAdv::computeTracerVAdvTend( const Array3DReal &TracerTend, //< [inout] tracer tendencies const Array3DReal &Tracers, //< [in] tracer array diff --git a/components/omega/src/ocn/VertAdv.h b/components/omega/src/ocn/VertAdv.h index f8daaa26d38f..6d74b91835ce 100644 --- a/components/omega/src/ocn/VertAdv.h +++ b/components/omega/src/ocn/VertAdv.h @@ -115,7 +115,7 @@ class VertAdv { /// Retrieve the default VertAdv static VertAdv *getDefault(); - /// Retreive a VertAdv by name + /// Retrieve a VertAdv by name static VertAdv *get(std::string name); /// Read and set config options diff --git a/components/omega/test/ocn/VertAdvTest.cpp b/components/omega/test/ocn/VertAdvTest.cpp index b8bcc0e22863..e60a6586aed0 100644 --- a/components/omega/test/ocn/VertAdvTest.cpp +++ b/components/omega/test/ocn/VertAdvTest.cpp @@ -203,7 +203,7 @@ int main(int argc, char *argv[]) { OMEGA_SCOPE(LocEOnC, DefDecomp->EdgesOnCell); OMEGA_SCOPE(LocESOnC, DefMesh->EdgeSignOnCell); - // Set unifrom values for layer thickness and velocity on edges of column + // Set uniform values for layer thickness and velocity on edges of column parallelFor( {NVertLayers}, KOKKOS_LAMBDA(int K) { for (int J = 0; J < NEdgesOnCell0; ++J) { From 578a35f7f961ddcc1ae247d495ce2d337fe3a5fe Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 19 Feb 2026 18:40:25 -0600 Subject: [PATCH 084/152] Update HorzMesh user doc --- components/omega/doc/userGuide/HorzMesh.md | 1 - 1 file changed, 1 deletion(-) diff --git a/components/omega/doc/userGuide/HorzMesh.md b/components/omega/doc/userGuide/HorzMesh.md index 7d50db5c529e..2160b51c82ae 100644 --- a/components/omega/doc/userGuide/HorzMesh.md +++ b/components/omega/doc/userGuide/HorzMesh.md @@ -23,7 +23,6 @@ This includes the following variables: | XCell, YCell, ZCell | Cartesian coordinates of cell centers | m | | XEdge, YEdge, ZEdge | Cartesian coordinates of edge centers | m | | XVertex, YVertex, ZVertex | Cartesian coordinates of vertices | m | -| BottomDepth | Depth of the bottom of the ocean at cell centers | m | | FCell, FEdge, FVertex | Coriolis parameter at cell centers/edges/vertices | radians/s | | LonCell, LatCell | Longitude/latitude coordinates of cell centers | radians | | LonEdge, LatEdge | Longitude/latitude coordinates of edge centers | radians | From ad15ca66f2f20c0a053edd62e2d51199dc233890 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 19 Feb 2026 18:41:13 -0600 Subject: [PATCH 085/152] Add sanity check for bottom depth --- components/omega/test/ocn/VertCoordTest.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/components/omega/test/ocn/VertCoordTest.cpp b/components/omega/test/ocn/VertCoordTest.cpp index 6c4648e7ac36..8fe474f41018 100644 --- a/components/omega/test/ocn/VertCoordTest.cpp +++ b/components/omega/test/ocn/VertCoordTest.cpp @@ -104,6 +104,26 @@ int main(int argc, char *argv[]) { I4 VertexDegree = DefMesh->VertexDegree; I4 NVertLayers = DefVertCoord->NVertLayers; + // Rest bottom depth successful read + R8 MaxBathy = -1e10; + R8 MinBathy = 1e10; + + for (int ICell = 0; ICell < NCellsOwned; ++ICell) { + if (DefVertCoord->BottomDepthH(ICell) < MinBathy) { + MinBathy = DefVertCoord->BottomDepthH(ICell); + } + if (DefVertCoord->BottomDepthH(ICell) > MaxBathy) { + MaxBathy = DefVertCoord->BottomDepthH(ICell); + } + } + + if ((MinBathy > 0) && (MaxBathy < 11000.)) { + LOG_INFO("VertCoordTest: Bathy min/max test PASS"); + } else { + ErrAll += Error( + ErrorCode::Fail, "VertCoordTest: Bathy min/max test FAIL"); + } + // Tests for computePressure Array2DReal LayerThickness("LayerThickness", NCellsSize, NVertLayers); From f9485b485edea3c4cd397987aee8c7de0f9096b6 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Thu, 19 Feb 2026 18:50:16 -0600 Subject: [PATCH 086/152] Fix linting fail --- components/omega/test/ocn/VertCoordTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/omega/test/ocn/VertCoordTest.cpp b/components/omega/test/ocn/VertCoordTest.cpp index 8fe474f41018..a1289cb5fef1 100644 --- a/components/omega/test/ocn/VertCoordTest.cpp +++ b/components/omega/test/ocn/VertCoordTest.cpp @@ -120,8 +120,8 @@ int main(int argc, char *argv[]) { if ((MinBathy > 0) && (MaxBathy < 11000.)) { LOG_INFO("VertCoordTest: Bathy min/max test PASS"); } else { - ErrAll += Error( - ErrorCode::Fail, "VertCoordTest: Bathy min/max test FAIL"); + ErrAll += + Error(ErrorCode::Fail, "VertCoordTest: Bathy min/max test FAIL"); } // Tests for computePressure From 5c2be5e5e62ff84a8e7fa9191c585f5306ce647a Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Fri, 20 Feb 2026 09:30:52 -0600 Subject: [PATCH 087/152] Rename TracerTend to LocTracerTend in function call --- components/omega/src/ocn/Tendencies.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 99a827734ff7..7d1c4b2b6519 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -646,7 +646,7 @@ void Tendencies::computeTracerTendenciesOnly( } else if (VAdv->VertAdvChoice == VertAdvOption::FCT) { ThicknessForVAdv = AuxState->LayerThicknessAux.ProvThickness; } - VAdv->computeTracerVAdvTend(TracerTend, TracerArray, ThicknessForVAdv, + VAdv->computeTracerVAdvTend(LocTracerTend, TracerArray, ThicknessForVAdv, TimeStep); Pacer::stop("Tend:computeTracerVAdvTend", 2); From 9b49bf7e032062c2063d6e608bbfeb3e031a180e Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 24 Feb 2026 08:21:43 -0600 Subject: [PATCH 088/152] Remove commented-out line --- components/omega/test/ocn/TendenciesTest.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index f686a9bf9af9..40afa13e531f 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -125,8 +125,6 @@ int initTendenciesTest(const std::string &mesh) { Tracers::init(); VertAdv::init(); - // VertCoord::init2(); - int StateErr = OceanState::init(); if (StateErr != 0) { Err++; From 5759e2707a07674ffa0b6f6445312ec5e4f95a3a Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 10 Mar 2026 09:00:40 -0700 Subject: [PATCH 089/152] Rebase: remove TracerAux computeVarsOnEdge --- components/omega/src/ocn/AuxiliaryState.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/components/omega/src/ocn/AuxiliaryState.cpp b/components/omega/src/ocn/AuxiliaryState.cpp index 60acf223b8b0..135728e1b14f 100644 --- a/components/omega/src/ocn/AuxiliaryState.cpp +++ b/components/omega/src/ocn/AuxiliaryState.cpp @@ -256,22 +256,6 @@ void AuxiliaryState::computeAll(const OceanState *State, }); Pacer::stop("AuxState:cellAuxState3", 2); - 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); From 520bdf858f7fd969c0eed53698278f1c94bd9909 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 10 Mar 2026 09:05:01 -0700 Subject: [PATCH 090/152] Rebase: fix variable name passed to computeVelocityVAdvTend --- components/omega/src/ocn/Tendencies.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 7d1c4b2b6519..b626ce44c21a 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -487,7 +487,7 @@ void Tendencies::computeVelocityTendenciesOnly( Pacer::start("Tend:computeVelocityVAdvTend", 2); // Compute velocity tendency from vertical advection - VAdv->computeVelocityVAdvTend(NormalVelocityTend, NormalVelEdge, + VAdv->computeVelocityVAdvTend(NormalVelocityTend, NormVelEdge, FluxLayerThickEdge); Pacer::stop("Tend:computeVelocityVAdvTend", 2); From b7fc515dd65490679c80f117206ac6922d5a5161 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Tue, 10 Mar 2026 09:08:45 -0700 Subject: [PATCH 091/152] Rebase: fix getLayerThickness usage --- components/omega/src/ocn/Tendencies.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index b626ce44c21a..b228c829082b 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -642,7 +642,7 @@ void Tendencies::computeTracerTendenciesOnly( // compute tracer tendencies from vertical advection Array2DReal ThicknessForVAdv; if (VAdv->VertAdvChoice == VertAdvOption::Standard) { - State->getLayerThickness(ThicknessForVAdv, ThickTimeLevel); + ThicknessForVAdv = State->getLayerThickness(ThickTimeLevel); } else if (VAdv->VertAdvChoice == VertAdvOption::FCT) { ThicknessForVAdv = AuxState->LayerThicknessAux.ProvThickness; } From bd5b2fbcf0ba124b2d044e8d4789bb087f06f0de Mon Sep 17 00:00:00 2001 From: Luke Van Roekel Date: Wed, 11 Mar 2026 10:42:46 -0600 Subject: [PATCH 092/152] Fixes links and updates tracer SGS terms --- .../omega/doc/design/OmegaV1GoverningEqns.md | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/components/omega/doc/design/OmegaV1GoverningEqns.md b/components/omega/doc/design/OmegaV1GoverningEqns.md index abf59d03f835..cb295674f227 100644 --- a/components/omega/doc/design/OmegaV1GoverningEqns.md +++ b/components/omega/doc/design/OmegaV1GoverningEqns.md @@ -34,7 +34,7 @@ This document describes the governing equations for the layered ocean model, whi The requirements in the [Omega-0 design document](OmegaV0ShallowWater) still apply. Additional design requirements for Omega-1 are: ### Omega will be a hydrostatic, non-Boussinesq ocean model. -Omega will adopt a non-Boussinesq formulation, meaning it retains the full, spatially and temporally varying fluid density $\rho({\bf x}, t)$ in all governing equations. This ensures exact mass conservation, as opposed to volume conservation in Boussinesq models that approximate density as a constant reference value $\rho_0$ outside the pressure gradient. The equations are derived in terms of layer-integrated mass per unit area (see [layered equations](#Layer Equations)), and no approximation is made that filters out compressibility or density variations. +Omega will adopt a non-Boussinesq formulation, meaning it retains the full, spatially and temporally varying fluid density $\rho({\bf x}, t)$ in all governing equations. This ensures exact mass conservation, as opposed to volume conservation in Boussinesq models that approximate density as a constant reference value $\rho_0$ outside the pressure gradient. The equations are derived in terms of layer-integrated mass per unit area (see [layered equations](#layer-equations)), and no approximation is made that filters out compressibility or density variations. The model also assumes hydrostatic balance in the vertical momentum equation, which is a standard and well-justified simplification for large-scale geophysical flows. In such regimes, vertical accelerations are typically small compared to the vertical pressure gradient and gravitational forces. This assumption simplifies the dynamics and removes most sound waves while retaining fidelity for the mesoscale to planetary-scale ocean circulation that Omega is designed to simulate. @@ -416,6 +416,7 @@ $$ (delta-vert-average) where the last equality is true by definition. +(layer-equations)= ## 9. Layer Equations ### Tracer & Mass @@ -431,7 +432,7 @@ $$ & = 0. $$ (Aintegral-tracer-first-reynolds) -Next, we use a Reynolds' decomposition on any terms involving products inside a Reynolds' average, +Next, we use the resolved/unresolved decomposition on any terms involving products inside a resolved component projection, $$ \frac{d}{dt} \int_A \int_{\tilde{z}_{k}^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left<\varphi \right> \, d\tilde{z} \, dA @@ -496,7 +497,7 @@ $$ & - \left\{ \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{top}}} - \left[\left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{ u}^{\prime}\right> \right]_{{\tilde z}={\tilde z}_{k}^{\text{bot}}}\right\}. $$ (layer-tracer) -A few notes on the layer-averaged tracer equation. In this complete form, it includes three types of fluctuating quantities that must be dealt with: (1) the layer averaged, Reynolds' averaged horizontal turbulent flux $\left( \overline{\left<\varphi^{\prime} {\bf u}^{\prime}\right>}^{\tilde{z}}_k \right)$, (2) the Reynolds' average vertical turbulent flux $\left( \left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{u}^{\prime}\right> \right)$, and (3) the layer-averaged product of deviations from the layer-integrated variables $\left(\overline{\delta \varphi \delta {\bf u}}^{\tilde{z}}_k \right)$. The details of the first two quantities will be discussed later in this document and follow-on design documents. The terms involving perturbations from the layer integrated quantity are necessary to extend beyond piecewise constant represenation of variables. In this equation, variables with no overline are the full field variable at the interfaces. +A few notes on the layer-averaged tracer equation. In this complete form, it includes three types of fluctuating quantities that must be dealt with: (1) the layer averaged horizontal turbulent flux $\left( \overline{\left<\varphi^{\prime} {\bf u}^{\prime}\right>}^{\tilde{z}}_k \right)$, (2) the vertical turbulent flux $\left( \left< \varphi^\prime {\tilde w}_{tr}^{\prime} \right> - \left< \varphi^\prime \tilde{u}^{\prime}\right> \right)$, and (3) the layer-averaged product of deviations from the layer-integrated variables $\left(\overline{\delta \varphi \delta {\bf u}}^{\tilde{z}}_k \right)$. The details of the first two quantities will be discussed later in this document and follow-on design documents. The terms involving perturbations from the layer integrated quantity are necessary to extend beyond piecewise constant represenation of variables. In this equation, variables with no overline are the full field variable at the interfaces. The mass equation is identical to the tracer equation with $\varphi=1$. @@ -514,7 +515,7 @@ $$ (layer-mass) Before deriving the layered momentum equation, we collect the pressure and gravitational contributions into a single **pressure gradient force (PGF)** in our pseudo-height framework. This subsection establishes the continuous form of the horizontal PGF per unit mass and serves as the target for the subsequent finite-volume, layer-averaged derivation. -Recall from [](vh-momentum-pseudo) that the horizontal momentum equation is written in terms of +Recall from [](#vh-momentum-pseudo) that the horizontal momentum equation is written in terms of - a **body force per unit mass** $\mathbf{b}_\perp$, and - a **traction** vector $\boldsymbol{\tau}$ that enters the weak form as $\boldsymbol{\tau}/\rho$, @@ -560,9 +561,9 @@ $$ (pgf-continuous) In Omega, the operator $\nabla$ denotes the **horizontal gradient along layers**, following the convention adopted throughout this section. It should not be interpreted as a gradient taken at constant geometric height $z$ or constant pseudo-height $\tilde{z}$. -Under the hydrostatic approximation [](hydrostatic), the form of the horizontal pressure–gravity force in [](pgf-continuous) is valid for *any* choice of vertical coordinate, provided the horizontal gradient is taken consistently with that coordinate. In particular, although the horizontal gradient of $\Phi$ vanishes in $z$-level coordinates in the absence of tides, self-attraction, and loading, the combined expression in [](pgf-continuous) remains the correct horizontal pressure–gravity force when written using the appropriate along-layer gradient operator. +Under the hydrostatic approximation [](#hydrostatic), the form of the horizontal pressure–gravity force in [](#pgf-continuous) is valid for *any* choice of vertical coordinate, provided the horizontal gradient is taken consistently with that coordinate. In particular, although the horizontal gradient of $\Phi$ vanishes in $z$-level coordinates in the absence of tides, self-attraction, and loading, the combined expression in [](#pgf-continuous) remains the correct horizontal pressure–gravity force when written using the appropriate along-layer gradient operator. -When inserted into the weak, finite-volume form of the momentum equation, $\mathbf{b}_{\text{PGF},\perp}$ represents the combined effect of pressure traction and gravitational body force. In the next subsection, we will layer-average [](pgf-continuous) over the pseudo-height thickness of each layer and combine it with the advective and Coriolis terms to obtain the layered horizontal momentum equation. The resulting horizontal pressure-gradient force in Omega must be a consistent finite-volume discretization of [](pgf-continuous), together with additional metric terms associated with sloping layer interfaces. +When inserted into the weak, finite-volume form of the momentum equation, $\mathbf{b}_{\text{PGF},\perp}$ represents the combined effect of pressure traction and gravitational body force. In the next subsection, we will layer-average [](#pgf-continuous) over the pseudo-height thickness of each layer and combine it with the advective and Coriolis terms to obtain the layered horizontal momentum equation. The resulting horizontal pressure-gradient force in Omega must be a consistent finite-volume discretization of [](#pgf-continuous), together with additional metric terms associated with sloping layer interfaces. ### Momentum @@ -581,7 +582,7 @@ The first term on the right hand side is the **Coriolis force**, where $ \mathbf The second and third terms are $\mathbf{b}_{\text{PGF},\perp}$ from the previous subsection. -Substituting (body-forces) and (surface-forces) into [](#vh-momentum-pseudo), we arrive at: +Substituting [](#body-forces) and the traction terms in [](#vh-momentum-pseudo), we arrive at: $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} {\bf u} \, d\tilde{z} \, dA @@ -601,7 +602,7 @@ $$ & = -\int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \, \left< {\bf f} \times \mathbf{u} + \frac{1}{\rho} \nabla p + \nabla \Phi \right> \, d\tilde{z} \, dA. $$ (vh-momentum-reynolds1) -Here we have also moved the Reynolds' average through the spatial integrals given the properties of the averaging. Next we decompose the nonlinear products into resolved and unresolved components, this yields +Here we have also moved the resolved component projection operator through the spatial integrals given the properties of the operator. Next we decompose the nonlinear products into resolved and unresolved components, this yields $$ \frac{d}{dt} \int_A \int_{\tilde{z}_k^{\text{bot}}}^{\tilde{z}_k^{\text{top}}} \left< {\bf u} \right> \, d\tilde{z} \, dA @@ -800,7 +801,7 @@ $$ z_{i,k}^{top} = z_{i}^{floor} + \rho_0 \sum_{k'=k}^{K_{max}} \alpha_{i,k'} \tilde{h}_{i,k'} $$ (discrete-z) -We refer to these as the discrete equations, but time derivatives remain continuous. The time discretization is described in the [time stepping design document](TimeStepping.md). The velocity, mass-thickness, and tracers are solved prognostically using [](discrete-momentum), [](discrete-mass), [](discrete-tracer). At the new time, these variables are used to compute pressure [](discrete-pressure), specific volume [](discrete-eos), and z-locations [](discrete-z). Additional variables are computed diagnostically at the new time: $\mathbf{u}^{\perp}$, $K$, $\zeta_a$, $z^{mid}$, $\Phi$, etc. The initial geopotential is simply $\Phi=gz$, but additional gravitational terms may be added later. +We refer to these as the discrete equations, but time derivatives remain continuous. The time discretization is described in the [time stepping design document](TimeStepping.md). The velocity, mass-thickness, and tracers are solved prognostically using [](#discrete-momentum), [](#discrete-mass), [](#discrete-tracer). At the new time, these variables are used to compute pressure [](#discrete-pressure), specific volume [](#discrete-eos), and z-locations [](#discrete-z). Additional variables are computed diagnostically at the new time: $\mathbf{u}^{\perp}$, $K$, $\zeta_a$, $z^{mid}$, $\Phi$, etc. The initial geopotential is simply $\Phi=gz$, but additional gravitational terms may be added later. The horizontal operators $\nabla$, $\nabla\cdot$, and $\nabla \times$ are now in their discrete form. In the TRiSK design, gradients ($\nabla$) map cell centers to edges; divergence ($\nabla \cdot$) maps edge quantities to cells; and curl ($\nabla \times$) maps edges to vertices. The exact form of operators and interpolation stencils remain the same as those given in {ref}`Omega-0 operator formulation <33-operator-formulation>` The discrete version of terms common with Omega-0, such as advection, potential vorticity, and $\nabla K$, can be found in {ref}`Omega-0 Momentum Terms <34-momentum-terms>` and {ref}`Omega-0 Thickness and Tracer Terms <35-thickness-and-tracer-terms>`. @@ -1095,7 +1096,7 @@ Table 1. Definition of variables. Geometric variables may be found in the {ref}` |$K_{min}$ | shallowest active layer | | |$K_{max}$ | deepest active layer | | |$K_{i,k}$ | kinetic energy | m$^2$ s$^{-2}$ | cell | KineticEnergyCell |$K = \left\| {\bf u} \right\|^2 / 2$ | -|$p_{i,k}$ | pressure | Pa | cell | Pressure | see [](discrete-pressure) | +|$p_{i,k}$ | pressure | Pa | cell | Pressure | see [](#discrete-pressure) | |$p^{floor}_i$ | bottom pressure | Pa | cell | PFloor | pressure at ocean floor |$p^{surf}_i$ | surface pressure | Pa | cell | PSurface | due to atm. pressure, sea ice, ice shelves |$q_{v,k}$ | potential vorticity | m$^{-1}$ s$^{-1}$ | vertex | PotentialVorticity |$q = \left(\zeta+f\right)/\tilde{h}$ | @@ -1111,7 +1112,7 @@ Table 1. Definition of variables. Geometric variables may be found in the {ref}` |$\tilde{w}_{tr\ i,k}$ | net vertical transport through a moving surface | m s$^{-1}$ | cell | NetVertTransportVelocity | volume transport per m$^2$ | |$\tilde{W}_{tr\ i,k}$ | total vertical velocity across a pseudo height surface | m s$^{-1}$ | cell | TotalVertTransportVelocity | $\tilde{W}_{tr} \equiv \tilde{w}_{tr} - \tilde{u}$ | |$\tilde{z}$ | vertical coordinate (pseudo-height) | m | - | | positive upward | -|$\tilde{z}^{top}_{i,k}$ | layer top $\tilde{z}$-location | m | cell | ZTop | see [](discrete-z) | +|$\tilde{z}^{top}_{i,k}$ | layer top $\tilde{z}$-location | m | cell | ZTop | see [](#discrete-z) | |$\tilde{z}^{mid}_{i,k}$ | layer mid-depth $\tilde{z}$-location | m | cell | ZMid | |$\tilde{z}^{surf}_{i}$ | ocean surface $\tilde{z}$-location | m | cell | ZSurface | |$\tilde{z}^{floor}_{i}$ | ocean floor $\tilde{z}$-location | m | cell | ZFloor | From ee8edf2769cb9d7ba93bdf9e39e0660ad5444390 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 11 Mar 2026 19:20:38 +0100 Subject: [PATCH 093/152] Clarify the choice of reference density for compatibility with E3SM. --- components/omega/doc/design/OmegaV1GoverningEqns.md | 1 + 1 file changed, 1 insertion(+) diff --git a/components/omega/doc/design/OmegaV1GoverningEqns.md b/components/omega/doc/design/OmegaV1GoverningEqns.md index cb295674f227..2262a437fe6c 100644 --- a/components/omega/doc/design/OmegaV1GoverningEqns.md +++ b/components/omega/doc/design/OmegaV1GoverningEqns.md @@ -155,6 +155,7 @@ In this formulation: [Griffies 2018](https://doi.org/10.2307/j.ctv301gzg) p. 37 argues for the use of pseudo-velocities, which he calls the density-weighted velocity, for non-Boussinesq models. Griffies recommends a value of $\rho_0=1035$ kg/m$^3$, following p. 47 of [Gill (1982)](https://doi.org/10.1016/S0074-6142(08)60028-5), because ocean density varies less than 2% from that value. +However, for compatibility with the rest of E3SM, we have decided to use $\rho_0=1026$, the value of [SHR_CONST_RHOSW](https://github.com/E3SM-Project/E3SM/blob/935c7d24b1d7542337fab6f1e58443f18354f62c/share/util/shr_const_mod.F90#L47). The use of a constant $\rho_0$ in defining pseudo-height does not imply the Boussinesq approximation. In Boussinesq models, $\rho$ is set to $\rho_0$ everywhere except in the buoyancy term (i.e., the vertical pressure gradient or gravitational forcing). Here, by contrast, we retain the full $\rho$ in all terms, and use $\rho_0$ only as a normalization constant—for example, so that $d\tilde{z} \approx dz$ when $\rho \approx \rho_0$. This preserves full mass conservation while making vertical units more intuitive. From 1815dba876587ab3871aea382ff5aac7ff8b4fdb Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 11 Mar 2026 13:30:27 -0700 Subject: [PATCH 094/152] Exchange halo for BottomDepth --- components/omega/src/ocn/VertCoord.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/omega/src/ocn/VertCoord.cpp b/components/omega/src/ocn/VertCoord.cpp index a9fb93fbd708..cd9296925390 100644 --- a/components/omega/src/ocn/VertCoord.cpp +++ b/components/omega/src/ocn/VertCoord.cpp @@ -553,6 +553,7 @@ void VertCoord::setStreamArrays(const bool ReadStream, Halo *MeshHalo) { // Exchange halos since stream only reads owned cells MeshHalo->exchangeFullArrayHalo(MinLayerCell, OnCell); MeshHalo->exchangeFullArrayHalo(MaxLayerCell, OnCell); + MeshHalo->exchangeFullArrayHalo(BottomDepth, OnCell); // The index ICell = NCellsAll represents an inactive cell OMEGA_SCOPE(LocNCellsAll, NCellsAll); From c870d364067157404393401d6bb3a9b7fec5bb29 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 1 Dec 2025 13:57:55 -0800 Subject: [PATCH 095/152] Add centered pressure gradient implimentation --- components/omega/src/ocn/Eos.cpp | 84 ++++++++--- components/omega/src/ocn/Eos.h | 5 + components/omega/src/ocn/PGrad.cpp | 187 ++++++++++++++++++++++++ components/omega/src/ocn/PGrad.h | 169 +++++++++++++++++++++ components/omega/src/ocn/Tendencies.cpp | 2 + 5 files changed, 429 insertions(+), 18 deletions(-) create mode 100644 components/omega/src/ocn/PGrad.cpp create mode 100644 components/omega/src/ocn/PGrad.h diff --git a/components/omega/src/ocn/Eos.cpp b/components/omega/src/ocn/Eos.cpp index 1b49fe400eea..4ee381b0e751 100644 --- a/components/omega/src/ocn/Eos.cpp +++ b/components/omega/src/ocn/Eos.cpp @@ -45,24 +45,6 @@ Eos::Eos(const std::string &Name, ///< [in] Name for eos object BruntVaisalaFreqSq = Array2DReal("BruntVaisalaFreqSq", Mesh->NCellsAll, VCoord->NVertLayers); - defineFields(); -} - -/// Destructor for Eos -Eos::~Eos() {} - -/// Instance management -Eos *Eos::Instance = nullptr; - -/// Get instance of Eos -Eos *Eos::getInstance() { return Instance; } - -/// Destroy instance of Eos -void Eos::destroyInstance() { - delete Instance; - Instance = nullptr; -} - /// Initializes the Eos (Equation of State) class and its options. /// it ASSUMES that HorzMesh was initialized and initializes the Eos class by /// using the default mesh, reading the config file, and setting parameters @@ -168,6 +150,72 @@ void Eos::computeSpecVol(const Array2DReal &ConservTemp, } } +/// Compute specific volume for all layer interfaces(no displacement) +void Eos::computeSpecVolInterface(const Array2DReal &ConservTemp, + const Array2DReal &AbsSalinity, + const Array2DReal &PressureInterface) { + OMEGA_SCOPE(LocSpecVolInterface, + SpecVolInterface); /// Create a local view for computation + OMEGA_SCOPE(LocComputeSpecVolLinear, + ComputeSpecVolLinear); /// Local view for linear EOS computation + OMEGA_SCOPE(LocComputeSpecVolTeos10, + ComputeSpecVolTeos10); /// Local view for TEOS-10 computation + deepCopy(LocSpecVolInterface, + 0); /// Initialize local specific volume to zero + + I4 KDisp = 0; /// No displacement in this case + + Array2DReal ConservTempInterface("ConservTempInterface", NCellsAll, + NVertLayers + 1); + Array2DReal AbsSalinityInterface("AbsSalinityInterface", NCellsAll, + NVertLayers + 1); + + // Compute interface values by averaging adjacent layer values + parallelFor( + "compute-interface-values", {NCellsAll, NVertLayers + 1}, + KOKKOS_LAMBDA(I4 ICell, I4 KInterface) { + if (KInterface == 0) { + ConservTempInterface(ICell, KInterface) = + ConservTemp(ICell, KInterface); + AbsSalinityInterface(ICell, KInterface) = + AbsSalinity(ICell, KInterface); + } else if (KInterface == NVertLayers) { + ConservTempInterface(ICell, KInterface) = + ConservTemp(ICell, KInterface - 1); + AbsSalinityInterface(ICell, KInterface) = + AbsSalinity(ICell, KInterface - 1); + } else { + ConservTempInterface(ICell, KInterface) = + 0.5_Real * (ConservTemp(ICell, KInterface - 1) + + ConservTemp(ICell, KInterface)); + AbsSalinityInterface(ICell, KInterface) = + 0.5_Real * (AbsSalinity(ICell, KInterface - 1) + + AbsSalinity(ICell, KInterface)); + } + }); + + int NChunksP1 = (NVertLayers + 1) / VecLength; + + /// Dispatch to the correct EOS calculation + if (EosChoice == EosType::LinearEos) { + parallelFor( + "eos-linear", {NCellsAll, NChunksP1}, + KOKKOS_LAMBDA(I4 ICell, I4 KChunk) { + LocComputeSpecVolLinear(LocSpecVolInterface, ICell, KChunk, + ConservTempInterface, + AbsSalinityInterface); + }); + } else if (EosChoice == EosType::Teos10Eos) { + parallelFor( + "eos-teos10", {NCellsAll, NChunksP1}, + KOKKOS_LAMBDA(I4 ICell, I4 KChunk) { + LocComputeSpecVolTeos10(LocSpecVolInterface, ICell, KChunk, + ConservTempInterface, AbsSalinityInterface, + PressureInterface, KDisp); + }); + } +} + /// Compute displaced specific volume (for vertical displacement) void Eos::computeSpecVolDisp(const Array2DReal &ConservTemp, const Array2DReal &AbsSalinity, diff --git a/components/omega/src/ocn/Eos.h b/components/omega/src/ocn/Eos.h index 707297f01113..0eb111741d58 100644 --- a/components/omega/src/ocn/Eos.h +++ b/components/omega/src/ocn/Eos.h @@ -542,6 +542,7 @@ class Eos { EosType EosChoice; ///< Current EOS type in use Array2DReal SpecVol; ///< Specific volume field at level centers + Array2DReal SpecVolInterface; ///< Specific volume field at interfaces Array2DReal SpecVolDisplaced; ///< Displaced specific volume field Array2DReal BruntVaisalaFreqSq; ///< Squared Brunt-Vaisala frequency field @@ -568,6 +569,10 @@ class Eos { const Array2DReal &AbsSalinity, const Array2DReal &Pressure, const Array2DReal &SpecVol); + /// Compute specific volume at interfaces for all cells/layers+1 + void computeSpecVolInterface(const Array2DReal &ConservTemp, + const Array2DReal &AbsSalinity, + const Array2DReal &PressureInterface); /// Initialize EOS from config and mesh static void init(); diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp new file mode 100644 index 000000000000..5bf2909667b2 --- /dev/null +++ b/components/omega/src/ocn/PGrad.cpp @@ -0,0 +1,187 @@ +//===-- ocn/PGrad.cpp - Pressure Gradient Term -----------------*- C++ -*-===// +// +// Implements the PGrad manager and two discretizations: Centered and +// HighOrder. +// +//===----------------------------------------------------------------------===// + +#include "PGrad.h" +#include "Error.h" +#include "Field.h" +#include "HorzMesh.h" +#include "VertCoord.h" + +namespace OMEGA { + +PressureGrad *PressureGrad::DefaultPGrad = nullptr; +std::map> PressureGrad::AllPGrads; + +//------------------------------------------------------------------------------ +// Initialize the PressureGrad. Assumes that HorzMesh and VertCoord have already +// been initialized. +void PressureGrad::init() { + + // Retrieve default mesh and vertical coordinate + HorzMesh *DefMesh = HorzMesh::getDefault(); + VertCoord *DefVCoord = VertCoord::getDefault(); + + // Retrieve PressureGrad config group + Config *OmegaConfig = Config::getOmegaConfig(); + Config PGradConfig("PressureGrad"); + Error Err; + Err += OmegaConfig->get(PGradConfig); + CHECK_ERROR_ABORT(Err, "PressureGrad: PressureGrad group not found in Config"); + + // Create the default PressureGrad and set pointer to it + PressureGrad::DefaultPGrad = PressureGrad::create( + "Default", DefMesh, DefVCoord, &PGradConfig); + +} // end init + +//------------------------------------------------------------------------------ +// Create a new PressureGrad object and add it to the map +PressureGrad *PressureGrad::create(const std::string &Name, /// [in] Name for PressureGrad + const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord, ///< [in] Vertical coordinate + Config *Options) { ///< [in] Configuration options + + // Check to see if a PressureGrad of the same name already exists and + // if so, exit with an error + if (AllPGrads.find(Name) != AllPGrads.end()) { + LOG_ERROR("Attempted to create a PressureGrad with name {} but a " + "PressureGrad of that name already exists", + Name); + return nullptr; + } + + // create a new PressureGrad on the heap and put it in a map of + // unique_ptrs, which will manage its lifetime + auto *NewPGrad = new PressureGrad(Mesh, VCoord, Options); + AllPGrads.emplace(Name, NewPGrad); + + return NewPGrad; + +} // end create + +//------------------------------------------------------------------------------ +// Get the default pressure gradient instance +PressureGrad *PressureGrad::getDefault() { + + return DefaultPGrad; + +} // end get default + +//------------------------------------------------------------------------------ +// Constructor for PressureGrad +PressureGrad::PressureGrad(const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord, ///< [in] Vertical coordinate + Config *Options) ///< [in] Configuration options + : CellsOnEdge(Mesh->CellsOnEdge), + DvEdge(Mesh->DvEdge), + EdgeSignOnCell(Mesh->EdgeSignOnCell), + CenteredPGrad(Mesh), HighOrderPGrad(Mesh) { + + // store mesh sizes + NEdgesAll = Mesh->NEdgesAll; + NVertLayers = VCoord->NVertLayers; + NChunks = NVertLayers / VecLength; + + // Read config options for PressureGrad type + // and enable the appropriate functor + Config PGradConfig("PressureGrad"); + Error Err; + Err += Options->get(PGradConfig); + CHECK_ERROR_ABORT(Err, "PressureGrad: PressureGrad group not found in Config"); + std::string PGradTypeStr; + Err += PGradConfig.get("PressureGradType", PGradTypeStr); + + if (PGradTypeStr == "centered" || PGradTypeStr == "Centered") { + PressureGradChoice = PressureGradType::Centered; + this->CenteredPGrad.Enabled = true; + } else if (PGradTypeStr == "HighOrder1") { + PressureGradChoice = PressureGradType::HighOrder1; + this->HighOrderPGrad.Enabled = true; + } else { + LOG_INFO("PGrad: Unknown PressureGradType in config, defaulting to centered"); + } + +} // end constructor + +//------------------------------------------------------------------------------ +// Destructor for PressureGrad +PressureGrad::~PressureGrad() { + + // No operations needed, Kokkos arrays removed when no longer in scope + +} // end destructor + +//------------------------------------------------------------------------------ +// Remove PressureGrad instances before exit +void PressureGrad::clear() { + + AllPGrads.clear(); + + } // end clear + +//------------------------------------------------------------------------------ +// Remove PressureGrad from list by name +void PressureGrad::erase(const std::string &Name) { + + AllPGrads.erase(Name); + +} // end erase + +//------------------------------------------------------------------------------ +// Get pressure gradient instance by name +PressureGrad *PressureGrad::get(const std::string &Name ///< [in] Name of +) { + + auto it = AllPGrads.find(Name); + + if (it != AllPGrads.end()) { + return it->second.get(); + } else { + LOG_ERROR("PressureGrad::get: Attempt to retrieve non-existent " + "PressureGrad:"); + LOG_ERROR("{} has not been defined or has been removed", Name); + return nullptr; + } + +} // end get pressure gradient + +//------------------------------------------------------------------------------ +// Compute pressure gradient tendencies and add into Tend array +void PressureGrad::computePressureGrad(Array2DReal Tend, + const OceanState *State, + const VertCoord *VCoord, + const Eos *EqState, + const int TimeLevel) { + + OMEGA_SCOPE(LocCenteredPGrad, CenteredPGrad); + OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); + + const Array2DReal &PressureMid = VCoord->PressureMid; + const Array2DReal &PressureInterface = VCoord->PressureInterface; + const Array2DReal &Geopotential = VCoord->GeopotentialMid; + const Array2DReal &SpecVol = EqState->SpecVol; + const Array2DReal &SpecVolInterface = EqState->SpecVolInterface; + const Array2DReal &ZInterface = VCoord->ZInterface; + Array2DReal LayerThick; + State->getLayerThickness(LayerThick, TimeLevel); + + if (PressureGradChoice == PressureGradType::Centered) { + parallelFor("pgrad-centered", {NEdgesAll, NChunks}, + KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { + LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, PressureInterface, Geopotential, + LayerThick, ZInterface, SpecVol, SpecVolInterface); + }); + } else { + parallelFor("pgrad-highorder", {NEdgesAll, NChunks}, + KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { + LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, Geopotential, + SpecVol); + }); + } +} // end compute pressure gradient + +} // namespace OMEGA \ No newline at end of file diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h new file mode 100644 index 000000000000..1d8ea87793c7 --- /dev/null +++ b/components/omega/src/ocn/PGrad.h @@ -0,0 +1,169 @@ +#ifndef OMEGA_PGRAD_H +#define OMEGA_PGRAD_H +//===-- ocn/PGrad.h - Pressure Gradient -----------------*- C++ -*-===// +/// +/// Implements the PressureGrad class which provides a centered and +/// high-order pressure gradient option and dispatches computations to +/// functor objects. This follows the patterns used in Eos.h/Eos.cpp. +// +//===----------------------------------------------------------------------===// + +#include "Config.h" +#include "HorzMesh.h" +#include "OceanState.h" +#include "Eos.h" +#include "OmegaKokkos.h" +#include "VertCoord.h" +#include + +namespace OMEGA { + +enum class PressureGradType { Centered, HighOrder1, HighOrder2 }; + +/// Centered pressure gradient functor +class PressureGradCentered { + public: + bool Enabled; + + /// constructor declaration + PressureGradCentered(const HorzMesh *Mesh); + + // Compute centered pressure gradient contribution for given edge and + // vertical chunk. This appends results into the Tend array (in-place). + KOKKOS_FUNCTION void operator()(const Array2DReal &Tend, I4 IEdge, I4 KChunk, + const Array2DReal &PressureMid, + const Array2DReal &PressureInterface, + const Array2DReal &Geopotential, + const Array2DReal &LayerThick, + const Array2DReal &ZInterface, + const Array2DReal &SpecVol, + const Array2DReal &SpecVolInterface) const { + const I4 KStart = KChunk * VecLength; + + const I4 ICell0 = CellsOnEdge(IEdge, 0); + const I4 ICell1 = CellsOnEdge(IEdge, 1); + const Real InvDcEdge = 1.0_Real / DcEdge(IEdge); + + for (int KVec = 0; KVec < VecLength; ++KVec) { + const I4 K = KStart + KVec; + + // Average quantities to edge + const Real AlphaEdgeK = 0.5*(SpecVolInterface(ICell1, K) + SpecVolInterface(ICell0, K)); + const Real PEdgeK = 0.5*(PressureInterface(ICell1, K) + PressureInterface(ICell0, K)); + const Real AlphaEdgeKP1 = 0.5*(SpecVolInterface(ICell1, K+1) + SpecVolInterface(ICell0, K+1)); + const Real PEdgeKP1 = 0.5*(PressureInterface(ICell1, K+1) + PressureInterface(ICell0, K+1)); + const Real InvLayerThickEdge = 2.0_Real / (LayerThick(ICell1, K) + LayerThick(ICell0, K)); + + Real GeoTerm = (Geopotential(ICell1, K) - Geopotential(ICell0, K)) * InvDcEdge; + Real PresTerm = (LayerThick(ICell1, K)*SpecVol(ICell1, K)*PressureMid(ICell1, K) + - LayerThick(ICell0, K)*SpecVol(ICell0, K)*PressureMid(ICell0, K)) * InvDcEdge * InvLayerThickEdge; + Real InterfaceTerm = (AlphaEdgeK * PEdgeK * (ZInterface(ICell1, K) - ZInterface(ICell0, K)) + - AlphaEdgeKP1 * PEdgeKP1 * (ZInterface(ICell1, K+1) - ZInterface(ICell0, K+1))) * InvDcEdge * InvLayerThickEdge; + + Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GeoTerm - PresTerm + InterfaceTerm); + } + } + + private: + Array2DI4 CellsOnEdge; + Array1DReal DcEdge; + Array2DReal EdgeMask; +}; + +/// High-order pressure gradient functor (placeholder) +class PressureGradHighOrder { + public: + bool Enabled; + + /// constructor declaration + PressureGradHighOrder(const HorzMesh *Mesh); + + KOKKOS_FUNCTION void operator()(const Array2DReal &Tend, I4 IEdge, I4 KChunk, + const Array2DReal &Pressure, + const Array2DReal &Geopotential, + const Array2DReal &SpecVol) const { + // Placeholder: for now, no-op (future high-order implementation) + const I4 KStart = KChunk * VecLength; + for (int KVec = 0; KVec < VecLength; ++KVec) { + const I4 K = KStart + KVec; + Tend(IEdge, K) += 0.0_Real; + } + } + private: + Array2DI4 CellsOnEdge; + Array1DReal DcEdge; + Array2DReal EdgeMask; +}; + +/// Pressure gradient manager class +class PressureGrad { + public: + /// Initialize the default instance + static void init(); + + /// Deallocates arrays and deletes instance + static void clear(); + + /// Remove pressure gradient object by name + static void erase(const std::string &Name ///< [in] + ); + + /// Get the default instance + static PressureGrad *getDefault(); + + /// Get instance by name + static PressureGrad *get(const std::string &Name ///< [in] + ); + + // Destructor + ~PressureGrad(); + + /// Compute pressure gradient tendencies and add into Tend array + void computePressureGrad(Array2DReal Tend, + const OceanState *State, + const VertCoord *VCoord, + const Eos *EqState, + const int TimeLevel); + + static PressureGrad *create(const std::string &Name, + const HorzMesh *Mesh, + const VertCoord *VCoord, + Config *Options); + + private: + // Construct a new pressure gradient object + PressureGrad(const HorzMesh *Mesh, + const VertCoord *VCoord, + Config *Options); + + // forbid copy and move construction + PressureGrad(const PressureGrad &) = delete; + PressureGrad(PressureGrad &&) = delete; + + // Pointer to default pressure gradient object + static PressureGrad *DefaultPGrad; + + // Mesh-related sizes + I4 NEdgesAll = 0; + I4 NChunks = 0; + I4 NVertLayers = 0; + + // Data required for computation (stored copies of mesh/VCoord arrays) + Array2DI4 CellsOnEdge; ///< cells surrounding each edge + Array1DReal DvEdge; ///< distance between cell centers across edge + Array2DReal EdgeSignOnCell; ///< orientation of edge relative to cell + + // Instances of functors + PressureGradCentered CenteredPGrad; + PressureGradHighOrder HighOrderPGrad; + + // Choice from config + PressureGradType PressureGradChoice = PressureGradType::Centered; + + // Map of all pressure gradient objects by name + static std::map> AllPGrads; + +}; // end class PressureGrad + +} // namespace OMEGA +#endif diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index b228c829082b..8714f56514ea 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -10,7 +10,9 @@ #include "Tendencies.h" #include "CustomTendencyTerms.h" +#include "Eos.h" #include "Error.h" +#include "PGrad.h" #include "Pacer.h" #include "TimeStepper.h" #include "Tracers.h" From 58069635142bfec8790e18442b1417dbdbacb393 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 4 Nov 2025 10:27:23 -0800 Subject: [PATCH 096/152] Udpate pressure gradient - Average specific volume to interfaces - Average product of spcific volume and pressure to edge - Add centered and high order functor constructors - Format linting --- components/omega/src/ocn/Eos.cpp | 85 ++++++++------- components/omega/src/ocn/PGrad.cpp | 161 ++++++++++++++++------------- components/omega/src/ocn/PGrad.h | 79 +++++++------- 3 files changed, 178 insertions(+), 147 deletions(-) diff --git a/components/omega/src/ocn/Eos.cpp b/components/omega/src/ocn/Eos.cpp index 4ee381b0e751..5926a77318bf 100644 --- a/components/omega/src/ocn/Eos.cpp +++ b/components/omega/src/ocn/Eos.cpp @@ -165,55 +165,62 @@ void Eos::computeSpecVolInterface(const Array2DReal &ConservTemp, I4 KDisp = 0; /// No displacement in this case - Array2DReal ConservTempInterface("ConservTempInterface", NCellsAll, - NVertLayers + 1); - Array2DReal AbsSalinityInterface("AbsSalinityInterface", NCellsAll, - NVertLayers + 1); + // Array2DReal ConservTempInterface("ConservTempInterface", NCellsAll, + // NVertLayers + 1); + // Array2DReal AbsSalinityInterface("AbsSalinityInterface", NCellsAll, + // NVertLayers + 1); // Compute interface values by averaging adjacent layer values parallelFor( "compute-interface-values", {NCellsAll, NVertLayers + 1}, KOKKOS_LAMBDA(I4 ICell, I4 KInterface) { if (KInterface == 0) { - ConservTempInterface(ICell, KInterface) = - ConservTemp(ICell, KInterface); - AbsSalinityInterface(ICell, KInterface) = - AbsSalinity(ICell, KInterface); - } else if (KInterface == NVertLayers) { - ConservTempInterface(ICell, KInterface) = - ConservTemp(ICell, KInterface - 1); - AbsSalinityInterface(ICell, KInterface) = - AbsSalinity(ICell, KInterface - 1); + // ConservTempInterface(ICell, KInterface) = + // ConservTemp(ICell, KInterface); + // AbsSalinityInterface(ICell, KInterface) = + // AbsSalinity(ICell, KInterface); + SpecVolInterface(ICell, KInterface) = SpecVol(ICell, KInterface); + } else if (KInterface == NVertLayers + 1) { + // ConservTempInterface(ICell, KInterface) = + // ConservTemp(ICell, KInterface - 1); + // AbsSalinityInterface(ICell, KInterface) = + // AbsSalinity(ICell, KInterface - 1); + SpecVolInterface(ICell, KInterface) = + SpecVol(ICell, KInterface - 1); } else { - ConservTempInterface(ICell, KInterface) = - 0.5_Real * (ConservTemp(ICell, KInterface - 1) + - ConservTemp(ICell, KInterface)); - AbsSalinityInterface(ICell, KInterface) = - 0.5_Real * (AbsSalinity(ICell, KInterface - 1) + - AbsSalinity(ICell, KInterface)); + // ConservTempInterface(ICell, KInterface) = + // 0.5_Real * (ConservTemp(ICell, KInterface - 1) + + // ConservTemp(ICell, KInterface)); + // AbsSalinityInterface(ICell, KInterface) = + // 0.5_Real * (AbsSalinity(ICell, KInterface - 1) + + // AbsSalinity(ICell, KInterface)); + SpecVolInterface(ICell, KInterface) = + 0.5_Real * + (SpecVol(ICell, KInterface - 1) + SpecVol(ICell, KInterface)); } }); - int NChunksP1 = (NVertLayers + 1) / VecLength; - - /// Dispatch to the correct EOS calculation - if (EosChoice == EosType::LinearEos) { - parallelFor( - "eos-linear", {NCellsAll, NChunksP1}, - KOKKOS_LAMBDA(I4 ICell, I4 KChunk) { - LocComputeSpecVolLinear(LocSpecVolInterface, ICell, KChunk, - ConservTempInterface, - AbsSalinityInterface); - }); - } else if (EosChoice == EosType::Teos10Eos) { - parallelFor( - "eos-teos10", {NCellsAll, NChunksP1}, - KOKKOS_LAMBDA(I4 ICell, I4 KChunk) { - LocComputeSpecVolTeos10(LocSpecVolInterface, ICell, KChunk, - ConservTempInterface, AbsSalinityInterface, - PressureInterface, KDisp); - }); - } + // int NChunksP1 = (NVertLayers + 1) / VecLength; + + ///// Dispatch to the correct EOS calculation + // if (EosChoice == EosType::LinearEos) { + // parallelFor( + // "eos-linear", {NCellsAll, NChunksP1}, + // KOKKOS_LAMBDA(I4 ICell, I4 KChunk) { + // LocComputeSpecVolLinear(LocSpecVolInterface, ICell, KChunk, + // ConservTempInterface, + // AbsSalinityInterface); + // }); + // } else if (EosChoice == EosType::Teos10Eos) { + // parallelFor( + // "eos-teos10", {NCellsAll, NChunksP1}, + // KOKKOS_LAMBDA(I4 ICell, I4 KChunk) { + // LocComputeSpecVolTeos10(LocSpecVolInterface, ICell, KChunk, + // ConservTempInterface, + // AbsSalinityInterface, PressureInterface, + // KDisp); + // }); + // } } /// Compute displaced specific volume (for vertical displacement) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 5bf2909667b2..e478b392e195 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -1,7 +1,7 @@ //===-- ocn/PGrad.cpp - Pressure Gradient Term -----------------*- C++ -*-===// // // Implements the PGrad manager and two discretizations: Centered and -// HighOrder. +// HighOrder. // //===----------------------------------------------------------------------===// @@ -22,7 +22,7 @@ std::map> PressureGrad::AllPGrads; void PressureGrad::init() { // Retrieve default mesh and vertical coordinate - HorzMesh *DefMesh = HorzMesh::getDefault(); + HorzMesh *DefMesh = HorzMesh::getDefault(); VertCoord *DefVCoord = VertCoord::getDefault(); // Retrieve PressureGrad config group @@ -30,20 +30,22 @@ void PressureGrad::init() { Config PGradConfig("PressureGrad"); Error Err; Err += OmegaConfig->get(PGradConfig); - CHECK_ERROR_ABORT(Err, "PressureGrad: PressureGrad group not found in Config"); + CHECK_ERROR_ABORT(Err, + "PressureGrad: PressureGrad group not found in Config"); // Create the default PressureGrad and set pointer to it - PressureGrad::DefaultPGrad = PressureGrad::create( - "Default", DefMesh, DefVCoord, &PGradConfig); + PressureGrad::DefaultPGrad = + PressureGrad::create("Default", DefMesh, DefVCoord, &PGradConfig); -} // end init +} // end init //------------------------------------------------------------------------------ // Create a new PressureGrad object and add it to the map -PressureGrad *PressureGrad::create(const std::string &Name, /// [in] Name for PressureGrad - const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord, ///< [in] Vertical coordinate - Config *Options) { ///< [in] Configuration options +PressureGrad * +PressureGrad::create(const std::string &Name, /// [in] Name for PressureGrad + const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord, ///< [in] Vertical coordinate + Config *Options) { ///< [in] Configuration options // Check to see if a PressureGrad of the same name already exists and // if so, exit with an error @@ -64,7 +66,7 @@ PressureGrad *PressureGrad::create(const std::string &Name, /// [in] Name for P } // end create //------------------------------------------------------------------------------ -// Get the default pressure gradient instance +// Get the default pressure gradient instance PressureGrad *PressureGrad::getDefault() { return DefaultPGrad; @@ -73,37 +75,39 @@ PressureGrad *PressureGrad::getDefault() { //------------------------------------------------------------------------------ // Constructor for PressureGrad -PressureGrad::PressureGrad(const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord, ///< [in] Vertical coordinate - Config *Options) ///< [in] Configuration options - : CellsOnEdge(Mesh->CellsOnEdge), - DvEdge(Mesh->DvEdge), - EdgeSignOnCell(Mesh->EdgeSignOnCell), - CenteredPGrad(Mesh), HighOrderPGrad(Mesh) { - - // store mesh sizes - NEdgesAll = Mesh->NEdgesAll; - NVertLayers = VCoord->NVertLayers; - NChunks = NVertLayers / VecLength; - - // Read config options for PressureGrad type - // and enable the appropriate functor - Config PGradConfig("PressureGrad"); - Error Err; - Err += Options->get(PGradConfig); - CHECK_ERROR_ABORT(Err, "PressureGrad: PressureGrad group not found in Config"); - std::string PGradTypeStr; - Err += PGradConfig.get("PressureGradType", PGradTypeStr); - - if (PGradTypeStr == "centered" || PGradTypeStr == "Centered") { - PressureGradChoice = PressureGradType::Centered; - this->CenteredPGrad.Enabled = true; - } else if (PGradTypeStr == "HighOrder1") { - PressureGradChoice = PressureGradType::HighOrder1; - this->HighOrderPGrad.Enabled = true; - } else { - LOG_INFO("PGrad: Unknown PressureGradType in config, defaulting to centered"); - } +PressureGrad::PressureGrad( + const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord, ///< [in] Vertical coordinate + Config *Options) ///< [in] Configuration options + : CellsOnEdge(Mesh->CellsOnEdge), DvEdge(Mesh->DvEdge), + EdgeSignOnCell(Mesh->EdgeSignOnCell), CenteredPGrad(Mesh), + HighOrderPGrad(Mesh) { + + // store mesh sizes + NEdgesAll = Mesh->NEdgesAll; + NVertLayers = VCoord->NVertLayers; + NChunks = NVertLayers / VecLength; + + // Read config options for PressureGrad type + // and enable the appropriate functor + Config PGradConfig("PressureGrad"); + Error Err; + Err += Options->get(PGradConfig); + CHECK_ERROR_ABORT(Err, + "PressureGrad: PressureGrad group not found in Config"); + std::string PGradTypeStr; + Err += PGradConfig.get("PressureGradType", PGradTypeStr); + + if (PGradTypeStr == "centered" || PGradTypeStr == "Centered") { + PressureGradChoice = PressureGradType::Centered; + this->CenteredPGrad.Enabled = true; + } else if (PGradTypeStr == "HighOrder1") { + PressureGradChoice = PressureGradType::HighOrder1; + this->HighOrderPGrad.Enabled = true; + } else { + LOG_INFO( + "PGrad: Unknown PressureGradType in config, defaulting to centered"); + } } // end constructor @@ -117,11 +121,7 @@ PressureGrad::~PressureGrad() { //------------------------------------------------------------------------------ // Remove PressureGrad instances before exit -void PressureGrad::clear() { - - AllPGrads.clear(); - - } // end clear +void PressureGrad::clear() { AllPGrads.clear(); } // end clear //------------------------------------------------------------------------------ // Remove PressureGrad from list by name @@ -157,31 +157,46 @@ void PressureGrad::computePressureGrad(Array2DReal Tend, const Eos *EqState, const int TimeLevel) { - OMEGA_SCOPE(LocCenteredPGrad, CenteredPGrad); - OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); - - const Array2DReal &PressureMid = VCoord->PressureMid; - const Array2DReal &PressureInterface = VCoord->PressureInterface; - const Array2DReal &Geopotential = VCoord->GeopotentialMid; - const Array2DReal &SpecVol = EqState->SpecVol; - const Array2DReal &SpecVolInterface = EqState->SpecVolInterface; - const Array2DReal &ZInterface = VCoord->ZInterface; - Array2DReal LayerThick; - State->getLayerThickness(LayerThick, TimeLevel); - - if (PressureGradChoice == PressureGradType::Centered) { - parallelFor("pgrad-centered", {NEdgesAll, NChunks}, - KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { - LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, PressureInterface, Geopotential, - LayerThick, ZInterface, SpecVol, SpecVolInterface); - }); - } else { - parallelFor("pgrad-highorder", {NEdgesAll, NChunks}, - KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { - LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, Geopotential, - SpecVol); - }); - } + OMEGA_SCOPE(LocCenteredPGrad, CenteredPGrad); + OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); + + const Array2DReal &PressureMid = VCoord->PressureMid; + const Array2DReal &PressureInterface = VCoord->PressureInterface; + const Array2DReal &Geopotential = VCoord->GeopotentialMid; + const Array2DReal &SpecVol = EqState->SpecVol; + const Array2DReal &SpecVolInterface = EqState->SpecVolInterface; + const Array2DReal &ZInterface = VCoord->ZInterface; + Array2DReal LayerThick; + State->getLayerThickness(LayerThick, TimeLevel); + + if (PressureGradChoice == PressureGradType::Centered) { + parallelFor( + "pgrad-centered", {NEdgesAll, NChunks}, + KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { + LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, + PressureInterface, Geopotential, LayerThick, + ZInterface, SpecVol, SpecVolInterface); + }); + } else { + parallelFor( + "pgrad-highorder", {NEdgesAll, NChunks}, + KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { + LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, Geopotential, + SpecVol); + }); + } } // end compute pressure gradient -} // namespace OMEGA \ No newline at end of file +//------------------------------------------------------------------------------ +// Constructor for centered pressure gradient functor +PressureGradCentered::PressureGradCentered(const HorzMesh *Mesh) + : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), + EdgeMask(Mesh->EdgeMask) {} + +//------------------------------------------------------------------------------ +// Constructor for high order pressure gradient functor +PressureGradHighOrder::PressureGradHighOrder(const HorzMesh *Mesh) + : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), + EdgeMask(Mesh->EdgeMask) {} + +} // namespace OMEGA diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 1d8ea87793c7..bb8b3262d785 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -9,9 +9,9 @@ //===----------------------------------------------------------------------===// #include "Config.h" +#include "Eos.h" #include "HorzMesh.h" #include "OceanState.h" -#include "Eos.h" #include "OmegaKokkos.h" #include "VertCoord.h" #include @@ -24,7 +24,7 @@ enum class PressureGradType { Centered, HighOrder1, HighOrder2 }; class PressureGradCentered { public: bool Enabled; - + /// constructor declaration PressureGradCentered(const HorzMesh *Mesh); @@ -40,27 +40,41 @@ class PressureGradCentered { const Array2DReal &SpecVolInterface) const { const I4 KStart = KChunk * VecLength; - const I4 ICell0 = CellsOnEdge(IEdge, 0); - const I4 ICell1 = CellsOnEdge(IEdge, 1); + const I4 ICell0 = CellsOnEdge(IEdge, 0); + const I4 ICell1 = CellsOnEdge(IEdge, 1); const Real InvDcEdge = 1.0_Real / DcEdge(IEdge); for (int KVec = 0; KVec < VecLength; ++KVec) { const I4 K = KStart + KVec; // Average quantities to edge - const Real AlphaEdgeK = 0.5*(SpecVolInterface(ICell1, K) + SpecVolInterface(ICell0, K)); - const Real PEdgeK = 0.5*(PressureInterface(ICell1, K) + PressureInterface(ICell0, K)); - const Real AlphaEdgeKP1 = 0.5*(SpecVolInterface(ICell1, K+1) + SpecVolInterface(ICell0, K+1)); - const Real PEdgeKP1 = 0.5*(PressureInterface(ICell1, K+1) + PressureInterface(ICell0, K+1)); - const Real InvLayerThickEdge = 2.0_Real / (LayerThick(ICell1, K) + LayerThick(ICell0, K)); - - Real GeoTerm = (Geopotential(ICell1, K) - Geopotential(ICell0, K)) * InvDcEdge; - Real PresTerm = (LayerThick(ICell1, K)*SpecVol(ICell1, K)*PressureMid(ICell1, K) - - LayerThick(ICell0, K)*SpecVol(ICell0, K)*PressureMid(ICell0, K)) * InvDcEdge * InvLayerThickEdge; - Real InterfaceTerm = (AlphaEdgeK * PEdgeK * (ZInterface(ICell1, K) - ZInterface(ICell0, K)) - - AlphaEdgeKP1 * PEdgeKP1 * (ZInterface(ICell1, K+1) - ZInterface(ICell0, K+1))) * InvDcEdge * InvLayerThickEdge; - - Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GeoTerm - PresTerm + InterfaceTerm); + const Real PAlphaEdgeK = + 0.5_Real * + (PressureInterface(ICell1, K) * SpecVolInterface(ICell1, K) + + PressureInterface(ICell0, K) * SpecVolInterface(ICell0, K)); + const Real PAlphaEdgeKP1 = + 0.5_Real * (PressureInterface(ICell1, K + 1) * + SpecVolInterface(ICell1, K + 1) + + PressureInterface(ICell0, K + 1) * + SpecVolInterface(ICell0, K + 1)); + const Real InvLayerThickEdge = + 2.0_Real / (LayerThick(ICell1, K) + LayerThick(ICell0, K)); + + Real GeoTerm = + (Geopotential(ICell1, K) - Geopotential(ICell0, K)) * InvDcEdge; + Real PresTerm = (LayerThick(ICell1, K) * SpecVol(ICell1, K) * + PressureMid(ICell1, K) - + LayerThick(ICell0, K) * SpecVol(ICell0, K) * + PressureMid(ICell0, K)) * + InvDcEdge * InvLayerThickEdge; + Real InterfaceTerm = + (PAlphaEdgeK * (ZInterface(ICell1, K) - ZInterface(ICell0, K)) - + PAlphaEdgeKP1 * + (ZInterface(ICell1, K + 1) - ZInterface(ICell0, K + 1))) * + InvDcEdge * InvLayerThickEdge; + + Tend(IEdge, K) += + EdgeMask(IEdge, K) * (-GeoTerm - PresTerm + InterfaceTerm); } } @@ -89,19 +103,20 @@ class PressureGradHighOrder { Tend(IEdge, K) += 0.0_Real; } } + private: Array2DI4 CellsOnEdge; Array1DReal DcEdge; Array2DReal EdgeMask; }; -/// Pressure gradient manager class +/// Pressure gradient manager class class PressureGrad { public: /// Initialize the default instance static void init(); - /// Deallocates arrays and deletes instance + /// Deallocates arrays and deletes instance static void clear(); /// Remove pressure gradient object by name @@ -119,38 +134,32 @@ class PressureGrad { ~PressureGrad(); /// Compute pressure gradient tendencies and add into Tend array - void computePressureGrad(Array2DReal Tend, - const OceanState *State, - const VertCoord *VCoord, - const Eos *EqState, + void computePressureGrad(Array2DReal Tend, const OceanState *State, + const VertCoord *VCoord, const Eos *EqState, const int TimeLevel); - static PressureGrad *create(const std::string &Name, - const HorzMesh *Mesh, - const VertCoord *VCoord, - Config *Options); + static PressureGrad *create(const std::string &Name, const HorzMesh *Mesh, + const VertCoord *VCoord, Config *Options); private: // Construct a new pressure gradient object - PressureGrad(const HorzMesh *Mesh, - const VertCoord *VCoord, - Config *Options); + PressureGrad(const HorzMesh *Mesh, const VertCoord *VCoord, Config *Options); // forbid copy and move construction PressureGrad(const PressureGrad &) = delete; - PressureGrad(PressureGrad &&) = delete; + PressureGrad(PressureGrad &&) = delete; // Pointer to default pressure gradient object static PressureGrad *DefaultPGrad; // Mesh-related sizes - I4 NEdgesAll = 0; - I4 NChunks = 0; + I4 NEdgesAll = 0; + I4 NChunks = 0; I4 NVertLayers = 0; // Data required for computation (stored copies of mesh/VCoord arrays) - Array2DI4 CellsOnEdge; ///< cells surrounding each edge - Array1DReal DvEdge; ///< distance between cell centers across edge + Array2DI4 CellsOnEdge; ///< cells surrounding each edge + Array1DReal DvEdge; ///< distance between cell centers across edge Array2DReal EdgeSignOnCell; ///< orientation of edge relative to cell // Instances of functors From eae3812a5197f4e8c862b15f030f3fbbdd4df244 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 4 Nov 2025 10:31:47 -0800 Subject: [PATCH 097/152] Add test driver --- components/omega/test/CMakeLists.txt | 11 ++ components/omega/test/ocn/PGradTest.cpp | 177 ++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 components/omega/test/ocn/PGradTest.cpp diff --git a/components/omega/test/CMakeLists.txt b/components/omega/test/CMakeLists.txt index 271af1c40d46..c2dceb09c5a1 100644 --- a/components/omega/test/CMakeLists.txt +++ b/components/omega/test/CMakeLists.txt @@ -346,6 +346,17 @@ add_omega_test( "-n;8" ) +################## +# Pressure gradient test +################## + +add_omega_test( + PGRAD_TEST + testPGrad.exe + ocn/PGradTest.cpp + "-n;8" +) + ################## # State test ################## diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp new file mode 100644 index 000000000000..fb479cccee3e --- /dev/null +++ b/components/omega/test/ocn/PGradTest.cpp @@ -0,0 +1,177 @@ +//===-- Test driver for OMEGA Pressure Gradient (PGrad) --------------*- C++-*-===/ +// +/// \file +/// \brief Test driver for PressureGrad module +// +//===----------------------------------------------------------------------===/ + +#include "PGrad.h" + +#include "DataTypes.h" +#include "Decomp.h" +#include "Dimension.h" +#include "Error.h" +#include "Eos.h" +#include "Field.h" +#include "Halo.h" +#include "HorzMesh.h" +#include "IO.h" +#include "Logging.h" +#include "MachEnv.h" +#include "OceanState.h" +#include "OmegaKokkos.h" +#include "Pacer.h" +#include "PGrad.h" +#include "TimeStepper.h" +#include "VertCoord.h" +#include "mpi.h" + +using namespace OMEGA; + +void initPGradTest() { + + Error Err; + int Err1; + + MachEnv::init(MPI_COMM_WORLD); + MachEnv *DefEnv = MachEnv::getDefault(); + MPI_Comm DefComm = DefEnv->getComm(); + + // Initialize the Logging system + initLogging(DefEnv); + + // Read default config if present + Config("Omega"); + Config::readAll("omega.yml"); + + // First step of time stepper initialization needed for IOstream + TimeStepper::init1(); + + // Initialize the IO system + IO::init(DefComm); + + // Create the default decomposition (initializes the decomposition) + Decomp::init(); + + // Initialize the default halo + Err1 = Halo::init(); + if (Err1 != 0) { + LOG_ERROR("PGrad: error initializing default halo"); + Err += Error(ErrorCode::Fail, + "PGrad: error initializing default halo"); + } + + // Begin initialization of the default vertical coordinate + VertCoord::init1(); + + // Initialize the default mesh + HorzMesh::init(); + + // Complete initialization of the default vertical coordinate + VertCoord::init2(); + + // Initialize the equation of state + Eos::init(); + + // Initialize ocean state + OceanState::init(); + + + CHECK_ERROR_ABORT(Err, "PGrad: error during initialization"); +} + +int main(int argc, char *argv[]) { + int RetVal = 0; + int Err; + + MPI_Init(&argc, &argv); + Kokkos::initialize(); + Pacer::initialize(MPI_COMM_WORLD); + Pacer::setPrefix("Omega:"); + { + initPGradTest(); + + // Initialize PressureGrad manager + PressureGrad::init(); + + MachEnv *DefEnv = MachEnv::getDefault(); + HorzMesh *DefMesh = HorzMesh::getDefault(); + VertCoord *DefVCoord = VertCoord::getDefault(); + OceanState *DefState = OceanState::getDefault(); + Eos *DefEos = Eos::getInstance(); + Config *Options = Config::getOmegaConfig(); + + I4 NEdgesOwned = DefMesh->NEdgesOwned; + I4 NVertLayers = VertCoord::getDefault()->NVertLayers; + I4 NChunks = NVertLayers / VecLength; + + // create arrays: Tend on edges, Pressure/Geopotential/SpecVol on cells + Array2DReal Tend("Tend", DefMesh->NEdgesAll, NVertLayers); + Array2DReal Pressure("Pressure", DefMesh->NCellsAll, NVertLayers); + Array2DReal Geopotential("Geopotential", DefMesh->NCellsAll, NVertLayers); + Array2DReal SpecVol("SpecVol", DefMesh->NCellsAll, NVertLayers); + + // initialize arrays to zero + parallelFor({DefMesh->NEdgesAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + Tend(i,k) = 0.0_Real; + }); + parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + Pressure(i,k) = 0.0_Real; + Geopotential(i,k) = 0.0_Real; + SpecVol(i,k) = 1.0_Real; // unit specific volume + }); + + // set a simple pressure difference between two neighboring cells + if (DefMesh->NCellsAll >= 2) { + Pressure(0,0) = 1.0_Real; + Pressure(1,0) = 2.0_Real; + } + + // call computePressureGrad + PressureGrad *Pg = PressureGrad::getDefault(); + if (!Pg) { + LOG_INFO("PGrad: default instance not present, creating via create"); + Pg = PressureGrad::create("default", DefMesh, DefVCoord, Options); + } + + int TimeLevel = 0; + if (Pg) { + Pg->computePressureGrad(Tend, DefState, DefVCoord, DefEos, TimeLevel); + + // simple check: ensure Tend contains some finite values (not all zero) + I4 CountNonZero = 0; + parallelReduce("countNonZero", {NEdgesOwned, NVertLayers}, + KOKKOS_LAMBDA(int e, int k, I4 &acc) { + if (Tend(e,k) != 0.0_Real) acc++; + }, CountNonZero); + + if (CountNonZero > 0) { + LOG_INFO("PGrad: computePressureGrad produced non-zero tendencies PASS"); + } else { + RetVal += 1; + LOG_ERROR("PGrad: computePressureGrad produced all-zero tendencies FAIL"); + } + } else { + RetVal += 1; + LOG_ERROR("PGrad: failed to obtain PressureGrad instance FAIL"); + } + + // cleanup + PressureGrad::clear(); + VertCoord::clear(); + OceanState::clear(); + HorzMesh::clear(); + Halo::clear(); + Decomp::clear(); + MachEnv::removeAll(); + FieldGroup::clear(); + Field::clear(); + Dimension::clear(); + } + Pacer::finalize(); + Kokkos::finalize(); + MPI_Finalize(); + + if (RetVal >= 256) RetVal = 255; + return RetVal; +} From 43dd27a015596cfa69275123102e196fb4467779 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 20 Nov 2025 14:47:07 -0800 Subject: [PATCH 098/152] Add PressureGrad section to Default.yml --- components/omega/configs/Default.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index d46de8c24e95..2964147e33ec 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -34,6 +34,8 @@ Omega: VertCoord: Density0: 1026.0 MovementWeightType: Uniform + PressureGrad: + PressureGradType: Centered Tendencies: ThicknessFluxTendencyEnable: true PVTendencyEnable: true From acaf474d51588747476d992a39e598d73bac975d Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 1 Dec 2025 14:01:52 -0800 Subject: [PATCH 099/152] Convert Pa to dbar in Teos10Eos functor --- components/omega/src/ocn/Eos.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/omega/src/ocn/Eos.h b/components/omega/src/ocn/Eos.h index 0eb111741d58..0807b8a1b484 100644 --- a/components/omega/src/ocn/Eos.h +++ b/components/omega/src/ocn/Eos.h @@ -44,7 +44,7 @@ class Teos10Eos { I4 KDisp) const { Real SpecVolPCoeffs[6 * VecLength]; - + Real PaToDBar = 1.0e-4_Real; // Conversion factor from Pa to dbar const I4 KStart = chunkStart(KChunk, MinLayerCell(ICell)); const I4 KLen = chunkLength(KChunk, KStart, MaxLayerCell(ICell)); @@ -63,15 +63,15 @@ class Teos10Eos { if (KDisp == 0) { // No displacement SpecVol(ICell, K) = - calcRefProfile(Pressure(ICell, K)) + - calcDelta(SpecVolPCoeffs, KVec, Pressure(ICell, K)); + calcRefProfile(Pressure(ICell, K) * PaToDBar) + + calcDelta(SpecVolPCoeffs, KVec, Pressure(ICell, K) * PaToDBar); } else { // Displacement, use the displaced pressure I4 KTmp = Kokkos::min(K + KDisp, MaxLayerCell(ICell)); KTmp = Kokkos::max(MinLayerCell(ICell), KTmp); SpecVol(ICell, K) = - calcRefProfile(Pressure(ICell, KTmp)) + - calcDelta(SpecVolPCoeffs, KVec, Pressure(ICell, KTmp)); + calcRefProfile(Pressure(ICell, KTmp) * PaToDBar) + + calcDelta(SpecVolPCoeffs, KVec, Pressure(ICell, KTmp) * PaToDBar); } } } From 5197b968a4613dbb071da99cc46b4a8b6ba4b93b Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 20 Nov 2025 14:56:09 -0800 Subject: [PATCH 100/152] Updates to PGradTest --- components/omega/src/ocn/PGrad.cpp | 11 +- components/omega/src/ocn/PGrad.h | 2 +- components/omega/test/ocn/PGradTest.cpp | 264 +++++++++++++++++++----- 3 files changed, 221 insertions(+), 56 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index e478b392e195..effdccca8a72 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -25,17 +25,12 @@ void PressureGrad::init() { HorzMesh *DefMesh = HorzMesh::getDefault(); VertCoord *DefVCoord = VertCoord::getDefault(); - // Retrieve PressureGrad config group + // Retrieve omega config Config *OmegaConfig = Config::getOmegaConfig(); - Config PGradConfig("PressureGrad"); - Error Err; - Err += OmegaConfig->get(PGradConfig); - CHECK_ERROR_ABORT(Err, - "PressureGrad: PressureGrad group not found in Config"); // Create the default PressureGrad and set pointer to it PressureGrad::DefaultPGrad = - PressureGrad::create("Default", DefMesh, DefVCoord, &PGradConfig); + PressureGrad::create("Default", DefMesh, DefVCoord, OmegaConfig); } // end init @@ -151,7 +146,7 @@ PressureGrad *PressureGrad::get(const std::string &Name ///< [in] Name of //------------------------------------------------------------------------------ // Compute pressure gradient tendencies and add into Tend array -void PressureGrad::computePressureGrad(Array2DReal Tend, +void PressureGrad::computePressureGrad(Array2DReal &Tend, const OceanState *State, const VertCoord *VCoord, const Eos *EqState, diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index bb8b3262d785..e104db110321 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -134,7 +134,7 @@ class PressureGrad { ~PressureGrad(); /// Compute pressure gradient tendencies and add into Tend array - void computePressureGrad(Array2DReal Tend, const OceanState *State, + void computePressureGrad(Array2DReal &Tend, const OceanState *State, const VertCoord *VCoord, const Eos *EqState, const int TimeLevel); diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index fb479cccee3e..14c838b8d2a4 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -16,12 +16,14 @@ #include "Halo.h" #include "HorzMesh.h" #include "IO.h" +#include "IOStream.h" #include "Logging.h" #include "MachEnv.h" #include "OceanState.h" #include "OmegaKokkos.h" #include "Pacer.h" #include "PGrad.h" +#include "Tracers.h" #include "TimeStepper.h" #include "VertCoord.h" #include "mpi.h" @@ -53,6 +55,9 @@ void initPGradTest() { // Create the default decomposition (initializes the decomposition) Decomp::init(); + // Initialize streams + IOStream::init(); + // Initialize the default halo Err1 = Halo::init(); if (Err1 != 0) { @@ -76,6 +81,8 @@ void initPGradTest() { // Initialize ocean state OceanState::init(); + // Initialize tracers + Tracers::init(); CHECK_ERROR_ABORT(Err, "PGrad: error during initialization"); } @@ -88,85 +95,248 @@ int main(int argc, char *argv[]) { Kokkos::initialize(); Pacer::initialize(MPI_COMM_WORLD); Pacer::setPrefix("Omega:"); + { initPGradTest(); - // Initialize PressureGrad manager PressureGrad::init(); + MachEnv *DefEnv = MachEnv::getDefault(); HorzMesh *DefMesh = HorzMesh::getDefault(); - VertCoord *DefVCoord = VertCoord::getDefault(); + //VertCoord *DefVCoord = VertCoord::getDefault(); + VertCoord *VCoord = VertCoord::getDefault(); OceanState *DefState = OceanState::getDefault(); + Decomp *DefDecomp = Decomp::getDefault(); Eos *DefEos = Eos::getInstance(); Config *Options = Config::getOmegaConfig(); - I4 NEdgesOwned = DefMesh->NEdgesOwned; - I4 NVertLayers = VertCoord::getDefault()->NVertLayers; + + I4 NVertLayers =VCoord->NVertLayers; I4 NChunks = NVertLayers / VecLength; - // create arrays: Tend on edges, Pressure/Geopotential/SpecVol on cells - Array2DReal Tend("Tend", DefMesh->NEdgesAll, NVertLayers); - Array2DReal Pressure("Pressure", DefMesh->NCellsAll, NVertLayers); - Array2DReal Geopotential("Geopotential", DefMesh->NCellsAll, NVertLayers); - Array2DReal SpecVol("SpecVol", DefMesh->NCellsAll, NVertLayers); - - // initialize arrays to zero - parallelFor({DefMesh->NEdgesAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { - Tend(i,k) = 0.0_Real; + // // set two column mesh + //DefMesh->NCellsAll = 2; + //DefMesh->NCellsOwned = 2; + //DefMesh->NEdgesAll = 1; + //DefMesh->NEdgesOwned = 1; + //DefDecomp->NCellsAll = 2; + //DefDecomp->NCellsSize = 2; + //DefDecomp->NCellsOwned = 2; + //DefDecomp->NEdgesAll = 1; + //DefDecomp->NEdgesOwned = 1; + + auto &MinLayerCell = VCoord->MinLayerCell; + auto &MaxLayerCell = VCoord->MaxLayerCell; + parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { + MinLayerCell(i) = 0; + MaxLayerCell(i) = NVertLayers - 1; }); - parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { - Pressure(i,k) = 0.0_Real; - Geopotential(i,k) = 0.0_Real; - SpecVol(i,k) = 1.0_Real; // unit specific volume + + auto &CellsOnEdge = DefMesh->CellsOnEdge; + auto &DcEdge = DefMesh->DcEdge; + auto &EdgeMask = DefMesh->EdgeMask; + parallelFor({DefMesh->NEdgesAll}, KOKKOS_LAMBDA(int e) { + CellsOnEdge(e, 0)= 0; + CellsOnEdge(e, 1)= 1; + DcEdge(e) = 100.0_Real; + for (int k = 0; k < NVertLayers; ++k) { + EdgeMask(e, k) = 1.0_Real; + } + }); + + // Fetch reference desnity from Config + Real Density0; + Error ErrorCode; + Config TendConfig("Tendencies"); + ErrorCode.reset(); + ErrorCode += Options->get(TendConfig); + CHECK_ERROR_ABORT(ErrorCode, "VertCoord: Tendencies group not found in Config"); + ErrorCode += TendConfig.get("Density0", Density0); + CHECK_ERROR_ABORT(ErrorCode, "VertCoord: Density0 not found in TendConfig"); + + I4 TimeLevel = 0; + + // create arrays: Tend on edges, Pressure/Geopotential/SpecVol on cells + Array2DReal Tend("Tend", DefMesh->NEdgesSize, NVertLayers); + Array2DReal SpecVolOld("SpecVolOld", DefMesh->NCellsSize, NVertLayers); + Array2DReal PressureMidOld("PressureMidOld", DefMesh->NCellsSize, NVertLayers); + Array2DReal LayerThick; + DefState->getLayerThickness(LayerThick, TimeLevel); + Array1DReal SurfacePressure("SurfacePressure", DefMesh->NCellsSize); + Array2DReal Temp; + Array2DReal Salinity; + Err = Tracers::getByName(Temp, TimeLevel, "Temperature"); + Err = Tracers::getByName(Salinity, TimeLevel, "Salinity"); + + + + // set Z interface and mid-point locations + Real ZBottom = -1000.0_Real; + Real dZ = -ZBottom / (NVertLayers + NVertLayers / 2); + auto &BottomDepth = VCoord->BottomDepth; + auto &ZInterface = VCoord->ZInterface; + auto &ZMid = VCoord->ZMid; + parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { + ZInterface(i, NVertLayers) = ZBottom; + SurfacePressure(i) = 0.0_Real; + BottomDepth(i) = 0.0_Real; + for (int k = NVertLayers - 1; k >= 0; --k) { + Real dz = (((k + i) % 2) + 1.0_Real) * dZ; // staggered layer thickness + ZInterface(i, k) = ZInterface(i, k + 1) + dz; + LayerThick(i, k) = ZInterface(i, k) - ZInterface(i, k + 1); + ZMid(i, k) = 0.5_Real * (ZInterface(i, k) + ZInterface(i, k + 1)); + BottomDepth(i) += dz; + } }); - // set a simple pressure difference between two neighboring cells - if (DefMesh->NCellsAll >= 2) { - Pressure(0,0) = 1.0_Real; - Pressure(1,0) = 2.0_Real; + LOG_INFO("NVertLayers = {}", NVertLayers); + for (int i = 0; i < 2; ++i) { + for (int k = 0; k <= NVertLayers; ++k) { + LOG_INFO("ZInterface({}, {}) = {}", i, k, ZInterface(i, k)); + } } - - // call computePressureGrad - PressureGrad *Pg = PressureGrad::getDefault(); - if (!Pg) { - LOG_INFO("PGrad: default instance not present, creating via create"); - Pg = PressureGrad::create("default", DefMesh, DefVCoord, Options); + LOG_INFO("NVertLayers = {}", NVertLayers); + for (int i = 0; i < 2; ++i) { + for (int k = 0; k <= NVertLayers; ++k) { + LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThick(i, k)); + } } + LOG_INFO("NVertLayers = {}", NVertLayers); + for (int i = 0; i < 2; ++i) { + for (int k = 0; k <= NVertLayers; ++k) { + LOG_INFO("ZMid({}, {}) = {}", i, k, ZMid(i, k)); + } + } + + // set simple temperature and salinity profiles + auto &SpecVol = DefEos->SpecVol; + parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + Real T0 = 30.0; + Real TB = 1.0; + Real S0 = 30.0; + Real SB = 40.0; - int TimeLevel = 0; - if (Pg) { - Pg->computePressureGrad(Tend, DefState, DefVCoord, DefEos, TimeLevel); - // simple check: ensure Tend contains some finite values (not all zero) - I4 CountNonZero = 0; - parallelReduce("countNonZero", {NEdgesOwned, NVertLayers}, - KOKKOS_LAMBDA(int e, int k, I4 &acc) { - if (Tend(e,k) != 0.0_Real) acc++; - }, CountNonZero); + Real phi0 = (ZMid(i, k) - ZBottom) / (-ZBottom); + Real phiB = 1.0_Real - phi0; - if (CountNonZero > 0) { - LOG_INFO("PGrad: computePressureGrad produced non-zero tendencies PASS"); + Temp(i, k) = T0 * phi0 + TB * phiB; + Salinity(i, k) = S0 * phi0 + SB * phiB; + SpecVol(i, k) = 1.0_Real / Density0; + SpecVolOld(i, k) = SpecVol(i, k); + }); + + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("Temp({}, {}) = {}", i, k, Temp(i, k)); + // } + //} + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("Salinity({}, {}) = {}", i, k, Salinity(i, k)); + // } + //} + + // iterate to converge SpecVol + auto &PressureMid = VCoord->PressureMid; + VCoord->computePressure(LayerThick, SurfacePressure); + deepCopy(PressureMidOld, PressureMid); + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("PressureMid({}, {}) = {}, PressureMidOld({}, {}) = {}", i, k, PressureMid(i, k), i, k, PressureMidOld(i, k)); + // } + //} + for (int iteration = 0; iteration < 5; ++iteration) { + + // compute specific volume from EOS + VCoord->computePressure(LayerThick, SurfacePressure); + DefEos->computeSpecVol(Temp, Salinity, PressureMid); + + for (int i = 0; i < 2; ++i) { + for (int k = 0; k < NVertLayers; ++k) { + LOG_INFO("PressureMid({}, {}) = {}, PressureMidOld({}, {}) = {}", i, k, PressureMid(i, k), i, k, PressureMidOld(i, k)); + } + } + for (int i = 0; i < 2; ++i) { + for (int k = 0; k < NVertLayers; ++k) { + LOG_INFO(" Density({}, {}) = {}", i, k, 1.0_Real / SpecVol(i, k)); + } + } + + // Compute psuedo thickness + parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + LayerThick(i, k) = 1.0_Real / (SpecVol(i, k) * Density0) * (ZInterface(i, k) - ZInterface(i, k + 1)); + }); + + // check for convergence + Real max_value = 0.0_Real; + parallelReduce({DefMesh->NCellsAll, NVertLayers}, + KOKKOS_LAMBDA(int i, int k, Real &max) { + Real diff = Kokkos::abs(SpecVol(i, k) - SpecVolOld(i, k)); + if (diff > max) max = diff; + }, Kokkos::Max(max_value) ); + + if (max_value < 1e-12_Real) { + LOG_INFO("converged: max diff = {}", max_value); + break; } else { - RetVal += 1; - LOG_ERROR("PGrad: computePressureGrad produced all-zero tendencies FAIL"); + LOG_INFO("max diff = {}", max_value); + parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + SpecVolOld(i, k) = SpecVol(i, k); + }); + } + + } + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("SpecVol({}, {}) = {}", i, k, SpecVol(i, k)); + // } + //} + + + VCoord->computeZHeight(LayerThick, SpecVol); + Array1DReal SelfAttractionLoading("SelfAttractionLoading", DefMesh->NCellsAll); + Array1DReal TidalPotential("TidalPotential", DefMesh->NCellsAll); + deepCopy(TidalPotential, 0.0_Real); + deepCopy(SelfAttractionLoading, 0.0_Real); + VCoord->computeGeopotential(TidalPotential, SelfAttractionLoading); + + // create PressureGrad instance + PressureGrad *DefPGrad = PressureGrad::getDefault(); + if (!DefPGrad) { + LOG_INFO("PGrad: default instance not created by init"); + } + auto PGradTest = PressureGrad::create("Test", DefMesh, VCoord, Options); + + for (int i = 0; i < 2; ++i) { + for (int k = 0; k < NVertLayers; ++k) { + Tend(i, k) = 0.0_Real; + LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); + } + } + PGradTest->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); + for (int i = 0; i < 2; ++i) { + for (int k = 0; k < NVertLayers; ++k) { + LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); } - } else { - RetVal += 1; - LOG_ERROR("PGrad: failed to obtain PressureGrad instance FAIL"); } + // cleanup PressureGrad::clear(); - VertCoord::clear(); + IOStream::finalize(); + TimeStepper::clear(); + Tracers::clear(); + Eos::destroyInstance(); OceanState::clear(); + VertCoord::clear(); + Field::clear(); + Dimension::clear(); HorzMesh::clear(); Halo::clear(); Decomp::clear(); MachEnv::removeAll(); - FieldGroup::clear(); - Field::clear(); - Dimension::clear(); } Pacer::finalize(); Kokkos::finalize(); From 177c917447621194cf347244c78cde7bea867d77 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Fri, 21 Nov 2025 08:59:50 -0800 Subject: [PATCH 101/152] Add tilt factor to PGradTest --- components/omega/test/ocn/PGradTest.cpp | 101 ++++++++++-------------- 1 file changed, 42 insertions(+), 59 deletions(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 14c838b8d2a4..02968e6bf4cf 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -104,30 +104,19 @@ int main(int argc, char *argv[]) { MachEnv *DefEnv = MachEnv::getDefault(); HorzMesh *DefMesh = HorzMesh::getDefault(); - //VertCoord *DefVCoord = VertCoord::getDefault(); - VertCoord *VCoord = VertCoord::getDefault(); + //VertCoord *DefDefVCoord = VertCoord::getDefault(); + VertCoord *DefVCoord = VertCoord::getDefault(); OceanState *DefState = OceanState::getDefault(); Decomp *DefDecomp = Decomp::getDefault(); Eos *DefEos = Eos::getInstance(); Config *Options = Config::getOmegaConfig(); - I4 NVertLayers =VCoord->NVertLayers; + I4 NVertLayers =DefVCoord->NVertLayers; I4 NChunks = NVertLayers / VecLength; - // // set two column mesh - //DefMesh->NCellsAll = 2; - //DefMesh->NCellsOwned = 2; - //DefMesh->NEdgesAll = 1; - //DefMesh->NEdgesOwned = 1; - //DefDecomp->NCellsAll = 2; - //DefDecomp->NCellsSize = 2; - //DefDecomp->NCellsOwned = 2; - //DefDecomp->NEdgesAll = 1; - //DefDecomp->NEdgesOwned = 1; - - auto &MinLayerCell = VCoord->MinLayerCell; - auto &MaxLayerCell = VCoord->MaxLayerCell; + auto &MinLayerCell = DefVCoord->MinLayerCell; + auto &MaxLayerCell = DefVCoord->MaxLayerCell; parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { MinLayerCell(i) = 0; MaxLayerCell(i) = NVertLayers - 1; @@ -139,10 +128,10 @@ int main(int argc, char *argv[]) { parallelFor({DefMesh->NEdgesAll}, KOKKOS_LAMBDA(int e) { CellsOnEdge(e, 0)= 0; CellsOnEdge(e, 1)= 1; - DcEdge(e) = 100.0_Real; - for (int k = 0; k < NVertLayers; ++k) { - EdgeMask(e, k) = 1.0_Real; - } + //DcEdge(e) = 100.0_Real; + //for (int k = 0; k < NVertLayers; ++k) { + // EdgeMask(e, k) = 1.0_Real; + //} }); // Fetch reference desnity from Config @@ -173,16 +162,18 @@ int main(int argc, char *argv[]) { // set Z interface and mid-point locations Real ZBottom = -1000.0_Real; - Real dZ = -ZBottom / (NVertLayers + NVertLayers / 2); - auto &BottomDepth = VCoord->BottomDepth; - auto &ZInterface = VCoord->ZInterface; - auto &ZMid = VCoord->ZMid; + Real dZ = 2.0_Real * (-ZBottom / NVertLayers); + auto &BottomDepth = DefVCoord->BottomDepth; + auto &ZInterface = DefVCoord->ZInterface; + auto &ZMid = DefVCoord->ZMid; + Real tilt_factor = 0.495_Real; parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { ZInterface(i, NVertLayers) = ZBottom; SurfacePressure(i) = 0.0_Real; BottomDepth(i) = 0.0_Real; for (int k = NVertLayers - 1; k >= 0; --k) { - Real dz = (((k + i) % 2) + 1.0_Real) * dZ; // staggered layer thickness + Real x = (k + i) % 2; + Real dz = ( 2.0_Real * tilt_factor - 1.0_Real ) * x * dZ + (1.0_Real - tilt_factor) * dZ; // staggered layer thickness ZInterface(i, k) = ZInterface(i, k + 1) + dz; LayerThick(i, k) = ZInterface(i, k) - ZInterface(i, k + 1); ZMid(i, k) = 0.5_Real * (ZInterface(i, k) + ZInterface(i, k + 1)); @@ -202,12 +193,12 @@ int main(int argc, char *argv[]) { LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThick(i, k)); } } - LOG_INFO("NVertLayers = {}", NVertLayers); - for (int i = 0; i < 2; ++i) { - for (int k = 0; k <= NVertLayers; ++k) { - LOG_INFO("ZMid({}, {}) = {}", i, k, ZMid(i, k)); - } - } + //LOG_INFO("NVertLayers = {}", NVertLayers); + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k <= NVertLayers; ++k) { + // LOG_INFO("ZMid({}, {}) = {}", i, k, ZMid(i, k)); + // } + //} // set simple temperature and salinity profiles auto &SpecVol = DefEos->SpecVol; @@ -239,37 +230,26 @@ int main(int argc, char *argv[]) { //} // iterate to converge SpecVol - auto &PressureMid = VCoord->PressureMid; - VCoord->computePressure(LayerThick, SurfacePressure); + auto &PressureMid = DefVCoord->PressureMid; + DefVCoord->computePressure(LayerThick, SurfacePressure); deepCopy(PressureMidOld, PressureMid); //for (int i = 0; i < 2; ++i) { // for (int k = 0; k < NVertLayers; ++k) { // LOG_INFO("PressureMid({}, {}) = {}, PressureMidOld({}, {}) = {}", i, k, PressureMid(i, k), i, k, PressureMidOld(i, k)); // } //} - for (int iteration = 0; iteration < 5; ++iteration) { + for (int iteration = 0; iteration < 15; ++iteration) { // compute specific volume from EOS - VCoord->computePressure(LayerThick, SurfacePressure); + DefVCoord->computePressure(LayerThick, SurfacePressure); DefEos->computeSpecVol(Temp, Salinity, PressureMid); - for (int i = 0; i < 2; ++i) { - for (int k = 0; k < NVertLayers; ++k) { - LOG_INFO("PressureMid({}, {}) = {}, PressureMidOld({}, {}) = {}", i, k, PressureMid(i, k), i, k, PressureMidOld(i, k)); - } - } - for (int i = 0; i < 2; ++i) { - for (int k = 0; k < NVertLayers; ++k) { - LOG_INFO(" Density({}, {}) = {}", i, k, 1.0_Real / SpecVol(i, k)); - } - } - - // Compute psuedo thickness + // compute psuedo thickness from specific volume parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { LayerThick(i, k) = 1.0_Real / (SpecVol(i, k) * Density0) * (ZInterface(i, k) - ZInterface(i, k + 1)); }); - // check for convergence + // compute difference from previous iteration Real max_value = 0.0_Real; parallelReduce({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k, Real &max) { @@ -277,6 +257,7 @@ int main(int argc, char *argv[]) { if (diff > max) max = diff; }, Kokkos::Max(max_value) ); + // check convergence if (max_value < 1e-12_Real) { LOG_INFO("converged: max diff = {}", max_value); break; @@ -294,28 +275,30 @@ int main(int argc, char *argv[]) { // } //} - - VCoord->computeZHeight(LayerThick, SpecVol); + // compute geopotential + //DefVCoord->computeZHeight(LayerThick, SpecVol); Array1DReal SelfAttractionLoading("SelfAttractionLoading", DefMesh->NCellsAll); Array1DReal TidalPotential("TidalPotential", DefMesh->NCellsAll); deepCopy(TidalPotential, 0.0_Real); deepCopy(SelfAttractionLoading, 0.0_Real); - VCoord->computeGeopotential(TidalPotential, SelfAttractionLoading); + DefVCoord->computeGeopotential(TidalPotential, SelfAttractionLoading); // create PressureGrad instance PressureGrad *DefPGrad = PressureGrad::getDefault(); if (!DefPGrad) { LOG_INFO("PGrad: default instance not created by init"); } - auto PGradTest = PressureGrad::create("Test", DefMesh, VCoord, Options); + auto PGradTest = PressureGrad::create("Test", DefMesh, DefVCoord, Options); - for (int i = 0; i < 2; ++i) { - for (int k = 0; k < NVertLayers; ++k) { - Tend(i, k) = 0.0_Real; - LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); - } - } - PGradTest->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // Tend(i, k) = 0.0_Real; + // LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); + // } + //} + + // compute pressure gradient + PGradTest->computePressureGrad(Tend, DefState, DefVCoord, DefEos, TimeLevel); for (int i = 0; i < 2; ++i) { for (int k = 0; k < NVertLayers; ++k) { LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); From 582d618e0c2f162b007d5200ac4fc1c2ce982033 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 24 Nov 2025 13:53:57 -0800 Subject: [PATCH 102/152] Test for pressure gradient convergence in PGradTest --- components/omega/test/ocn/PGradTest.cpp | 382 +++++++++++++----------- 1 file changed, 204 insertions(+), 178 deletions(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 02968e6bf4cf..ac66e6ee9b3f 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -104,207 +104,233 @@ int main(int argc, char *argv[]) { MachEnv *DefEnv = MachEnv::getDefault(); HorzMesh *DefMesh = HorzMesh::getDefault(); - //VertCoord *DefDefVCoord = VertCoord::getDefault(); - VertCoord *DefVCoord = VertCoord::getDefault(); + //VertCoord *DefVCoord = VertCoord::getDefault(); + VertCoord *VCoord = VertCoord::getDefault(); OceanState *DefState = OceanState::getDefault(); Decomp *DefDecomp = Decomp::getDefault(); Eos *DefEos = Eos::getInstance(); Config *Options = Config::getOmegaConfig(); - - I4 NVertLayers =DefVCoord->NVertLayers; - I4 NChunks = NVertLayers / VecLength; - - auto &MinLayerCell = DefVCoord->MinLayerCell; - auto &MaxLayerCell = DefVCoord->MaxLayerCell; - parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { - MinLayerCell(i) = 0; - MaxLayerCell(i) = NVertLayers - 1; - }); - - auto &CellsOnEdge = DefMesh->CellsOnEdge; - auto &DcEdge = DefMesh->DcEdge; - auto &EdgeMask = DefMesh->EdgeMask; - parallelFor({DefMesh->NEdgesAll}, KOKKOS_LAMBDA(int e) { - CellsOnEdge(e, 0)= 0; - CellsOnEdge(e, 1)= 1; - //DcEdge(e) = 100.0_Real; - //for (int k = 0; k < NVertLayers; ++k) { - // EdgeMask(e, k) = 1.0_Real; - //} - }); - - // Fetch reference desnity from Config - Real Density0; - Error ErrorCode; - Config TendConfig("Tendencies"); - ErrorCode.reset(); - ErrorCode += Options->get(TendConfig); - CHECK_ERROR_ABORT(ErrorCode, "VertCoord: Tendencies group not found in Config"); - ErrorCode += TendConfig.get("Density0", Density0); - CHECK_ERROR_ABORT(ErrorCode, "VertCoord: Density0 not found in TendConfig"); - - I4 TimeLevel = 0; + I4 NVertLayers = 60; + Real dC = 30000.0_Real; // create arrays: Tend on edges, Pressure/Geopotential/SpecVol on cells Array2DReal Tend("Tend", DefMesh->NEdgesSize, NVertLayers); Array2DReal SpecVolOld("SpecVolOld", DefMesh->NCellsSize, NVertLayers); Array2DReal PressureMidOld("PressureMidOld", DefMesh->NCellsSize, NVertLayers); - Array2DReal LayerThick; - DefState->getLayerThickness(LayerThick, TimeLevel); Array1DReal SurfacePressure("SurfacePressure", DefMesh->NCellsSize); - Array2DReal Temp; - Array2DReal Salinity; - Err = Tracers::getByName(Temp, TimeLevel, "Temperature"); - Err = Tracers::getByName(Salinity, TimeLevel, "Salinity"); - - - - // set Z interface and mid-point locations - Real ZBottom = -1000.0_Real; - Real dZ = 2.0_Real * (-ZBottom / NVertLayers); - auto &BottomDepth = DefVCoord->BottomDepth; - auto &ZInterface = DefVCoord->ZInterface; - auto &ZMid = DefVCoord->ZMid; - Real tilt_factor = 0.495_Real; - parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { - ZInterface(i, NVertLayers) = ZBottom; - SurfacePressure(i) = 0.0_Real; - BottomDepth(i) = 0.0_Real; - for (int k = NVertLayers - 1; k >= 0; --k) { - Real x = (k + i) % 2; - Real dz = ( 2.0_Real * tilt_factor - 1.0_Real ) * x * dZ + (1.0_Real - tilt_factor) * dZ; // staggered layer thickness - ZInterface(i, k) = ZInterface(i, k + 1) + dz; - LayerThick(i, k) = ZInterface(i, k) - ZInterface(i, k + 1); - ZMid(i, k) = 0.5_Real * (ZInterface(i, k) + ZInterface(i, k + 1)); - BottomDepth(i) += dz; - } - }); - LOG_INFO("NVertLayers = {}", NVertLayers); - for (int i = 0; i < 2; ++i) { - for (int k = 0; k <= NVertLayers; ++k) { - LOG_INFO("ZInterface({}, {}) = {}", i, k, ZInterface(i, k)); - } - } - LOG_INFO("NVertLayers = {}", NVertLayers); - for (int i = 0; i < 2; ++i) { - for (int k = 0; k <= NVertLayers; ++k) { - LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThick(i, k)); + for (int refinement = 0; refinement < 3; ++refinement) { + + LOG_INFO("PGradTest: Starting refinement level {}", refinement); + VCoord->NVertLayers = NVertLayers; + VCoord->NVertLayersP1 = NVertLayers + 1; + + auto &MinLayerCell = VCoord->MinLayerCell; + auto &MaxLayerCell = VCoord->MaxLayerCell; + parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { + MinLayerCell(i) = 0; + MaxLayerCell(i) = NVertLayers - 1; + }); + + auto &CellsOnEdge = DefMesh->CellsOnEdge; + auto &DcEdge = DefMesh->DcEdge; + auto &EdgeMask = DefMesh->EdgeMask; + parallelFor({DefMesh->NEdgesAll}, KOKKOS_LAMBDA(int e) { + CellsOnEdge(e, 0)= 0; + CellsOnEdge(e, 1)= 1; + DcEdge(e) = dC; + //for (int k = 0; k < NVertLayers; ++k) { + // EdgeMask(e, k) = 1.0_Real; + //} + }); + + // Fetch reference desnity from Config + Real Density0; + Error ErrorCode; + Config TendConfig("Tendencies"); + ErrorCode.reset(); + ErrorCode += Options->get(TendConfig); + CHECK_ERROR_ABORT(ErrorCode, "VertCoord: Tendencies group not found in Config"); + ErrorCode += TendConfig.get("Density0", Density0); + CHECK_ERROR_ABORT(ErrorCode, "VertCoord: Density0 not found in TendConfig"); + + I4 TimeLevel = 0; + + // get state and tracer arrays + Array2DReal LayerThick; + DefState->getLayerThickness(LayerThick, TimeLevel); + Array2DReal Temp; + Array2DReal Salinity; + Err = Tracers::getByName(Temp, TimeLevel, "Temperature"); + Err = Tracers::getByName(Salinity, TimeLevel, "Salinity"); + + + + // set Z interface and mid-point locations + Real ZBottom = -1000.0_Real; + Real dZ = 2.0_Real * (-ZBottom / NVertLayers); + auto &BottomDepth = VCoord->BottomDepth; + auto &ZInterface = VCoord->ZInterface; + auto &ZMid = VCoord->ZMid; + Real tilt_factor = 0.495_Real; + parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { + ZInterface(i, NVertLayers) = ZBottom; + SurfacePressure(i) = 0.0_Real; + BottomDepth(i) = 0.0_Real; + for (int k = NVertLayers - 1; k >= 0; --k) { + Real x = (k + i) % 2; + Real dz = ( 2.0_Real * tilt_factor - 1.0_Real ) * x * dZ + (1.0_Real - tilt_factor) * dZ; // staggered layer thickness + ZInterface(i, k) = ZInterface(i, k + 1) + dz; + LayerThick(i, k) = ZInterface(i, k) - ZInterface(i, k + 1); + ZMid(i, k) = 0.5_Real * (ZInterface(i, k) + ZInterface(i, k + 1)); + BottomDepth(i) += dz; + } + }); + + LOG_INFO("NVertLayers = {}", NVertLayers); + LOG_INFO("dC = {}", dC); + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k <= NVertLayers; ++k) { + // LOG_INFO("ZInterface({}, {}) = {}", i, k, ZInterface(i, k)); + // } + //} + //LOG_INFO("NVertLayers = {}", NVertLayers); + for (int i = 0; i < 2; ++i) { + //for (int k = 0; k <= NVertLayers; ++k) { + for (int k = 0; k < 2; ++k) { + LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThick(i, k)); + } } - } - //LOG_INFO("NVertLayers = {}", NVertLayers); - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k <= NVertLayers; ++k) { - // LOG_INFO("ZMid({}, {}) = {}", i, k, ZMid(i, k)); - // } - //} - - // set simple temperature and salinity profiles - auto &SpecVol = DefEos->SpecVol; - parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { - Real T0 = 30.0; - Real TB = 1.0; - Real S0 = 30.0; - Real SB = 40.0; - - - Real phi0 = (ZMid(i, k) - ZBottom) / (-ZBottom); - Real phiB = 1.0_Real - phi0; - - Temp(i, k) = T0 * phi0 + TB * phiB; - Salinity(i, k) = S0 * phi0 + SB * phiB; - SpecVol(i, k) = 1.0_Real / Density0; - SpecVolOld(i, k) = SpecVol(i, k); - }); - - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("Temp({}, {}) = {}", i, k, Temp(i, k)); - // } - //} - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("Salinity({}, {}) = {}", i, k, Salinity(i, k)); - // } - //} - - // iterate to converge SpecVol - auto &PressureMid = DefVCoord->PressureMid; - DefVCoord->computePressure(LayerThick, SurfacePressure); - deepCopy(PressureMidOld, PressureMid); - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("PressureMid({}, {}) = {}, PressureMidOld({}, {}) = {}", i, k, PressureMid(i, k), i, k, PressureMidOld(i, k)); - // } - //} - for (int iteration = 0; iteration < 15; ++iteration) { - - // compute specific volume from EOS - DefVCoord->computePressure(LayerThick, SurfacePressure); - DefEos->computeSpecVol(Temp, Salinity, PressureMid); - - // compute psuedo thickness from specific volume + //LOG_INFO("NVertLayers = {}", NVertLayers); + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k <= NVertLayers; ++k) { + // LOG_INFO("ZMid({}, {}) = {}", i, k, ZMid(i, k)); + // } + //} + + // set simple temperature and salinity profiles + auto &SpecVol = DefEos->SpecVol; parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { - LayerThick(i, k) = 1.0_Real / (SpecVol(i, k) * Density0) * (ZInterface(i, k) - ZInterface(i, k + 1)); + Real T0 = 30.0; + Real TB = 5.0; + Real S0 = 30.0; + Real SB = 40.0; + + + Real phi0 = (ZMid(i, k) - ZBottom) / (-ZBottom); + Real phiB = 1.0_Real - phi0; + + Temp(i, k) = T0 * phi0 + TB * phiB; + Salinity(i, k) = S0 * phi0 + SB * phiB; + SpecVol(i, k) = 1.0_Real / Density0; + SpecVolOld(i, k) = SpecVol(i, k); }); - // compute difference from previous iteration - Real max_value = 0.0_Real; - parallelReduce({DefMesh->NCellsAll, NVertLayers}, - KOKKOS_LAMBDA(int i, int k, Real &max) { - Real diff = Kokkos::abs(SpecVol(i, k) - SpecVolOld(i, k)); - if (diff > max) max = diff; - }, Kokkos::Max(max_value) ); + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("Temp({}, {}) = {}", i, k, Temp(i, k)); + // } + //} + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("Salinity({}, {}) = {}", i, k, Salinity(i, k)); + // } + //} + + // iterate to converge SpecVol + auto &PressureMid = VCoord->PressureMid; + VCoord->computePressure(LayerThick, SurfacePressure); + deepCopy(PressureMidOld, PressureMid); + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("PressureMid({}, {}) = {}, PressureMidOld({}, {}) = {}", i, k, PressureMid(i, k), i, k, PressureMidOld(i, k)); + // } + //} + for (int iteration = 0; iteration < 15; ++iteration) { + + // compute specific volume from EOS + VCoord->computePressure(LayerThick, SurfacePressure); + DefEos->computeSpecVol(Temp, Salinity, PressureMid); - // check convergence - if (max_value < 1e-12_Real) { - LOG_INFO("converged: max diff = {}", max_value); - break; - } else { - LOG_INFO("max diff = {}", max_value); + // compute psuedo thickness from specific volume parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { - SpecVolOld(i, k) = SpecVol(i, k); + LayerThick(i, k) = 1.0_Real / (SpecVol(i, k) * Density0) * (ZInterface(i, k) - ZInterface(i, k + 1)); }); + + // compute difference from previous iteration + Real max_value = 0.0_Real; + parallelReduce({DefMesh->NCellsAll, NVertLayers}, + KOKKOS_LAMBDA(int i, int k, Real &max) { + Real diff = Kokkos::abs(SpecVol(i, k) - SpecVolOld(i, k)); + if (diff > max) max = diff; + }, Kokkos::Max(max_value) ); + + // check convergence + if (max_value < 1e-12_Real) { + LOG_INFO("converged: max diff = {}", max_value); + break; + } else { + //LOG_INFO("max diff = {}", max_value); + parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + SpecVolOld(i, k) = SpecVol(i, k); + }); + } + } + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("SpecVol({}, {}) = {}", i, k, SpecVol(i, k)); + // } + //} - } - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("SpecVol({}, {}) = {}", i, k, SpecVol(i, k)); - // } - //} - - // compute geopotential - //DefVCoord->computeZHeight(LayerThick, SpecVol); - Array1DReal SelfAttractionLoading("SelfAttractionLoading", DefMesh->NCellsAll); - Array1DReal TidalPotential("TidalPotential", DefMesh->NCellsAll); - deepCopy(TidalPotential, 0.0_Real); - deepCopy(SelfAttractionLoading, 0.0_Real); - DefVCoord->computeGeopotential(TidalPotential, SelfAttractionLoading); - - // create PressureGrad instance - PressureGrad *DefPGrad = PressureGrad::getDefault(); - if (!DefPGrad) { - LOG_INFO("PGrad: default instance not created by init"); - } - auto PGradTest = PressureGrad::create("Test", DefMesh, DefVCoord, Options); - - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // Tend(i, k) = 0.0_Real; - // LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); - // } - //} - - // compute pressure gradient - PGradTest->computePressureGrad(Tend, DefState, DefVCoord, DefEos, TimeLevel); - for (int i = 0; i < 2; ++i) { - for (int k = 0; k < NVertLayers; ++k) { - LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); + // compute geopotential + //VCoord->computeZHeight(LayerThick, SpecVol); + Array1DReal SelfAttractionLoading("SelfAttractionLoading", DefMesh->NCellsAll); + Array1DReal TidalPotential("TidalPotential", DefMesh->NCellsAll); + deepCopy(TidalPotential, 0.0_Real); + deepCopy(SelfAttractionLoading, 0.0_Real); + VCoord->computeGeopotential(TidalPotential, SelfAttractionLoading); + + // create PressureGrad instance + PressureGrad *DefPGrad = PressureGrad::getDefault(); + if (!DefPGrad) { + LOG_INFO("PGrad: default instance not created by init"); } - } + //auto PGradTest = PressureGrad::create("Test", DefMesh, VCoord, Options); + + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // Tend(i, k) = 0.0_Real; + // LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); + // } + //} + + // compute pressure gradient + //PGradTest->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); + DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); + //for (int i = 0; i < 2; ++i) { + // for (int k = 0; k < NVertLayers; ++k) { + // LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); + // } + //} + Real max_value = 0.0_Real; + parallelReduce({DefMesh->NEdgesAll, NVertLayers}, + KOKKOS_LAMBDA(int i, int k, Real &max) { + Real val = Kokkos::abs(Tend(i, k)); + if (val > max) max = val; + }, Kokkos::Max(max_value) ); + Real sum_value = 0.0_Real; + parallelReduce({DefMesh->NEdgesAll, NVertLayers}, + KOKKOS_LAMBDA(int i, int k, Real &lsum) { + lsum += Tend(i,k) * Tend(i, k); + }, Kokkos::Sum(sum_value) ); + LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, std::sqrt(sum_value) / (DefMesh->NCellsAll * NVertLayers)); + + dC = dC * 2.0_Real; + NVertLayers = NVertLayers / 2; + } // refinement loop // cleanup PressureGrad::clear(); From 0127a95085f4c94c4e54994a81de1b476143c334 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 25 Nov 2025 14:38:38 -0800 Subject: [PATCH 103/152] Update interface product averaging --- components/omega/src/ocn/PGrad.cpp | 58 +++++++++++++++++++++++-- components/omega/src/ocn/PGrad.h | 35 ++++++--------- components/omega/test/ocn/PGradTest.cpp | 22 +++++----- 3 files changed, 81 insertions(+), 34 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index effdccca8a72..5fe768e5214b 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -74,15 +74,18 @@ PressureGrad::PressureGrad( const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate Config *Options) ///< [in] Configuration options - : CellsOnEdge(Mesh->CellsOnEdge), DvEdge(Mesh->DvEdge), + : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), EdgeSignOnCell(Mesh->EdgeSignOnCell), CenteredPGrad(Mesh), HighOrderPGrad(Mesh) { // store mesh sizes NEdgesAll = Mesh->NEdgesAll; NVertLayers = VCoord->NVertLayers; + NVertLayersP1 = NVertLayers + 1; NChunks = NVertLayers / VecLength; + InterfaceProduct = Array2DReal("InterfaceProduct", NEdgesAll, NVertLayersP1); + // Read config options for PressureGrad type // and enable the appropriate functor Config PGradConfig("PressureGrad"); @@ -154,6 +157,7 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, OMEGA_SCOPE(LocCenteredPGrad, CenteredPGrad); OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); + OMEGA_SCOPE(LocInterfaceProduct, InterfaceProduct); const Array2DReal &PressureMid = VCoord->PressureMid; const Array2DReal &PressureInterface = VCoord->PressureInterface; @@ -165,12 +169,16 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, State->getLayerThickness(LayerThick, TimeLevel); if (PressureGradChoice == PressureGradType::Centered) { + + computeInterfaceProduct(PressureMid, SpecVol, + LayerThick, ZInterface); + parallelFor( "pgrad-centered", {NEdgesAll, NChunks}, KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, - PressureInterface, Geopotential, LayerThick, - ZInterface, SpecVol, SpecVolInterface); + Geopotential, LayerThick, + LocInterfaceProduct, SpecVol); }); } else { parallelFor( @@ -182,6 +190,50 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, } } // end compute pressure gradient +//------------------------------------------------------------------------------ +// Compute interface product needed for centered pressure gradient +void PressureGrad::computeInterfaceProduct( + const Array2DReal &PressureMid, + const Array2DReal &SpecVol, + const Array2DReal &LayerThick, + const Array2DReal &ZInterface) { + + + OMEGA_SCOPE(LocCellsOnEdge, CellsOnEdge); + OMEGA_SCOPE(LocDcEdge, DcEdge); + OMEGA_SCOPE(LocInterfaceProduct, InterfaceProduct); + parallelFor( + "compute-interface-product", {NEdgesAll, NVertLayersP1}, + KOKKOS_LAMBDA(I4 IEdge, I4 K) { + const I4 ICell0 = LocCellsOnEdge(IEdge, 0); + const I4 ICell1 = LocCellsOnEdge(IEdge, 1); + + if (K == 0) { + LocInterfaceProduct(IEdge, K) = + PressureMid(ICell1, K) * SpecVol(ICell1, K) * LayerThick(ICell1, K) + + PressureMid(ICell0, K) * SpecVol(ICell0, K) * LayerThick(ICell0, K); + LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K)); + + } else if (K == NVertLayers) { + LocInterfaceProduct(IEdge, K) = + PressureMid(ICell1, K-1) * SpecVol(ICell1, K-1) * LayerThick(ICell1, K-1) + + PressureMid(ICell0, K-1) * SpecVol(ICell0, K-1) * LayerThick(ICell0, K-1); + LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K-1) + LayerThick(ICell0, K-1)); + + } else { + LocInterfaceProduct(IEdge, K) = + PressureMid(ICell1, K) * SpecVol(ICell1, K) * LayerThick(ICell1, K) + + PressureMid(ICell0, K) * SpecVol(ICell0, K) * LayerThick(ICell0, K) + + PressureMid(ICell1, K-1) * SpecVol(ICell1, K-1) * LayerThick(ICell1, K-1) + + PressureMid(ICell0, K-1) * SpecVol(ICell0, K-1) * LayerThick(ICell0, K-1); + LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K) + + LayerThick(ICell1, K-1) + LayerThick(ICell0, K-1));} + + LocInterfaceProduct(IEdge, K) *= (ZInterface(ICell1, K) - ZInterface(ICell0, K)) / LocDcEdge(IEdge); + }); + +} // end compute interface product + //------------------------------------------------------------------------------ // Constructor for centered pressure gradient functor PressureGradCentered::PressureGradCentered(const HorzMesh *Mesh) diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index e104db110321..f17813f3fc4e 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -32,12 +32,10 @@ class PressureGradCentered { // vertical chunk. This appends results into the Tend array (in-place). KOKKOS_FUNCTION void operator()(const Array2DReal &Tend, I4 IEdge, I4 KChunk, const Array2DReal &PressureMid, - const Array2DReal &PressureInterface, const Array2DReal &Geopotential, const Array2DReal &LayerThick, - const Array2DReal &ZInterface, - const Array2DReal &SpecVol, - const Array2DReal &SpecVolInterface) const { + const Array2DReal &InterfaceProduct, + const Array2DReal &SpecVol) const { const I4 KStart = KChunk * VecLength; const I4 ICell0 = CellsOnEdge(IEdge, 0); @@ -46,17 +44,6 @@ class PressureGradCentered { for (int KVec = 0; KVec < VecLength; ++KVec) { const I4 K = KStart + KVec; - - // Average quantities to edge - const Real PAlphaEdgeK = - 0.5_Real * - (PressureInterface(ICell1, K) * SpecVolInterface(ICell1, K) + - PressureInterface(ICell0, K) * SpecVolInterface(ICell0, K)); - const Real PAlphaEdgeKP1 = - 0.5_Real * (PressureInterface(ICell1, K + 1) * - SpecVolInterface(ICell1, K + 1) + - PressureInterface(ICell0, K + 1) * - SpecVolInterface(ICell0, K + 1)); const Real InvLayerThickEdge = 2.0_Real / (LayerThick(ICell1, K) + LayerThick(ICell0, K)); @@ -67,11 +54,8 @@ class PressureGradCentered { LayerThick(ICell0, K) * SpecVol(ICell0, K) * PressureMid(ICell0, K)) * InvDcEdge * InvLayerThickEdge; - Real InterfaceTerm = - (PAlphaEdgeK * (ZInterface(ICell1, K) - ZInterface(ICell0, K)) - - PAlphaEdgeKP1 * - (ZInterface(ICell1, K + 1) - ZInterface(ICell0, K + 1))) * - InvDcEdge * InvLayerThickEdge; + Real InterfaceTerm = (InterfaceProduct(IEdge, K) - InterfaceProduct(IEdge, K+1)) * + InvLayerThickEdge; Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GeoTerm - PresTerm + InterfaceTerm); @@ -133,11 +117,19 @@ class PressureGrad { // Destructor ~PressureGrad(); + Array2DReal InterfaceProduct; + /// Compute pressure gradient tendencies and add into Tend array void computePressureGrad(Array2DReal &Tend, const OceanState *State, const VertCoord *VCoord, const Eos *EqState, const int TimeLevel); + void computeInterfaceProduct(const Array2DReal &PressureMid, + const Array2DReal &SpecVol, + const Array2DReal &LayerThick, + const Array2DReal &ZInterface); + + /// Create a new pressure gradient object and add to map static PressureGrad *create(const std::string &Name, const HorzMesh *Mesh, const VertCoord *VCoord, Config *Options); @@ -156,10 +148,11 @@ class PressureGrad { I4 NEdgesAll = 0; I4 NChunks = 0; I4 NVertLayers = 0; + I4 NVertLayersP1 = 0; // Data required for computation (stored copies of mesh/VCoord arrays) Array2DI4 CellsOnEdge; ///< cells surrounding each edge - Array1DReal DvEdge; ///< distance between cell centers across edge + Array1DReal DcEdge; ///< distance between cell centers across edge Array2DReal EdgeSignOnCell; ///< orientation of edge relative to cell // Instances of functors diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index ac66e6ee9b3f..5c9e9e84827e 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -111,15 +111,15 @@ int main(int argc, char *argv[]) { Eos *DefEos = Eos::getInstance(); Config *Options = Config::getOmegaConfig(); - I4 NVertLayers = 60; - Real dC = 30000.0_Real; // create arrays: Tend on edges, Pressure/Geopotential/SpecVol on cells - Array2DReal Tend("Tend", DefMesh->NEdgesSize, NVertLayers); - Array2DReal SpecVolOld("SpecVolOld", DefMesh->NCellsSize, NVertLayers); - Array2DReal PressureMidOld("PressureMidOld", DefMesh->NCellsSize, NVertLayers); + Array2DReal Tend("Tend", DefMesh->NEdgesSize, VCoord->NVertLayers); + Array2DReal SpecVolOld("SpecVolOld", DefMesh->NCellsSize, VCoord->NVertLayers); + Array2DReal PressureMidOld("PressureMidOld", DefMesh->NCellsSize, VCoord->NVertLayers); Array1DReal SurfacePressure("SurfacePressure", DefMesh->NCellsSize); + I4 NVertLayers = 60; + Real dC = 30000.0_Real; for (int refinement = 0; refinement < 3; ++refinement) { LOG_INFO("PGradTest: Starting refinement level {}", refinement); @@ -174,6 +174,7 @@ int main(int argc, char *argv[]) { auto &ZInterface = VCoord->ZInterface; auto &ZMid = VCoord->ZMid; Real tilt_factor = 0.495_Real; + //Real tilt_factor = 0.45_Real; parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { ZInterface(i, NVertLayers) = ZBottom; SurfacePressure(i) = 0.0_Real; @@ -308,6 +309,7 @@ int main(int argc, char *argv[]) { // compute pressure gradient //PGradTest->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); + deepCopy(Tend, 0.0_Real); DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); //for (int i = 0; i < 2; ++i) { // for (int k = 0; k < NVertLayers; ++k) { @@ -315,17 +317,17 @@ int main(int argc, char *argv[]) { // } //} Real max_value = 0.0_Real; - parallelReduce({DefMesh->NEdgesAll, NVertLayers}, + parallelReduce({DefMesh->NEdgesAll, NVertLayers - 1}, KOKKOS_LAMBDA(int i, int k, Real &max) { - Real val = Kokkos::abs(Tend(i, k)); + Real val = Kokkos::abs(Tend(i + 1, k)); if (val > max) max = val; }, Kokkos::Max(max_value) ); Real sum_value = 0.0_Real; - parallelReduce({DefMesh->NEdgesAll, NVertLayers}, + parallelReduce({DefMesh->NEdgesAll, NVertLayers - 1}, KOKKOS_LAMBDA(int i, int k, Real &lsum) { - lsum += Tend(i,k) * Tend(i, k); + lsum += Tend(i + 1, k) * Tend(i + 1, k); }, Kokkos::Sum(sum_value) ); - LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, std::sqrt(sum_value) / (DefMesh->NCellsAll * NVertLayers)); + LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, std::sqrt(sum_value) / (DefMesh->NEdgesAll * (NVertLayers - 1))); dC = dC * 2.0_Real; NVertLayers = NVertLayers / 2; From 6f1b9df3d5504c6959801bb1e70001752d340966 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 1 Dec 2025 14:07:36 -0800 Subject: [PATCH 104/152] Remove SpecVolInterface calculation --- components/omega/src/ocn/Eos.cpp | 91 ++++++------------------------ components/omega/src/ocn/Eos.h | 6 -- components/omega/src/ocn/PGrad.cpp | 1 - 3 files changed, 18 insertions(+), 80 deletions(-) diff --git a/components/omega/src/ocn/Eos.cpp b/components/omega/src/ocn/Eos.cpp index 5926a77318bf..1b49fe400eea 100644 --- a/components/omega/src/ocn/Eos.cpp +++ b/components/omega/src/ocn/Eos.cpp @@ -45,6 +45,24 @@ Eos::Eos(const std::string &Name, ///< [in] Name for eos object BruntVaisalaFreqSq = Array2DReal("BruntVaisalaFreqSq", Mesh->NCellsAll, VCoord->NVertLayers); + defineFields(); +} + +/// Destructor for Eos +Eos::~Eos() {} + +/// Instance management +Eos *Eos::Instance = nullptr; + +/// Get instance of Eos +Eos *Eos::getInstance() { return Instance; } + +/// Destroy instance of Eos +void Eos::destroyInstance() { + delete Instance; + Instance = nullptr; +} + /// Initializes the Eos (Equation of State) class and its options. /// it ASSUMES that HorzMesh was initialized and initializes the Eos class by /// using the default mesh, reading the config file, and setting parameters @@ -150,79 +168,6 @@ void Eos::computeSpecVol(const Array2DReal &ConservTemp, } } -/// Compute specific volume for all layer interfaces(no displacement) -void Eos::computeSpecVolInterface(const Array2DReal &ConservTemp, - const Array2DReal &AbsSalinity, - const Array2DReal &PressureInterface) { - OMEGA_SCOPE(LocSpecVolInterface, - SpecVolInterface); /// Create a local view for computation - OMEGA_SCOPE(LocComputeSpecVolLinear, - ComputeSpecVolLinear); /// Local view for linear EOS computation - OMEGA_SCOPE(LocComputeSpecVolTeos10, - ComputeSpecVolTeos10); /// Local view for TEOS-10 computation - deepCopy(LocSpecVolInterface, - 0); /// Initialize local specific volume to zero - - I4 KDisp = 0; /// No displacement in this case - - // Array2DReal ConservTempInterface("ConservTempInterface", NCellsAll, - // NVertLayers + 1); - // Array2DReal AbsSalinityInterface("AbsSalinityInterface", NCellsAll, - // NVertLayers + 1); - - // Compute interface values by averaging adjacent layer values - parallelFor( - "compute-interface-values", {NCellsAll, NVertLayers + 1}, - KOKKOS_LAMBDA(I4 ICell, I4 KInterface) { - if (KInterface == 0) { - // ConservTempInterface(ICell, KInterface) = - // ConservTemp(ICell, KInterface); - // AbsSalinityInterface(ICell, KInterface) = - // AbsSalinity(ICell, KInterface); - SpecVolInterface(ICell, KInterface) = SpecVol(ICell, KInterface); - } else if (KInterface == NVertLayers + 1) { - // ConservTempInterface(ICell, KInterface) = - // ConservTemp(ICell, KInterface - 1); - // AbsSalinityInterface(ICell, KInterface) = - // AbsSalinity(ICell, KInterface - 1); - SpecVolInterface(ICell, KInterface) = - SpecVol(ICell, KInterface - 1); - } else { - // ConservTempInterface(ICell, KInterface) = - // 0.5_Real * (ConservTemp(ICell, KInterface - 1) + - // ConservTemp(ICell, KInterface)); - // AbsSalinityInterface(ICell, KInterface) = - // 0.5_Real * (AbsSalinity(ICell, KInterface - 1) + - // AbsSalinity(ICell, KInterface)); - SpecVolInterface(ICell, KInterface) = - 0.5_Real * - (SpecVol(ICell, KInterface - 1) + SpecVol(ICell, KInterface)); - } - }); - - // int NChunksP1 = (NVertLayers + 1) / VecLength; - - ///// Dispatch to the correct EOS calculation - // if (EosChoice == EosType::LinearEos) { - // parallelFor( - // "eos-linear", {NCellsAll, NChunksP1}, - // KOKKOS_LAMBDA(I4 ICell, I4 KChunk) { - // LocComputeSpecVolLinear(LocSpecVolInterface, ICell, KChunk, - // ConservTempInterface, - // AbsSalinityInterface); - // }); - // } else if (EosChoice == EosType::Teos10Eos) { - // parallelFor( - // "eos-teos10", {NCellsAll, NChunksP1}, - // KOKKOS_LAMBDA(I4 ICell, I4 KChunk) { - // LocComputeSpecVolTeos10(LocSpecVolInterface, ICell, KChunk, - // ConservTempInterface, - // AbsSalinityInterface, PressureInterface, - // KDisp); - // }); - // } -} - /// Compute displaced specific volume (for vertical displacement) void Eos::computeSpecVolDisp(const Array2DReal &ConservTemp, const Array2DReal &AbsSalinity, diff --git a/components/omega/src/ocn/Eos.h b/components/omega/src/ocn/Eos.h index 0807b8a1b484..4f10d75c8fec 100644 --- a/components/omega/src/ocn/Eos.h +++ b/components/omega/src/ocn/Eos.h @@ -542,7 +542,6 @@ class Eos { EosType EosChoice; ///< Current EOS type in use Array2DReal SpecVol; ///< Specific volume field at level centers - Array2DReal SpecVolInterface; ///< Specific volume field at interfaces Array2DReal SpecVolDisplaced; ///< Displaced specific volume field Array2DReal BruntVaisalaFreqSq; ///< Squared Brunt-Vaisala frequency field @@ -569,11 +568,6 @@ class Eos { const Array2DReal &AbsSalinity, const Array2DReal &Pressure, const Array2DReal &SpecVol); - /// Compute specific volume at interfaces for all cells/layers+1 - void computeSpecVolInterface(const Array2DReal &ConservTemp, - const Array2DReal &AbsSalinity, - const Array2DReal &PressureInterface); - /// Initialize EOS from config and mesh static void init(); diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 5fe768e5214b..ddad9d5b878f 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -163,7 +163,6 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, const Array2DReal &PressureInterface = VCoord->PressureInterface; const Array2DReal &Geopotential = VCoord->GeopotentialMid; const Array2DReal &SpecVol = EqState->SpecVol; - const Array2DReal &SpecVolInterface = EqState->SpecVolInterface; const Array2DReal &ZInterface = VCoord->ZInterface; Array2DReal LayerThick; State->getLayerThickness(LayerThick, TimeLevel); From 3dcad106aa01ad6f6e04ee26cd69facd0bafbbf3 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 1 Dec 2025 13:18:20 -0800 Subject: [PATCH 105/152] Increase number of refinements in convergence test loop --- components/omega/test/ocn/PGradTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 5c9e9e84827e..c59a50af73b5 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -120,7 +120,7 @@ int main(int argc, char *argv[]) { I4 NVertLayers = 60; Real dC = 30000.0_Real; - for (int refinement = 0; refinement < 3; ++refinement) { + for (int refinement = 0; refinement < 4; ++refinement) { LOG_INFO("PGradTest: Starting refinement level {}", refinement); VCoord->NVertLayers = NVertLayers; From 55c69de4f3757491ba7a44a89a1c44424b5ced96 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 1 Dec 2025 13:19:03 -0800 Subject: [PATCH 106/152] Remove debug comments --- components/omega/test/ocn/PGradTest.cpp | 54 ------------------------- 1 file changed, 54 deletions(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index c59a50af73b5..da15fd2ba9f8 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -104,7 +104,6 @@ int main(int argc, char *argv[]) { MachEnv *DefEnv = MachEnv::getDefault(); HorzMesh *DefMesh = HorzMesh::getDefault(); - //VertCoord *DefVCoord = VertCoord::getDefault(); VertCoord *VCoord = VertCoord::getDefault(); OceanState *DefState = OceanState::getDefault(); Decomp *DefDecomp = Decomp::getDefault(); @@ -140,9 +139,6 @@ int main(int argc, char *argv[]) { CellsOnEdge(e, 0)= 0; CellsOnEdge(e, 1)= 1; DcEdge(e) = dC; - //for (int k = 0; k < NVertLayers; ++k) { - // EdgeMask(e, k) = 1.0_Real; - //} }); // Fetch reference desnity from Config @@ -165,8 +161,6 @@ int main(int argc, char *argv[]) { Err = Tracers::getByName(Temp, TimeLevel, "Temperature"); Err = Tracers::getByName(Salinity, TimeLevel, "Salinity"); - - // set Z interface and mid-point locations Real ZBottom = -1000.0_Real; Real dZ = 2.0_Real * (-ZBottom / NVertLayers); @@ -191,24 +185,12 @@ int main(int argc, char *argv[]) { LOG_INFO("NVertLayers = {}", NVertLayers); LOG_INFO("dC = {}", dC); - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k <= NVertLayers; ++k) { - // LOG_INFO("ZInterface({}, {}) = {}", i, k, ZInterface(i, k)); - // } - //} - //LOG_INFO("NVertLayers = {}", NVertLayers); for (int i = 0; i < 2; ++i) { //for (int k = 0; k <= NVertLayers; ++k) { for (int k = 0; k < 2; ++k) { LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThick(i, k)); } } - //LOG_INFO("NVertLayers = {}", NVertLayers); - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k <= NVertLayers; ++k) { - // LOG_INFO("ZMid({}, {}) = {}", i, k, ZMid(i, k)); - // } - //} // set simple temperature and salinity profiles auto &SpecVol = DefEos->SpecVol; @@ -228,26 +210,10 @@ int main(int argc, char *argv[]) { SpecVolOld(i, k) = SpecVol(i, k); }); - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("Temp({}, {}) = {}", i, k, Temp(i, k)); - // } - //} - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("Salinity({}, {}) = {}", i, k, Salinity(i, k)); - // } - //} - // iterate to converge SpecVol auto &PressureMid = VCoord->PressureMid; VCoord->computePressure(LayerThick, SurfacePressure); deepCopy(PressureMidOld, PressureMid); - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("PressureMid({}, {}) = {}, PressureMidOld({}, {}) = {}", i, k, PressureMid(i, k), i, k, PressureMidOld(i, k)); - // } - //} for (int iteration = 0; iteration < 15; ++iteration) { // compute specific volume from EOS @@ -272,18 +238,12 @@ int main(int argc, char *argv[]) { LOG_INFO("converged: max diff = {}", max_value); break; } else { - //LOG_INFO("max diff = {}", max_value); parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { SpecVolOld(i, k) = SpecVol(i, k); }); } } - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("SpecVol({}, {}) = {}", i, k, SpecVol(i, k)); - // } - //} // compute geopotential //VCoord->computeZHeight(LayerThick, SpecVol); @@ -298,24 +258,10 @@ int main(int argc, char *argv[]) { if (!DefPGrad) { LOG_INFO("PGrad: default instance not created by init"); } - //auto PGradTest = PressureGrad::create("Test", DefMesh, VCoord, Options); - - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // Tend(i, k) = 0.0_Real; - // LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); - // } - //} // compute pressure gradient - //PGradTest->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); deepCopy(Tend, 0.0_Real); DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); - //for (int i = 0; i < 2; ++i) { - // for (int k = 0; k < NVertLayers; ++k) { - // LOG_INFO("Tend({}, {}) = {}", i, k, Tend(i, k)); - // } - //} Real max_value = 0.0_Real; parallelReduce({DefMesh->NEdgesAll, NVertLayers - 1}, KOKKOS_LAMBDA(int i, int k, Real &max) { From 19698f2c0df8ecf97d2b264e9dadcc95c2431974 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 1 Dec 2025 13:47:38 -0800 Subject: [PATCH 107/152] Run PGrad test in serial --- components/omega/test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/omega/test/CMakeLists.txt b/components/omega/test/CMakeLists.txt index c2dceb09c5a1..301a04ede49c 100644 --- a/components/omega/test/CMakeLists.txt +++ b/components/omega/test/CMakeLists.txt @@ -354,7 +354,7 @@ add_omega_test( PGRAD_TEST testPGrad.exe ocn/PGradTest.cpp - "-n;8" + "-n;1" ) ################## From 96574767bf71d2b77fd310b3eb5ebb5876424035 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 2 Dec 2025 10:26:29 -0800 Subject: [PATCH 108/152] Fix error calculation to exclude top and bottom levels --- components/omega/test/ocn/PGradTest.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index da15fd2ba9f8..55b7d25bccfb 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -186,7 +186,6 @@ int main(int argc, char *argv[]) { LOG_INFO("NVertLayers = {}", NVertLayers); LOG_INFO("dC = {}", dC); for (int i = 0; i < 2; ++i) { - //for (int k = 0; k <= NVertLayers; ++k) { for (int k = 0; k < 2; ++k) { LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThick(i, k)); } @@ -263,17 +262,17 @@ int main(int argc, char *argv[]) { deepCopy(Tend, 0.0_Real); DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); Real max_value = 0.0_Real; - parallelReduce({DefMesh->NEdgesAll, NVertLayers - 1}, + parallelReduce({DefMesh->NEdgesAll, NVertLayers - 2}, KOKKOS_LAMBDA(int i, int k, Real &max) { Real val = Kokkos::abs(Tend(i + 1, k)); if (val > max) max = val; }, Kokkos::Max(max_value) ); Real sum_value = 0.0_Real; - parallelReduce({DefMesh->NEdgesAll, NVertLayers - 1}, + parallelReduce({DefMesh->NEdgesAll, NVertLayers - 2}, KOKKOS_LAMBDA(int i, int k, Real &lsum) { lsum += Tend(i + 1, k) * Tend(i + 1, k); }, Kokkos::Sum(sum_value) ); - LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, std::sqrt(sum_value) / (DefMesh->NEdgesAll * (NVertLayers - 1))); + LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, std::sqrt(sum_value) / (DefMesh->NEdgesAll * (NVertLayers - 2))); dC = dC * 2.0_Real; NVertLayers = NVertLayers / 2; From 7bebc868b55afbe7d7846aba36445e351b647c42 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 2 Dec 2025 10:32:41 -0800 Subject: [PATCH 109/152] Convert pressure to Pa in EosTest --- components/omega/test/ocn/EosTest.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/omega/test/ocn/EosTest.cpp b/components/omega/test/ocn/EosTest.cpp index 5202594abedb..1b507781e8e9 100644 --- a/components/omega/test/ocn/EosTest.cpp +++ b/components/omega/test/ocn/EosTest.cpp @@ -57,6 +57,7 @@ const Real P = 1000.0; // Pressure in dbar const I4 KDisp = 1; // Displate parcel to K=1 for TEOS-10 eos const Real RTol = 1e-10; // Relative tolerance for isApprox checks +double PaToDBar = 1e-4; // Conversion factor from Pa to dbar /// The initialization routine for Eos testing. It calls various /// init routines, including the creation of the default decomposition. @@ -119,7 +120,7 @@ void testEosLinear() { /// Use Kokkos::deep_copy to fill the entire view with the ref value deepCopy(SArray, Sa); deepCopy(TArray, Ct); - deepCopy(PArray, P); + deepCopy(PArray, P / PaToDBar); deepCopy(TestEos->SpecVol, 0.0); /// Compute specific volume @@ -189,7 +190,7 @@ void testEosLinearDisplaced() { /// Use Kokkos::deep_copy to fill the entire view with the ref value deepCopy(SArray, Sa); deepCopy(TArray, Ct); - deepCopy(PArray, P); + deepCopy(PArray, P / PaToDBar); deepCopy(TestEos->SpecVolDisplaced, 0.0); /// Compute displaced specific volume @@ -384,7 +385,7 @@ void testEosTeos10() { /// Use Kokkos::deep_copy to fill the entire view with the ref value deepCopy(SArray, Sa); deepCopy(TArray, Ct); - deepCopy(PArray, P); + deepCopy(PArray, P / PaToDBar); deepCopy(TestEos->SpecVol, 0.0); /// Compute specific volume @@ -454,7 +455,7 @@ void testEosTeos10Displaced() { /// Use Kokkos::deep_copy to fill the entire view with the ref value deepCopy(SArray, Sa); deepCopy(TArray, Ct); - deepCopy(PArray, P); + deepCopy(PArray, P / PaToDBar); deepCopy(TestEos->SpecVolDisplaced, 0.0); /// Compute displaced specific volume From 730be5bf6eb0683fd81c7c1f262099202cbaa9c3 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 2 Dec 2025 13:30:27 -0800 Subject: [PATCH 110/152] Switch to hierarchical parallelism --- components/omega/src/ocn/PGrad.cpp | 193 ++++++++++++++++++------ components/omega/src/ocn/PGrad.h | 28 +++- components/omega/test/ocn/PGradTest.cpp | 4 + 3 files changed, 175 insertions(+), 50 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index ddad9d5b878f..cfbe2b85b6b1 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -10,6 +10,7 @@ #include "Field.h" #include "HorzMesh.h" #include "VertCoord.h" +#include "OmegaKokkos.h" namespace OMEGA { @@ -75,8 +76,9 @@ PressureGrad::PressureGrad( const VertCoord *VCoord, ///< [in] Vertical coordinate Config *Options) ///< [in] Configuration options : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), - EdgeSignOnCell(Mesh->EdgeSignOnCell), CenteredPGrad(Mesh), - HighOrderPGrad(Mesh) { + EdgeSignOnCell(Mesh->EdgeSignOnCell), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), CenteredPGrad(Mesh, VCoord), + HighOrderPGrad(Mesh, VCoord) { // store mesh sizes NEdgesAll = Mesh->NEdgesAll; @@ -158,6 +160,8 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, OMEGA_SCOPE(LocCenteredPGrad, CenteredPGrad); OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); OMEGA_SCOPE(LocInterfaceProduct, InterfaceProduct); + OMEGA_SCOPE(LocMinLayerEdgeBot, MinLayerEdgeBot); + OMEGA_SCOPE(LocMaxLayerEdgeTop, MaxLayerEdgeTop); const Array2DReal &PressureMid = VCoord->PressureMid; const Array2DReal &PressureInterface = VCoord->PressureInterface; @@ -172,20 +176,51 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, computeInterfaceProduct(PressureMid, SpecVol, LayerThick, ZInterface); - parallelFor( - "pgrad-centered", {NEdgesAll, NChunks}, - KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { - LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, - Geopotential, LayerThick, - LocInterfaceProduct, SpecVol); + parallelForOuter( + "pgrad-centered", {NEdgesAll}, + KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { + const int KMin = LocMinLayerEdgeBot(IEdge); + const int KMax = LocMaxLayerEdgeTop(IEdge); + const int KRange = vertRange(KMin, KMax); + + parallelForInner( + Team, KRange, + INNER_LAMBDA(int KChunk) { + LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, + Geopotential, LayerThick, + LocInterfaceProduct, SpecVol); + }); }); + + //parallelFor( + // "pgrad-centered", {NEdgesAll, NChunks}, + // KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { + // LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, + // Geopotential, LayerThick, + // LocInterfaceProduct, SpecVol); + // }); } else { - parallelFor( - "pgrad-highorder", {NEdgesAll, NChunks}, - KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { - LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, Geopotential, - SpecVol); + parallelForOuter( + "pgrad-highorder", {NEdgesAll}, + KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { + const int KMin = MinLayerEdgeBot(IEdge); + const int KMax = MaxLayerEdgeTop(IEdge); + const int KRange = vertRange(KMin, KMax); + + parallelForInner( + Team, KRange, + INNER_LAMBDA(int KChunk) { + LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, + Geopotential, SpecVol); + }); }); + + //parallelFor( + // "pgrad-highorder", {NEdgesAll, NChunks}, + // KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { + // LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, Geopotential, + // SpecVol); + // }); } } // end compute pressure gradient @@ -201,48 +236,120 @@ void PressureGrad::computeInterfaceProduct( OMEGA_SCOPE(LocCellsOnEdge, CellsOnEdge); OMEGA_SCOPE(LocDcEdge, DcEdge); OMEGA_SCOPE(LocInterfaceProduct, InterfaceProduct); - parallelFor( - "compute-interface-product", {NEdgesAll, NVertLayersP1}, - KOKKOS_LAMBDA(I4 IEdge, I4 K) { + OMEGA_SCOPE(LocMinLayerEdgeBot, MinLayerEdgeBot); + OMEGA_SCOPE(LocMaxLayerEdgeTop, MaxLayerEdgeTop); + parallelForOuter( + "compute-interface-product", {NEdgesAll}, + KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { + const int KMin = LocMinLayerEdgeBot(IEdge); + const int KMax = LocMaxLayerEdgeTop(IEdge); + const I4 ICell0 = LocCellsOnEdge(IEdge, 0); const I4 ICell1 = LocCellsOnEdge(IEdge, 1); - if (K == 0) { - LocInterfaceProduct(IEdge, K) = - PressureMid(ICell1, K) * SpecVol(ICell1, K) * LayerThick(ICell1, K) + - PressureMid(ICell0, K) * SpecVol(ICell0, K) * LayerThick(ICell0, K); - LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K)); - - } else if (K == NVertLayers) { - LocInterfaceProduct(IEdge, K) = - PressureMid(ICell1, K-1) * SpecVol(ICell1, K-1) * LayerThick(ICell1, K-1) + - PressureMid(ICell0, K-1) * SpecVol(ICell0, K-1) * LayerThick(ICell0, K-1); - LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K-1) + LayerThick(ICell0, K-1)); - - } else { - LocInterfaceProduct(IEdge, K) = - PressureMid(ICell1, K) * SpecVol(ICell1, K) * LayerThick(ICell1, K) + - PressureMid(ICell0, K) * SpecVol(ICell0, K) * LayerThick(ICell0, K) + - PressureMid(ICell1, K-1) * SpecVol(ICell1, K-1) * LayerThick(ICell1, K-1) + - PressureMid(ICell0, K-1) * SpecVol(ICell0, K-1) * LayerThick(ICell0, K-1); - LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K) + - LayerThick(ICell1, K-1) + LayerThick(ICell0, K-1));} - - LocInterfaceProduct(IEdge, K) *= (ZInterface(ICell1, K) - ZInterface(ICell0, K)) / LocDcEdge(IEdge); - }); + LocInterfaceProduct(IEdge, KMin) = + PressureMid(ICell1, KMin) * SpecVol(ICell1, KMin) * + LayerThick(ICell1, KMin) + + PressureMid(ICell0, KMin) * SpecVol(ICell0, KMin) * + LayerThick(ICell0, KMin); + LocInterfaceProduct(IEdge, KMin) /= + (LayerThick(ICell1, KMin) + LayerThick(ICell0, KMin)); + + LocInterfaceProduct(IEdge, KMin) *= + (ZInterface(ICell1, KMin) - ZInterface(ICell0, KMin)) / + LocDcEdge(IEdge); + + const int KRange = vertRange(KMin + 1, KMax - 1); + parallelForInner( + Team, KRange, + INNER_LAMBDA(int KChunk) { + const I4 KStart = chunkStart(KChunk, KMin + 1); + const I4 KLen = chunkLength(KChunk, KStart, KMax - 1); + + + for (int KVec = 0; KVec < KLen; ++KVec) { + const I4 K = KStart + KVec; + + LocInterfaceProduct(IEdge, K) = + PressureMid(ICell1, K) * SpecVol(ICell1, K) * + LayerThick(ICell1, K) + + PressureMid(ICell0, K) * SpecVol(ICell0, K) * + LayerThick(ICell0, K) + + PressureMid(ICell1, K - 1) * SpecVol(ICell1, K - 1) * + LayerThick(ICell1, K - 1) + + PressureMid(ICell0, K - 1) * SpecVol(ICell0, K - 1) * + LayerThick(ICell0, K - 1); + LocInterfaceProduct(IEdge, K) /= + (LayerThick(ICell1, K) + LayerThick(ICell0, K) + + LayerThick(ICell1, K - 1) + + LayerThick(ICell0, K - 1)); + + LocInterfaceProduct(IEdge, K) *= + (ZInterface(ICell1, K) - ZInterface(ICell0, K)) / + LocDcEdge(IEdge); + } + }); + + LocInterfaceProduct(IEdge, KMax) = + PressureMid(ICell1, KMax - 1) * SpecVol(ICell1, KMax - 1) * + LayerThick(ICell1, KMax - 1) + + PressureMid(ICell0, KMax - 1) * SpecVol(ICell0, KMax - 1) * + LayerThick(ICell0, KMax - 1); + LocInterfaceProduct(IEdge, KMax) /= + (LayerThick(ICell1, KMax - 1) + + LayerThick(ICell0, KMax - 1)); + LocInterfaceProduct(IEdge, KMax) *= + (ZInterface(ICell1, KMax) - ZInterface(ICell0, KMax)) / + LocDcEdge(IEdge); + }); + //parallelFor( + // "compute-interface-product", {NEdgesAll, NVertLayersP1}, + // KOKKOS_LAMBDA(I4 IEdge, I4 K) { + // const I4 ICell0 = LocCellsOnEdge(IEdge, 0); + // const I4 ICell1 = LocCellsOnEdge(IEdge, 1); + + // if (K == 0) { + // LocInterfaceProduct(IEdge, K) = + // PressureMid(ICell1, K) * SpecVol(ICell1, K) * LayerThick(ICell1, K) + + // PressureMid(ICell0, K) * SpecVol(ICell0, K) * LayerThick(ICell0, K); + // LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K)); + + // } else if (K == NVertLayers) { + // LocInterfaceProduct(IEdge, K) = + // PressureMid(ICell1, K-1) * SpecVol(ICell1, K-1) * LayerThick(ICell1, K-1) + + // PressureMid(ICell0, K-1) * SpecVol(ICell0, K-1) * LayerThick(ICell0, K-1); + // LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K-1) + LayerThick(ICell0, K-1)); + + // } else { + // LocInterfaceProduct(IEdge, K) = + // PressureMid(ICell1, K) * SpecVol(ICell1, K) * LayerThick(ICell1, K) + + // PressureMid(ICell0, K) * SpecVol(ICell0, K) * LayerThick(ICell0, K) + + // PressureMid(ICell1, K-1) * SpecVol(ICell1, K-1) * LayerThick(ICell1, K-1) + + // PressureMid(ICell0, K-1) * SpecVol(ICell0, K-1) * LayerThick(ICell0, K-1); + // LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K) + + // LayerThick(ICell1, K-1) + LayerThick(ICell0, K-1));} + + // LocInterfaceProduct(IEdge, K) *= (ZInterface(ICell1, K) - ZInterface(ICell0, K)) / LocDcEdge(IEdge); + // }); } // end compute interface product //------------------------------------------------------------------------------ // Constructor for centered pressure gradient functor -PressureGradCentered::PressureGradCentered(const HorzMesh *Mesh) +PressureGradCentered::PressureGradCentered(const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord ///< [in] Vertical coordinate + ) : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), - EdgeMask(Mesh->EdgeMask) {} + EdgeMask(Mesh->EdgeMask), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} //------------------------------------------------------------------------------ // Constructor for high order pressure gradient functor -PressureGradHighOrder::PressureGradHighOrder(const HorzMesh *Mesh) +PressureGradHighOrder::PressureGradHighOrder(const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord ///< [in] Vertical coordinate + ) : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), - EdgeMask(Mesh->EdgeMask) {} + EdgeMask(Mesh->EdgeMask), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} } // namespace OMEGA diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index f17813f3fc4e..04d6254f754e 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -26,7 +26,9 @@ class PressureGradCentered { bool Enabled; /// constructor declaration - PressureGradCentered(const HorzMesh *Mesh); + PressureGradCentered(const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord ///< [in] Vertical coordinate + ); // Compute centered pressure gradient contribution for given edge and // vertical chunk. This appends results into the Tend array (in-place). @@ -36,13 +38,14 @@ class PressureGradCentered { const Array2DReal &LayerThick, const Array2DReal &InterfaceProduct, const Array2DReal &SpecVol) const { - const I4 KStart = KChunk * VecLength; - + const I4 KStart = chunkStart(KChunk, MinLayerEdgeBot(IEdge)); + const I4 KLen = chunkLength(KChunk, KStart, MaxLayerEdgeTop(IEdge)); + const I4 ICell0 = CellsOnEdge(IEdge, 0); const I4 ICell1 = CellsOnEdge(IEdge, 1); const Real InvDcEdge = 1.0_Real / DcEdge(IEdge); - for (int KVec = 0; KVec < VecLength; ++KVec) { + for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; const Real InvLayerThickEdge = 2.0_Real / (LayerThick(ICell1, K) + LayerThick(ICell0, K)); @@ -66,6 +69,8 @@ class PressureGradCentered { Array2DI4 CellsOnEdge; Array1DReal DcEdge; Array2DReal EdgeMask; + Array1DI4 MinLayerEdgeBot; + Array1DI4 MaxLayerEdgeTop; }; /// High-order pressure gradient functor (placeholder) @@ -74,15 +79,20 @@ class PressureGradHighOrder { bool Enabled; /// constructor declaration - PressureGradHighOrder(const HorzMesh *Mesh); + PressureGradHighOrder(const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord ///< [in] Vertical coordinate + ); KOKKOS_FUNCTION void operator()(const Array2DReal &Tend, I4 IEdge, I4 KChunk, const Array2DReal &Pressure, const Array2DReal &Geopotential, const Array2DReal &SpecVol) const { + // Placeholder: for now, no-op (future high-order implementation) - const I4 KStart = KChunk * VecLength; - for (int KVec = 0; KVec < VecLength; ++KVec) { + const I4 KStart = chunkStart(KChunk, MinLayerEdgeBot(IEdge)); + const I4 KLen = chunkLength(KChunk, KStart, MaxLayerEdgeTop(IEdge)); + + for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; Tend(IEdge, K) += 0.0_Real; } @@ -92,6 +102,8 @@ class PressureGradHighOrder { Array2DI4 CellsOnEdge; Array1DReal DcEdge; Array2DReal EdgeMask; + Array1DI4 MinLayerEdgeBot; + Array1DI4 MaxLayerEdgeTop; }; /// Pressure gradient manager class @@ -154,6 +166,8 @@ class PressureGrad { Array2DI4 CellsOnEdge; ///< cells surrounding each edge Array1DReal DcEdge; ///< distance between cell centers across edge Array2DReal EdgeSignOnCell; ///< orientation of edge relative to cell + Array1DI4 MinLayerEdgeBot; ///< min vertical layer on each edge + Array1DI4 MaxLayerEdgeTop; ///< max vertical layer on each edge // Instances of functors PressureGradCentered CenteredPGrad; diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 55b7d25bccfb..23f39b03216e 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -127,9 +127,13 @@ int main(int argc, char *argv[]) { auto &MinLayerCell = VCoord->MinLayerCell; auto &MaxLayerCell = VCoord->MaxLayerCell; + auto &MinLayerEdgeBot = VCoord->MinLayerEdgeBot; + auto &MaxLayerEdgeTop = VCoord->MaxLayerEdgeTop; parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { MinLayerCell(i) = 0; MaxLayerCell(i) = NVertLayers - 1; + MinLayerEdgeBot(i) = 0; + MaxLayerEdgeTop(i) = NVertLayers - 1; }); auto &CellsOnEdge = DefMesh->CellsOnEdge; From 75bea527f393c344eaaa5648cbbb951918ffc631 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 4 Dec 2025 14:23:11 -0800 Subject: [PATCH 111/152] Fix bounds for bottom interface product --- components/omega/src/ocn/PGrad.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index cfbe2b85b6b1..0a01de95d72a 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -259,13 +259,13 @@ void PressureGrad::computeInterfaceProduct( (ZInterface(ICell1, KMin) - ZInterface(ICell0, KMin)) / LocDcEdge(IEdge); - const int KRange = vertRange(KMin + 1, KMax - 1); + // Four-point average for interior interface products + const int KRange = vertRange(KMin + 1, KMax); parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { const I4 KStart = chunkStart(KChunk, KMin + 1); - const I4 KLen = chunkLength(KChunk, KStart, KMax - 1); - + const I4 KLen = chunkLength(KChunk, KStart, KMax); for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; @@ -290,16 +290,17 @@ void PressureGrad::computeInterfaceProduct( } }); - LocInterfaceProduct(IEdge, KMax) = - PressureMid(ICell1, KMax - 1) * SpecVol(ICell1, KMax - 1) * - LayerThick(ICell1, KMax - 1) + - PressureMid(ICell0, KMax - 1) * SpecVol(ICell0, KMax - 1) * - LayerThick(ICell0, KMax - 1); - LocInterfaceProduct(IEdge, KMax) /= - (LayerThick(ICell1, KMax - 1) + - LayerThick(ICell0, KMax - 1)); - LocInterfaceProduct(IEdge, KMax) *= - (ZInterface(ICell1, KMax) - ZInterface(ICell0, KMax)) / + // Two-point average for bottom interface product + LocInterfaceProduct(IEdge, KMax + 1) = + PressureMid(ICell1, KMax) * SpecVol(ICell1, KMax) * + LayerThick(ICell1, KMax) + + PressureMid(ICell0, KMax) * SpecVol(ICell0, KMax) * + LayerThick(ICell0, KMax); + LocInterfaceProduct(IEdge, KMax + 1) /= + (LayerThick(ICell1, KMax) + + LayerThick(ICell0, KMax)); + LocInterfaceProduct(IEdge, KMax + 1) *= + (ZInterface(ICell1, KMax + 1) - ZInterface(ICell0, KMax + 1)) / LocDcEdge(IEdge); }); //parallelFor( From cb8ae86a66f6a447253fa385e2739e8c56f20ce7 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 4 Dec 2025 14:25:18 -0800 Subject: [PATCH 112/152] Remove debug comments and cleanup --- components/omega/src/ocn/PGrad.cpp | 52 ++++--------------------- components/omega/test/ocn/PGradTest.cpp | 49 ++++++++++++++--------- 2 files changed, 37 insertions(+), 64 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 0a01de95d72a..8ed4cec67a4a 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -173,9 +173,11 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, if (PressureGradChoice == PressureGradType::Centered) { + // computes alpha*p*grad(z) term at edge interfaces computeInterfaceProduct(PressureMid, SpecVol, LayerThick, ZInterface); + // computes centered geopotential and pressure gradient tendency parallelForOuter( "pgrad-centered", {NEdgesAll}, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { @@ -192,13 +194,6 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, }); }); - //parallelFor( - // "pgrad-centered", {NEdgesAll, NChunks}, - // KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { - // LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, - // Geopotential, LayerThick, - // LocInterfaceProduct, SpecVol); - // }); } else { parallelForOuter( "pgrad-highorder", {NEdgesAll}, @@ -215,29 +210,24 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, }); }); - //parallelFor( - // "pgrad-highorder", {NEdgesAll, NChunks}, - // KOKKOS_LAMBDA(I4 IEdge, I4 KChunk) { - // LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, Geopotential, - // SpecVol); - // }); } } // end compute pressure gradient //------------------------------------------------------------------------------ -// Compute interface product needed for centered pressure gradient +// Compute the alpha*p*grad(z) product at edge interfaces that is +// needed for the centered pressure gradient void PressureGrad::computeInterfaceProduct( const Array2DReal &PressureMid, const Array2DReal &SpecVol, const Array2DReal &LayerThick, const Array2DReal &ZInterface) { - OMEGA_SCOPE(LocCellsOnEdge, CellsOnEdge); OMEGA_SCOPE(LocDcEdge, DcEdge); OMEGA_SCOPE(LocInterfaceProduct, InterfaceProduct); OMEGA_SCOPE(LocMinLayerEdgeBot, MinLayerEdgeBot); OMEGA_SCOPE(LocMaxLayerEdgeTop, MaxLayerEdgeTop); + parallelForOuter( "compute-interface-product", {NEdgesAll}, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { @@ -247,6 +237,7 @@ void PressureGrad::computeInterfaceProduct( const I4 ICell0 = LocCellsOnEdge(IEdge, 0); const I4 ICell1 = LocCellsOnEdge(IEdge, 1); + // Two-point average for surface interface product LocInterfaceProduct(IEdge, KMin) = PressureMid(ICell1, KMin) * SpecVol(ICell1, KMin) * LayerThick(ICell1, KMin) + @@ -302,36 +293,7 @@ void PressureGrad::computeInterfaceProduct( LocInterfaceProduct(IEdge, KMax + 1) *= (ZInterface(ICell1, KMax + 1) - ZInterface(ICell0, KMax + 1)) / LocDcEdge(IEdge); - }); - //parallelFor( - // "compute-interface-product", {NEdgesAll, NVertLayersP1}, - // KOKKOS_LAMBDA(I4 IEdge, I4 K) { - // const I4 ICell0 = LocCellsOnEdge(IEdge, 0); - // const I4 ICell1 = LocCellsOnEdge(IEdge, 1); - - // if (K == 0) { - // LocInterfaceProduct(IEdge, K) = - // PressureMid(ICell1, K) * SpecVol(ICell1, K) * LayerThick(ICell1, K) + - // PressureMid(ICell0, K) * SpecVol(ICell0, K) * LayerThick(ICell0, K); - // LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K)); - - // } else if (K == NVertLayers) { - // LocInterfaceProduct(IEdge, K) = - // PressureMid(ICell1, K-1) * SpecVol(ICell1, K-1) * LayerThick(ICell1, K-1) + - // PressureMid(ICell0, K-1) * SpecVol(ICell0, K-1) * LayerThick(ICell0, K-1); - // LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K-1) + LayerThick(ICell0, K-1)); - - // } else { - // LocInterfaceProduct(IEdge, K) = - // PressureMid(ICell1, K) * SpecVol(ICell1, K) * LayerThick(ICell1, K) + - // PressureMid(ICell0, K) * SpecVol(ICell0, K) * LayerThick(ICell0, K) + - // PressureMid(ICell1, K-1) * SpecVol(ICell1, K-1) * LayerThick(ICell1, K-1) + - // PressureMid(ICell0, K-1) * SpecVol(ICell0, K-1) * LayerThick(ICell0, K-1); - // LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K) + - // LayerThick(ICell1, K-1) + LayerThick(ICell0, K-1));} - - // LocInterfaceProduct(IEdge, K) *= (ZInterface(ICell1, K) - ZInterface(ICell0, K)) / LocDcEdge(IEdge); - // }); + }); } // end compute interface product diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 23f39b03216e..abd7cca99d89 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -117,6 +117,9 @@ int main(int argc, char *argv[]) { Array2DReal PressureMidOld("PressureMidOld", DefMesh->NCellsSize, VCoord->NVertLayers); Array1DReal SurfacePressure("SurfacePressure", DefMesh->NCellsSize); + I4 NEdgesAll = DefMesh->NEdgesAll; + I4 NCellsAll = DefMesh->NCellsAll; + I4 NVertLayers = 60; Real dC = 30000.0_Real; for (int refinement = 0; refinement < 4; ++refinement) { @@ -127,22 +130,26 @@ int main(int argc, char *argv[]) { auto &MinLayerCell = VCoord->MinLayerCell; auto &MaxLayerCell = VCoord->MaxLayerCell; - auto &MinLayerEdgeBot = VCoord->MinLayerEdgeBot; - auto &MaxLayerEdgeTop = VCoord->MaxLayerEdgeTop; - parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { + parallelFor({NCellsAll}, KOKKOS_LAMBDA(int i) { MinLayerCell(i) = 0; MaxLayerCell(i) = NVertLayers - 1; + }); + + auto &MinLayerEdgeBot = VCoord->MinLayerEdgeBot; + auto &MaxLayerEdgeTop = VCoord->MaxLayerEdgeTop; + parallelFor({NEdgesAll}, KOKKOS_LAMBDA(int i) { MinLayerEdgeBot(i) = 0; - MaxLayerEdgeTop(i) = NVertLayers - 1; + MaxLayerEdgeTop(i) = NVertLayers - 1; + //MaxLayerEdgeTop(i) = 4; }); auto &CellsOnEdge = DefMesh->CellsOnEdge; auto &DcEdge = DefMesh->DcEdge; auto &EdgeMask = DefMesh->EdgeMask; - parallelFor({DefMesh->NEdgesAll}, KOKKOS_LAMBDA(int e) { - CellsOnEdge(e, 0)= 0; - CellsOnEdge(e, 1)= 1; - DcEdge(e) = dC; + parallelFor({NEdgesAll}, KOKKOS_LAMBDA(int i) { + CellsOnEdge(i, 0)= 0; + CellsOnEdge(i, 1)= 1; + DcEdge(i) = dC; }); // Fetch reference desnity from Config @@ -173,7 +180,7 @@ int main(int argc, char *argv[]) { auto &ZMid = VCoord->ZMid; Real tilt_factor = 0.495_Real; //Real tilt_factor = 0.45_Real; - parallelFor({DefMesh->NCellsAll}, KOKKOS_LAMBDA(int i) { + parallelFor({NCellsAll}, KOKKOS_LAMBDA(int i) { ZInterface(i, NVertLayers) = ZBottom; SurfacePressure(i) = 0.0_Real; BottomDepth(i) = 0.0_Real; @@ -197,7 +204,7 @@ int main(int argc, char *argv[]) { // set simple temperature and salinity profiles auto &SpecVol = DefEos->SpecVol; - parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + parallelFor({NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { Real T0 = 30.0; Real TB = 5.0; Real S0 = 30.0; @@ -213,7 +220,7 @@ int main(int argc, char *argv[]) { SpecVolOld(i, k) = SpecVol(i, k); }); - // iterate to converge SpecVol + // Iterate to converge LayerThick, SpecVol, PressureMid auto &PressureMid = VCoord->PressureMid; VCoord->computePressure(LayerThick, SurfacePressure); deepCopy(PressureMidOld, PressureMid); @@ -224,13 +231,13 @@ int main(int argc, char *argv[]) { DefEos->computeSpecVol(Temp, Salinity, PressureMid); // compute psuedo thickness from specific volume - parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + parallelFor({NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { LayerThick(i, k) = 1.0_Real / (SpecVol(i, k) * Density0) * (ZInterface(i, k) - ZInterface(i, k + 1)); }); // compute difference from previous iteration Real max_value = 0.0_Real; - parallelReduce({DefMesh->NCellsAll, NVertLayers}, + parallelReduce({NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k, Real &max) { Real diff = Kokkos::abs(SpecVol(i, k) - SpecVolOld(i, k)); if (diff > max) max = diff; @@ -241,17 +248,20 @@ int main(int argc, char *argv[]) { LOG_INFO("converged: max diff = {}", max_value); break; } else { - parallelFor({DefMesh->NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + parallelFor({NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { SpecVolOld(i, k) = SpecVol(i, k); }); } } + + // compute pressure once more with converged LayerThick + VCoord->computePressure(LayerThick, SurfacePressure); // compute geopotential //VCoord->computeZHeight(LayerThick, SpecVol); - Array1DReal SelfAttractionLoading("SelfAttractionLoading", DefMesh->NCellsAll); - Array1DReal TidalPotential("TidalPotential", DefMesh->NCellsAll); + Array1DReal SelfAttractionLoading("SelfAttractionLoading", NCellsAll); + Array1DReal TidalPotential("TidalPotential", NCellsAll); deepCopy(TidalPotential, 0.0_Real); deepCopy(SelfAttractionLoading, 0.0_Real); VCoord->computeGeopotential(TidalPotential, SelfAttractionLoading); @@ -266,18 +276,19 @@ int main(int argc, char *argv[]) { deepCopy(Tend, 0.0_Real); DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); Real max_value = 0.0_Real; - parallelReduce({DefMesh->NEdgesAll, NVertLayers - 2}, + parallelReduce({NEdgesAll, NVertLayers - 2}, KOKKOS_LAMBDA(int i, int k, Real &max) { Real val = Kokkos::abs(Tend(i + 1, k)); if (val > max) max = val; }, Kokkos::Max(max_value) ); Real sum_value = 0.0_Real; - parallelReduce({DefMesh->NEdgesAll, NVertLayers - 2}, + parallelReduce({NEdgesAll, NVertLayers - 2}, KOKKOS_LAMBDA(int i, int k, Real &lsum) { lsum += Tend(i + 1, k) * Tend(i + 1, k); }, Kokkos::Sum(sum_value) ); - LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, std::sqrt(sum_value) / (DefMesh->NEdgesAll * (NVertLayers - 2))); + LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, std::sqrt(sum_value) / (NEdgesAll * (NVertLayers - 2))); + // coarsen for next iteration dC = dC * 2.0_Real; NVertLayers = NVertLayers / 2; From 315e9aa1036e8c8c7813c2b9c65fba0266bdd26e Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Fri, 5 Dec 2025 13:09:24 -0800 Subject: [PATCH 113/152] Clean up --- components/omega/src/ocn/PGrad.cpp | 2 ++ components/omega/src/ocn/PGrad.h | 53 ++++++++++++++++-------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 8ed4cec67a4a..61d7183da66e 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -195,6 +195,8 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, }); } else { + + // computes high-order geopotential and pressure gradient tendency parallelForOuter( "pgrad-highorder", {NEdgesAll}, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 04d6254f754e..bb02388204c9 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -20,12 +20,12 @@ namespace OMEGA { enum class PressureGradType { Centered, HighOrder1, HighOrder2 }; -/// Centered pressure gradient functor +// Centered pressure gradient functor class PressureGradCentered { public: bool Enabled; - /// constructor declaration + // constructor declaration PressureGradCentered(const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord ///< [in] Vertical coordinate ); @@ -73,12 +73,12 @@ class PressureGradCentered { Array1DI4 MaxLayerEdgeTop; }; -/// High-order pressure gradient functor (placeholder) +// High-order pressure gradient functor (placeholder) class PressureGradHighOrder { public: bool Enabled; - /// constructor declaration + // constructor declaration PressureGradHighOrder(const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord ///< [in] Vertical coordinate ); @@ -106,44 +106,38 @@ class PressureGradHighOrder { Array1DI4 MaxLayerEdgeTop; }; -/// Pressure gradient manager class +// Pressure gradient manager class class PressureGrad { public: - /// Initialize the default instance + // Initialize the default instance static void init(); - /// Deallocates arrays and deletes instance - static void clear(); - - /// Remove pressure gradient object by name - static void erase(const std::string &Name ///< [in] - ); + // Create a new pressure gradient object and add to map + static PressureGrad *create(const std::string &Name, const HorzMesh *Mesh, + const VertCoord *VCoord, Config *Options); - /// Get the default instance + // Get the default instance static PressureGrad *getDefault(); - /// Get instance by name + // Get instance by name static PressureGrad *get(const std::string &Name ///< [in] ); + // Deallocates arrays and deletes instance + static void clear(); + + // Remove pressure gradient object by name + static void erase(const std::string &Name ///< [in] + ); + // Destructor ~PressureGrad(); - Array2DReal InterfaceProduct; - - /// Compute pressure gradient tendencies and add into Tend array + // Compute pressure gradient tendencies and add into Tend array void computePressureGrad(Array2DReal &Tend, const OceanState *State, const VertCoord *VCoord, const Eos *EqState, const int TimeLevel); - void computeInterfaceProduct(const Array2DReal &PressureMid, - const Array2DReal &SpecVol, - const Array2DReal &LayerThick, - const Array2DReal &ZInterface); - - /// Create a new pressure gradient object and add to map - static PressureGrad *create(const std::string &Name, const HorzMesh *Mesh, - const VertCoord *VCoord, Config *Options); private: // Construct a new pressure gradient object @@ -173,12 +167,21 @@ class PressureGrad { PressureGradCentered CenteredPGrad; PressureGradHighOrder HighOrderPGrad; + // Array for interface product needed in centered pressure gradient + Array2DReal InterfaceProduct; + // Choice from config PressureGradType PressureGradChoice = PressureGradType::Centered; // Map of all pressure gradient objects by name static std::map> AllPGrads; + // Compute interface product needed for centered pressure gradient + void computeInterfaceProduct(const Array2DReal &PressureMid, + const Array2DReal &SpecVol, + const Array2DReal &LayerThick, + const Array2DReal &ZInterface); + }; // end class PressureGrad } // namespace OMEGA From 11114f7f9e7706f8502dbeca29808626a8d83da0 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 8 Dec 2025 14:03:57 -0800 Subject: [PATCH 114/152] Fix for gradient in interface term --- components/omega/src/ocn/PGrad.cpp | 22 ++++++++++++---------- components/omega/src/ocn/PGrad.h | 4 +++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 61d7183da66e..162fd14249db 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -167,7 +167,6 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, const Array2DReal &PressureInterface = VCoord->PressureInterface; const Array2DReal &Geopotential = VCoord->GeopotentialMid; const Array2DReal &SpecVol = EqState->SpecVol; - const Array2DReal &ZInterface = VCoord->ZInterface; Array2DReal LayerThick; State->getLayerThickness(LayerThick, TimeLevel); @@ -175,7 +174,7 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, // computes alpha*p*grad(z) term at edge interfaces computeInterfaceProduct(PressureMid, SpecVol, - LayerThick, ZInterface); + LayerThick, PressureInterface); // computes centered geopotential and pressure gradient tendency parallelForOuter( @@ -195,7 +194,7 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, }); } else { - + // computes high-order geopotential and pressure gradient tendency parallelForOuter( "pgrad-highorder", {NEdgesAll}, @@ -222,7 +221,7 @@ void PressureGrad::computeInterfaceProduct( const Array2DReal &PressureMid, const Array2DReal &SpecVol, const Array2DReal &LayerThick, - const Array2DReal &ZInterface) { + const Array2DReal &PInterface) { OMEGA_SCOPE(LocCellsOnEdge, CellsOnEdge); OMEGA_SCOPE(LocDcEdge, DcEdge); @@ -230,6 +229,9 @@ void PressureGrad::computeInterfaceProduct( OMEGA_SCOPE(LocMinLayerEdgeBot, MinLayerEdgeBot); OMEGA_SCOPE(LocMaxLayerEdgeTop, MaxLayerEdgeTop); + Real Gravity = 9.80616_Real; + Real Rho0 = 1026.0_Real; + parallelForOuter( "compute-interface-product", {NEdgesAll}, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { @@ -249,8 +251,8 @@ void PressureGrad::computeInterfaceProduct( (LayerThick(ICell1, KMin) + LayerThick(ICell0, KMin)); LocInterfaceProduct(IEdge, KMin) *= - (ZInterface(ICell1, KMin) - ZInterface(ICell0, KMin)) / - LocDcEdge(IEdge); + -(PInterface(ICell1, KMin) - PInterface(ICell0, KMin)) / + (LocDcEdge(IEdge) * Gravity * Rho0); // Four-point average for interior interface products const int KRange = vertRange(KMin + 1, KMax); @@ -278,8 +280,8 @@ void PressureGrad::computeInterfaceProduct( LayerThick(ICell0, K - 1)); LocInterfaceProduct(IEdge, K) *= - (ZInterface(ICell1, K) - ZInterface(ICell0, K)) / - LocDcEdge(IEdge); + -(PInterface(ICell1, K) - PInterface(ICell0, K)) / + (LocDcEdge(IEdge) * Gravity * Rho0); } }); @@ -293,8 +295,8 @@ void PressureGrad::computeInterfaceProduct( (LayerThick(ICell1, KMax) + LayerThick(ICell0, KMax)); LocInterfaceProduct(IEdge, KMax + 1) *= - (ZInterface(ICell1, KMax + 1) - ZInterface(ICell0, KMax + 1)) / - LocDcEdge(IEdge); + -(PInterface(ICell1, KMax + 1) - PInterface(ICell0, KMax + 1)) / + (LocDcEdge(IEdge) * Gravity * Rho0); }); } // end compute interface product diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index bb02388204c9..623ffb17d9eb 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -61,7 +61,9 @@ class PressureGradCentered { InvLayerThickEdge; Tend(IEdge, K) += - EdgeMask(IEdge, K) * (-GeoTerm - PresTerm + InterfaceTerm); + EdgeMask(IEdge, K) * (GeoTerm + PresTerm - InterfaceTerm); + if (IEdge == 0) + LOG_INFO("IEdge {}, K {}: GeoTerm {}, PresTerm {}, InterfaceTerm {}, Tend {}", IEdge, K, GeoTerm, PresTerm, InterfaceTerm, Tend(IEdge, K)); } } From de7a550df6f431eb317f8a3367678c854bcb588c Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 15 Dec 2025 13:32:49 -0800 Subject: [PATCH 115/152] Add Liebnez terms for geopotential gradient --- components/omega/src/ocn/PGrad.cpp | 22 ++++++++++++++++++---- components/omega/src/ocn/PGrad.h | 21 ++++++++++++++++----- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 162fd14249db..55e23f3556a5 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -167,6 +167,8 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, const Array2DReal &PressureInterface = VCoord->PressureInterface; const Array2DReal &Geopotential = VCoord->GeopotentialMid; const Array2DReal &SpecVol = EqState->SpecVol; + const Array2DReal &ZInterface = VCoord->ZInterface; + const Array2DReal &ZMid = VCoord->ZMid; Array2DReal LayerThick; State->getLayerThickness(LayerThick, TimeLevel); @@ -174,7 +176,8 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, // computes alpha*p*grad(z) term at edge interfaces computeInterfaceProduct(PressureMid, SpecVol, - LayerThick, PressureInterface); + LayerThick, PressureInterface, + ZMid, Geopotential); // computes centered geopotential and pressure gradient tendency parallelForOuter( @@ -189,7 +192,7 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, INNER_LAMBDA(int KChunk) { LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, Geopotential, LayerThick, - LocInterfaceProduct, SpecVol); + LocInterfaceProduct, SpecVol, ZMid); }); }); @@ -221,7 +224,10 @@ void PressureGrad::computeInterfaceProduct( const Array2DReal &PressureMid, const Array2DReal &SpecVol, const Array2DReal &LayerThick, - const Array2DReal &PInterface) { + const Array2DReal &PInterface, + const Array2DReal &ZMid, + const Array2DReal &Geopotential + ) { OMEGA_SCOPE(LocCellsOnEdge, CellsOnEdge); OMEGA_SCOPE(LocDcEdge, DcEdge); @@ -246,7 +252,9 @@ void PressureGrad::computeInterfaceProduct( PressureMid(ICell1, KMin) * SpecVol(ICell1, KMin) * LayerThick(ICell1, KMin) + PressureMid(ICell0, KMin) * SpecVol(ICell0, KMin) * - LayerThick(ICell0, KMin); + LayerThick(ICell0, KMin) + + Geopotential(ICell1, KMin) + Geopotential(ICell0, KMin) - + Gravity * (ZMid(ICell1, KMin) + ZMid(ICell0, KMin)); LocInterfaceProduct(IEdge, KMin) /= (LayerThick(ICell1, KMin) + LayerThick(ICell0, KMin)); @@ -274,6 +282,10 @@ void PressureGrad::computeInterfaceProduct( LayerThick(ICell1, K - 1) + PressureMid(ICell0, K - 1) * SpecVol(ICell0, K - 1) * LayerThick(ICell0, K - 1); + Geopotential(ICell1, K) + Geopotential(ICell0, K) + + Geopotential(ICell1, K - 1) + Geopotential(ICell0, K - 1) - + Gravity * (ZMid(ICell1, K) + ZMid(ICell0, K) + + ZMid(ICell1, K - 1) + ZMid(ICell0, K - 1)); LocInterfaceProduct(IEdge, K) /= (LayerThick(ICell1, K) + LayerThick(ICell0, K) + LayerThick(ICell1, K - 1) + @@ -291,6 +303,8 @@ void PressureGrad::computeInterfaceProduct( LayerThick(ICell1, KMax) + PressureMid(ICell0, KMax) * SpecVol(ICell0, KMax) * LayerThick(ICell0, KMax); + Geopotential(ICell1, KMax) + Geopotential(ICell0, KMax) - + Gravity * (ZMid(ICell1, KMax) + ZMid(ICell0, KMax)); LocInterfaceProduct(IEdge, KMax + 1) /= (LayerThick(ICell1, KMax) + LayerThick(ICell0, KMax)); diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 623ffb17d9eb..e28e0cff4c6f 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -37,21 +37,30 @@ class PressureGradCentered { const Array2DReal &Geopotential, const Array2DReal &LayerThick, const Array2DReal &InterfaceProduct, - const Array2DReal &SpecVol) const { + const Array2DReal &SpecVol, + const Array2DReal &ZMid) const { + const I4 KStart = chunkStart(KChunk, MinLayerEdgeBot(IEdge)); const I4 KLen = chunkLength(KChunk, KStart, MaxLayerEdgeTop(IEdge)); const I4 ICell0 = CellsOnEdge(IEdge, 0); const I4 ICell1 = CellsOnEdge(IEdge, 1); const Real InvDcEdge = 1.0_Real / DcEdge(IEdge); + Real Rho0 = 1026.0_Real; + Real Gravity = 9.80616_Real; for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; const Real InvLayerThickEdge = 2.0_Real / (LayerThick(ICell1, K) + LayerThick(ICell0, K)); + //Real RhoEdge = 1.0_Real / (0.5_Real*(SpecVol(ICell1, K) + SpecVol(ICell0, K))); + //Real GeoEdgeK = (ZInterface(ICell1, K) + ZInterface(ICell0, K)) * 0.5_Real; + //Real GeoEdgeKp1 = (ZInterface(ICell1, K+1) + ZInterface(ICell0, K+1)) * 0.5_Real; Real GeoTerm = - (Geopotential(ICell1, K) - Geopotential(ICell0, K)) * InvDcEdge; + (LayerThick(ICell1, K) * (Geopotential(ICell1, K) - Gravity * ZMid(ICell1, K)) - + LayerThick(ICell0, K) * (Geopotential(ICell0, K) - Gravity * ZMid(ICell0, K))) * InvDcEdge; + //GeoTerm *= (1.0_Real - (RhoEdge / Rho0)*(GeoEdgeK - GeoEdgeKp1) * InvLayerThickEdge); Real PresTerm = (LayerThick(ICell1, K) * SpecVol(ICell1, K) * PressureMid(ICell1, K) - LayerThick(ICell0, K) * SpecVol(ICell0, K) * @@ -180,9 +189,11 @@ class PressureGrad { // Compute interface product needed for centered pressure gradient void computeInterfaceProduct(const Array2DReal &PressureMid, - const Array2DReal &SpecVol, - const Array2DReal &LayerThick, - const Array2DReal &ZInterface); + const Array2DReal &SpecVol, + const Array2DReal &LayerThick, + const Array2DReal &PressureInterface, + const Array2DReal &ZMid, + const Array2DReal &Geopotential); }; // end class PressureGrad From 98dcd61dbc80c4c1add5ae80e9944cb793fa4ef3 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 15 Dec 2025 15:28:42 -0800 Subject: [PATCH 116/152] Montgomery potential implementation --- components/omega/src/ocn/PGrad.cpp | 118 ++--------------------------- components/omega/src/ocn/PGrad.h | 49 ++++-------- 2 files changed, 21 insertions(+), 146 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 55e23f3556a5..096c90d1f262 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -75,10 +75,9 @@ PressureGrad::PressureGrad( const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate Config *Options) ///< [in] Configuration options - : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), - EdgeSignOnCell(Mesh->EdgeSignOnCell), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), - MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), CenteredPGrad(Mesh, VCoord), - HighOrderPGrad(Mesh, VCoord) { + : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), EdgeSignOnCell(Mesh->EdgeSignOnCell), + MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), + CenteredPGrad(Mesh, VCoord), HighOrderPGrad(Mesh, VCoord) { // store mesh sizes NEdgesAll = Mesh->NEdgesAll; @@ -86,8 +85,6 @@ PressureGrad::PressureGrad( NVertLayersP1 = NVertLayers + 1; NChunks = NVertLayers / VecLength; - InterfaceProduct = Array2DReal("InterfaceProduct", NEdgesAll, NVertLayersP1); - // Read config options for PressureGrad type // and enable the appropriate functor Config PGradConfig("PressureGrad"); @@ -159,7 +156,6 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, OMEGA_SCOPE(LocCenteredPGrad, CenteredPGrad); OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); - OMEGA_SCOPE(LocInterfaceProduct, InterfaceProduct); OMEGA_SCOPE(LocMinLayerEdgeBot, MinLayerEdgeBot); OMEGA_SCOPE(LocMaxLayerEdgeTop, MaxLayerEdgeTop); @@ -174,11 +170,6 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, if (PressureGradChoice == PressureGradType::Centered) { - // computes alpha*p*grad(z) term at edge interfaces - computeInterfaceProduct(PressureMid, SpecVol, - LayerThick, PressureInterface, - ZMid, Geopotential); - // computes centered geopotential and pressure gradient tendency parallelForOuter( "pgrad-centered", {NEdgesAll}, @@ -191,8 +182,9 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, Team, KRange, INNER_LAMBDA(int KChunk) { LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, - Geopotential, LayerThick, - LocInterfaceProduct, SpecVol, ZMid); + PressureInterface, ZInterface, + LayerThick, + SpecVol); }); }); @@ -217,104 +209,6 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, } } // end compute pressure gradient -//------------------------------------------------------------------------------ -// Compute the alpha*p*grad(z) product at edge interfaces that is -// needed for the centered pressure gradient -void PressureGrad::computeInterfaceProduct( - const Array2DReal &PressureMid, - const Array2DReal &SpecVol, - const Array2DReal &LayerThick, - const Array2DReal &PInterface, - const Array2DReal &ZMid, - const Array2DReal &Geopotential - ) { - - OMEGA_SCOPE(LocCellsOnEdge, CellsOnEdge); - OMEGA_SCOPE(LocDcEdge, DcEdge); - OMEGA_SCOPE(LocInterfaceProduct, InterfaceProduct); - OMEGA_SCOPE(LocMinLayerEdgeBot, MinLayerEdgeBot); - OMEGA_SCOPE(LocMaxLayerEdgeTop, MaxLayerEdgeTop); - - Real Gravity = 9.80616_Real; - Real Rho0 = 1026.0_Real; - - parallelForOuter( - "compute-interface-product", {NEdgesAll}, - KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { - const int KMin = LocMinLayerEdgeBot(IEdge); - const int KMax = LocMaxLayerEdgeTop(IEdge); - - const I4 ICell0 = LocCellsOnEdge(IEdge, 0); - const I4 ICell1 = LocCellsOnEdge(IEdge, 1); - - // Two-point average for surface interface product - LocInterfaceProduct(IEdge, KMin) = - PressureMid(ICell1, KMin) * SpecVol(ICell1, KMin) * - LayerThick(ICell1, KMin) + - PressureMid(ICell0, KMin) * SpecVol(ICell0, KMin) * - LayerThick(ICell0, KMin) + - Geopotential(ICell1, KMin) + Geopotential(ICell0, KMin) - - Gravity * (ZMid(ICell1, KMin) + ZMid(ICell0, KMin)); - LocInterfaceProduct(IEdge, KMin) /= - (LayerThick(ICell1, KMin) + LayerThick(ICell0, KMin)); - - LocInterfaceProduct(IEdge, KMin) *= - -(PInterface(ICell1, KMin) - PInterface(ICell0, KMin)) / - (LocDcEdge(IEdge) * Gravity * Rho0); - - // Four-point average for interior interface products - const int KRange = vertRange(KMin + 1, KMax); - parallelForInner( - Team, KRange, - INNER_LAMBDA(int KChunk) { - const I4 KStart = chunkStart(KChunk, KMin + 1); - const I4 KLen = chunkLength(KChunk, KStart, KMax); - - for (int KVec = 0; KVec < KLen; ++KVec) { - const I4 K = KStart + KVec; - - LocInterfaceProduct(IEdge, K) = - PressureMid(ICell1, K) * SpecVol(ICell1, K) * - LayerThick(ICell1, K) + - PressureMid(ICell0, K) * SpecVol(ICell0, K) * - LayerThick(ICell0, K) + - PressureMid(ICell1, K - 1) * SpecVol(ICell1, K - 1) * - LayerThick(ICell1, K - 1) + - PressureMid(ICell0, K - 1) * SpecVol(ICell0, K - 1) * - LayerThick(ICell0, K - 1); - Geopotential(ICell1, K) + Geopotential(ICell0, K) + - Geopotential(ICell1, K - 1) + Geopotential(ICell0, K - 1) - - Gravity * (ZMid(ICell1, K) + ZMid(ICell0, K) + - ZMid(ICell1, K - 1) + ZMid(ICell0, K - 1)); - LocInterfaceProduct(IEdge, K) /= - (LayerThick(ICell1, K) + LayerThick(ICell0, K) + - LayerThick(ICell1, K - 1) + - LayerThick(ICell0, K - 1)); - - LocInterfaceProduct(IEdge, K) *= - -(PInterface(ICell1, K) - PInterface(ICell0, K)) / - (LocDcEdge(IEdge) * Gravity * Rho0); - } - }); - - // Two-point average for bottom interface product - LocInterfaceProduct(IEdge, KMax + 1) = - PressureMid(ICell1, KMax) * SpecVol(ICell1, KMax) * - LayerThick(ICell1, KMax) + - PressureMid(ICell0, KMax) * SpecVol(ICell0, KMax) * - LayerThick(ICell0, KMax); - Geopotential(ICell1, KMax) + Geopotential(ICell0, KMax) - - Gravity * (ZMid(ICell1, KMax) + ZMid(ICell0, KMax)); - LocInterfaceProduct(IEdge, KMax + 1) /= - (LayerThick(ICell1, KMax) + - LayerThick(ICell0, KMax)); - LocInterfaceProduct(IEdge, KMax + 1) *= - -(PInterface(ICell1, KMax + 1) - PInterface(ICell0, KMax + 1)) / - (LocDcEdge(IEdge) * Gravity * Rho0); - }); - -} // end compute interface product - //------------------------------------------------------------------------------ // Constructor for centered pressure gradient functor PressureGradCentered::PressureGradCentered(const HorzMesh *Mesh, ///< [in] Horizontal mesh diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index e28e0cff4c6f..11004978ccf0 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -34,11 +34,11 @@ class PressureGradCentered { // vertical chunk. This appends results into the Tend array (in-place). KOKKOS_FUNCTION void operator()(const Array2DReal &Tend, I4 IEdge, I4 KChunk, const Array2DReal &PressureMid, - const Array2DReal &Geopotential, + const Array2DReal &PressureInterface, + const Array2DReal &ZInterface, const Array2DReal &LayerThick, - const Array2DReal &InterfaceProduct, - const Array2DReal &SpecVol, - const Array2DReal &ZMid) const { + const Array2DReal &SpecVol + ) const { const I4 KStart = chunkStart(KChunk, MinLayerEdgeBot(IEdge)); const I4 KLen = chunkLength(KChunk, KStart, MaxLayerEdgeTop(IEdge)); @@ -51,28 +51,20 @@ class PressureGradCentered { for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; - const Real InvLayerThickEdge = - 2.0_Real / (LayerThick(ICell1, K) + LayerThick(ICell0, K)); - //Real RhoEdge = 1.0_Real / (0.5_Real*(SpecVol(ICell1, K) + SpecVol(ICell0, K))); - //Real GeoEdgeK = (ZInterface(ICell1, K) + ZInterface(ICell0, K)) * 0.5_Real; - //Real GeoEdgeKp1 = (ZInterface(ICell1, K+1) + ZInterface(ICell0, K+1)) * 0.5_Real; - - Real GeoTerm = - (LayerThick(ICell1, K) * (Geopotential(ICell1, K) - Gravity * ZMid(ICell1, K)) - - LayerThick(ICell0, K) * (Geopotential(ICell0, K) - Gravity * ZMid(ICell0, K))) * InvDcEdge; - //GeoTerm *= (1.0_Real - (RhoEdge / Rho0)*(GeoEdgeK - GeoEdgeKp1) * InvLayerThickEdge); - Real PresTerm = (LayerThick(ICell1, K) * SpecVol(ICell1, K) * - PressureMid(ICell1, K) - - LayerThick(ICell0, K) * SpecVol(ICell0, K) * - PressureMid(ICell0, K)) * - InvDcEdge * InvLayerThickEdge; - Real InterfaceTerm = (InterfaceProduct(IEdge, K) - InterfaceProduct(IEdge, K+1)) * - InvLayerThickEdge; + Real MontPotCell0K = PressureInterface(ICell0, K) * SpecVol(ICell0, K) + Gravity * ZInterface(ICell0, K); + Real MontPotCell1K = PressureInterface(ICell1, K) * SpecVol(ICell1, K) + Gravity * ZInterface(ICell1, K); + Real GradMontPotK = (MontPotCell1K - MontPotCell0K) * InvDcEdge; + + Real MontPotCell0Kp1 = PressureInterface(ICell0, K+1) * SpecVol(ICell0, K) + Gravity * ZInterface(ICell0, K+1); + Real MontPotCell1Kp1 = PressureInterface(ICell1, K+1) * SpecVol(ICell1, K) + Gravity * ZInterface(ICell1, K+1); + Real GradMontPotKp1 = (MontPotCell1Kp1 - MontPotCell0Kp1) * InvDcEdge; + Real GradMontPot = 0.5_Real * (GradMontPotK + GradMontPotKp1); + Real PGradAlpha = 0.5 * (PressureMid(ICell1, K) + PressureMid(ICell0, K)) * (SpecVol(ICell1, K) - SpecVol(ICell0, K)) * InvDcEdge; Tend(IEdge, K) += - EdgeMask(IEdge, K) * (GeoTerm + PresTerm - InterfaceTerm); + EdgeMask(IEdge, K) * (GradMontPot - PGradAlpha); if (IEdge == 0) - LOG_INFO("IEdge {}, K {}: GeoTerm {}, PresTerm {}, InterfaceTerm {}, Tend {}", IEdge, K, GeoTerm, PresTerm, InterfaceTerm, Tend(IEdge, K)); + LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); } } @@ -178,23 +170,12 @@ class PressureGrad { PressureGradCentered CenteredPGrad; PressureGradHighOrder HighOrderPGrad; - // Array for interface product needed in centered pressure gradient - Array2DReal InterfaceProduct; - // Choice from config PressureGradType PressureGradChoice = PressureGradType::Centered; // Map of all pressure gradient objects by name static std::map> AllPGrads; - // Compute interface product needed for centered pressure gradient - void computeInterfaceProduct(const Array2DReal &PressureMid, - const Array2DReal &SpecVol, - const Array2DReal &LayerThick, - const Array2DReal &PressureInterface, - const Array2DReal &ZMid, - const Array2DReal &Geopotential); - }; // end class PressureGrad } // namespace OMEGA From 757298e0ca04ccb354a0b35281458042fcc1a33f Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 23 Dec 2025 15:24:28 -0800 Subject: [PATCH 117/152] Work toward outputing PGrad tendencies (need to fix unit tests) --- components/omega/configs/Default.yml | 1 + components/omega/src/ocn/PGrad.cpp | 2 +- components/omega/src/ocn/PGrad.h | 8 +- components/omega/src/ocn/Tendencies.cpp | 82 ++++++++++++++++++- components/omega/src/ocn/Tendencies.h | 12 ++- components/omega/test/ocn/TendenciesTest.cpp | 19 ++++- .../test/timeStepping/TimeStepperTest.cpp | 11 ++- 7 files changed, 122 insertions(+), 13 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 2964147e33ec..d71f8538d4b8 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -59,6 +59,7 @@ Omega: ThicknessVertAdvTendencyEnable: true VelocityVertAdvTendencyEnable: true TracerVertAdvTendencyEnable: true + PressureGradTendencyEnable: true Tracers: Base: [Temperature, Salinity] Debug: [Debug1, Debug2, Debug3] diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 096c90d1f262..0fc1a4b022cc 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -152,7 +152,7 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, const OceanState *State, const VertCoord *VCoord, const Eos *EqState, - const int TimeLevel) { + const int TimeLevel) const { OMEGA_SCOPE(LocCenteredPGrad, CenteredPGrad); OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 11004978ccf0..31e9386fc36c 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -62,7 +62,7 @@ class PressureGradCentered { Real PGradAlpha = 0.5 * (PressureMid(ICell1, K) + PressureMid(ICell0, K)) * (SpecVol(ICell1, K) - SpecVol(ICell0, K)) * InvDcEdge; Tend(IEdge, K) += - EdgeMask(IEdge, K) * (GradMontPot - PGradAlpha); + EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha); if (IEdge == 0) LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); } @@ -112,6 +112,10 @@ class PressureGradHighOrder { // Pressure gradient manager class class PressureGrad { public: + + // Flag to indicate if pressure gradient term is enabled + bool Enabled; + // Initialize the default instance static void init(); @@ -139,7 +143,7 @@ class PressureGrad { // Compute pressure gradient tendencies and add into Tend array void computePressureGrad(Array2DReal &Tend, const OceanState *State, const VertCoord *VCoord, const Eos *EqState, - const int TimeLevel); + const int TimeLevel) const; private: diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 8714f56514ea..4aa68b2ea998 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -12,6 +12,7 @@ #include "CustomTendencyTerms.h" #include "Eos.h" #include "Error.h" +#include "Field.h" #include "PGrad.h" #include "Pacer.h" #include "TimeStepper.h" @@ -34,6 +35,8 @@ void Tendencies::init() { VertCoord *DefVertCoord = VertCoord::getDefault(); VertAdv *DefVertAdv = VertAdv::getDefault(); TimeStepper *DefTimeStepper = TimeStepper::getDefault(); + Eos *DefEos = Eos::getInstance(); + PressureGrad *DefPGrad = PressureGrad::getDefault(); I4 NTracers = Tracers::getNumTracers(); @@ -74,7 +77,7 @@ void Tendencies::init() { // Ceate default tendencies Tendencies::DefaultTendencies = - create("Default", DefHorzMesh, DefVertCoord, DefVertAdv, NTracers, + create("Default", DefHorzMesh, DefVertCoord, DefVertAdv, DefPGrad, DefEos, NTracers, TimeStep, &TendConfig, CustomThickTend, CustomVelTend); DefaultTendencies->readConfig(OmegaConfig); @@ -242,12 +245,75 @@ void Tendencies::readConfig(Config *OmegaConfig ///< [in] Omega config } } +//------------------------------------------------------------------------------ +// Define fields associated with tendencies +void Tendencies::defineFields() { + auto LayerThicknessTendFieldName = "LayerThicknessTend"; + auto NormalVelocityTendFieldName = "NormalVelocityTend"; + auto TracerTendFieldName = "TracerTend"; + + int NDims = 2; + std::vector DimNamesThickness(NDims); + DimNamesThickness[0] = "NCells"; + DimNamesThickness[1] = "NVertLayers"; + auto LayerThicknessTendField = Field::create(LayerThicknessTendFieldName, + "Layer thickness tendency", + "m/s", + "cell_thickness_tendency", + -9.99E+10, + 9.99E+10, + -9.99E+30, + NDims, + DimNamesThickness); + NDims = 3; + std::vector DimNamesTracer(NDims); + DimNamesTracer[0] = "NTracers"; + DimNamesTracer[1] = "NCells"; + DimNamesTracer[2] = "NVertLayers"; + auto TracerTendField = Field::create(TracerTendFieldName, + "Tracer tendency", + "kg/m^3/s", + "tracer_tendency", + -9.99E+10, + 9.99E+10, + -9.99E+30, + NDims, + DimNamesTracer); + NDims = 2; + std::vector DimNamesVelocity(NDims); + DimNamesVelocity[0] = "NEdges"; + DimNamesVelocity[1] = "NVertLayers"; + auto NormalVelocityTendField = Field::create(NormalVelocityTendFieldName, + "Normal velocity tendency", + "m/s^2", + "sea_water_velocity_tendency", + -9.99E+10, + 9.99E+10, + -9.99E+30, + NDims, + DimNamesVelocity); + + auto TendGroupName = "Tendencies"; + auto TendGroup = FieldGroup::create(TendGroupName); + + TendGroup->addField(LayerThicknessTendFieldName); + TendGroup->addField(NormalVelocityTendFieldName); + TendGroup->addField(TracerTendFieldName); + + LayerThicknessTendField->attachData(LayerThicknessTend); + NormalVelocityTendField->attachData(NormalVelocityTend); + TracerTendField->attachData(TracerTend); + +} // end defineFields + //------------------------------------------------------------------------------ // Construct a new group of tendencies Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection + const PressureGrad *PGrad, ///< [in] Pressure gradient + const Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step Config *Options, ///< [in] Configuration options @@ -260,7 +326,8 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies BottomDrag(Mesh, VCoord), TracerDiffusion(Mesh, VCoord), TracerHyperDiff(Mesh, VCoord), TracerHorzAdv(Mesh, VCoord), CustomThicknessTend(InCustomThicknessTend), - CustomVelocityTend(InCustomVelocityTend) { + CustomVelocityTend(InCustomVelocityTend), + EqState(EqState), PGrad(PGrad) { // Tendency arrays LayerThicknessTend = @@ -279,10 +346,12 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection + const PressureGrad *PGrad, ///< [in] Pressure gradient + const Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step Config *Options) ///< [in] Configuration options - : Tendencies(Name, Mesh, VCoord, VAdv, NTracersIn, TimeStepIn, Options, + : Tendencies(Name, Mesh, VCoord, VAdv, PGrad, EqState, NTracersIn, TimeStepIn, Options, CustomTendencyType{}, CustomTendencyType{}) {} //------------------------------------------------------------------------------ @@ -531,6 +600,13 @@ void Tendencies::computeVelocityTendenciesOnly( Pacer::stop("Tend:customVelocityTend", 2); } + if (PGrad->Enabled) { + Pacer::start("Tend:pressureGradTerm", 2); + PGrad->computePressureGrad( + LocNormalVelocityTend, State, VCoord, EqState, VelTimeLevel); + Pacer::stop("Tend:pressureGradTerm", 2); + } + Pacer::stop("Tend:computeVelocityTendenciesOnly", 1); } // end velocity tendency compute diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 9bc4f0584b5a..06e91144d8d4 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -37,6 +37,8 @@ #include "OceanState.h" #include "TendencyTerms.h" #include "TimeMgr.h" +#include "Eos.h" +#include "PGrad.h" #include "VertAdv.h" #include @@ -152,6 +154,8 @@ class Tendencies { const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection + const PressureGrad *PGrad, ///< [in] Pressure gradient + const Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStep, ///< [in] Time step Config *Options, ///< [in] Configuration options @@ -162,18 +166,24 @@ class Tendencies { const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection + const PressureGrad *PGrad, ///< [in] Pressure gradient + const Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStep, ///< [in] Time step Config *Options ///< [in] Configuration options ); + void defineFields(); + // forbid copy and move construction Tendencies(const Tendencies &) = delete; Tendencies(Tendencies &&) = delete; const HorzMesh *Mesh; ///< Pointer to horizontal mesh const VertCoord *VCoord; ///< Pointer to vertical coordinate - VertAdv *VAdv; ///< Pointer to vertical advection + VertAdv *VAdv; ///< Pointer to vertical advection + const PressureGrad *PGrad; ///< Pointer to pressure gradient + const Eos *EqState; ///< Pointer to equation of state I4 NTracers; ///< Number of tracers TimeInterval TimeStep; ///< Time step diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 40afa13e531f..35a78b6888fb 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -5,6 +5,7 @@ #include "Decomp.h" #include "Dimension.h" #include "Error.h" +#include "Eos.h" #include "Field.h" #include "GlobalConstants.h" #include "Halo.h" @@ -16,6 +17,7 @@ #include "OceanTestCommon.h" #include "OmegaKokkos.h" #include "Pacer.h" +#include "PGrad.h" #include "TimeStepper.h" #include "VertCoord.h" #include "mpi.h" @@ -57,8 +59,15 @@ int initState() { auto *VCoord = VertCoord::getDefault(); auto *State = OceanState::getDefault(); - Array2DReal LayerThickCell = State->getLayerThickness(0); - Array2DReal NormalVelEdge = State->getNormalVelocity(0); + // Define tendency fields + int NDims = 2; + std::vector DimNamesThickness(NDims); + DimNamesThickness[0] = "NCells"; + + Array2DReal LayerThickCell; + Array2DReal NormalVelEdge; + Err += State->getLayerThickness(LayerThickCell, 0); + Err += State->getNormalVelocity(NormalVelEdge, 0); Array3DReal TracersArray = Tracers::getAll(0); const auto &TracersCell = TracersArray; @@ -156,13 +165,15 @@ int testTendencies() { const auto Mesh = HorzMesh::getDefault(); const auto VCoord = VertCoord::getDefault(); const auto VAdv = VertAdv::getDefault(); + auto *PGrad = PressureGrad::getDefault(); + auto *EqState = Eos::getInstance(); VCoord->NVertLayers = 12; // test creation of another tendencies TimeInterval ZeroTimeStep; // Zero-length time step placeholder Config *Options = Config::getOmegaConfig(); - Tendencies::create("TestTendencies", Mesh, VCoord, VAdv, 3, ZeroTimeStep, - Options); + Tendencies::create("TestTendencies", Mesh, VCoord, VAdv, PGrad, EqState, 3, + ZeroTimeStep, Options); // test retrievel of another tendencies if (Tendencies::get("TestTendencies")) { diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 95aca280fa42..dc9965139909 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -19,6 +19,7 @@ #include "DataTypes.h" #include "Decomp.h" #include "Dimension.h" +#include "Eos.h" #include "Error.h" #include "Field.h" #include "Halo.h" @@ -29,6 +30,7 @@ #include "OceanState.h" #include "OmegaKokkos.h" #include "Pacer.h" +#include "PGrad.h" #include "TendencyTerms.h" #include "TimeMgr.h" #include "Tracers.h" @@ -173,6 +175,8 @@ int initTimeStepperTest(const std::string &mesh) { auto *DefVAdv = VertAdv::getDefault(); AuxiliaryState::init(); + Eos::init(); + PressureGrad::init(); Tendencies::init(); // finish initializing default time stepper @@ -189,6 +193,8 @@ int initTimeStepperTest(const std::string &mesh) { auto *DefMesh = HorzMesh::getDefault(); auto *DefHalo = Halo::getDefault(); + auto *DefEos = Eos::getInstance(); + auto *DefPGrad = PressureGrad::getDefault(); int NTracers = Tracers::getNumTracers(); const int NTimeLevels = 2; @@ -216,8 +222,9 @@ int initTimeStepperTest(const std::string &mesh) { // Creating non-default tendencies with custom velocity tendencies auto *TestTendencies = Tendencies::create( - "TestTendencies", DefMesh, DefVertCoord, DefVAdv, NTracers, ZeroTimeStep, - &Options, Tendencies::CustomTendencyType{}, DecayVelocityTendency{}); + "TestTendencies", DefMesh, DefVertCoord, DefVAdv, DefPGrad, DefEos, NTracers, + ZeroTimeStep, &Options, Tendencies::CustomTendencyType{}, + DecayVelocityTendency{}); if (!TestTendencies) { Err++; LOG_ERROR("TimeStepperTest: error creating test tendencies"); From 73966d0c8f732e32d5f905dfaccb13ae4cf48e81 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 6 Jan 2026 11:13:24 -0800 Subject: [PATCH 118/152] Fix Driver and Timestepper ctests --- components/omega/src/ocn/OceanFinal.cpp | 4 ++++ components/omega/src/ocn/OceanInit.cpp | 4 ++++ components/omega/test/timeStepping/TimeStepperTest.cpp | 2 ++ 3 files changed, 10 insertions(+) diff --git a/components/omega/src/ocn/OceanFinal.cpp b/components/omega/src/ocn/OceanFinal.cpp index 3715e95baef6..a518db52a175 100644 --- a/components/omega/src/ocn/OceanFinal.cpp +++ b/components/omega/src/ocn/OceanFinal.cpp @@ -7,6 +7,7 @@ #include "AuxiliaryState.h" #include "Decomp.h" +#include "Eos.h" #include "Field.h" #include "Halo.h" #include "HorzMesh.h" @@ -14,6 +15,7 @@ #include "MachEnv.h" #include "OceanDriver.h" #include "OceanState.h" +#include "PGrad.h" #include "Tendencies.h" #include "TimeMgr.h" #include "TimeStepper.h" @@ -33,6 +35,8 @@ int ocnFinalize(const TimeInstant &CurrTime ///< [in] current sim time // clean up all objects Tracers::clear(); TimeStepper::clear(); + PressureGrad::clear(); + Eos::destroyInstance(); Tendencies::clear(); AuxiliaryState::clear(); OceanState::clear(); diff --git a/components/omega/src/ocn/OceanInit.cpp b/components/omega/src/ocn/OceanInit.cpp index b5c21aa1508f..022b18d5b498 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -11,6 +11,7 @@ #include "Config.h" #include "DataTypes.h" #include "Decomp.h" +#include "Eos.h" #include "Error.h" #include "Field.h" #include "Halo.h" @@ -22,6 +23,7 @@ #include "OceanDriver.h" #include "OceanState.h" #include "Pacer.h" +#include "PGrad.h" #include "Tendencies.h" #include "TimeMgr.h" #include "TimeStepper.h" @@ -134,6 +136,8 @@ int initOmegaModules(MPI_Comm Comm) { Tracers::init(); VertAdv::init(); AuxiliaryState::init(); + PressureGrad::init(); + Eos::init(); Tendencies::init(); TimeStepper::init2(); diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index dc9965139909..1d3ce044b1d2 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -282,6 +282,8 @@ void timeLoop(TimeInstant TimeStart, Real TimeEnd) { void finalizeTimeStepperTest() { Tracers::clear(); TimeStepper::clear(); + PressureGrad::clear(); + Eos::destroyInstance(); Tendencies::clear(); AuxiliaryState::clear(); OceanState::clear(); From eb07a5571d640ffe154144d6875aaad916bb912c Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 8 Jan 2026 09:04:36 -0800 Subject: [PATCH 119/152] Fix Tendencies ctest --- components/omega/test/ocn/TendenciesTest.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 35a78b6888fb..6a0b82f687cf 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -1,6 +1,7 @@ #include "Tendencies.h" #include "AuxiliaryState.h" #include "Config.h" +#include "CustomTendencyTerms.h" #include "DataTypes.h" #include "Decomp.h" #include "Dimension.h" @@ -147,6 +148,7 @@ int initTendenciesTest(const std::string &mesh) { int testTendencies() { int Err = 0; + Error Err1; // test initialization Tendencies::init(); @@ -165,15 +167,20 @@ int testTendencies() { const auto Mesh = HorzMesh::getDefault(); const auto VCoord = VertCoord::getDefault(); const auto VAdv = VertAdv::getDefault(); - auto *PGrad = PressureGrad::getDefault(); - auto *EqState = Eos::getInstance(); + const auto PGrad = PressureGrad::getDefault(); + const auto EqState = Eos::getInstance(); VCoord->NVertLayers = 12; + // test creation of another tendencies TimeInterval ZeroTimeStep; // Zero-length time step placeholder Config *Options = Config::getOmegaConfig(); - Tendencies::create("TestTendencies", Mesh, VCoord, VAdv, PGrad, EqState, 3, - ZeroTimeStep, Options); + Config TendConfig("Tendencies"); + Err1 = Options->get(TendConfig); + int NTracersTest = 3; + + Tendencies::create("TestTendencies", Mesh, VCoord, VAdv, PGrad, EqState, + NTracersTest, ZeroTimeStep, &TendConfig); // test retrievel of another tendencies if (Tendencies::get("TestTendencies")) { @@ -246,6 +253,8 @@ int testTendencies() { void finalizeTendenciesTest() { Tracers::clear(); + PressureGrad::clear(); + Eos::destroyInstance(); AuxiliaryState::clear(); OceanState::clear(); VertAdv::clear(); From 7b4fad6e19cec9d9085d2be9a8d8d9d0ecc289de Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 8 Jan 2026 09:06:15 -0800 Subject: [PATCH 120/152] Fix some rebase issues - EdgeMask moved to VertCoord = Density0 no longer in omega.yaml --- components/omega/src/ocn/PGrad.cpp | 4 ++-- components/omega/test/ocn/PGradTest.cpp | 18 +++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 0fc1a4b022cc..286b13bfe75e 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -215,7 +215,7 @@ PressureGradCentered::PressureGradCentered(const HorzMesh *Mesh, ///< [in] Horiz const VertCoord *VCoord ///< [in] Vertical coordinate ) : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), - EdgeMask(Mesh->EdgeMask), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + EdgeMask(VCoord->EdgeMask), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} //------------------------------------------------------------------------------ @@ -224,7 +224,7 @@ PressureGradHighOrder::PressureGradHighOrder(const HorzMesh *Mesh, ///< [in] Hor const VertCoord *VCoord ///< [in] Vertical coordinate ) : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), - EdgeMask(Mesh->EdgeMask), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + EdgeMask(VCoord->EdgeMask), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} } // namespace OMEGA diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index abd7cca99d89..fb5e34f6af26 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -13,6 +13,7 @@ #include "Error.h" #include "Eos.h" #include "Field.h" +#include "GlobalConstants.h" #include "Halo.h" #include "HorzMesh.h" #include "IO.h" @@ -66,14 +67,11 @@ void initPGradTest() { "PGrad: error initializing default halo"); } - // Begin initialization of the default vertical coordinate - VertCoord::init1(); - // Initialize the default mesh HorzMesh::init(); - // Complete initialization of the default vertical coordinate - VertCoord::init2(); + // Initialize the default vertical coordinate + VertCoord::init(); // Initialize the equation of state Eos::init(); @@ -145,7 +143,7 @@ int main(int argc, char *argv[]) { auto &CellsOnEdge = DefMesh->CellsOnEdge; auto &DcEdge = DefMesh->DcEdge; - auto &EdgeMask = DefMesh->EdgeMask; + auto &EdgeMask = VCoord->EdgeMask; parallelFor({NEdgesAll}, KOKKOS_LAMBDA(int i) { CellsOnEdge(i, 0)= 0; CellsOnEdge(i, 1)= 1; @@ -154,13 +152,7 @@ int main(int argc, char *argv[]) { // Fetch reference desnity from Config Real Density0; - Error ErrorCode; - Config TendConfig("Tendencies"); - ErrorCode.reset(); - ErrorCode += Options->get(TendConfig); - CHECK_ERROR_ABORT(ErrorCode, "VertCoord: Tendencies group not found in Config"); - ErrorCode += TendConfig.get("Density0", Density0); - CHECK_ERROR_ABORT(ErrorCode, "VertCoord: Density0 not found in TendConfig"); + Density0 = RhoSw; I4 TimeLevel = 0; From 8860b31f859779d88bfa29df1af72340b98a68ab Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 8 Jan 2026 09:08:53 -0800 Subject: [PATCH 121/152] Compute BruntVaisalaFreqSq with input pressure in Pa --- components/omega/src/ocn/Eos.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/components/omega/src/ocn/Eos.h b/components/omega/src/ocn/Eos.h index 4f10d75c8fec..06c9695f7267 100644 --- a/components/omega/src/ocn/Eos.h +++ b/components/omega/src/ocn/Eos.h @@ -44,7 +44,6 @@ class Teos10Eos { I4 KDisp) const { Real SpecVolPCoeffs[6 * VecLength]; - Real PaToDBar = 1.0e-4_Real; // Conversion factor from Pa to dbar const I4 KStart = chunkStart(KChunk, MinLayerCell(ICell)); const I4 KLen = chunkLength(KChunk, KStart, MaxLayerCell(ICell)); @@ -63,15 +62,15 @@ class Teos10Eos { if (KDisp == 0) { // No displacement SpecVol(ICell, K) = - calcRefProfile(Pressure(ICell, K) * PaToDBar) + - calcDelta(SpecVolPCoeffs, KVec, Pressure(ICell, K) * PaToDBar); + calcRefProfile(Pressure(ICell, K) * Pa2Db) + + calcDelta(SpecVolPCoeffs, KVec, Pressure(ICell, K) * Pa2Db); } else { // Displacement, use the displaced pressure I4 KTmp = Kokkos::min(K + KDisp, MaxLayerCell(ICell)); KTmp = Kokkos::max(MinLayerCell(ICell), KTmp); SpecVol(ICell, K) = - calcRefProfile(Pressure(ICell, KTmp) * PaToDBar) + - calcDelta(SpecVolPCoeffs, KVec, Pressure(ICell, KTmp) * PaToDBar); + calcRefProfile(Pressure(ICell, KTmp) * Pa2Db) + + calcDelta(SpecVolPCoeffs, KVec, Pressure(ICell, KTmp) * Pa2Db); } } } @@ -312,15 +311,15 @@ class Teos10BruntVaisalaFreqSq { Real PInt = 0.5_Real * (Pressure(ICell, K) + Pressure(ICell, K - 1)); Real SpInt = 0.5_Real * (SpecVol(ICell, K) + SpecVol(ICell, K - 1)); - Real AlphaInt = calcAlpha(SaInt, CtInt, PInt, SpInt); - Real BetaInt = calcBeta(SaInt, CtInt, PInt, SpInt); + Real AlphaInt = calcAlpha(SaInt, CtInt, PInt * Pa2Db, SpInt); + Real BetaInt = calcBeta(SaInt, CtInt, PInt * Pa2Db, SpInt); Real DSa = AbsSalinity(ICell, K) - AbsSalinity(ICell, K - 1); Real DCt = ConservTemp(ICell, K) - ConservTemp(ICell, K - 1); Real DP = Pressure(ICell, K) - Pressure(ICell, K - 1); BruntVaisalaFreqSq(ICell, K) = Gravity * Gravity * (BetaInt * DSa - AlphaInt * DCt) / - (SpInt * Db2Pa * DP); + (SpInt * DP); } } } From ba644920021054afda6cc73934193aff33adfbb1 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 8 Jan 2026 09:09:45 -0800 Subject: [PATCH 122/152] Fix EosTest, pressure dbar -> Pa conversion --- components/omega/test/ocn/EosTest.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/components/omega/test/ocn/EosTest.cpp b/components/omega/test/ocn/EosTest.cpp index 1b507781e8e9..5797b77798d2 100644 --- a/components/omega/test/ocn/EosTest.cpp +++ b/components/omega/test/ocn/EosTest.cpp @@ -53,11 +53,10 @@ const Real GswBVFExpValue = /// Test input values const Real Sa = 30.0; // Absolute Salinity in g/kg const Real Ct = 10.0; // Conservative Temperature in degC -const Real P = 1000.0; // Pressure in dbar +const Real P = 1000.0 * Db2Pa; // Pressure in Pa const I4 KDisp = 1; // Displate parcel to K=1 for TEOS-10 eos const Real RTol = 1e-10; // Relative tolerance for isApprox checks -double PaToDBar = 1e-4; // Conversion factor from Pa to dbar /// The initialization routine for Eos testing. It calls various /// init routines, including the creation of the default decomposition. @@ -120,7 +119,7 @@ void testEosLinear() { /// Use Kokkos::deep_copy to fill the entire view with the ref value deepCopy(SArray, Sa); deepCopy(TArray, Ct); - deepCopy(PArray, P / PaToDBar); + deepCopy(PArray, P); deepCopy(TestEos->SpecVol, 0.0); /// Compute specific volume @@ -190,7 +189,7 @@ void testEosLinearDisplaced() { /// Use Kokkos::deep_copy to fill the entire view with the ref value deepCopy(SArray, Sa); deepCopy(TArray, Ct); - deepCopy(PArray, P / PaToDBar); + deepCopy(PArray, P); deepCopy(TestEos->SpecVolDisplaced, 0.0); /// Compute displaced specific volume @@ -385,7 +384,7 @@ void testEosTeos10() { /// Use Kokkos::deep_copy to fill the entire view with the ref value deepCopy(SArray, Sa); deepCopy(TArray, Ct); - deepCopy(PArray, P / PaToDBar); + deepCopy(PArray, P); deepCopy(TestEos->SpecVol, 0.0); /// Compute specific volume @@ -455,7 +454,7 @@ void testEosTeos10Displaced() { /// Use Kokkos::deep_copy to fill the entire view with the ref value deepCopy(SArray, Sa); deepCopy(TArray, Ct); - deepCopy(PArray, P / PaToDBar); + deepCopy(PArray, P); deepCopy(TestEos->SpecVolDisplaced, 0.0); /// Compute displaced specific volume @@ -541,17 +540,17 @@ void testBruntVaisalaFreqSqTeos10() { ZMid(ICell, 1) = -993.1071379053125_Real; SArray(ICell, 1) = Sa; TArray(ICell, 1) = Ct + 10.0_Real; - PArray(ICell, 1) = P + 1.0_Real; + PArray(ICell, 1) = (P * Pa2Db + 1.0_Real) * Db2Pa; } else if (K == 2) { ZMid(ICell, 2) = -994.0968821072275_Real; SArray(ICell, 2) = Sa + 1.0_Real; TArray(ICell, 2) = Ct + 5.0_Real; - PArray(ICell, K) = P + 2.0_Real; + PArray(ICell, K) = (P * Pa2Db + 2.0_Real) * Db2Pa; } else { // fill rest with valid junk to avoid Nans and Inf ZMid(ICell, K) = -994.0968821072275_Real - 0.1_Real * K; SArray(ICell, K) = Sa + 1.0_Real + 0.1_Real * K; TArray(ICell, K) = Ct + 5.0_Real - 0.01_Real * K; - PArray(ICell, K) = P + 2.0_Real + 0.1_Real * K; + PArray(ICell, K) = (P * Pa2Db + 2.0_Real + 0.1_Real * K) * Db2Pa; } }); @@ -648,7 +647,7 @@ void checkValueGswcSpecVol() { const Real RTol = 1e-10; /// Get specific volume from GSW-C library - double SpecVol = gsw_specvol(Sa, Ct, P); + double SpecVol = gsw_specvol(Sa, Ct, P * Pa2Db); /// Check the value against the expected TEOS-10 value bool Check = isApprox(SpecVol, TeosSVExpValue, RTol); if (!Check) { @@ -669,7 +668,7 @@ void checkValueGswcN2() { double Salt[4] = {Sa - 1.0, Sa, Sa + 1.0}; // Absolute Salinity (g/kg) double Temp[4] = {Ct + 15.0, Ct + 10.0, Ct + 5.0}; // Conservative Temperature (deg C) - double Press[4] = {P, P + 1.0, P + 2.0}; // Pressure (dbar) + double Press[4] = {P * Pa2Db, P * Pa2Db + 1.0, P * Pa2Db + 2.0}; // Pressure (dbar) // Latitude (degrees north) double Latitude[4] = {0.0, 0.0, 0.0}; From 2ab7c8a7a2b3f68461b84d70371569d81a138a46 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 8 Jan 2026 14:09:01 -0800 Subject: [PATCH 123/152] Fix Tendencies ctest --- components/omega/test/ocn/TendenciesTest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 6a0b82f687cf..60591ef7331b 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -134,6 +134,8 @@ int initTendenciesTest(const std::string &mesh) { VertCoord::init(); Tracers::init(); VertAdv::init(); + PressureGrad::init(); + Eos::init(); int StateErr = OceanState::init(); if (StateErr != 0) { From e07dade600320b454747ed7ef0e94c8e1e785f72 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 10 Feb 2026 14:31:28 -0800 Subject: [PATCH 124/152] Add computation of SpecVol to Tendencies - Requires compptation of pressure --- components/omega/src/ocn/PGrad.cpp | 1 + components/omega/src/ocn/Tendencies.cpp | 27 +++++++++++++++++++++---- components/omega/src/ocn/Tendencies.h | 12 +++++------ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 286b13bfe75e..f2afcdf36557 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -6,6 +6,7 @@ //===----------------------------------------------------------------------===// #include "PGrad.h" +#include "Eos.h" #include "Error.h" #include "Field.h" #include "HorzMesh.h" diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 4aa68b2ea998..2d6bf11f01a2 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -310,10 +310,10 @@ void Tendencies::defineFields() { // Construct a new group of tendencies Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord, ///< [in] Vertical coordinate + VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection const PressureGrad *PGrad, ///< [in] Pressure gradient - const Eos *EqState, ///< [in] Equation of state + Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step Config *Options, ///< [in] Configuration options @@ -340,14 +340,16 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies NTracers = NTracersIn; TimeStep = TimeStepIn; + defineFields(); + } // end constructor Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord, ///< [in] Vertical coordinate + VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection const PressureGrad *PGrad, ///< [in] Pressure gradient - const Eos *EqState, ///< [in] Equation of state + Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step Config *Options) ///< [in] Configuration options @@ -600,8 +602,25 @@ void Tendencies::computeVelocityTendenciesOnly( Pacer::stop("Tend:customVelocityTend", 2); } + // Compute pressure gradient if (PGrad->Enabled) { + I4 Err; + Array2DReal Temp; + Array2DReal Salinity; + Array2DReal LayerThick; + + // Temporary handling of surface pressure + Array1DReal SurfacePressure("SurfacePressure", Mesh->NCellsSize); + deepCopy(SurfacePressure, 0.0_Real); + Pacer::start("Tend:pressureGradTerm", 2); + State->getLayerThickness(LayerThick, VelTimeLevel); + VCoord->computePressure(LayerThick, SurfacePressure); + OMEGA_SCOPE(LocPressureMid, VCoord->PressureMid); + Err = Tracers::getByName(Temp, VelTimeLevel, "Temperature"); + Err = Tracers::getByName(Salinity, VelTimeLevel, "Salinity"); + + EqState->computeSpecVol(Temp, Salinity, LocPressureMid); PGrad->computePressureGrad( LocNormalVelocityTend, State, VCoord, EqState, VelTimeLevel); Pacer::stop("Tend:pressureGradTerm", 2); diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 06e91144d8d4..5cbd565c6f6f 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -152,10 +152,10 @@ class Tendencies { // Construct a new tendency object Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord, ///< [in] Vertical coordinate + VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection const PressureGrad *PGrad, ///< [in] Pressure gradient - const Eos *EqState, ///< [in] Equation of state + Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStep, ///< [in] Time step Config *Options, ///< [in] Configuration options @@ -164,10 +164,10 @@ class Tendencies { Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord, ///< [in] Vertical coordinate + VertCoord *VCoord, ///< [in] Vertical coordinate VertAdv *VAdv, ///< [in] Vertical advection const PressureGrad *PGrad, ///< [in] Pressure gradient - const Eos *EqState, ///< [in] Equation of state + Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStep, ///< [in] Time step Config *Options ///< [in] Configuration options @@ -180,10 +180,10 @@ class Tendencies { Tendencies(Tendencies &&) = delete; const HorzMesh *Mesh; ///< Pointer to horizontal mesh - const VertCoord *VCoord; ///< Pointer to vertical coordinate + VertCoord *VCoord; ///< Pointer to vertical coordinate VertAdv *VAdv; ///< Pointer to vertical advection const PressureGrad *PGrad; ///< Pointer to pressure gradient - const Eos *EqState; ///< Pointer to equation of state + Eos *EqState; ///< Pointer to equation of state I4 NTracers; ///< Number of tracers TimeInterval TimeStep; ///< Time step From ee875b6a56636d2103860dc103f92cfe40057854 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 10 Feb 2026 15:04:14 -0800 Subject: [PATCH 125/152] Read PressureGradTendencyEnable and set PGrad enable flag --- components/omega/src/ocn/Tendencies.cpp | 19 ++++++++++++------- components/omega/src/ocn/Tendencies.h | 20 ++++++++++---------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 2d6bf11f01a2..0f3be0956644 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -243,6 +243,11 @@ void Tendencies::readConfig(Config *OmegaConfig ///< [in] Omega config Err += TendConfig.get("EddyDiff4", this->TracerHyperDiff.EddyDiff4); CHECK_ERROR_ABORT(Err, "Tendencies: EddyDiff4 not found in TendConfig"); } + + Err += TendConfig->get("PressureGradTendencyEnable", + this->PGrad->Enabled); + CHECK_ERROR_ABORT( + Err, "Tendencies: PressureGradTendencyEnable not found in TendConfig"); } //------------------------------------------------------------------------------ @@ -310,9 +315,9 @@ void Tendencies::defineFields() { // Construct a new group of tendencies Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - const PressureGrad *PGrad, ///< [in] Pressure gradient + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step @@ -346,9 +351,9 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - const PressureGrad *PGrad, ///< [in] Pressure gradient + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step @@ -608,7 +613,7 @@ void Tendencies::computeVelocityTendenciesOnly( Array2DReal Temp; Array2DReal Salinity; Array2DReal LayerThick; - + // Temporary handling of surface pressure Array1DReal SurfacePressure("SurfacePressure", Mesh->NCellsSize); deepCopy(SurfacePressure, 0.0_Real); diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 5cbd565c6f6f..68ebefca3af4 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -152,9 +152,9 @@ class Tendencies { // Construct a new tendency object Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - const PressureGrad *PGrad, ///< [in] Pressure gradient + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStep, ///< [in] Time step @@ -164,9 +164,9 @@ class Tendencies { Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - const PressureGrad *PGrad, ///< [in] Pressure gradient + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStep, ///< [in] Time step @@ -180,10 +180,10 @@ class Tendencies { Tendencies(Tendencies &&) = delete; const HorzMesh *Mesh; ///< Pointer to horizontal mesh - VertCoord *VCoord; ///< Pointer to vertical coordinate - VertAdv *VAdv; ///< Pointer to vertical advection - const PressureGrad *PGrad; ///< Pointer to pressure gradient - Eos *EqState; ///< Pointer to equation of state + VertCoord *VCoord; ///< Pointer to vertical coordinate + VertAdv *VAdv; ///< Pointer to vertical advection + PressureGrad *PGrad; ///< Pointer to pressure gradient + Eos *EqState; ///< Pointer to equation of state I4 NTracers; ///< Number of tracers TimeInterval TimeStep; ///< Time step From 91c6a74858db186b7ddc87cca91fd6b2bfdadef8 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Tue, 17 Feb 2026 07:57:06 -0600 Subject: [PATCH 126/152] Lintng of Tendencies.cpp --- components/omega/src/ocn/Tendencies.cpp | 132 ++++++++++-------------- 1 file changed, 57 insertions(+), 75 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 0f3be0956644..216e68227fad 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -77,8 +77,8 @@ void Tendencies::init() { // Ceate default tendencies Tendencies::DefaultTendencies = - create("Default", DefHorzMesh, DefVertCoord, DefVertAdv, DefPGrad, DefEos, NTracers, - TimeStep, &TendConfig, CustomThickTend, CustomVelTend); + create("Default", DefHorzMesh, DefVertCoord, DefVertAdv, DefPGrad, DefEos, + NTracers, TimeStep, &TendConfig, CustomThickTend, CustomVelTend); DefaultTendencies->readConfig(OmegaConfig); @@ -244,8 +244,7 @@ void Tendencies::readConfig(Config *OmegaConfig ///< [in] Omega config CHECK_ERROR_ABORT(Err, "Tendencies: EddyDiff4 not found in TendConfig"); } - Err += TendConfig->get("PressureGradTendencyEnable", - this->PGrad->Enabled); + Err += TendConfig->get("PressureGradTendencyEnable", this->PGrad->Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: PressureGradTendencyEnable not found in TendConfig"); } @@ -253,72 +252,56 @@ void Tendencies::readConfig(Config *OmegaConfig ///< [in] Omega config //------------------------------------------------------------------------------ // Define fields associated with tendencies void Tendencies::defineFields() { - auto LayerThicknessTendFieldName = "LayerThicknessTend"; - auto NormalVelocityTendFieldName = "NormalVelocityTend"; - auto TracerTendFieldName = "TracerTend"; - - int NDims = 2; - std::vector DimNamesThickness(NDims); - DimNamesThickness[0] = "NCells"; - DimNamesThickness[1] = "NVertLayers"; - auto LayerThicknessTendField = Field::create(LayerThicknessTendFieldName, - "Layer thickness tendency", - "m/s", - "cell_thickness_tendency", - -9.99E+10, - 9.99E+10, - -9.99E+30, - NDims, - DimNamesThickness); - NDims = 3; - std::vector DimNamesTracer(NDims); - DimNamesTracer[0] = "NTracers"; - DimNamesTracer[1] = "NCells"; - DimNamesTracer[2] = "NVertLayers"; - auto TracerTendField = Field::create(TracerTendFieldName, - "Tracer tendency", - "kg/m^3/s", - "tracer_tendency", - -9.99E+10, - 9.99E+10, - -9.99E+30, - NDims, - DimNamesTracer); - NDims = 2; - std::vector DimNamesVelocity(NDims); - DimNamesVelocity[0] = "NEdges"; - DimNamesVelocity[1] = "NVertLayers"; - auto NormalVelocityTendField = Field::create(NormalVelocityTendFieldName, - "Normal velocity tendency", - "m/s^2", - "sea_water_velocity_tendency", - -9.99E+10, - 9.99E+10, - -9.99E+30, - NDims, - DimNamesVelocity); - - auto TendGroupName = "Tendencies"; - auto TendGroup = FieldGroup::create(TendGroupName); - - TendGroup->addField(LayerThicknessTendFieldName); - TendGroup->addField(NormalVelocityTendFieldName); - TendGroup->addField(TracerTendFieldName); - - LayerThicknessTendField->attachData(LayerThicknessTend); - NormalVelocityTendField->attachData(NormalVelocityTend); - TracerTendField->attachData(TracerTend); - -} // end defineFields + auto LayerThicknessTendFieldName = "LayerThicknessTend"; + auto NormalVelocityTendFieldName = "NormalVelocityTend"; + auto TracerTendFieldName = "TracerTend"; + + int NDims = 2; + std::vector DimNamesThickness(NDims); + DimNamesThickness[0] = "NCells"; + DimNamesThickness[1] = "NVertLayers"; + auto LayerThicknessTendField = + Field::create(LayerThicknessTendFieldName, "Layer thickness tendency", + "m/s", "cell_thickness_tendency", -9.99E+10, 9.99E+10, + -9.99E+30, NDims, DimNamesThickness); + NDims = 3; + std::vector DimNamesTracer(NDims); + DimNamesTracer[0] = "NTracers"; + DimNamesTracer[1] = "NCells"; + DimNamesTracer[2] = "NVertLayers"; + auto TracerTendField = Field::create( + TracerTendFieldName, "Tracer tendency", "kg/m^3/s", "tracer_tendency", + -9.99E+10, 9.99E+10, -9.99E+30, NDims, DimNamesTracer); + NDims = 2; + std::vector DimNamesVelocity(NDims); + DimNamesVelocity[0] = "NEdges"; + DimNamesVelocity[1] = "NVertLayers"; + auto NormalVelocityTendField = + Field::create(NormalVelocityTendFieldName, "Normal velocity tendency", + "m/s^2", "sea_water_velocity_tendency", -9.99E+10, + 9.99E+10, -9.99E+30, NDims, DimNamesVelocity); + + auto TendGroupName = "Tendencies"; + auto TendGroup = FieldGroup::create(TendGroupName); + + TendGroup->addField(LayerThicknessTendFieldName); + TendGroup->addField(NormalVelocityTendFieldName); + TendGroup->addField(TracerTendFieldName); + + LayerThicknessTendField->attachData(LayerThicknessTend); + NormalVelocityTendField->attachData(NormalVelocityTend); + TracerTendField->attachData(TracerTend); + +} // end defineFields //------------------------------------------------------------------------------ // Construct a new group of tendencies Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - PressureGrad *PGrad, ///< [in] Pressure gradient - Eos *EqState, ///< [in] Equation of state + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient + Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step Config *Options, ///< [in] Configuration options @@ -331,8 +314,7 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies BottomDrag(Mesh, VCoord), TracerDiffusion(Mesh, VCoord), TracerHyperDiff(Mesh, VCoord), TracerHorzAdv(Mesh, VCoord), CustomThicknessTend(InCustomThicknessTend), - CustomVelocityTend(InCustomVelocityTend), - EqState(EqState), PGrad(PGrad) { + CustomVelocityTend(InCustomVelocityTend), EqState(EqState), PGrad(PGrad) { // Tendency arrays LayerThicknessTend = @@ -351,10 +333,10 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - PressureGrad *PGrad, ///< [in] Pressure gradient - Eos *EqState, ///< [in] Equation of state + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient + Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step Config *Options) ///< [in] Configuration options @@ -613,7 +595,7 @@ void Tendencies::computeVelocityTendenciesOnly( Array2DReal Temp; Array2DReal Salinity; Array2DReal LayerThick; - + // Temporary handling of surface pressure Array1DReal SurfacePressure("SurfacePressure", Mesh->NCellsSize); deepCopy(SurfacePressure, 0.0_Real); @@ -624,10 +606,10 @@ void Tendencies::computeVelocityTendenciesOnly( OMEGA_SCOPE(LocPressureMid, VCoord->PressureMid); Err = Tracers::getByName(Temp, VelTimeLevel, "Temperature"); Err = Tracers::getByName(Salinity, VelTimeLevel, "Salinity"); - + EqState->computeSpecVol(Temp, Salinity, LocPressureMid); - PGrad->computePressureGrad( - LocNormalVelocityTend, State, VCoord, EqState, VelTimeLevel); + PGrad->computePressureGrad(LocNormalVelocityTend, State, VCoord, EqState, + VelTimeLevel); Pacer::stop("Tend:pressureGradTerm", 2); } From 2f9bb86c72e7ea471b6be045556aa2500090b3d9 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Tue, 17 Feb 2026 07:57:56 -0600 Subject: [PATCH 127/152] Add temporary call to computeZHeight() in pgrad tendency --- components/omega/src/ocn/Tendencies.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 216e68227fad..9625c6853c37 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -607,6 +607,10 @@ void Tendencies::computeVelocityTendenciesOnly( Err = Tracers::getByName(Temp, VelTimeLevel, "Temperature"); Err = Tracers::getByName(Salinity, VelTimeLevel, "Salinity"); + // Temporary: ensure vertical geometric/geopotential fields are updated + // for pressure-gradient tendency calculations. + VCoord->computeZHeight(LayerThick, EqState->SpecVol); + EqState->computeSpecVol(Temp, Salinity, LocPressureMid); PGrad->computePressureGrad(LocNormalVelocityTend, State, VCoord, EqState, VelTimeLevel); From ad892fa36ff4d3e62a2bc84f75ffc65993cefc5e Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Tue, 17 Feb 2026 11:46:12 -0600 Subject: [PATCH 128/152] Compute geometric z after specific volume --- components/omega/src/ocn/Tendencies.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 9625c6853c37..809c80c98a4b 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -607,11 +607,12 @@ void Tendencies::computeVelocityTendenciesOnly( Err = Tracers::getByName(Temp, VelTimeLevel, "Temperature"); Err = Tracers::getByName(Salinity, VelTimeLevel, "Salinity"); + EqState->computeSpecVol(Temp, Salinity, LocPressureMid); + // Temporary: ensure vertical geometric/geopotential fields are updated // for pressure-gradient tendency calculations. VCoord->computeZHeight(LayerThick, EqState->SpecVol); - EqState->computeSpecVol(Temp, Salinity, LocPressureMid); PGrad->computePressureGrad(LocNormalVelocityTend, State, VCoord, EqState, VelTimeLevel); Pacer::stop("Tend:pressureGradTerm", 2); From f541d5de6e305a3fd47ec1a2d5e42020c7e820ca Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Tue, 17 Feb 2026 11:50:31 -0600 Subject: [PATCH 129/152] Lint pressure-gradient code --- components/omega/src/ocn/PGrad.cpp | 48 +++++++++++----------- components/omega/src/ocn/PGrad.h | 65 +++++++++++++++++------------- 2 files changed, 60 insertions(+), 53 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index f2afcdf36557..5d9c2056bd37 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -10,8 +10,8 @@ #include "Error.h" #include "Field.h" #include "HorzMesh.h" -#include "VertCoord.h" #include "OmegaKokkos.h" +#include "VertCoord.h" namespace OMEGA { @@ -76,15 +76,17 @@ PressureGrad::PressureGrad( const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate Config *Options) ///< [in] Configuration options - : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), EdgeSignOnCell(Mesh->EdgeSignOnCell), - MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), - CenteredPGrad(Mesh, VCoord), HighOrderPGrad(Mesh, VCoord) { + : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), + EdgeSignOnCell(Mesh->EdgeSignOnCell), + MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), CenteredPGrad(Mesh, VCoord), + HighOrderPGrad(Mesh, VCoord) { // store mesh sizes - NEdgesAll = Mesh->NEdgesAll; - NVertLayers = VCoord->NVertLayers; + NEdgesAll = Mesh->NEdgesAll; + NVertLayers = VCoord->NVertLayers; NVertLayersP1 = NVertLayers + 1; - NChunks = NVertLayers / VecLength; + NChunks = NVertLayers / VecLength; // Read config options for PressureGrad type // and enable the appropriate functor @@ -175,16 +177,14 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, parallelForOuter( "pgrad-centered", {NEdgesAll}, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { - const int KMin = LocMinLayerEdgeBot(IEdge); - const int KMax = LocMaxLayerEdgeTop(IEdge); + const int KMin = LocMinLayerEdgeBot(IEdge); + const int KMax = LocMaxLayerEdgeTop(IEdge); const int KRange = vertRange(KMin, KMax); parallelForInner( - Team, KRange, - INNER_LAMBDA(int KChunk) { + Team, KRange, INNER_LAMBDA(int KChunk) { LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, - PressureInterface, ZInterface, - LayerThick, + PressureInterface, ZInterface, LayerThick, SpecVol); }); }); @@ -195,35 +195,35 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, parallelForOuter( "pgrad-highorder", {NEdgesAll}, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { - const int KMin = MinLayerEdgeBot(IEdge); - const int KMax = MaxLayerEdgeTop(IEdge); + const int KMin = MinLayerEdgeBot(IEdge); + const int KMax = MaxLayerEdgeTop(IEdge); const int KRange = vertRange(KMin, KMax); parallelForInner( - Team, KRange, - INNER_LAMBDA(int KChunk) { + Team, KRange, INNER_LAMBDA(int KChunk) { LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, Geopotential, SpecVol); }); }); - } } // end compute pressure gradient //------------------------------------------------------------------------------ // Constructor for centered pressure gradient functor -PressureGradCentered::PressureGradCentered(const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord ///< [in] Vertical coordinate - ) +PressureGradCentered::PressureGradCentered( + const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord ///< [in] Vertical coordinate + ) : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), EdgeMask(VCoord->EdgeMask), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} //------------------------------------------------------------------------------ // Constructor for high order pressure gradient functor -PressureGradHighOrder::PressureGradHighOrder(const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord ///< [in] Vertical coordinate - ) +PressureGradHighOrder::PressureGradHighOrder( + const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord ///< [in] Vertical coordinate + ) : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), EdgeMask(VCoord->EdgeMask), MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop) {} diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 31e9386fc36c..629059c9cfec 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -26,9 +26,9 @@ class PressureGradCentered { bool Enabled; // constructor declaration - PressureGradCentered(const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord ///< [in] Vertical coordinate - ); + PressureGradCentered(const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord ///< [in] Vertical coordinate + ); // Compute centered pressure gradient contribution for given edge and // vertical chunk. This appends results into the Tend array (in-place). @@ -37,34 +37,43 @@ class PressureGradCentered { const Array2DReal &PressureInterface, const Array2DReal &ZInterface, const Array2DReal &LayerThick, - const Array2DReal &SpecVol - ) const { + const Array2DReal &SpecVol) const { const I4 KStart = chunkStart(KChunk, MinLayerEdgeBot(IEdge)); const I4 KLen = chunkLength(KChunk, KStart, MaxLayerEdgeTop(IEdge)); - + const I4 ICell0 = CellsOnEdge(IEdge, 0); const I4 ICell1 = CellsOnEdge(IEdge, 1); const Real InvDcEdge = 1.0_Real / DcEdge(IEdge); - Real Rho0 = 1026.0_Real; - Real Gravity = 9.80616_Real; + Real Rho0 = 1026.0_Real; + Real Gravity = 9.80616_Real; for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; - Real MontPotCell0K = PressureInterface(ICell0, K) * SpecVol(ICell0, K) + Gravity * ZInterface(ICell0, K); - Real MontPotCell1K = PressureInterface(ICell1, K) * SpecVol(ICell1, K) + Gravity * ZInterface(ICell1, K); + Real MontPotCell0K = + PressureInterface(ICell0, K) * SpecVol(ICell0, K) + + Gravity * ZInterface(ICell0, K); + Real MontPotCell1K = + PressureInterface(ICell1, K) * SpecVol(ICell1, K) + + Gravity * ZInterface(ICell1, K); Real GradMontPotK = (MontPotCell1K - MontPotCell0K) * InvDcEdge; - Real MontPotCell0Kp1 = PressureInterface(ICell0, K+1) * SpecVol(ICell0, K) + Gravity * ZInterface(ICell0, K+1); - Real MontPotCell1Kp1 = PressureInterface(ICell1, K+1) * SpecVol(ICell1, K) + Gravity * ZInterface(ICell1, K+1); + Real MontPotCell0Kp1 = + PressureInterface(ICell0, K + 1) * SpecVol(ICell0, K) + + Gravity * ZInterface(ICell0, K + 1); + Real MontPotCell1Kp1 = + PressureInterface(ICell1, K + 1) * SpecVol(ICell1, K) + + Gravity * ZInterface(ICell1, K + 1); Real GradMontPotKp1 = (MontPotCell1Kp1 - MontPotCell0Kp1) * InvDcEdge; - Real GradMontPot = 0.5_Real * (GradMontPotK + GradMontPotKp1); - - Real PGradAlpha = 0.5 * (PressureMid(ICell1, K) + PressureMid(ICell0, K)) * (SpecVol(ICell1, K) - SpecVol(ICell0, K)) * InvDcEdge; - Tend(IEdge, K) += - EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha); - if (IEdge == 0) - LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); + Real GradMontPot = 0.5_Real * (GradMontPotK + GradMontPotKp1); + + Real PGradAlpha = + 0.5 * (PressureMid(ICell1, K) + PressureMid(ICell0, K)) * + (SpecVol(ICell1, K) - SpecVol(ICell0, K)) * InvDcEdge; + Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha); + if (IEdge == 0) + LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", + IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); } } @@ -82,9 +91,9 @@ class PressureGradHighOrder { bool Enabled; // constructor declaration - PressureGradHighOrder(const HorzMesh *Mesh, ///< [in] Horizontal mesh - const VertCoord *VCoord ///< [in] Vertical coordinate - ); + PressureGradHighOrder(const HorzMesh *Mesh, ///< [in] Horizontal mesh + const VertCoord *VCoord ///< [in] Vertical coordinate + ); KOKKOS_FUNCTION void operator()(const Array2DReal &Tend, I4 IEdge, I4 KChunk, const Array2DReal &Pressure, @@ -112,7 +121,6 @@ class PressureGradHighOrder { // Pressure gradient manager class class PressureGrad { public: - // Flag to indicate if pressure gradient term is enabled bool Enabled; @@ -145,7 +153,6 @@ class PressureGrad { const VertCoord *VCoord, const Eos *EqState, const int TimeLevel) const; - private: // Construct a new pressure gradient object PressureGrad(const HorzMesh *Mesh, const VertCoord *VCoord, Config *Options); @@ -158,17 +165,17 @@ class PressureGrad { static PressureGrad *DefaultPGrad; // Mesh-related sizes - I4 NEdgesAll = 0; - I4 NChunks = 0; - I4 NVertLayers = 0; + I4 NEdgesAll = 0; + I4 NChunks = 0; + I4 NVertLayers = 0; I4 NVertLayersP1 = 0; // Data required for computation (stored copies of mesh/VCoord arrays) Array2DI4 CellsOnEdge; ///< cells surrounding each edge Array1DReal DcEdge; ///< distance between cell centers across edge Array2DReal EdgeSignOnCell; ///< orientation of edge relative to cell - Array1DI4 MinLayerEdgeBot; ///< min vertical layer on each edge - Array1DI4 MaxLayerEdgeTop; ///< max vertical layer on each edge + Array1DI4 MinLayerEdgeBot; ///< min vertical layer on each edge + Array1DI4 MaxLayerEdgeTop; ///< max vertical layer on each edge // Instances of functors PressureGradCentered CenteredPGrad; From 52d7f765da37d25ff9e40795dd4a5b78862be3ff Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Tue, 17 Feb 2026 11:52:34 -0600 Subject: [PATCH 130/152] Use Gravity from GlobalConstants in PGrad Drop unused Rho0 --- components/omega/src/ocn/PGrad.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 629059c9cfec..21fb500ca20c 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -10,6 +10,7 @@ #include "Config.h" #include "Eos.h" +#include "GlobalConstants.h" #include "HorzMesh.h" #include "OceanState.h" #include "OmegaKokkos.h" @@ -45,8 +46,6 @@ class PressureGradCentered { const I4 ICell0 = CellsOnEdge(IEdge, 0); const I4 ICell1 = CellsOnEdge(IEdge, 1); const Real InvDcEdge = 1.0_Real / DcEdge(IEdge); - Real Rho0 = 1026.0_Real; - Real Gravity = 9.80616_Real; for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; From 4dbbc50be19639f46555a76838c8b6c60da6c3d1 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 19 Feb 2026 12:51:51 -0800 Subject: [PATCH 131/152] Initialize/clear Tendencies in IOStreamTest --- components/omega/test/infra/IOStreamTest.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/omega/test/infra/IOStreamTest.cpp b/components/omega/test/infra/IOStreamTest.cpp index ff89d97f71b5..9e3c1823344e 100644 --- a/components/omega/test/infra/IOStreamTest.cpp +++ b/components/omega/test/infra/IOStreamTest.cpp @@ -26,6 +26,7 @@ #include "Pacer.h" #include "TimeMgr.h" #include "TimeStepper.h" +#include "Tendencies.h" #include "Tracers.h" #include "VertCoord.h" #include "mpi.h" @@ -103,6 +104,9 @@ void initIOStreamTest(Clock *&ModelClock // Model clock if (TmpErr != 0) ABORT_ERROR("IOStreamTest: Error initializing OceanState"); + // Intialize Tendencies + Tendencies::init(); + // Initialize Tracers Tracers::init(); @@ -220,6 +224,7 @@ int main(int argc, char **argv) { // Clean up environments TimeStepper::clear(); + Tendencies::clear(); Tracers::clear(); OceanState::clear(); AuxiliaryState::clear(); From 188ee3508d40c53852fae791f67a95a95fdcaf92 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 23 Feb 2026 07:47:37 -0800 Subject: [PATCH 132/152] Add gradient of tidal potential and SAL --- components/omega/src/ocn/OceanState.cpp | 3 +++ components/omega/src/ocn/PGrad.cpp | 17 ++++++++++--- components/omega/src/ocn/PGrad.h | 34 +++++++++++++++++-------- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/components/omega/src/ocn/OceanState.cpp b/components/omega/src/ocn/OceanState.cpp index aef68a66ef51..4d3e7ce8919a 100644 --- a/components/omega/src/ocn/OceanState.cpp +++ b/components/omega/src/ocn/OceanState.cpp @@ -44,6 +44,9 @@ int OceanState::init() { LOG_ERROR("TimeStepper needs to be initialized before OceanState"); } int NTimeLevels = DefTimeStepper->getNTimeLevels(); + LOG_INFO("OceanState: Initializing default state with {} vertical layers " + "and {} time levels", + NVertLayers, NTimeLevels); if (NTimeLevels < 2) { LOG_ERROR("OceanState: the number of time level is lower than 2"); diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 5d9c2056bd37..66b50923f438 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -109,6 +109,10 @@ PressureGrad::PressureGrad( "PGrad: Unknown PressureGradType in config, defaulting to centered"); } + // Temporary: initialization of tidal potential and SAL + TidalPotential = Array1DReal("TidalPotential", Mesh->NCellsSize); + SelfAttractionLoading = Array1DReal("SelfAttractionLoading", Mesh->NCellsSize); + } // end constructor //------------------------------------------------------------------------------ @@ -161,6 +165,8 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); OMEGA_SCOPE(LocMinLayerEdgeBot, MinLayerEdgeBot); OMEGA_SCOPE(LocMaxLayerEdgeTop, MaxLayerEdgeTop); + OMEGA_SCOPE(LocTidalPotential, TidalPotential); + OMEGA_SCOPE(LocSelfAttractionLoading, SelfAttractionLoading); const Array2DReal &PressureMid = VCoord->PressureMid; const Array2DReal &PressureInterface = VCoord->PressureInterface; @@ -184,7 +190,8 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, - PressureInterface, ZInterface, LayerThick, + PressureInterface, ZInterface, + LocTidalPotential, LocSelfAttractionLoading, SpecVol); }); }); @@ -195,14 +202,16 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, parallelForOuter( "pgrad-highorder", {NEdgesAll}, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { - const int KMin = MinLayerEdgeBot(IEdge); - const int KMax = MaxLayerEdgeTop(IEdge); + const int KMin = LocMinLayerEdgeBot(IEdge); + const int KMax = LocMaxLayerEdgeTop(IEdge); const int KRange = vertRange(KMin, KMax); parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, - Geopotential, SpecVol); + PressureInterface, ZInterface, + LocTidalPotential, LocSelfAttractionLoading, + SpecVol); }); }); } diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 21fb500ca20c..7d20b5927614 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -37,8 +37,10 @@ class PressureGradCentered { const Array2DReal &PressureMid, const Array2DReal &PressureInterface, const Array2DReal &ZInterface, - const Array2DReal &LayerThick, - const Array2DReal &SpecVol) const { + const Array1DReal &TidalPotential, + const Array1DReal &SelfAttractionLoading, + const Array2DReal &SpecVol + ) const { const I4 KStart = chunkStart(KChunk, MinLayerEdgeBot(IEdge)); const I4 KLen = chunkLength(KChunk, KStart, MaxLayerEdgeTop(IEdge)); @@ -47,6 +49,9 @@ class PressureGradCentered { const I4 ICell1 = CellsOnEdge(IEdge, 1); const Real InvDcEdge = 1.0_Real / DcEdge(IEdge); + Real GradGeoPot = (TidalPotential(ICell1) - TidalPotential(ICell0)) * InvDcEdge + + (SelfAttractionLoading(ICell1) - SelfAttractionLoading(ICell0)) * InvDcEdge; + for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; Real MontPotCell0K = @@ -69,10 +74,10 @@ class PressureGradCentered { Real PGradAlpha = 0.5 * (PressureMid(ICell1, K) + PressureMid(ICell0, K)) * (SpecVol(ICell1, K) - SpecVol(ICell0, K)) * InvDcEdge; - Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha); - if (IEdge == 0) - LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", - IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); + Tend(IEdge, K) += + EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha - GradGeoPot); + //if (IEdge == 0) + //LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); } } @@ -95,8 +100,11 @@ class PressureGradHighOrder { ); KOKKOS_FUNCTION void operator()(const Array2DReal &Tend, I4 IEdge, I4 KChunk, - const Array2DReal &Pressure, - const Array2DReal &Geopotential, + const Array2DReal &PressureMid, + const Array2DReal &PressureInterface, + const Array2DReal &ZInterface, + const Array1DReal &TidalPotential, + const Array1DReal &SelfAttractionLoading, const Array2DReal &SpecVol) const { // Placeholder: for now, no-op (future high-order implementation) @@ -134,21 +142,21 @@ class PressureGrad { static PressureGrad *getDefault(); // Get instance by name - static PressureGrad *get(const std::string &Name ///< [in] + static PressureGrad *get(const std::string &Name ///< [in] Name of PressureGrad ); // Deallocates arrays and deletes instance static void clear(); // Remove pressure gradient object by name - static void erase(const std::string &Name ///< [in] + static void erase(const std::string &Name ///< [in] Name of PressureGrad ); // Destructor ~PressureGrad(); // Compute pressure gradient tendencies and add into Tend array - void computePressureGrad(Array2DReal &Tend, const OceanState *State, + void computePressureGrad(Array2DReal &Tend, const OceanState *State, const VertCoord *VCoord, const Eos *EqState, const int TimeLevel) const; @@ -176,6 +184,10 @@ class PressureGrad { Array1DI4 MinLayerEdgeBot; ///< min vertical layer on each edge Array1DI4 MaxLayerEdgeTop; ///< max vertical layer on each edge + // Temporary: to be moveed to tidal forcing module in future + Array1DReal TidalPotential; ///< Tidal potential for tidal forcing + Array1DReal SelfAttractionLoading; ///< Self attraction and loading for tidal forcing + // Instances of functors PressureGradCentered CenteredPGrad; PressureGradHighOrder HighOrderPGrad; From 2fb9994609b14b2c66f0115d8770d7a4d7e38ce8 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 23 Feb 2026 10:04:24 -0800 Subject: [PATCH 133/152] Fix creation of tendency fields with same name --- components/omega/src/ocn/Tendencies.cpp | 56 +++++++++++++++---------- components/omega/src/ocn/Tendencies.h | 2 + 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 809c80c98a4b..eec5ec015b1b 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -252,9 +252,14 @@ void Tendencies::readConfig(Config *OmegaConfig ///< [in] Omega config //------------------------------------------------------------------------------ // Define fields associated with tendencies void Tendencies::defineFields() { - auto LayerThicknessTendFieldName = "LayerThicknessTend"; - auto NormalVelocityTendFieldName = "NormalVelocityTend"; - auto TracerTendFieldName = "TracerTend"; + std::string LayerThicknessTendFieldName = "LayerThicknessTend"; + std::string NormalVelocityTendFieldName = "NormalVelocityTend"; + std::string TracerTendFieldName = "TracerTend"; + if (Name != "Default") { + LayerThicknessTendFieldName.append(Name); + NormalVelocityTendFieldName.append(Name); + TracerTendFieldName.append(Name); + } int NDims = 2; std::vector DimNamesThickness(NDims); @@ -281,7 +286,10 @@ void Tendencies::defineFields() { "m/s^2", "sea_water_velocity_tendency", -9.99E+10, 9.99E+10, -9.99E+30, NDims, DimNamesVelocity); - auto TendGroupName = "Tendencies"; + std::string TendGroupName = "Tendencies"; + if (Name != "Default") { + TendGroupName.append(Name); + } auto TendGroup = FieldGroup::create(TendGroupName); TendGroup->addField(LayerThicknessTendFieldName); @@ -296,15 +304,15 @@ void Tendencies::defineFields() { //------------------------------------------------------------------------------ // Construct a new group of tendencies -Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies - const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - PressureGrad *PGrad, ///< [in] Pressure gradient - Eos *EqState, ///< [in] Equation of state - int NTracersIn, ///< [in] Number of tracers - TimeInterval TimeStepIn, ///< [in] Time step - Config *Options, ///< [in] Configuration options +Tendencies::Tendencies(const std::string &Name_, ///< [in] Name for tendencies + const HorzMesh *Mesh, ///< [in] Horizontal mesh + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient + Eos *EqState, ///< [in] Equation of state + int NTracersIn, ///< [in] Number of tracers + TimeInterval TimeStepIn, ///< [in] Time step + Config *Options, ///< [in] Configuration options CustomTendencyType InCustomThicknessTend, CustomTendencyType InCustomVelocityTend) : Mesh(Mesh), VCoord(VCoord), VAdv(VAdv), ThicknessFluxDiv(Mesh, VCoord), @@ -324,6 +332,8 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies TracerTend = Array3DReal("TracerTend", NTracersIn, Mesh->NCellsSize, VCoord->NVertLayers); + Name = Name_; + NTracers = NTracersIn; TimeStep = TimeStepIn; @@ -331,16 +341,16 @@ Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies } // end constructor -Tendencies::Tendencies(const std::string &Name, ///< [in] Name for tendencies - const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - PressureGrad *PGrad, ///< [in] Pressure gradient - Eos *EqState, ///< [in] Equation of state - int NTracersIn, ///< [in] Number of tracers - TimeInterval TimeStepIn, ///< [in] Time step - Config *Options) ///< [in] Configuration options - : Tendencies(Name, Mesh, VCoord, VAdv, PGrad, EqState, NTracersIn, TimeStepIn, Options, +Tendencies::Tendencies(const std::string &Name_, ///< [in] Name for tendencies + const HorzMesh *Mesh, ///< [in] Horizontal mesh + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient + Eos *EqState, ///< [in] Equation of state + int NTracersIn, ///< [in] Number of tracers + TimeInterval TimeStepIn, ///< [in] Time step + Config *Options) ///< [in] Configuration options + : Tendencies(Name_, Mesh, VCoord, VAdv, PGrad, EqState, NTracersIn, TimeStepIn, Options, CustomTendencyType{}, CustomTendencyType{}) {} //------------------------------------------------------------------------------ diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 68ebefca3af4..882f04c61ea4 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -71,6 +71,8 @@ class Tendencies { TracerDiffOnCell TracerDiffusion; TracerHyperDiffOnCell TracerHyperDiff; + std::string Name; + // Methods to compute tendency groups void computeThicknessTendencies(const OceanState *State, const AuxiliaryState *AuxState, From b9b31792dbd4be973595a1cf6536f16147cdf93c Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 23 Feb 2026 10:05:33 -0800 Subject: [PATCH 134/152] Initialize pressure gradient in state ctest --- components/omega/test/ocn/StateTest.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/omega/test/ocn/StateTest.cpp b/components/omega/test/ocn/StateTest.cpp index 0c2f631db277..15ce8d3e3169 100644 --- a/components/omega/test/ocn/StateTest.cpp +++ b/components/omega/test/ocn/StateTest.cpp @@ -24,6 +24,8 @@ #include "OceanState.h" #include "OmegaKokkos.h" #include "Pacer.h" +#include "PGrad.h" +#include "Tendencies.h" #include "TimeStepper.h" #include "VertCoord.h" #include "mpi.h" @@ -90,6 +92,9 @@ void initStateTest() { // Initialize Aux State variables AuxiliaryState::init(); + // Initialize pressure gradient + PressureGrad::init(); + // Create tendencies Tendencies::init(); @@ -399,6 +404,7 @@ int main(int argc, char *argv[]) { OceanState::clear(); Tracers::clear(); AuxiliaryState::clear(); + PressureGrad::clear(); Tendencies::clear(); TimeStepper::clear(); HorzMesh::clear(); From bf37ee5b367e5ad037ac5389eccf80714d75f4c4 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 23 Feb 2026 11:19:16 -0800 Subject: [PATCH 135/152] Fix IOStreamTest --- components/omega/test/infra/IOStreamTest.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/omega/test/infra/IOStreamTest.cpp b/components/omega/test/infra/IOStreamTest.cpp index 9e3c1823344e..ee0cd4608a41 100644 --- a/components/omega/test/infra/IOStreamTest.cpp +++ b/components/omega/test/infra/IOStreamTest.cpp @@ -24,6 +24,7 @@ #include "OceanState.h" #include "OmegaKokkos.h" #include "Pacer.h" +#include "PGrad.h" #include "TimeMgr.h" #include "TimeStepper.h" #include "Tendencies.h" @@ -104,6 +105,8 @@ void initIOStreamTest(Clock *&ModelClock // Model clock if (TmpErr != 0) ABORT_ERROR("IOStreamTest: Error initializing OceanState"); + PressureGrad::init(); + // Intialize Tendencies Tendencies::init(); @@ -224,6 +227,7 @@ int main(int argc, char **argv) { // Clean up environments TimeStepper::clear(); + PressureGrad::clear(); Tendencies::clear(); Tracers::clear(); OceanState::clear(); From ebf9999920a71519ad714e7dac015af124228fcc Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 23 Feb 2026 13:13:56 -0800 Subject: [PATCH 136/152] Fix PGradTest for GPU --- components/omega/test/ocn/PGradTest.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index fb5e34f6af26..eb1033448c03 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -188,9 +188,12 @@ int main(int argc, char *argv[]) { LOG_INFO("NVertLayers = {}", NVertLayers); LOG_INFO("dC = {}", dC); + DefState->copyToHost(0); + HostArray2DReal LayerThickH; + DefState->getLayerThicknessH(LayerThickH,0); for (int i = 0; i < 2; ++i) { for (int k = 0; k < 2; ++k) { - LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThick(i, k)); + LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThickH(i, k)); } } From 1421f7ae982d14381bbcbd364b3c81c05091d93c Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 23 Feb 2026 15:27:43 -0800 Subject: [PATCH 137/152] Add pass/fail criteria to PGradTest --- components/omega/test/ocn/PGradTest.cpp | 35 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index eb1033448c03..8045c88669e0 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -96,7 +96,7 @@ int main(int argc, char *argv[]) { { initPGradTest(); - // Initialize PressureGrad manager + // Initialize default PressureGrad PressureGrad::init(); @@ -120,7 +120,9 @@ int main(int argc, char *argv[]) { I4 NVertLayers = 60; Real dC = 30000.0_Real; - for (int refinement = 0; refinement < 4; ++refinement) { + I4 NRefinements = 4; + HostArray1DReal RMSE("RMSE", NRefinements); + for (int refinement = 0; refinement < NRefinements; ++refinement) { LOG_INFO("PGradTest: Starting refinement level {}", refinement); VCoord->NVertLayers = NVertLayers; @@ -253,15 +255,10 @@ int main(int argc, char *argv[]) { // compute pressure once more with converged LayerThick VCoord->computePressure(LayerThick, SurfacePressure); - // compute geopotential - //VCoord->computeZHeight(LayerThick, SpecVol); - Array1DReal SelfAttractionLoading("SelfAttractionLoading", NCellsAll); - Array1DReal TidalPotential("TidalPotential", NCellsAll); - deepCopy(TidalPotential, 0.0_Real); - deepCopy(SelfAttractionLoading, 0.0_Real); - VCoord->computeGeopotential(TidalPotential, SelfAttractionLoading); + // compute z levels + VCoord->computeZHeight(LayerThick, SpecVol); - // create PressureGrad instance + // get PressureGrad instance PressureGrad *DefPGrad = PressureGrad::getDefault(); if (!DefPGrad) { LOG_INFO("PGrad: default instance not created by init"); @@ -270,18 +267,23 @@ int main(int argc, char *argv[]) { // compute pressure gradient deepCopy(Tend, 0.0_Real); DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); + + //compute errors Real max_value = 0.0_Real; parallelReduce({NEdgesAll, NVertLayers - 2}, KOKKOS_LAMBDA(int i, int k, Real &max) { - Real val = Kokkos::abs(Tend(i + 1, k)); + Real val = Kokkos::abs(Tend(i, k + 1)); if (val > max) max = val; }, Kokkos::Max(max_value) ); Real sum_value = 0.0_Real; parallelReduce({NEdgesAll, NVertLayers - 2}, KOKKOS_LAMBDA(int i, int k, Real &lsum) { - lsum += Tend(i + 1, k) * Tend(i + 1, k); + lsum += Tend(i, k + 1) * Tend(i, k + 1); }, Kokkos::Sum(sum_value) ); - LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, std::sqrt(sum_value) / (NEdgesAll * (NVertLayers - 2))); + Real rmse = std::sqrt(sum_value / (NEdgesAll * (NVertLayers - 2))); + RMSE(refinement) = rmse; + + LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, rmse); // coarsen for next iteration dC = dC * 2.0_Real; @@ -289,6 +291,13 @@ int main(int argc, char *argv[]) { } // refinement loop + // Test for second order convergence + if (RMSE(NRefinements) < RMSE(0) / pow(4.0_Real, NRefinements - 1)) { + RetVal = 0; + } else { + RetVal = 1; + } + // cleanup PressureGrad::clear(); IOStream::finalize(); From e5acfee3a66b7587afef050c6faf6bdb7123a6c2 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Tue, 24 Feb 2026 08:33:54 -0800 Subject: [PATCH 138/152] Lint files --- components/omega/src/ocn/OceanInit.cpp | 2 +- components/omega/src/ocn/PGrad.cpp | 15 +- components/omega/src/ocn/PGrad.h | 22 +- components/omega/src/ocn/Tendencies.cpp | 4 +- components/omega/src/ocn/Tendencies.h | 27 ++- components/omega/test/infra/IOStreamTest.cpp | 4 +- components/omega/test/ocn/EosTest.cpp | 9 +- components/omega/test/ocn/PGradTest.cpp | 226 ++++++++++-------- components/omega/test/ocn/StateTest.cpp | 4 +- components/omega/test/ocn/TendenciesTest.cpp | 6 +- .../test/timeStepping/TimeStepperTest.cpp | 8 +- 11 files changed, 177 insertions(+), 150 deletions(-) diff --git a/components/omega/src/ocn/OceanInit.cpp b/components/omega/src/ocn/OceanInit.cpp index 022b18d5b498..a22e3b3b0667 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -22,8 +22,8 @@ #include "MachEnv.h" #include "OceanDriver.h" #include "OceanState.h" -#include "Pacer.h" #include "PGrad.h" +#include "Pacer.h" #include "Tendencies.h" #include "TimeMgr.h" #include "TimeStepper.h" diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 66b50923f438..84f25086916c 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -111,7 +111,8 @@ PressureGrad::PressureGrad( // Temporary: initialization of tidal potential and SAL TidalPotential = Array1DReal("TidalPotential", Mesh->NCellsSize); - SelfAttractionLoading = Array1DReal("SelfAttractionLoading", Mesh->NCellsSize); + SelfAttractionLoading = + Array1DReal("SelfAttractionLoading", Mesh->NCellsSize); } // end constructor @@ -191,8 +192,8 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, Team, KRange, INNER_LAMBDA(int KChunk) { LocCenteredPGrad(Tend, IEdge, KChunk, PressureMid, PressureInterface, ZInterface, - LocTidalPotential, LocSelfAttractionLoading, - SpecVol); + LocTidalPotential, + LocSelfAttractionLoading, SpecVol); }); }); @@ -202,16 +203,16 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, parallelForOuter( "pgrad-highorder", {NEdgesAll}, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { - const int KMin = LocMinLayerEdgeBot(IEdge); - const int KMax = LocMaxLayerEdgeTop(IEdge); + const int KMin = LocMinLayerEdgeBot(IEdge); + const int KMax = LocMaxLayerEdgeTop(IEdge); const int KRange = vertRange(KMin, KMax); parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { LocHighOrderPGrad(Tend, IEdge, KChunk, PressureMid, PressureInterface, ZInterface, - LocTidalPotential, LocSelfAttractionLoading, - SpecVol); + LocTidalPotential, + LocSelfAttractionLoading, SpecVol); }); }); } diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 7d20b5927614..f879e6f333b8 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -39,8 +39,7 @@ class PressureGradCentered { const Array2DReal &ZInterface, const Array1DReal &TidalPotential, const Array1DReal &SelfAttractionLoading, - const Array2DReal &SpecVol - ) const { + const Array2DReal &SpecVol) const { const I4 KStart = chunkStart(KChunk, MinLayerEdgeBot(IEdge)); const I4 KLen = chunkLength(KChunk, KStart, MaxLayerEdgeTop(IEdge)); @@ -49,8 +48,10 @@ class PressureGradCentered { const I4 ICell1 = CellsOnEdge(IEdge, 1); const Real InvDcEdge = 1.0_Real / DcEdge(IEdge); - Real GradGeoPot = (TidalPotential(ICell1) - TidalPotential(ICell0)) * InvDcEdge + - (SelfAttractionLoading(ICell1) - SelfAttractionLoading(ICell0)) * InvDcEdge; + Real GradGeoPot = + (TidalPotential(ICell1) - TidalPotential(ICell0)) * InvDcEdge + + (SelfAttractionLoading(ICell1) - SelfAttractionLoading(ICell0)) * + InvDcEdge; for (int KVec = 0; KVec < KLen; ++KVec) { const I4 K = KStart + KVec; @@ -76,8 +77,9 @@ class PressureGradCentered { (SpecVol(ICell1, K) - SpecVol(ICell0, K)) * InvDcEdge; Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha - GradGeoPot); - //if (IEdge == 0) - //LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); + // if (IEdge == 0) + // LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", + // IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); } } @@ -142,7 +144,8 @@ class PressureGrad { static PressureGrad *getDefault(); // Get instance by name - static PressureGrad *get(const std::string &Name ///< [in] Name of PressureGrad + static PressureGrad * + get(const std::string &Name ///< [in] Name of PressureGrad ); // Deallocates arrays and deletes instance @@ -156,7 +159,7 @@ class PressureGrad { ~PressureGrad(); // Compute pressure gradient tendencies and add into Tend array - void computePressureGrad(Array2DReal &Tend, const OceanState *State, + void computePressureGrad(Array2DReal &Tend, const OceanState *State, const VertCoord *VCoord, const Eos *EqState, const int TimeLevel) const; @@ -186,7 +189,8 @@ class PressureGrad { // Temporary: to be moveed to tidal forcing module in future Array1DReal TidalPotential; ///< Tidal potential for tidal forcing - Array1DReal SelfAttractionLoading; ///< Self attraction and loading for tidal forcing + Array1DReal + SelfAttractionLoading; ///< Self attraction and loading for tidal forcing // Instances of functors PressureGradCentered CenteredPGrad; diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index eec5ec015b1b..092b7b74d281 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -259,7 +259,7 @@ void Tendencies::defineFields() { LayerThicknessTendFieldName.append(Name); NormalVelocityTendFieldName.append(Name); TracerTendFieldName.append(Name); - } + } int NDims = 2; std::vector DimNamesThickness(NDims); @@ -290,7 +290,7 @@ void Tendencies::defineFields() { if (Name != "Default") { TendGroupName.append(Name); } - auto TendGroup = FieldGroup::create(TendGroupName); + auto TendGroup = FieldGroup::create(TendGroupName); TendGroup->addField(LayerThicknessTendFieldName); TendGroup->addField(NormalVelocityTendFieldName); diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 882f04c61ea4..ad5bd8b4f3ee 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -33,13 +33,14 @@ #include "AuxiliaryState.h" #include "Config.h" +#include "Eos.h" #include "HorzMesh.h" #include "OceanState.h" +#include "PGrad.h" #include "TendencyTerms.h" #include "TimeMgr.h" -#include "Eos.h" -#include "PGrad.h" #include "VertAdv.h" +#include "VertCoord.h" #include #include @@ -154,10 +155,10 @@ class Tendencies { // Construct a new tendency object Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - PressureGrad *PGrad, ///< [in] Pressure gradient - Eos *EqState, ///< [in] Equation of state + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient + Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStep, ///< [in] Time step Config *Options, ///< [in] Configuration options @@ -166,10 +167,10 @@ class Tendencies { Tendencies(const std::string &Name, ///< [in] Name for tendencies const HorzMesh *Mesh, ///< [in] Horizontal mesh - VertCoord *VCoord, ///< [in] Vertical coordinate - VertAdv *VAdv, ///< [in] Vertical advection - PressureGrad *PGrad, ///< [in] Pressure gradient - Eos *EqState, ///< [in] Equation of state + VertCoord *VCoord, ///< [in] Vertical coordinate + VertAdv *VAdv, ///< [in] Vertical advection + PressureGrad *PGrad, ///< [in] Pressure gradient + Eos *EqState, ///< [in] Equation of state int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStep, ///< [in] Time step Config *Options ///< [in] Configuration options @@ -181,13 +182,13 @@ class Tendencies { Tendencies(const Tendencies &) = delete; Tendencies(Tendencies &&) = delete; - const HorzMesh *Mesh; ///< Pointer to horizontal mesh + const HorzMesh *Mesh; ///< Pointer to horizontal mesh VertCoord *VCoord; ///< Pointer to vertical coordinate VertAdv *VAdv; ///< Pointer to vertical advection PressureGrad *PGrad; ///< Pointer to pressure gradient Eos *EqState; ///< Pointer to equation of state - I4 NTracers; ///< Number of tracers - TimeInterval TimeStep; ///< Time step + I4 NTracers; ///< Number of tracers + TimeInterval TimeStep; ///< Time step // Pointer to default tendencies static Tendencies *DefaultTendencies; diff --git a/components/omega/test/infra/IOStreamTest.cpp b/components/omega/test/infra/IOStreamTest.cpp index ee0cd4608a41..451ca4293edf 100644 --- a/components/omega/test/infra/IOStreamTest.cpp +++ b/components/omega/test/infra/IOStreamTest.cpp @@ -23,11 +23,11 @@ #include "MachEnv.h" #include "OceanState.h" #include "OmegaKokkos.h" -#include "Pacer.h" #include "PGrad.h" +#include "Pacer.h" +#include "Tendencies.h" #include "TimeMgr.h" #include "TimeStepper.h" -#include "Tendencies.h" #include "Tracers.h" #include "VertCoord.h" #include "mpi.h" diff --git a/components/omega/test/ocn/EosTest.cpp b/components/omega/test/ocn/EosTest.cpp index 5797b77798d2..aaf7b1d96e80 100644 --- a/components/omega/test/ocn/EosTest.cpp +++ b/components/omega/test/ocn/EosTest.cpp @@ -51,8 +51,8 @@ const Real GswBVFExpValue = 0.02081197958166906; // Expected value from GSW-C library /// Test input values -const Real Sa = 30.0; // Absolute Salinity in g/kg -const Real Ct = 10.0; // Conservative Temperature in degC +const Real Sa = 30.0; // Absolute Salinity in g/kg +const Real Ct = 10.0; // Conservative Temperature in degC const Real P = 1000.0 * Db2Pa; // Pressure in Pa const I4 KDisp = 1; // Displate parcel to K=1 for TEOS-10 eos @@ -667,8 +667,9 @@ void checkValueGswcN2() { // Input arrays: length nz+1 double Salt[4] = {Sa - 1.0, Sa, Sa + 1.0}; // Absolute Salinity (g/kg) double Temp[4] = {Ct + 15.0, Ct + 10.0, - Ct + 5.0}; // Conservative Temperature (deg C) - double Press[4] = {P * Pa2Db, P * Pa2Db + 1.0, P * Pa2Db + 2.0}; // Pressure (dbar) + Ct + 5.0}; // Conservative Temperature (deg C) + double Press[4] = {P * Pa2Db, P * Pa2Db + 1.0, + P * Pa2Db + 2.0}; // Pressure (dbar) // Latitude (degrees north) double Latitude[4] = {0.0, 0.0, 0.0}; diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 8045c88669e0..84b40842b5ae 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -1,4 +1,5 @@ -//===-- Test driver for OMEGA Pressure Gradient (PGrad) --------------*- C++-*-===/ +//===-- Test driver for OMEGA Pressure Gradient (PGrad) --------------*- +// C++-*-===/ // /// \file /// \brief Test driver for PressureGrad module @@ -10,8 +11,8 @@ #include "DataTypes.h" #include "Decomp.h" #include "Dimension.h" -#include "Error.h" #include "Eos.h" +#include "Error.h" #include "Field.h" #include "GlobalConstants.h" #include "Halo.h" @@ -22,10 +23,10 @@ #include "MachEnv.h" #include "OceanState.h" #include "OmegaKokkos.h" -#include "Pacer.h" #include "PGrad.h" -#include "Tracers.h" +#include "Pacer.h" #include "TimeStepper.h" +#include "Tracers.h" #include "VertCoord.h" #include "mpi.h" @@ -37,7 +38,7 @@ void initPGradTest() { int Err1; MachEnv::init(MPI_COMM_WORLD); - MachEnv *DefEnv = MachEnv::getDefault(); + MachEnv *DefEnv = MachEnv::getDefault(); MPI_Comm DefComm = DefEnv->getComm(); // Initialize the Logging system @@ -63,8 +64,7 @@ void initPGradTest() { Err1 = Halo::init(); if (Err1 != 0) { LOG_ERROR("PGrad: error initializing default halo"); - Err += Error(ErrorCode::Fail, - "PGrad: error initializing default halo"); + Err += Error(ErrorCode::Fail, "PGrad: error initializing default halo"); } // Initialize the default mesh @@ -93,69 +93,72 @@ int main(int argc, char *argv[]) { Kokkos::initialize(); Pacer::initialize(MPI_COMM_WORLD); Pacer::setPrefix("Omega:"); - + { initPGradTest(); // Initialize default PressureGrad PressureGrad::init(); - - MachEnv *DefEnv = MachEnv::getDefault(); - HorzMesh *DefMesh = HorzMesh::getDefault(); - VertCoord *VCoord = VertCoord::getDefault(); + MachEnv *DefEnv = MachEnv::getDefault(); + HorzMesh *DefMesh = HorzMesh::getDefault(); + VertCoord *VCoord = VertCoord::getDefault(); OceanState *DefState = OceanState::getDefault(); - Decomp *DefDecomp = Decomp::getDefault(); - Eos *DefEos = Eos::getInstance(); - Config *Options = Config::getOmegaConfig(); - + Decomp *DefDecomp = Decomp::getDefault(); + Eos *DefEos = Eos::getInstance(); + Config *Options = Config::getOmegaConfig(); // create arrays: Tend on edges, Pressure/Geopotential/SpecVol on cells Array2DReal Tend("Tend", DefMesh->NEdgesSize, VCoord->NVertLayers); - Array2DReal SpecVolOld("SpecVolOld", DefMesh->NCellsSize, VCoord->NVertLayers); - Array2DReal PressureMidOld("PressureMidOld", DefMesh->NCellsSize, VCoord->NVertLayers); + Array2DReal SpecVolOld("SpecVolOld", DefMesh->NCellsSize, + VCoord->NVertLayers); + Array2DReal PressureMidOld("PressureMidOld", DefMesh->NCellsSize, + VCoord->NVertLayers); Array1DReal SurfacePressure("SurfacePressure", DefMesh->NCellsSize); I4 NEdgesAll = DefMesh->NEdgesAll; I4 NCellsAll = DefMesh->NCellsAll; - I4 NVertLayers = 60; - Real dC = 30000.0_Real; + I4 NVertLayers = 60; + Real dC = 30000.0_Real; I4 NRefinements = 4; HostArray1DReal RMSE("RMSE", NRefinements); for (int refinement = 0; refinement < NRefinements; ++refinement) { LOG_INFO("PGradTest: Starting refinement level {}", refinement); - VCoord->NVertLayers = NVertLayers; + VCoord->NVertLayers = NVertLayers; VCoord->NVertLayersP1 = NVertLayers + 1; auto &MinLayerCell = VCoord->MinLayerCell; auto &MaxLayerCell = VCoord->MaxLayerCell; - parallelFor({NCellsAll}, KOKKOS_LAMBDA(int i) { - MinLayerCell(i) = 0; - MaxLayerCell(i) = NVertLayers - 1; - }); + parallelFor( + {NCellsAll}, KOKKOS_LAMBDA(int i) { + MinLayerCell(i) = 0; + MaxLayerCell(i) = NVertLayers - 1; + }); auto &MinLayerEdgeBot = VCoord->MinLayerEdgeBot; auto &MaxLayerEdgeTop = VCoord->MaxLayerEdgeTop; - parallelFor({NEdgesAll}, KOKKOS_LAMBDA(int i) { - MinLayerEdgeBot(i) = 0; - MaxLayerEdgeTop(i) = NVertLayers - 1; - //MaxLayerEdgeTop(i) = 4; - }); + parallelFor( + {NEdgesAll}, KOKKOS_LAMBDA(int i) { + MinLayerEdgeBot(i) = 0; + MaxLayerEdgeTop(i) = NVertLayers - 1; + // MaxLayerEdgeTop(i) = 4; + }); auto &CellsOnEdge = DefMesh->CellsOnEdge; - auto &DcEdge = DefMesh->DcEdge; - auto &EdgeMask = VCoord->EdgeMask; - parallelFor({NEdgesAll}, KOKKOS_LAMBDA(int i) { - CellsOnEdge(i, 0)= 0; - CellsOnEdge(i, 1)= 1; - DcEdge(i) = dC; - }); + auto &DcEdge = DefMesh->DcEdge; + auto &EdgeMask = VCoord->EdgeMask; + parallelFor( + {NEdgesAll}, KOKKOS_LAMBDA(int i) { + CellsOnEdge(i, 0) = 0; + CellsOnEdge(i, 1) = 1; + DcEdge(i) = dC; + }); // Fetch reference desnity from Config Real Density0; Density0 = RhoSw; - + I4 TimeLevel = 0; // get state and tracer arrays @@ -167,32 +170,36 @@ int main(int argc, char *argv[]) { Err = Tracers::getByName(Salinity, TimeLevel, "Salinity"); // set Z interface and mid-point locations - Real ZBottom = -1000.0_Real; - Real dZ = 2.0_Real * (-ZBottom / NVertLayers); + Real ZBottom = -1000.0_Real; + Real dZ = 2.0_Real * (-ZBottom / NVertLayers); auto &BottomDepth = VCoord->BottomDepth; - auto &ZInterface = VCoord->ZInterface; - auto &ZMid = VCoord->ZMid; - Real tilt_factor = 0.495_Real; - //Real tilt_factor = 0.45_Real; - parallelFor({NCellsAll}, KOKKOS_LAMBDA(int i) { - ZInterface(i, NVertLayers) = ZBottom; - SurfacePressure(i) = 0.0_Real; - BottomDepth(i) = 0.0_Real; - for (int k = NVertLayers - 1; k >= 0; --k) { - Real x = (k + i) % 2; - Real dz = ( 2.0_Real * tilt_factor - 1.0_Real ) * x * dZ + (1.0_Real - tilt_factor) * dZ; // staggered layer thickness - ZInterface(i, k) = ZInterface(i, k + 1) + dz; - LayerThick(i, k) = ZInterface(i, k) - ZInterface(i, k + 1); - ZMid(i, k) = 0.5_Real * (ZInterface(i, k) + ZInterface(i, k + 1)); - BottomDepth(i) += dz; - } - }); + auto &ZInterface = VCoord->ZInterface; + auto &ZMid = VCoord->ZMid; + Real tilt_factor = 0.495_Real; + // Real tilt_factor = 0.45_Real; + parallelFor( + {NCellsAll}, KOKKOS_LAMBDA(int i) { + ZInterface(i, NVertLayers) = ZBottom; + SurfacePressure(i) = 0.0_Real; + BottomDepth(i) = 0.0_Real; + for (int k = NVertLayers - 1; k >= 0; --k) { + Real x = (k + i) % 2; + Real dz = (2.0_Real * tilt_factor - 1.0_Real) * x * dZ + + (1.0_Real - tilt_factor) * + dZ; // staggered layer thickness + ZInterface(i, k) = ZInterface(i, k + 1) + dz; + LayerThick(i, k) = ZInterface(i, k) - ZInterface(i, k + 1); + ZMid(i, k) = + 0.5_Real * (ZInterface(i, k) + ZInterface(i, k + 1)); + BottomDepth(i) += dz; + } + }); LOG_INFO("NVertLayers = {}", NVertLayers); LOG_INFO("dC = {}", dC); DefState->copyToHost(0); HostArray2DReal LayerThickH; - DefState->getLayerThicknessH(LayerThickH,0); + DefState->getLayerThicknessH(LayerThickH, 0); for (int i = 0; i < 2; ++i) { for (int k = 0; k < 2; ++k) { LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThickH(i, k)); @@ -201,21 +208,21 @@ int main(int argc, char *argv[]) { // set simple temperature and salinity profiles auto &SpecVol = DefEos->SpecVol; - parallelFor({NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { - Real T0 = 30.0; - Real TB = 5.0; - Real S0 = 30.0; - Real SB = 40.0; - - - Real phi0 = (ZMid(i, k) - ZBottom) / (-ZBottom); - Real phiB = 1.0_Real - phi0; - - Temp(i, k) = T0 * phi0 + TB * phiB; - Salinity(i, k) = S0 * phi0 + SB * phiB; - SpecVol(i, k) = 1.0_Real / Density0; - SpecVolOld(i, k) = SpecVol(i, k); - }); + parallelFor( + {NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + Real T0 = 30.0; + Real TB = 5.0; + Real S0 = 30.0; + Real SB = 40.0; + + Real phi0 = (ZMid(i, k) - ZBottom) / (-ZBottom); + Real phiB = 1.0_Real - phi0; + + Temp(i, k) = T0 * phi0 + TB * phiB; + Salinity(i, k) = S0 * phi0 + SB * phiB; + SpecVol(i, k) = 1.0_Real / Density0; + SpecVolOld(i, k) = SpecVol(i, k); + }); // Iterate to converge LayerThick, SpecVol, PressureMid auto &PressureMid = VCoord->PressureMid; @@ -228,37 +235,42 @@ int main(int argc, char *argv[]) { DefEos->computeSpecVol(Temp, Salinity, PressureMid); // compute psuedo thickness from specific volume - parallelFor({NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { - LayerThick(i, k) = 1.0_Real / (SpecVol(i, k) * Density0) * (ZInterface(i, k) - ZInterface(i, k + 1)); - }); + parallelFor( + {NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + LayerThick(i, k) = 1.0_Real / (SpecVol(i, k) * Density0) * + (ZInterface(i, k) - ZInterface(i, k + 1)); + }); // compute difference from previous iteration Real max_value = 0.0_Real; - parallelReduce({NCellsAll, NVertLayers}, - KOKKOS_LAMBDA(int i, int k, Real &max) { - Real diff = Kokkos::abs(SpecVol(i, k) - SpecVolOld(i, k)); - if (diff > max) max = diff; - }, Kokkos::Max(max_value) ); - - // check convergence + parallelReduce( + {NCellsAll, NVertLayers}, + KOKKOS_LAMBDA(int i, int k, Real &max) { + Real diff = Kokkos::abs(SpecVol(i, k) - SpecVolOld(i, k)); + if (diff > max) + max = diff; + }, + Kokkos::Max(max_value)); + + // check convergence if (max_value < 1e-12_Real) { LOG_INFO("converged: max diff = {}", max_value); break; } else { - parallelFor({NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { - SpecVolOld(i, k) = SpecVol(i, k); - }); + parallelFor( + {NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k) { + SpecVolOld(i, k) = SpecVol(i, k); + }); } - } - + // compute pressure once more with converged LayerThick VCoord->computePressure(LayerThick, SurfacePressure); // compute z levels VCoord->computeZHeight(LayerThick, SpecVol); - // get PressureGrad instance + // get PressureGrad instance PressureGrad *DefPGrad = PressureGrad::getDefault(); if (!DefPGrad) { LOG_INFO("PGrad: default instance not created by init"); @@ -266,27 +278,34 @@ int main(int argc, char *argv[]) { // compute pressure gradient deepCopy(Tend, 0.0_Real); - DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, TimeLevel); + DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, + TimeLevel); - //compute errors + // compute errors Real max_value = 0.0_Real; - parallelReduce({NEdgesAll, NVertLayers - 2}, - KOKKOS_LAMBDA(int i, int k, Real &max) { - Real val = Kokkos::abs(Tend(i, k + 1)); - if (val > max) max = val; - }, Kokkos::Max(max_value) ); + parallelReduce( + {NEdgesAll, NVertLayers - 2}, + KOKKOS_LAMBDA(int i, int k, Real &max) { + Real val = Kokkos::abs(Tend(i, k + 1)); + if (val > max) + max = val; + }, + Kokkos::Max(max_value)); Real sum_value = 0.0_Real; - parallelReduce({NEdgesAll, NVertLayers - 2}, - KOKKOS_LAMBDA(int i, int k, Real &lsum) { - lsum += Tend(i, k + 1) * Tend(i, k + 1); - }, Kokkos::Sum(sum_value) ); + parallelReduce( + {NEdgesAll, NVertLayers - 2}, + KOKKOS_LAMBDA(int i, int k, Real &lsum) { + lsum += Tend(i, k + 1) * Tend(i, k + 1); + }, + Kokkos::Sum(sum_value)); Real rmse = std::sqrt(sum_value / (NEdgesAll * (NVertLayers - 2))); RMSE(refinement) = rmse; - LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", refinement, max_value, rmse); + LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", + refinement, max_value, rmse); // coarsen for next iteration - dC = dC * 2.0_Real; + dC = dC * 2.0_Real; NVertLayers = NVertLayers / 2; } // refinement loop @@ -317,6 +336,7 @@ int main(int argc, char *argv[]) { Kokkos::finalize(); MPI_Finalize(); - if (RetVal >= 256) RetVal = 255; + if (RetVal >= 256) + RetVal = 255; return RetVal; } diff --git a/components/omega/test/ocn/StateTest.cpp b/components/omega/test/ocn/StateTest.cpp index 15ce8d3e3169..e93a32ad2030 100644 --- a/components/omega/test/ocn/StateTest.cpp +++ b/components/omega/test/ocn/StateTest.cpp @@ -23,8 +23,8 @@ #include "MachEnv.h" #include "OceanState.h" #include "OmegaKokkos.h" -#include "Pacer.h" #include "PGrad.h" +#include "Pacer.h" #include "Tendencies.h" #include "TimeStepper.h" #include "VertCoord.h" @@ -93,7 +93,7 @@ void initStateTest() { AuxiliaryState::init(); // Initialize pressure gradient - PressureGrad::init(); + PressureGrad::init(); // Create tendencies Tendencies::init(); diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 60591ef7331b..cef94e9e2144 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -5,8 +5,8 @@ #include "DataTypes.h" #include "Decomp.h" #include "Dimension.h" -#include "Error.h" #include "Eos.h" +#include "Error.h" #include "Field.h" #include "GlobalConstants.h" #include "Halo.h" @@ -17,8 +17,8 @@ #include "MachEnv.h" #include "OceanTestCommon.h" #include "OmegaKokkos.h" -#include "Pacer.h" #include "PGrad.h" +#include "Pacer.h" #include "TimeStepper.h" #include "VertCoord.h" #include "mpi.h" @@ -178,7 +178,7 @@ int testTendencies() { TimeInterval ZeroTimeStep; // Zero-length time step placeholder Config *Options = Config::getOmegaConfig(); Config TendConfig("Tendencies"); - Err1 = Options->get(TendConfig); + Err1 = Options->get(TendConfig); int NTracersTest = 3; Tendencies::create("TestTendencies", Mesh, VCoord, VAdv, PGrad, EqState, diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 1d3ce044b1d2..10f802617e5a 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -29,8 +29,8 @@ #include "MachEnv.h" #include "OceanState.h" #include "OmegaKokkos.h" -#include "Pacer.h" #include "PGrad.h" +#include "Pacer.h" #include "TendencyTerms.h" #include "TimeMgr.h" #include "Tracers.h" @@ -191,9 +191,9 @@ int initTimeStepperTest(const std::string &mesh) { // Creating non-default state and auxiliary state to use only one vertical // layer - auto *DefMesh = HorzMesh::getDefault(); - auto *DefHalo = Halo::getDefault(); - auto *DefEos = Eos::getInstance(); + auto *DefMesh = HorzMesh::getDefault(); + auto *DefHalo = Halo::getDefault(); + auto *DefEos = Eos::getInstance(); auto *DefPGrad = PressureGrad::getDefault(); int NTracers = Tracers::getNumTracers(); From 25586561d23222a92e7a15d7b96f277047a335ef Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 26 Feb 2026 13:14:40 -0800 Subject: [PATCH 139/152] Remove commented out code for debugging --- components/omega/src/ocn/PGrad.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index f879e6f333b8..0d029b163973 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -77,9 +77,6 @@ class PressureGradCentered { (SpecVol(ICell1, K) - SpecVol(ICell0, K)) * InvDcEdge; Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha - GradGeoPot); - // if (IEdge == 0) - // LOG_INFO("IEdge {}, K {}: GradMontPot {}, PGradAlpha {}, Tend {}", - // IEdge, K, GradMontPot, PGradAlpha, Tend(IEdge, K)); } } From 70ae706d5e3c4a5268b79b746d87d5bc126216be Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 26 Feb 2026 13:15:20 -0800 Subject: [PATCH 140/152] Remove unsed member variables --- components/omega/src/ocn/PGrad.cpp | 4 +--- components/omega/src/ocn/PGrad.h | 9 +++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 84f25086916c..7af9baeed21d 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -76,9 +76,7 @@ PressureGrad::PressureGrad( const HorzMesh *Mesh, ///< [in] Horizontal mesh const VertCoord *VCoord, ///< [in] Vertical coordinate Config *Options) ///< [in] Configuration options - : CellsOnEdge(Mesh->CellsOnEdge), DcEdge(Mesh->DcEdge), - EdgeSignOnCell(Mesh->EdgeSignOnCell), - MinLayerEdgeBot(VCoord->MinLayerEdgeBot), + : MinLayerEdgeBot(VCoord->MinLayerEdgeBot), MaxLayerEdgeTop(VCoord->MaxLayerEdgeTop), CenteredPGrad(Mesh, VCoord), HighOrderPGrad(Mesh, VCoord) { diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 0d029b163973..e0fa06e8c2fc 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -177,12 +177,9 @@ class PressureGrad { I4 NVertLayers = 0; I4 NVertLayersP1 = 0; - // Data required for computation (stored copies of mesh/VCoord arrays) - Array2DI4 CellsOnEdge; ///< cells surrounding each edge - Array1DReal DcEdge; ///< distance between cell centers across edge - Array2DReal EdgeSignOnCell; ///< orientation of edge relative to cell - Array1DI4 MinLayerEdgeBot; ///< min vertical layer on each edge - Array1DI4 MaxLayerEdgeTop; ///< max vertical layer on each edge + // Data required for computation (stored copies of VCoord arrays) + Array1DI4 MinLayerEdgeBot; ///< min vertical layer on each edge + Array1DI4 MaxLayerEdgeTop; ///< max vertical layer on each edge // Temporary: to be moveed to tidal forcing module in future Array1DReal TidalPotential; ///< Tidal potential for tidal forcing From 5864ca31240e45d69411d4c0269d298fca5b517a Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 26 Feb 2026 14:24:35 -0800 Subject: [PATCH 141/152] Fix for updated accessor state/tracer functions following rebase --- components/omega/src/ocn/PGrad.cpp | 3 +-- components/omega/src/ocn/Tendencies.cpp | 11 ++++------- components/omega/test/ocn/PGradTest.cpp | 12 ++++-------- components/omega/test/ocn/TendenciesTest.cpp | 6 ++---- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 7af9baeed21d..82438be6d30c 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -173,8 +173,7 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, const Array2DReal &SpecVol = EqState->SpecVol; const Array2DReal &ZInterface = VCoord->ZInterface; const Array2DReal &ZMid = VCoord->ZMid; - Array2DReal LayerThick; - State->getLayerThickness(LayerThick, TimeLevel); + Array2DReal LayerThick = State->getLayerThickness(TimeLevel); if (PressureGradChoice == PressureGradType::Centered) { diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 092b7b74d281..e5441b0383cc 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -13,6 +13,7 @@ #include "Eos.h" #include "Error.h" #include "Field.h" +#include "OceanState.h" #include "PGrad.h" #include "Pacer.h" #include "TimeStepper.h" @@ -601,21 +602,17 @@ void Tendencies::computeVelocityTendenciesOnly( // Compute pressure gradient if (PGrad->Enabled) { - I4 Err; - Array2DReal Temp; - Array2DReal Salinity; - Array2DReal LayerThick; // Temporary handling of surface pressure Array1DReal SurfacePressure("SurfacePressure", Mesh->NCellsSize); deepCopy(SurfacePressure, 0.0_Real); Pacer::start("Tend:pressureGradTerm", 2); - State->getLayerThickness(LayerThick, VelTimeLevel); + Array2DReal LayerThick = State->getLayerThickness(VelTimeLevel); VCoord->computePressure(LayerThick, SurfacePressure); OMEGA_SCOPE(LocPressureMid, VCoord->PressureMid); - Err = Tracers::getByName(Temp, VelTimeLevel, "Temperature"); - Err = Tracers::getByName(Salinity, VelTimeLevel, "Salinity"); + Array2DReal Temp = Tracers::getByName(VelTimeLevel, "Temperature"); + Array2DReal Salinity = Tracers::getByName(VelTimeLevel, "Salinity"); EqState->computeSpecVol(Temp, Salinity, LocPressureMid); diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 84b40842b5ae..8c1e63100c00 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -162,12 +162,9 @@ int main(int argc, char *argv[]) { I4 TimeLevel = 0; // get state and tracer arrays - Array2DReal LayerThick; - DefState->getLayerThickness(LayerThick, TimeLevel); - Array2DReal Temp; - Array2DReal Salinity; - Err = Tracers::getByName(Temp, TimeLevel, "Temperature"); - Err = Tracers::getByName(Salinity, TimeLevel, "Salinity"); + Array2DReal LayerThick = DefState->getLayerThickness(TimeLevel); + Array2DReal Temp = Tracers::getByName(TimeLevel, "Temperature"); + Array2DReal Salinity = Tracers::getByName(TimeLevel, "Salinity"); // set Z interface and mid-point locations Real ZBottom = -1000.0_Real; @@ -198,8 +195,7 @@ int main(int argc, char *argv[]) { LOG_INFO("NVertLayers = {}", NVertLayers); LOG_INFO("dC = {}", dC); DefState->copyToHost(0); - HostArray2DReal LayerThickH; - DefState->getLayerThicknessH(LayerThickH, 0); + HostArray2DReal LayerThickH = DefState->getLayerThicknessH(TimeLevel); for (int i = 0; i < 2; ++i) { for (int k = 0; k < 2; ++k) { LOG_INFO("LayerThick({}, {}) = {}", i, k, LayerThickH(i, k)); diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index cef94e9e2144..09a42e15ff74 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -65,10 +65,8 @@ int initState() { std::vector DimNamesThickness(NDims); DimNamesThickness[0] = "NCells"; - Array2DReal LayerThickCell; - Array2DReal NormalVelEdge; - Err += State->getLayerThickness(LayerThickCell, 0); - Err += State->getNormalVelocity(NormalVelEdge, 0); + Array2DReal LayerThickCell = State->getLayerThickness(0); + Array2DReal NormalVelEdge = State->getNormalVelocity(0); Array3DReal TracersArray = Tracers::getAll(0); const auto &TracersCell = TracersArray; From e29cf6f880bd5dce958c18e7df589aeb57a64e58 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 26 Feb 2026 14:57:34 -0800 Subject: [PATCH 142/152] Add user and dev documentation --- components/omega/doc/devGuide/PGrad.md | 185 ++++++++++++++++++++++++ components/omega/doc/index.md | 2 + components/omega/doc/userGuide/PGrad.md | 68 +++++++++ 3 files changed, 255 insertions(+) create mode 100644 components/omega/doc/devGuide/PGrad.md create mode 100644 components/omega/doc/userGuide/PGrad.md diff --git a/components/omega/doc/devGuide/PGrad.md b/components/omega/doc/devGuide/PGrad.md new file mode 100644 index 000000000000..cfbe45fcabd8 --- /dev/null +++ b/components/omega/doc/devGuide/PGrad.md @@ -0,0 +1,185 @@ +(omega-dev-pgrad)= + +# Pressure Gradient (PGrad) + +Omega includes a `PressureGrad` class that computes horizontal pressure gradient +tendencies for the non-Boussinesq momentum equation. The implementation supports a +centered difference scheme as the default, with a placeholder for future high-order +methods. The class follows the same factory pattern used by other Omega modules. + +## PressureGradType enum + +An enumeration of the available pressure gradient schemes is defined in `PGrad.h`: + +```c++ +enum class PressureGradType { Centered, HighOrder1, HighOrder2 }; +``` + +This is used to select which pressure gradient method is applied at runtime. + +## Initialization + +An instance of `PressureGrad` requires both a [`HorzMesh`](#omega-dev-horz-mesh) and +a [`VertCoord`](#omega-dev-vert-coord), so these classes and all of their dependencies +must be initialized before `PressureGrad` can be initialized. The static method: + +```c++ +OMEGA::PressureGrad::init(); +``` + +initializes the default `PressureGrad` instance using the default `HorzMesh` and +`VertCoord` instances and the global Omega configuration. A pointer to the default +instance can be retrieved at any time using: + +```c++ +OMEGA::PressureGrad* DefPGrad = OMEGA::PressureGrad::getDefault(); +``` + +## Creating additional instances + +Additional named instances can be created using: + +```c++ +OMEGA::PressureGrad* MyPGrad = + OMEGA::PressureGrad::create("MyPGrad", Mesh, VCoord, Options); +``` + +where `Mesh` is a pointer to a `HorzMesh`, `VCoord` is a pointer to a `VertCoord`, +and `Options` is a pointer to the `Config`. A named instance can be retrieved later +using: + +```c++ +OMEGA::PressureGrad* MyPGrad = OMEGA::PressureGrad::get("MyPGrad"); +``` + +## Constructor behavior + +The constructor reads the `PressureGrad` section from the configuration, stores +references to mesh and vertical coordinate data needed for computation, and enables +the appropriate functor based on the configured `PressureGradType`. It also allocates +placeholder arrays for tidal potential and self-attraction/loading, which are intended +to be populated by a future tidal forcing module. Currently these arrays are initialized +to zero. + +## Computing the pressure gradient + +To compute pressure gradient tendencies and accumulate them into a tendency array: + +```c++ +PGrad->computePressureGrad(Tend, State, VCoord, EqState, TimeLevel); +``` + +where: +- `Tend` is a 2D array `(NEdgesAll × NVertLayers)` that the pressure gradient + tendency is accumulated into +- `State` is the current `OceanState`, from which layer thickness is extracted + at the given `TimeLevel` +- `VCoord` provides pressure, interface height, and geopotential fields +- `EqState` provides the specific volume field +- `TimeLevel` selects which time level of the state to use + +The method uses hierarchical Kokkos parallelism: an outer `parallelForOuter` loop +iterates over edges, and an inner `parallelForInner` loop iterates over vertical +chunks. The appropriate functor is dispatched based on `PressureGradChoice`. + +## Functors + +### PressureGradCentered + +This functor implements a centered difference approximation of the pressure gradient +tendency. For each edge, it first computes the layer-invariant tidal and +self-attraction/loading contribution: + +``` +GradGeoPot = grad(TidalPotential) + grad(SelfAttractionLoading) +``` + +Then, for each vertical layer `K`, it computes three terms: + +1. **Montgomery potential gradient**: The average of the horizontal gradients of the + Montgomery potential ($\alpha p + g z$) at the top (interface `K`) and bottom + (interface `K+1`) of the layer. This compactly represents the combined effect + of the pressure gradient and the geopotential contribution from tilted coordinate + surfaces. + +2. **Specific volume correction**: A correction term equal to the edge-averaged + pressure at mid-layer multiplied by the horizontal gradient of specific volume. + This accounts for horizontal density variations that are not captured by the + Montgomery potential form. + +3. **Tidal and geopotential forcing** (`GradGeoPot`): The external geopotential + contribution from tidal forcing and self-attraction/loading, applied uniformly + across all layers at an edge. + +The tendency update for each layer is: + +``` +Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha - GradGeoPot) +``` + +where `EdgeMask` is applied to enforce land boundary conditions. The functor operator +signature is: + +```c++ +KOKKOS_FUNCTION void operator()(const Array2DReal &Tend, I4 IEdge, I4 KChunk, + const Array2DReal &PressureMid, + const Array2DReal &PressureInterface, + const Array2DReal &ZInterface, + const Array1DReal &TidalPotential, + const Array1DReal &SelfAttractionLoading, + const Array2DReal &SpecVol) const; +``` + +### PressureGradHighOrder + +This functor is a placeholder for a future high-order pressure gradient implementation +suitable for ice shelf cavities and complex bathymetry. Currently it performs no +computation (a no-op). + +## Configuration + +The pressure gradient type is selected in the input YAML file: + +```yaml +PressureGrad: + PressureGradType: 'centered' +``` + +Valid options for `PressureGradType` are: +- `'centered'` or `'Centered'`: centered difference approximation (default) +- `'HighOrder1'`: first high-order method (placeholder, future implementation) + +If an unrecognized value is provided, the implementation falls back to the centered +scheme and logs an informational message. + +## Data members + +The `PressureGrad` class stores the following key data: + +| Member | Type | Description | +| ------ | ---- | ----------- | +| `NEdgesAll` | `I4` | Total number of edges including halo | +| `NChunks` | `I4` | Number of vertical chunks for vectorization | +| `NVertLayers` | `I4` | Number of vertical layers | +| `NVertLayersP1` | `I4` | Number of vertical layers plus one | +| `MinLayerEdgeBot` | `Array1DI4` | Minimum active layer index for each edge | +| `MaxLayerEdgeTop` | `Array1DI4` | Maximum active layer index for each edge | +| `TidalPotential` | `Array1DReal` | Tidal potential (placeholder, currently zero) | +| `SelfAttractionLoading` | `Array1DReal` | Self-attraction and loading term (placeholder, currently zero) | +| `CenteredPGrad` | `PressureGradCentered` | Centered pressure gradient functor | +| `HighOrderPGrad` | `PressureGradHighOrder` | High-order pressure gradient functor | +| `PressureGradChoice` | `PressureGradType` | Selected pressure gradient method | + +## Removal + +To remove all `PressureGrad` instances: + +```c++ +OMEGA::PressureGrad::clear(); +``` + +To remove a specific named instance: + +```c++ +OMEGA::PressureGrad::erase("MyPGrad"); +``` diff --git a/components/omega/doc/index.md b/components/omega/doc/index.md index 1dd11e400a43..41bd611140b3 100644 --- a/components/omega/doc/index.md +++ b/components/omega/doc/index.md @@ -49,6 +49,7 @@ userGuide/Reductions userGuide/Tracers userGuide/TridiagonalSolvers userGuide/VertCoord +userGuide/PGrad userGuide/Timing userGuide/VerticalMixingCoeff userGuide/VertAdv @@ -93,6 +94,7 @@ devGuide/Reductions devGuide/Tracers devGuide/TridiagonalSolvers devGuide/VertCoord +devGuide/PGrad devGuide/Timing devGuide/VerticalMixingCoeff devGuide/VertAdv diff --git a/components/omega/doc/userGuide/PGrad.md b/components/omega/doc/userGuide/PGrad.md new file mode 100644 index 000000000000..0fc7bb5968a2 --- /dev/null +++ b/components/omega/doc/userGuide/PGrad.md @@ -0,0 +1,68 @@ +(omega-user-pgrad)= + +# Pressure Gradient + +The pressure gradient term in the momentum equation represents the force per unit +mass due to horizontal variations in pressure and geopotential. This term is +essential for capturing the dynamics of +ocean circulation, including both barotropic and baroclinic motions. + +## Physical Background + +In the layered non-Boussinesq momentum equation solved in Omega, the pressure +gradient tendency for each edge and layer includes three contributions: + +1. **Montgomery potential gradient**: The horizontal gradient of the Montgomery + potential ($\alpha p + g z$), averaged across the top and bottom interfaces of + each layer. The Montgomery potential combines the pressure gradient and the + geopotential, and its gradient along coordinate surfaces accounts for both the + direct pressure force and the effect of tilted layer interfaces that arise when + using a general vertical coordinate. + +2. **Specific volume correction**: A correction term proportional to the gradient + of specific volume (inverse density) at each edge. This term ensures that + horizontal density variations between the two cells sharing an edge are properly + represented in the pressure gradient force. + +3. **External geopotential forcing**: Contributions from the tidal potential and + the self-attraction and loading (SAL) terms. These represent gravitational + forcing from astronomical tides and the deformation of the solid Earth and ocean + surface in response to the ocean mass distribution. These terms are currently set + to zero and will be provided by a future tidal forcing module. + +## Configuration Options + +The pressure gradient method is configured in the input YAML file under the +`PressureGrad` section: + +```yaml +PressureGrad: + PressureGradType: 'centered' +``` + +### Available Methods + +**Centered Difference** (`'centered'` or `'Centered'`) +- Computes the pressure gradient using a centered finite-difference approximation + of the Montgomery potential gradient and specific volume correction +- Suitable for global ocean simulations without ice shelf cavities +- Default and currently the only fully implemented option + +**High-Order** (`'HighOrder1'`) +- Placeholder for a future high-order pressure gradient method based on volume + integral formulations +- Intended for simulations with ice shelf cavities and steep bathymetry where the + centered scheme may be inaccurate +- Not yet implemented; selecting this option produces zero pressure gradient tendency + +## Dependencies + +The pressure gradient calculation requires the following Omega components to be +initialized first: + +- [**Horizontal Mesh**](omega-user-horz-mesh): provides mesh geometry including + distances between cell centers and edge connectivity +- [**Vertical Coordinate**](omega-user-vert-coord): provides pressure at layer + mid-points and interfaces, interface heights ($z$), and geopotential +- [**Equation of State**](omega-user-eos): provides the specific volume field +- [**Ocean State**](omega-user-state): provides the current layer thicknesses From 491b75ce8c0c49821ec9fa6d08b2cd010572f6c3 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 5 Mar 2026 15:52:32 -0800 Subject: [PATCH 143/152] Fix compiler warnings from unsed variables --- components/omega/src/ocn/PGrad.cpp | 2 -- components/omega/src/ocn/Tendencies.h | 6 +++--- components/omega/test/ocn/PGradTest.cpp | 5 ----- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 82438be6d30c..bbd11a889fb4 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -169,10 +169,8 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, const Array2DReal &PressureMid = VCoord->PressureMid; const Array2DReal &PressureInterface = VCoord->PressureInterface; - const Array2DReal &Geopotential = VCoord->GeopotentialMid; const Array2DReal &SpecVol = EqState->SpecVol; const Array2DReal &ZInterface = VCoord->ZInterface; - const Array2DReal &ZMid = VCoord->ZMid; Array2DReal LayerThick = State->getLayerThickness(TimeLevel); if (PressureGradChoice == PressureGradType::Centered) { diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index ad5bd8b4f3ee..1296e8fd80ce 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -185,8 +185,10 @@ class Tendencies { const HorzMesh *Mesh; ///< Pointer to horizontal mesh VertCoord *VCoord; ///< Pointer to vertical coordinate VertAdv *VAdv; ///< Pointer to vertical advection - PressureGrad *PGrad; ///< Pointer to pressure gradient + CustomTendencyType CustomThicknessTend; + CustomTendencyType CustomVelocityTend; Eos *EqState; ///< Pointer to equation of state + PressureGrad *PGrad; ///< Pointer to pressure gradient I4 NTracers; ///< Number of tracers TimeInterval TimeStep; ///< Time step @@ -196,8 +198,6 @@ class Tendencies { // Map of all tendency objects static std::map> AllTendencies; - CustomTendencyType CustomThicknessTend; - CustomTendencyType CustomVelocityTend; }; // end class Tendencies diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 8c1e63100c00..6d70640f10b6 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -87,7 +87,6 @@ void initPGradTest() { int main(int argc, char *argv[]) { int RetVal = 0; - int Err; MPI_Init(&argc, &argv); Kokkos::initialize(); @@ -99,13 +98,10 @@ int main(int argc, char *argv[]) { // Initialize default PressureGrad PressureGrad::init(); - MachEnv *DefEnv = MachEnv::getDefault(); HorzMesh *DefMesh = HorzMesh::getDefault(); VertCoord *VCoord = VertCoord::getDefault(); OceanState *DefState = OceanState::getDefault(); - Decomp *DefDecomp = Decomp::getDefault(); Eos *DefEos = Eos::getInstance(); - Config *Options = Config::getOmegaConfig(); // create arrays: Tend on edges, Pressure/Geopotential/SpecVol on cells Array2DReal Tend("Tend", DefMesh->NEdgesSize, VCoord->NVertLayers); @@ -147,7 +143,6 @@ int main(int argc, char *argv[]) { auto &CellsOnEdge = DefMesh->CellsOnEdge; auto &DcEdge = DefMesh->DcEdge; - auto &EdgeMask = VCoord->EdgeMask; parallelFor( {NEdgesAll}, KOKKOS_LAMBDA(int i) { CellsOnEdge(i, 0) = 0; From 9f02c7e6d97318934e4e269ecdb711a448ce8dc6 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 5 Mar 2026 15:54:45 -0800 Subject: [PATCH 144/152] Fix linting issues --- components/omega/src/ocn/Tendencies.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 1296e8fd80ce..e6b98bdfd0b1 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -187,9 +187,9 @@ class Tendencies { VertAdv *VAdv; ///< Pointer to vertical advection CustomTendencyType CustomThicknessTend; CustomTendencyType CustomVelocityTend; - Eos *EqState; ///< Pointer to equation of state - PressureGrad *PGrad; ///< Pointer to pressure gradient - I4 NTracers; ///< Number of tracers + Eos *EqState; ///< Pointer to equation of state + PressureGrad *PGrad; ///< Pointer to pressure gradient + I4 NTracers; ///< Number of tracers TimeInterval TimeStep; ///< Time step // Pointer to default tendencies @@ -198,7 +198,6 @@ class Tendencies { // Map of all tendency objects static std::map> AllTendencies; - }; // end class Tendencies } // namespace OMEGA From ee4be1a92dfb38bb29dfed7da967c7a5dcb779e7 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 16 Mar 2026 18:15:26 -0700 Subject: [PATCH 145/152] Fix linting issues after rebase --- components/omega/src/ocn/Tendencies.cpp | 13 +++++++------ components/omega/src/ocn/Tendencies.h | 6 +++--- .../omega/test/timeStepping/TimeStepperTest.cpp | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index e5441b0383cc..8d46f598e1f3 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -77,9 +77,9 @@ void Tendencies::init() { TimeInterval TimeStep = DefTimeStepper->getTimeStep(); // Ceate default tendencies - Tendencies::DefaultTendencies = - create("Default", DefHorzMesh, DefVertCoord, DefVertAdv, DefPGrad, DefEos, - NTracers, TimeStep, &TendConfig, CustomThickTend, CustomVelTend); + Tendencies::DefaultTendencies = create( + "Default", DefHorzMesh, DefVertCoord, DefVertAdv, DefPGrad, DefEos, + NTracers, TimeStep, &TendConfig, CustomThickTend, CustomVelTend); DefaultTendencies->readConfig(OmegaConfig); @@ -245,7 +245,7 @@ void Tendencies::readConfig(Config *OmegaConfig ///< [in] Omega config CHECK_ERROR_ABORT(Err, "Tendencies: EddyDiff4 not found in TendConfig"); } - Err += TendConfig->get("PressureGradTendencyEnable", this->PGrad->Enabled); + Err += TendConfig.get("PressureGradTendencyEnable", this->PGrad->Enabled); CHECK_ERROR_ABORT( Err, "Tendencies: PressureGradTendencyEnable not found in TendConfig"); } @@ -351,8 +351,9 @@ Tendencies::Tendencies(const std::string &Name_, ///< [in] Name for tendencies int NTracersIn, ///< [in] Number of tracers TimeInterval TimeStepIn, ///< [in] Time step Config *Options) ///< [in] Configuration options - : Tendencies(Name_, Mesh, VCoord, VAdv, PGrad, EqState, NTracersIn, TimeStepIn, Options, - CustomTendencyType{}, CustomTendencyType{}) {} + : Tendencies(Name_, Mesh, VCoord, VAdv, PGrad, EqState, NTracersIn, + TimeStepIn, Options, CustomTendencyType{}, + CustomTendencyType{}) {} //------------------------------------------------------------------------------ // Compute tendencies for layer thickness equation diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index e6b98bdfd0b1..757abc168d45 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -187,9 +187,9 @@ class Tendencies { VertAdv *VAdv; ///< Pointer to vertical advection CustomTendencyType CustomThicknessTend; CustomTendencyType CustomVelocityTend; - Eos *EqState; ///< Pointer to equation of state - PressureGrad *PGrad; ///< Pointer to pressure gradient - I4 NTracers; ///< Number of tracers + Eos *EqState; ///< Pointer to equation of state + PressureGrad *PGrad; ///< Pointer to pressure gradient + I4 NTracers; ///< Number of tracers TimeInterval TimeStep; ///< Time step // Pointer to default tendencies diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 10f802617e5a..0de465c17fe4 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -222,8 +222,8 @@ int initTimeStepperTest(const std::string &mesh) { // Creating non-default tendencies with custom velocity tendencies auto *TestTendencies = Tendencies::create( - "TestTendencies", DefMesh, DefVertCoord, DefVAdv, DefPGrad, DefEos, NTracers, - ZeroTimeStep, &Options, Tendencies::CustomTendencyType{}, + "TestTendencies", DefMesh, DefVertCoord, DefVAdv, DefPGrad, DefEos, + NTracers, ZeroTimeStep, &Options, Tendencies::CustomTendencyType{}, DecayVelocityTendency{}); if (!TestTendencies) { Err++; From e58f59680157f2c5ca02d88cf6b5c3c2e9039bd0 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 19 Mar 2026 14:46:54 -0700 Subject: [PATCH 146/152] Fix vertRange -> vertRangeChunked --- components/omega/src/ocn/PGrad.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index bbd11a889fb4..20257fc6ccdd 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -181,7 +181,7 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { const int KMin = LocMinLayerEdgeBot(IEdge); const int KMax = LocMaxLayerEdgeTop(IEdge); - const int KRange = vertRange(KMin, KMax); + const int KRange = vertRangeChunked(KMin, KMax); parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { @@ -200,7 +200,7 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, KOKKOS_LAMBDA(I4 IEdge, const TeamMember &Team) { const int KMin = LocMinLayerEdgeBot(IEdge); const int KMax = LocMaxLayerEdgeTop(IEdge); - const int KRange = vertRange(KMin, KMax); + const int KRange = vertRangeChunked(KMin, KMax); parallelForInner( Team, KRange, INNER_LAMBDA(int KChunk) { From 45b90e5be1da98b631cc8b3218fb3a4fd5fbceb0 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 19 Mar 2026 15:30:03 -0700 Subject: [PATCH 147/152] Fix converence criteria in CTest --- components/omega/test/ocn/PGradTest.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 6d70640f10b6..4259c300a3d5 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -302,7 +302,8 @@ int main(int argc, char *argv[]) { } // refinement loop // Test for second order convergence - if (RMSE(NRefinements) < RMSE(0) / pow(4.0_Real, NRefinements - 1)) { + // resolution (dC) increases in refimenent loop + if (RMSE(0) < RMSE(NRefinements - 1) / pow(4.0_Real, NRefinements - 1)) { RetVal = 0; } else { RetVal = 1; From f016d0f5fffa3b4ef56d5b2f89b54927e46f214e Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 26 Mar 2026 14:20:08 -0700 Subject: [PATCH 148/152] Fix variable naming convention in PGradTest.cpp --- components/omega/test/ocn/PGradTest.cpp | 82 ++++++++++++------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 4259c300a3d5..d2a58a8dd365 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -115,12 +115,12 @@ int main(int argc, char *argv[]) { I4 NCellsAll = DefMesh->NCellsAll; I4 NVertLayers = 60; - Real dC = 30000.0_Real; + Real DC = 30000.0_Real; I4 NRefinements = 4; - HostArray1DReal RMSE("RMSE", NRefinements); - for (int refinement = 0; refinement < NRefinements; ++refinement) { + HostArray1DReal Rmse("Rmse", NRefinements); + for (int Refinement = 0; Refinement < NRefinements; ++Refinement) { - LOG_INFO("PGradTest: Starting refinement level {}", refinement); + LOG_INFO("PGradTest: Starting refinement level {}", Refinement); VCoord->NVertLayers = NVertLayers; VCoord->NVertLayersP1 = NVertLayers + 1; @@ -138,7 +138,6 @@ int main(int argc, char *argv[]) { {NEdgesAll}, KOKKOS_LAMBDA(int i) { MinLayerEdgeBot(i) = 0; MaxLayerEdgeTop(i) = NVertLayers - 1; - // MaxLayerEdgeTop(i) = 4; }); auto &CellsOnEdge = DefMesh->CellsOnEdge; @@ -147,7 +146,7 @@ int main(int argc, char *argv[]) { {NEdgesAll}, KOKKOS_LAMBDA(int i) { CellsOnEdge(i, 0) = 0; CellsOnEdge(i, 1) = 1; - DcEdge(i) = dC; + DcEdge(i) = DC; }); // Fetch reference desnity from Config @@ -163,32 +162,31 @@ int main(int argc, char *argv[]) { // set Z interface and mid-point locations Real ZBottom = -1000.0_Real; - Real dZ = 2.0_Real * (-ZBottom / NVertLayers); + Real DZ = 2.0_Real * (-ZBottom / NVertLayers); auto &BottomDepth = VCoord->BottomDepth; auto &ZInterface = VCoord->ZInterface; auto &ZMid = VCoord->ZMid; - Real tilt_factor = 0.495_Real; - // Real tilt_factor = 0.45_Real; + Real TiltFactor = 0.495_Real; parallelFor( {NCellsAll}, KOKKOS_LAMBDA(int i) { ZInterface(i, NVertLayers) = ZBottom; SurfacePressure(i) = 0.0_Real; BottomDepth(i) = 0.0_Real; for (int k = NVertLayers - 1; k >= 0; --k) { - Real x = (k + i) % 2; - Real dz = (2.0_Real * tilt_factor - 1.0_Real) * x * dZ + - (1.0_Real - tilt_factor) * - dZ; // staggered layer thickness - ZInterface(i, k) = ZInterface(i, k + 1) + dz; + Real X = (k + i) % 2; + Real Dz = (2.0_Real * TiltFactor - 1.0_Real) * X * DZ + + (1.0_Real - TiltFactor) * + DZ; // staggered layer thickness + ZInterface(i, k) = ZInterface(i, k + 1) + Dz; LayerThick(i, k) = ZInterface(i, k) - ZInterface(i, k + 1); ZMid(i, k) = 0.5_Real * (ZInterface(i, k) + ZInterface(i, k + 1)); - BottomDepth(i) += dz; + BottomDepth(i) += Dz; } }); LOG_INFO("NVertLayers = {}", NVertLayers); - LOG_INFO("dC = {}", dC); + LOG_INFO("dC = {}", DC); DefState->copyToHost(0); HostArray2DReal LayerThickH = DefState->getLayerThicknessH(TimeLevel); for (int i = 0; i < 2; ++i) { @@ -206,11 +204,11 @@ int main(int argc, char *argv[]) { Real S0 = 30.0; Real SB = 40.0; - Real phi0 = (ZMid(i, k) - ZBottom) / (-ZBottom); - Real phiB = 1.0_Real - phi0; + Real Phi0 = (ZMid(i, k) - ZBottom) / (-ZBottom); + Real PhiB = 1.0_Real - Phi0; - Temp(i, k) = T0 * phi0 + TB * phiB; - Salinity(i, k) = S0 * phi0 + SB * phiB; + Temp(i, k) = T0 * Phi0 + TB * PhiB; + Salinity(i, k) = S0 * Phi0 + SB * PhiB; SpecVol(i, k) = 1.0_Real / Density0; SpecVolOld(i, k) = SpecVol(i, k); }); @@ -219,7 +217,7 @@ int main(int argc, char *argv[]) { auto &PressureMid = VCoord->PressureMid; VCoord->computePressure(LayerThick, SurfacePressure); deepCopy(PressureMidOld, PressureMid); - for (int iteration = 0; iteration < 15; ++iteration) { + for (int Iteration = 0; Iteration < 15; ++Iteration) { // compute specific volume from EOS VCoord->computePressure(LayerThick, SurfacePressure); @@ -233,19 +231,19 @@ int main(int argc, char *argv[]) { }); // compute difference from previous iteration - Real max_value = 0.0_Real; + Real MaxValue = 0.0_Real; parallelReduce( {NCellsAll, NVertLayers}, KOKKOS_LAMBDA(int i, int k, Real &max) { - Real diff = Kokkos::abs(SpecVol(i, k) - SpecVolOld(i, k)); - if (diff > max) - max = diff; + Real Diff = Kokkos::abs(SpecVol(i, k) - SpecVolOld(i, k)); + if (Diff > max) + max = Diff; }, - Kokkos::Max(max_value)); + Kokkos::Max(MaxValue)); // check convergence - if (max_value < 1e-12_Real) { - LOG_INFO("converged: max diff = {}", max_value); + if (MaxValue < 1e-12_Real) { + LOG_INFO("converged: max diff = {}", MaxValue); break; } else { parallelFor( @@ -273,37 +271,37 @@ int main(int argc, char *argv[]) { TimeLevel); // compute errors - Real max_value = 0.0_Real; + Real MaxValue = 0.0_Real; parallelReduce( {NEdgesAll, NVertLayers - 2}, KOKKOS_LAMBDA(int i, int k, Real &max) { - Real val = Kokkos::abs(Tend(i, k + 1)); - if (val > max) - max = val; + Real Val = Kokkos::abs(Tend(i, k + 1)); + if (Val > max) + max = Val; }, - Kokkos::Max(max_value)); - Real sum_value = 0.0_Real; + Kokkos::Max(MaxValue)); + Real SumValue = 0.0_Real; parallelReduce( {NEdgesAll, NVertLayers - 2}, - KOKKOS_LAMBDA(int i, int k, Real &lsum) { - lsum += Tend(i, k + 1) * Tend(i, k + 1); + KOKKOS_LAMBDA(int i, int k, Real &LSum) { + LSum += Tend(i, k + 1) * Tend(i, k + 1); }, - Kokkos::Sum(sum_value)); - Real rmse = std::sqrt(sum_value / (NEdgesAll * (NVertLayers - 2))); - RMSE(refinement) = rmse; + Kokkos::Sum(SumValue)); + Real RmseVal = std::sqrt(SumValue / (NEdgesAll * (NVertLayers - 2))); + Rmse(Refinement) = RmseVal; LOG_INFO("refinement level {}: max |Tend| = {}, average Tend = {}", - refinement, max_value, rmse); + Refinement, MaxValue, RmseVal); // coarsen for next iteration - dC = dC * 2.0_Real; + DC = DC * 2.0_Real; NVertLayers = NVertLayers / 2; } // refinement loop // Test for second order convergence // resolution (dC) increases in refimenent loop - if (RMSE(0) < RMSE(NRefinements - 1) / pow(4.0_Real, NRefinements - 1)) { + if (Rmse(0) < Rmse(NRefinements - 1) / pow(4.0_Real, NRefinements - 1)) { RetVal = 0; } else { RetVal = 1; From bb0e1be95ae165ff7d66d12a63c2abc5537ea643 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 26 Mar 2026 14:50:14 -0700 Subject: [PATCH 149/152] Incorporate review feedback --- components/omega/src/ocn/PGrad.cpp | 25 +++++++++-------- components/omega/src/ocn/PGrad.h | 13 +++++---- components/omega/src/ocn/Tendencies.cpp | 28 ++++++++++++------- components/omega/src/ocn/Tendencies.h | 7 +++-- .../timeStepping/ForwardBackwardStepper.cpp | 2 +- .../src/timeStepping/RungeKutta2Stepper.cpp | 4 +-- .../src/timeStepping/RungeKutta4Stepper.cpp | 4 +-- components/omega/test/ocn/PGradTest.cpp | 7 +++-- components/omega/test/ocn/TendenciesTest.cpp | 4 ++- 9 files changed, 56 insertions(+), 38 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index 20257fc6ccdd..e91d03b28695 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -82,9 +82,9 @@ PressureGrad::PressureGrad( // store mesh sizes NEdgesAll = Mesh->NEdgesAll; + NEdgesOwned = Mesh->NEdgesOwned; NVertLayers = VCoord->NVertLayers; NVertLayersP1 = NVertLayers + 1; - NChunks = NVertLayers / VecLength; // Read config options for PressureGrad type // and enable the appropriate functor @@ -124,7 +124,13 @@ PressureGrad::~PressureGrad() { //------------------------------------------------------------------------------ // Remove PressureGrad instances before exit -void PressureGrad::clear() { AllPGrads.clear(); } // end clear +void PressureGrad::clear() { + + AllPGrads.clear(); + + DefaultPGrad = nullptr; // prevent dangling pointe'r + + } // end clear //------------------------------------------------------------------------------ // Remove PressureGrad from list by name @@ -155,10 +161,11 @@ PressureGrad *PressureGrad::get(const std::string &Name ///< [in] Name of //------------------------------------------------------------------------------ // Compute pressure gradient tendencies and add into Tend array void PressureGrad::computePressureGrad(Array2DReal &Tend, - const OceanState *State, - const VertCoord *VCoord, - const Eos *EqState, - const int TimeLevel) const { + const Array2DReal &PressureMid, + const Array2DReal &PressureInterface, + const Array2DReal &SpecVol, + const Array2DReal &ZInterface, + const Array2DReal &LayerThick) const { OMEGA_SCOPE(LocCenteredPGrad, CenteredPGrad); OMEGA_SCOPE(LocHighOrderPGrad, HighOrderPGrad); @@ -167,12 +174,6 @@ void PressureGrad::computePressureGrad(Array2DReal &Tend, OMEGA_SCOPE(LocTidalPotential, TidalPotential); OMEGA_SCOPE(LocSelfAttractionLoading, SelfAttractionLoading); - const Array2DReal &PressureMid = VCoord->PressureMid; - const Array2DReal &PressureInterface = VCoord->PressureInterface; - const Array2DReal &SpecVol = EqState->SpecVol; - const Array2DReal &ZInterface = VCoord->ZInterface; - Array2DReal LayerThick = State->getLayerThickness(TimeLevel); - if (PressureGradChoice == PressureGradType::Centered) { // computes centered geopotential and pressure gradient tendency diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index e0fa06e8c2fc..50941762ef76 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -73,7 +73,7 @@ class PressureGradCentered { Real GradMontPot = 0.5_Real * (GradMontPotK + GradMontPotKp1); Real PGradAlpha = - 0.5 * (PressureMid(ICell1, K) + PressureMid(ICell0, K)) * + 0.5_Real * (PressureMid(ICell1, K) + PressureMid(ICell0, K)) * (SpecVol(ICell1, K) - SpecVol(ICell0, K)) * InvDcEdge; Tend(IEdge, K) += EdgeMask(IEdge, K) * (-GradMontPot + PGradAlpha - GradGeoPot); @@ -156,9 +156,12 @@ class PressureGrad { ~PressureGrad(); // Compute pressure gradient tendencies and add into Tend array - void computePressureGrad(Array2DReal &Tend, const OceanState *State, - const VertCoord *VCoord, const Eos *EqState, - const int TimeLevel) const; + void computePressureGrad(Array2DReal &Tend, + const Array2DReal &PressureMid, + const Array2DReal &PressureInterface, + const Array2DReal &SpecVol, + const Array2DReal &ZInterface, + const Array2DReal &LayerThick) const; private: // Construct a new pressure gradient object @@ -173,7 +176,7 @@ class PressureGrad { // Mesh-related sizes I4 NEdgesAll = 0; - I4 NChunks = 0; + I4 NEdgesOwned = 0; I4 NVertLayers = 0; I4 NVertLayersP1 = 0; diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 8d46f598e1f3..c42b9fa3ed76 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -431,6 +431,7 @@ void Tendencies::computeVelocityTendenciesOnly( const AuxiliaryState *AuxState, ///< [in] Auxilary state variables int ThickTimeLevel, ///< [in] Time level int VelTimeLevel, ///< [in] Time level + int TracerTimeLevel, ///< [in] Time level TimeInstant Time ///< [in] Time ) { @@ -609,20 +610,24 @@ void Tendencies::computeVelocityTendenciesOnly( deepCopy(SurfacePressure, 0.0_Real); Pacer::start("Tend:pressureGradTerm", 2); - Array2DReal LayerThick = State->getLayerThickness(VelTimeLevel); + Array2DReal LayerThick = State->getLayerThickness(ThickTimeLevel); VCoord->computePressure(LayerThick, SurfacePressure); - OMEGA_SCOPE(LocPressureMid, VCoord->PressureMid); - Array2DReal Temp = Tracers::getByName(VelTimeLevel, "Temperature"); - Array2DReal Salinity = Tracers::getByName(VelTimeLevel, "Salinity"); - EqState->computeSpecVol(Temp, Salinity, LocPressureMid); + const auto &PressureMid = VCoord->PressureMid; + const auto &PressureInterface = VCoord->PressureInterface; + Array2DReal Temp = Tracers::getByName(TracerTimeLevel, "Temperature"); + Array2DReal Salinity = Tracers::getByName(TracerTimeLevel, "Salinity"); + EqState->computeSpecVol(Temp, Salinity, PressureMid); // Temporary: ensure vertical geometric/geopotential fields are updated // for pressure-gradient tendency calculations. - VCoord->computeZHeight(LayerThick, EqState->SpecVol); + const auto &SpecVol = EqState->SpecVol; + VCoord->computeZHeight(LayerThick, SpecVol); - PGrad->computePressureGrad(LocNormalVelocityTend, State, VCoord, EqState, - VelTimeLevel); + const auto &ZInterface = VCoord->ZInterface; + PGrad->computePressureGrad(LocNormalVelocityTend, PressureMid, + PressureInterface, SpecVol, ZInterface, + LayerThick); Pacer::stop("Tend:pressureGradTerm", 2); } @@ -795,13 +800,14 @@ void Tendencies::computeVelocityTendencies( const AuxiliaryState *AuxState, ///< [in] Auxilary state variables int ThickTimeLevel, ///< [in] Time level int VelTimeLevel, ///< [in] Time level + int TracerTimeLevel, ///< [in] Time level TimeInstant Time ///< [in] Time ) { Pacer::start("Tend:computeVelocityTendencies", 1); AuxState->computeMomAux(State, ThickTimeLevel, VelTimeLevel); computeVelocityTendenciesOnly(State, AuxState, ThickTimeLevel, VelTimeLevel, - Time); + TracerTimeLevel, Time); Pacer::stop("Tend:computeVelocityTendencies", 1); } @@ -856,13 +862,15 @@ void Tendencies::computeAllTendencies( const Array3DReal &TracerArray, ///< [in] Tracer array int ThickTimeLevel, ///< [in] Time level int VelTimeLevel, ///< [in] Time level + int TracerTimeLevel, ///< [in] Time level TimeInstant Time ///< [in] Time ) { AuxState->computeAll(State, TracerArray, ThickTimeLevel, VelTimeLevel); + computeThicknessTendenciesOnly(State, AuxState, ThickTimeLevel, VelTimeLevel, Time); computeVelocityTendenciesOnly(State, AuxState, ThickTimeLevel, VelTimeLevel, - Time); + TracerTimeLevel, Time); computeTracerTendenciesOnly(State, AuxState, TracerArray, ThickTimeLevel, VelTimeLevel, Time); } // end all tendency compute diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 757abc168d45..1955fa7c130e 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -82,7 +82,7 @@ class Tendencies { void computeVelocityTendencies(const OceanState *State, const AuxiliaryState *AuxState, int ThickTimeLevel, int VelTimeLevel, - TimeInstant Time); + int TracerTimeLevel, TimeInstant Time); void computeTracerTendencies(const OceanState *State, const AuxiliaryState *AuxState, const Array3DReal &TracerArray, @@ -91,7 +91,8 @@ class Tendencies { void computeAllTendencies(const OceanState *State, const AuxiliaryState *AuxState, const Array3DReal &TracerArray, int ThickTimeLevel, - int VelTimeLevel, TimeInstant Time); + int VelTimeLevel, int TracerTimeLevel, + TimeInstant Time); void computeThicknessTendenciesOnly(const OceanState *State, const AuxiliaryState *AuxState, int ThickTimeLevel, int VelTimeLevel, @@ -99,7 +100,7 @@ class Tendencies { void computeVelocityTendenciesOnly(const OceanState *State, const AuxiliaryState *AuxState, int ThickTimeLevel, int VelTimeLevel, - TimeInstant Time); + int TracerTimeLevel, TimeInstant Time); void computeTracerTendenciesOnly(const OceanState *State, const AuxiliaryState *AuxState, const Array3DReal &TracerArray, diff --git a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp index 12c53a59a436..a686dcfae0d6 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp @@ -57,7 +57,7 @@ void ForwardBackwardStepper::doStep( // R_u^{n+1} = RHS_u(u^{n}, h^{n+1}, t^{n+1}) Tend->computeVelocityTendencies(State, AuxState, NextLevel, CurLevel, - SimTime + TimeStep); + NextLevel, SimTime + TimeStep); // u^{n+1} = u^{n} + R_u^{n+1} updateVelocityByTend(State, NextLevel, State, CurLevel, TimeStep); diff --git a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp index f4b697529db0..47ffc54b71fe 100644 --- a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp @@ -37,7 +37,7 @@ void RungeKutta2Stepper::doStep(OceanState *State, // model state // q = (h,u,phi) // R_q^{n} = RHS_q(u^{n}, h^{n}, phi^{n}, t^{n}) Tend->computeAllTendencies(State, AuxState, CurTracerArray, CurLevel, - CurLevel, SimTime); + CurLevel, CurLevel, SimTime); // q^{n+0.5} = q^{n} + 0.5*dt*R_q^{n} updateStateByTend(State, NextLevel, State, CurLevel, 0.5 * TimeStep); @@ -49,7 +49,7 @@ void RungeKutta2Stepper::doStep(OceanState *State, // model state // R_q^{n+0.5} = RHS_q(u^{n+0.5}, h^{n+0.5}, phi^{n+0.5}, t^{n+0.5}) Tend->computeAllTendencies(State, AuxState, NextTracerArray, NextLevel, - NextLevel, SimTime + 0.5 * TimeStep); + NextLevel, NextLevel, SimTime + 0.5 * TimeStep); // q^{n+1} = q^{n} + dt*R_q^{n+0.5} updateStateByTend(State, NextLevel, State, CurLevel, TimeStep); diff --git a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp index 1b882e7631b9..1160a4ab7213 100644 --- a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp @@ -85,7 +85,7 @@ void RungeKutta4Stepper::doStep(OceanState *State, // model state if (Stage == 0) { weightTracers(NextTracerArray, CurTracerArray, State, CurLevel); Tend->computeAllTendencies(State, AuxState, CurTracerArray, CurLevel, - CurLevel, StageTime); + CurLevel, CurLevel, StageTime); updateStateByTend(State, NextLevel, State, CurLevel, RKB[Stage] * TimeStep); accumulateTracersUpdate(NextTracerArray, RKB[Stage] * TimeStep); @@ -111,7 +111,7 @@ void RungeKutta4Stepper::doStep(OceanState *State, // model state Pacer::stop("RK4:haloExchProvis", 3); Tend->computeAllTendencies(ProvisState, AuxState, ProvisTracers, - CurLevel, CurLevel, StageTime); + CurLevel, CurLevel, CurLevel, StageTime); updateStateByTend(State, NextLevel, State, NextLevel, RKB[Stage] * TimeStep); accumulateTracersUpdate(NextTracerArray, RKB[Stage] * TimeStep); diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index d2a58a8dd365..8fabb3a0bab2 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -267,8 +267,11 @@ int main(int argc, char *argv[]) { // compute pressure gradient deepCopy(Tend, 0.0_Real); - DefPGrad->computePressureGrad(Tend, DefState, VCoord, DefEos, - TimeLevel); + + const auto &PressureInterface = VCoord->PressureInterface; + DefPGrad->computePressureGrad(Tend, PressureMid, PressureInterface, + SpecVol, ZInterface, + LayerThick); // compute errors Real MaxValue = 0.0_Real; diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 09a42e15ff74..72c680b0d303 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -213,9 +213,11 @@ int testTendencies() { Array3DReal TracerArray = Tracers::getAll(0); int ThickTimeLevel = 0; int VelTimeLevel = 0; + int TracerTimeLevel = 0; TimeInstant Time; DefTendencies->computeAllTendencies(State, AuxState, TracerArray, - ThickTimeLevel, VelTimeLevel, Time); + ThickTimeLevel, VelTimeLevel, + TracerTimeLevel, Time); // check that everything got computed correctly int NCellsOwned = Mesh->NCellsOwned; From 6f2b1a523496eee4340ae707730d17a625c6cd2e Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Thu, 26 Mar 2026 15:14:36 -0700 Subject: [PATCH 150/152] Fix linting issues --- components/omega/src/ocn/PGrad.cpp | 2 +- components/omega/src/ocn/PGrad.h | 3 +-- components/omega/src/ocn/Tendencies.cpp | 2 +- components/omega/src/ocn/Tendencies.h | 2 +- components/omega/test/ocn/PGradTest.cpp | 5 ++--- components/omega/test/ocn/TendenciesTest.cpp | 4 ++-- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/components/omega/src/ocn/PGrad.cpp b/components/omega/src/ocn/PGrad.cpp index e91d03b28695..33f3320c7606 100644 --- a/components/omega/src/ocn/PGrad.cpp +++ b/components/omega/src/ocn/PGrad.cpp @@ -130,7 +130,7 @@ void PressureGrad::clear() { DefaultPGrad = nullptr; // prevent dangling pointe'r - } // end clear +} // end clear //------------------------------------------------------------------------------ // Remove PressureGrad from list by name diff --git a/components/omega/src/ocn/PGrad.h b/components/omega/src/ocn/PGrad.h index 50941762ef76..9c64b4b86e82 100644 --- a/components/omega/src/ocn/PGrad.h +++ b/components/omega/src/ocn/PGrad.h @@ -156,8 +156,7 @@ class PressureGrad { ~PressureGrad(); // Compute pressure gradient tendencies and add into Tend array - void computePressureGrad(Array2DReal &Tend, - const Array2DReal &PressureMid, + void computePressureGrad(Array2DReal &Tend, const Array2DReal &PressureMid, const Array2DReal &PressureInterface, const Array2DReal &SpecVol, const Array2DReal &ZInterface, diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index c42b9fa3ed76..39611b85bc35 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -613,7 +613,7 @@ void Tendencies::computeVelocityTendenciesOnly( Array2DReal LayerThick = State->getLayerThickness(ThickTimeLevel); VCoord->computePressure(LayerThick, SurfacePressure); - const auto &PressureMid = VCoord->PressureMid; + const auto &PressureMid = VCoord->PressureMid; const auto &PressureInterface = VCoord->PressureInterface; Array2DReal Temp = Tracers::getByName(TracerTimeLevel, "Temperature"); Array2DReal Salinity = Tracers::getByName(TracerTimeLevel, "Salinity"); diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 1955fa7c130e..605517034a79 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -91,7 +91,7 @@ class Tendencies { void computeAllTendencies(const OceanState *State, const AuxiliaryState *AuxState, const Array3DReal &TracerArray, int ThickTimeLevel, - int VelTimeLevel, int TracerTimeLevel, + int VelTimeLevel, int TracerTimeLevel, TimeInstant Time); void computeThicknessTendenciesOnly(const OceanState *State, const AuxiliaryState *AuxState, diff --git a/components/omega/test/ocn/PGradTest.cpp b/components/omega/test/ocn/PGradTest.cpp index 8fabb3a0bab2..09887a6d9ce6 100644 --- a/components/omega/test/ocn/PGradTest.cpp +++ b/components/omega/test/ocn/PGradTest.cpp @@ -267,11 +267,10 @@ int main(int argc, char *argv[]) { // compute pressure gradient deepCopy(Tend, 0.0_Real); - + const auto &PressureInterface = VCoord->PressureInterface; DefPGrad->computePressureGrad(Tend, PressureMid, PressureInterface, - SpecVol, ZInterface, - LayerThick); + SpecVol, ZInterface, LayerThick); // compute errors Real MaxValue = 0.0_Real; diff --git a/components/omega/test/ocn/TendenciesTest.cpp b/components/omega/test/ocn/TendenciesTest.cpp index 72c680b0d303..c87828f6bccf 100644 --- a/components/omega/test/ocn/TendenciesTest.cpp +++ b/components/omega/test/ocn/TendenciesTest.cpp @@ -213,10 +213,10 @@ int testTendencies() { Array3DReal TracerArray = Tracers::getAll(0); int ThickTimeLevel = 0; int VelTimeLevel = 0; - int TracerTimeLevel = 0; + int TracerTimeLevel = 0; TimeInstant Time; DefTendencies->computeAllTendencies(State, AuxState, TracerArray, - ThickTimeLevel, VelTimeLevel, + ThickTimeLevel, VelTimeLevel, TracerTimeLevel, Time); // check that everything got computed correctly From afd6126d53c9598f53cc4218c0a9697ecd167bdd Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 30 Mar 2026 19:08:16 -0700 Subject: [PATCH 151/152] Use provis tracers for RK4 --- components/omega/src/ocn/Tendencies.cpp | 15 ++++++++++----- components/omega/src/ocn/Tendencies.h | 2 ++ .../src/timeStepping/ForwardBackwardStepper.cpp | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 39611b85bc35..677e7bd9a42e 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -429,6 +429,7 @@ void Tendencies::computeThicknessTendenciesOnly( void Tendencies::computeVelocityTendenciesOnly( const OceanState *State, ///< [in] State variables const AuxiliaryState *AuxState, ///< [in] Auxilary state variables + const Array3DReal &TracerArray, ///< [in] Tracer array int ThickTimeLevel, ///< [in] Time level int VelTimeLevel, ///< [in] Time level int TracerTimeLevel, ///< [in] Time level @@ -615,8 +616,10 @@ void Tendencies::computeVelocityTendenciesOnly( const auto &PressureMid = VCoord->PressureMid; const auto &PressureInterface = VCoord->PressureInterface; - Array2DReal Temp = Tracers::getByName(TracerTimeLevel, "Temperature"); - Array2DReal Salinity = Tracers::getByName(TracerTimeLevel, "Salinity"); + Array2DReal Temp = Kokkos::subview(TracerArray, Tracers::IndxTemp, + Kokkos::ALL, Kokkos::ALL); + Array2DReal Salinity = Kokkos::subview(TracerArray, Tracers::IndxSalt, + Kokkos::ALL, Kokkos::ALL); EqState->computeSpecVol(Temp, Salinity, PressureMid); // Temporary: ensure vertical geometric/geopotential fields are updated @@ -798,6 +801,7 @@ void Tendencies::computeThicknessTendencies( void Tendencies::computeVelocityTendencies( const OceanState *State, ///< [in] State variables const AuxiliaryState *AuxState, ///< [in] Auxilary state variables + const Array3DReal &TracerArray, ///< [in] Tracer array int ThickTimeLevel, ///< [in] Time level int VelTimeLevel, ///< [in] Time level int TracerTimeLevel, ///< [in] Time level @@ -806,7 +810,8 @@ void Tendencies::computeVelocityTendencies( Pacer::start("Tend:computeVelocityTendencies", 1); AuxState->computeMomAux(State, ThickTimeLevel, VelTimeLevel); - computeVelocityTendenciesOnly(State, AuxState, ThickTimeLevel, VelTimeLevel, + computeVelocityTendenciesOnly(State, AuxState, TracerArray, + ThickTimeLevel, VelTimeLevel, TracerTimeLevel, Time); Pacer::stop("Tend:computeVelocityTendencies", 1); @@ -869,8 +874,8 @@ void Tendencies::computeAllTendencies( computeThicknessTendenciesOnly(State, AuxState, ThickTimeLevel, VelTimeLevel, Time); - computeVelocityTendenciesOnly(State, AuxState, ThickTimeLevel, VelTimeLevel, - TracerTimeLevel, Time); + computeVelocityTendenciesOnly(State, AuxState, TracerArray, ThickTimeLevel, + VelTimeLevel, TracerTimeLevel, Time); computeTracerTendenciesOnly(State, AuxState, TracerArray, ThickTimeLevel, VelTimeLevel, Time); } // end all tendency compute diff --git a/components/omega/src/ocn/Tendencies.h b/components/omega/src/ocn/Tendencies.h index 605517034a79..4ba53a313e82 100644 --- a/components/omega/src/ocn/Tendencies.h +++ b/components/omega/src/ocn/Tendencies.h @@ -81,6 +81,7 @@ class Tendencies { TimeInstant Time); void computeVelocityTendencies(const OceanState *State, const AuxiliaryState *AuxState, + const Array3DReal &TracerArray, int ThickTimeLevel, int VelTimeLevel, int TracerTimeLevel, TimeInstant Time); void computeTracerTendencies(const OceanState *State, @@ -99,6 +100,7 @@ class Tendencies { TimeInstant Time); void computeVelocityTendenciesOnly(const OceanState *State, const AuxiliaryState *AuxState, + const Array3DReal &TracerArray, int ThickTimeLevel, int VelTimeLevel, int TracerTimeLevel, TimeInstant Time); void computeTracerTendenciesOnly(const OceanState *State, diff --git a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp index a686dcfae0d6..418a01f928ad 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp @@ -56,7 +56,7 @@ void ForwardBackwardStepper::doStep( CurLevel, TimeStep); // R_u^{n+1} = RHS_u(u^{n}, h^{n+1}, t^{n+1}) - Tend->computeVelocityTendencies(State, AuxState, NextLevel, CurLevel, + Tend->computeVelocityTendencies(State, AuxState, NextTracerArray, NextLevel, CurLevel, NextLevel, SimTime + TimeStep); // u^{n+1} = u^{n} + R_u^{n+1} From bd19daae91788c82e0d7639cc379867e979a65f8 Mon Sep 17 00:00:00 2001 From: Steven Brus Date: Mon, 30 Mar 2026 20:02:41 -0700 Subject: [PATCH 152/152] Fix clang-format issues --- components/omega/src/ocn/Tendencies.cpp | 9 ++++----- .../omega/src/timeStepping/ForwardBackwardStepper.cpp | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/components/omega/src/ocn/Tendencies.cpp b/components/omega/src/ocn/Tendencies.cpp index 677e7bd9a42e..23259f36c379 100644 --- a/components/omega/src/ocn/Tendencies.cpp +++ b/components/omega/src/ocn/Tendencies.cpp @@ -616,8 +616,8 @@ void Tendencies::computeVelocityTendenciesOnly( const auto &PressureMid = VCoord->PressureMid; const auto &PressureInterface = VCoord->PressureInterface; - Array2DReal Temp = Kokkos::subview(TracerArray, Tracers::IndxTemp, - Kokkos::ALL, Kokkos::ALL); + Array2DReal Temp = Kokkos::subview(TracerArray, Tracers::IndxTemp, + Kokkos::ALL, Kokkos::ALL); Array2DReal Salinity = Kokkos::subview(TracerArray, Tracers::IndxSalt, Kokkos::ALL, Kokkos::ALL); EqState->computeSpecVol(Temp, Salinity, PressureMid); @@ -810,9 +810,8 @@ void Tendencies::computeVelocityTendencies( Pacer::start("Tend:computeVelocityTendencies", 1); AuxState->computeMomAux(State, ThickTimeLevel, VelTimeLevel); - computeVelocityTendenciesOnly(State, AuxState, TracerArray, - ThickTimeLevel, VelTimeLevel, - TracerTimeLevel, Time); + computeVelocityTendenciesOnly(State, AuxState, TracerArray, ThickTimeLevel, + VelTimeLevel, TracerTimeLevel, Time); Pacer::stop("Tend:computeVelocityTendencies", 1); } diff --git a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp index 418a01f928ad..e72719a3b2f4 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp @@ -56,8 +56,8 @@ void ForwardBackwardStepper::doStep( CurLevel, TimeStep); // R_u^{n+1} = RHS_u(u^{n}, h^{n+1}, t^{n+1}) - Tend->computeVelocityTendencies(State, AuxState, NextTracerArray, NextLevel, CurLevel, - NextLevel, SimTime + TimeStep); + Tend->computeVelocityTendencies(State, AuxState, NextTracerArray, NextLevel, + CurLevel, NextLevel, SimTime + TimeStep); // u^{n+1} = u^{n} + R_u^{n+1} updateVelocityByTend(State, NextLevel, State, CurLevel, TimeStep);