From a2005a2daadd5f8808f0509a0b93b23a71d34468 Mon Sep 17 00:00:00 2001 From: Alice Barthel Date: Mon, 22 Jun 2026 15:53:01 -0700 Subject: [PATCH 1/6] added the Forcing IO in yml and read from streams at startup --- components/omega/configs/Default.yml | 13 ++++++++++ components/omega/src/ocn/Forcing.cpp | 34 +++++++++++++++++++++++++++ components/omega/src/ocn/Forcing.h | 3 +++ components/omega/src/ocn/OceanRun.cpp | 4 ++-- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 08164bdaa373..4dcb82cd86b1 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -130,6 +130,19 @@ Omega: Contents: - State - Base + # Forcing is the external forcing in standalone mode. + # It contains the surface stress, surface fluxes + # and the surface restoring target files (if appl.). + Forcing: + UsePointerFile: false + Filename: forcing.nc + Mode: read + Precision: double + Freq: 1 + FreqUnits: OnStartup + UseStartEnd: false + Contents: + - Forcing # Restarts are used to initialize for all job submissions after the very # first startup job. We use UseStartEnd with a start time just after the # simulation start time so that omega does not attempt to use a restart diff --git a/components/omega/src/ocn/Forcing.cpp b/components/omega/src/ocn/Forcing.cpp index 5d106d3ad754..8dfd536d9f5a 100644 --- a/components/omega/src/ocn/Forcing.cpp +++ b/components/omega/src/ocn/Forcing.cpp @@ -11,6 +11,7 @@ #include "Forcing.h" #include "Field.h" +#include "IOStream.h" #include "Logging.h" #include "Pacer.h" @@ -58,6 +59,7 @@ Forcing *Forcing::create(const std::string &Name, const HorzMesh *Mesh, } // Initialize the default forcing instance and read configuration. +// Reads the forcing fields from streams at startup (for now). void Forcing::init() { if (DefaultForcing != nullptr) { return; @@ -78,6 +80,10 @@ void Forcing::init() { Config *OmegaConfig = Config::getOmegaConfig(); DefaultForcing->readConfigOptions(OmegaConfig); + // for now, forcing fields are read at start-up only. + // to be extended to include switch from standalone to coupled. + // to be moved to a Forcing->prepareForStep(SimTime) method later. + DefaultForcing->readStreamIntoArrays(); } // Return the default forcing instance. @@ -159,4 +165,32 @@ I4 Forcing::exchangeHalo() const { return Err; } +// Read forcing fields from input stream. +// To be extended with time indexing later. +void Forcing::readStreamIntoArrays() { + Error Err; + + Real FillValueReal = -999._Real; + deepCopy(SfcStressForcing.ZonalStressCell, FillValueReal); + deepCopy(SfcStressForcing.MeridStressCell, FillValueReal); + + std::string StreamName = "Forcing"; + + // Attempt to read stream; if unavailable, log and fall back to zero forcing. + Err = IOStream::read(StreamName); + if (Err.isFail()) { + LOG_INFO("Forcing: Error while reading {} stream, using zero forcing", + StreamName); + deepCopy(SfcStressForcing.ZonalStressCell, 0._Real); + deepCopy(SfcStressForcing.MeridStressCell, 0._Real); + } + + I4 HaloErr = exchangeHalo(); + if (HaloErr != 0) { + ABORT_ERROR("Forcing: Error exchanging halo for startup forcing fields"); + } + + computeAll(); +} + } // namespace OMEGA diff --git a/components/omega/src/ocn/Forcing.h b/components/omega/src/ocn/Forcing.h index 5689e0b17c02..5fdae7e550b8 100644 --- a/components/omega/src/ocn/Forcing.h +++ b/components/omega/src/ocn/Forcing.h @@ -66,6 +66,9 @@ class Forcing { /// Unregister surface stress fields from IO streams void unregisterFields() const; + /// Read forcing fields from input stream at startup + void readStreamIntoArrays(); + /// Compute all forcing variables void computeAll() const; diff --git a/components/omega/src/ocn/OceanRun.cpp b/components/omega/src/ocn/OceanRun.cpp index 6a8bb160f112..25d215045270 100644 --- a/components/omega/src/ocn/OceanRun.cpp +++ b/components/omega/src/ocn/OceanRun.cpp @@ -44,8 +44,8 @@ int ocnRun(TimeInstant &CurrTime ///< [inout] current sim time // track step count ++IStep; - // call forcing routines, anything needed pre-timestep - DefForcing->computeAll(); + // placeholder: call needed pre-timestep compute here + // (e.g. forcing routine) // do forward time step // first call to doStep can sometimes take very long From 013c0bdd40c57136288897123f3df42bfcb132e4 Mon Sep 17 00:00:00 2001 From: Alice Barthel Date: Thu, 25 Jun 2026 16:59:40 -0700 Subject: [PATCH 2/6] added Forcing::init() needed before the IO calls --- components/omega/src/ocn/Forcing.cpp | 11 +++++++++-- components/omega/test/infra/IOStreamTest.cpp | 4 ++++ components/omega/test/ocn/StateTest.cpp | 6 ++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/components/omega/src/ocn/Forcing.cpp b/components/omega/src/ocn/Forcing.cpp index 8dfd536d9f5a..d7fe48469b09 100644 --- a/components/omega/src/ocn/Forcing.cpp +++ b/components/omega/src/ocn/Forcing.cpp @@ -179,8 +179,15 @@ void Forcing::readStreamIntoArrays() { // Attempt to read stream; if unavailable, log and fall back to zero forcing. Err = IOStream::read(StreamName); if (Err.isFail()) { - LOG_INFO("Forcing: Error while reading {} stream, using zero forcing", - StreamName); + std::string StreamFile = IOStream::getFilename(StreamName); + LOG_WARN("Forcing: Error while reading {} stream from file {}", + StreamName, StreamFile); + if (!Err.Msg.empty()) { + LOG_WARN("Forcing: stream read details:\n{}", Err.Msg); + } + LOG_WARN("Forcing: expected fields {} and {} on dimension NCells; " + "falling back to zero forcing", + "SfcStressZonal", "SfcStressMeridional"); deepCopy(SfcStressForcing.ZonalStressCell, 0._Real); deepCopy(SfcStressForcing.MeridStressCell, 0._Real); } diff --git a/components/omega/test/infra/IOStreamTest.cpp b/components/omega/test/infra/IOStreamTest.cpp index b3126ad21a64..7d8b37a50a38 100644 --- a/components/omega/test/infra/IOStreamTest.cpp +++ b/components/omega/test/infra/IOStreamTest.cpp @@ -122,6 +122,10 @@ void initIOStreamTest(Clock *&ModelClock // Model clock // Initialize Tracers Tracers::init(); + // IOStream::validateAll() depends on Forcing::init() so Forcing fields + // are registered before stream validation. + Forcing::init(); + // Initialize Aux State AuxiliaryState::init(); diff --git a/components/omega/test/ocn/StateTest.cpp b/components/omega/test/ocn/StateTest.cpp index 09caff2e6d73..34890d74589c 100644 --- a/components/omega/test/ocn/StateTest.cpp +++ b/components/omega/test/ocn/StateTest.cpp @@ -16,6 +16,7 @@ #include "Eos.h" #include "Error.h" #include "Field.h" +#include "Forcing.h" #include "Halo.h" #include "HorzMesh.h" #include "IO.h" @@ -91,6 +92,10 @@ void initStateTest() { // Initialize tracers Tracers::init(); + // IOStream::validateAll() depends on Forcing::init() so Forcing fields + // are registered before stream validation. + Forcing::init(); + // Initialize Aux State variables AuxiliaryState::init(); @@ -414,6 +419,7 @@ int main(int argc, char *argv[]) { // Finalize Omega objects OceanState::clear(); Tracers::clear(); + Forcing::clear(); AuxiliaryState::clear(); PressureGrad::clear(); VertMix::destroyInstance(); From d4f885c233be1d7b18e57e890af98b0b308ca438 Mon Sep 17 00:00:00 2001 From: Alice Barthel Date: Thu, 25 Jun 2026 17:11:58 -0700 Subject: [PATCH 3/6] added the forcing file name change for the mesh change --- components/omega/test/ocn/ForcingTest.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/components/omega/test/ocn/ForcingTest.cpp b/components/omega/test/ocn/ForcingTest.cpp index 13e0bdc54388..20217e1c1db1 100644 --- a/components/omega/test/ocn/ForcingTest.cpp +++ b/components/omega/test/ocn/ForcingTest.cpp @@ -62,13 +62,15 @@ struct TestSetupSphere { }; #ifdef FORCING_TEST_PLANE -constexpr Geometry Geom = Geometry::Planar; -constexpr char DefaultMeshFile[] = "OmegaPlanarMesh.nc"; -using TestSetup = TestSetupPlane; +constexpr Geometry Geom = Geometry::Planar; +constexpr char DefaultMeshFile[] = "OmegaPlanarMesh.nc"; +constexpr char DefaultForcingFile[] = "forcingPlanar.nc"; +using TestSetup = TestSetupPlane; #else -constexpr Geometry Geom = Geometry::Spherical; -constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc"; -using TestSetup = TestSetupSphere; +constexpr Geometry Geom = Geometry::Spherical; +constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc"; +constexpr char DefaultForcingFile[] = "forcingSphere.nc"; +using TestSetup = TestSetupSphere; #endif int testSfcStressForcingVars(Real RTol) { @@ -144,6 +146,10 @@ int initForcingTest(const std::string &MeshFile) { Field::init(ModelClock); IOStream::init(ModelClock); + // Select the case-specific forcing file before Forcing::init() performs + // startup stream reads. + IOStream::changeFilename("Forcing", DefaultForcingFile); + Err = Halo::init(); if (Err != 0) { ABORT_ERROR("ForcingTest: error initializing default halo"); From 3ecb834c9d6b14ac3c494dc211da6e22514db91a Mon Sep 17 00:00:00 2001 From: Alice Barthel Date: Thu, 25 Jun 2026 17:39:23 -0700 Subject: [PATCH 4/6] revert change to error message for read in --- components/omega/src/ocn/Forcing.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/components/omega/src/ocn/Forcing.cpp b/components/omega/src/ocn/Forcing.cpp index d7fe48469b09..8dfd536d9f5a 100644 --- a/components/omega/src/ocn/Forcing.cpp +++ b/components/omega/src/ocn/Forcing.cpp @@ -179,15 +179,8 @@ void Forcing::readStreamIntoArrays() { // Attempt to read stream; if unavailable, log and fall back to zero forcing. Err = IOStream::read(StreamName); if (Err.isFail()) { - std::string StreamFile = IOStream::getFilename(StreamName); - LOG_WARN("Forcing: Error while reading {} stream from file {}", - StreamName, StreamFile); - if (!Err.Msg.empty()) { - LOG_WARN("Forcing: stream read details:\n{}", Err.Msg); - } - LOG_WARN("Forcing: expected fields {} and {} on dimension NCells; " - "falling back to zero forcing", - "SfcStressZonal", "SfcStressMeridional"); + LOG_INFO("Forcing: Error while reading {} stream, using zero forcing", + StreamName); deepCopy(SfcStressForcing.ZonalStressCell, 0._Real); deepCopy(SfcStressForcing.MeridStressCell, 0._Real); } From 6dbac48f0315cda792ff87608343a0beb02a4fd0 Mon Sep 17 00:00:00 2001 From: Alice Barthel Date: Wed, 1 Jul 2026 14:30:02 -0700 Subject: [PATCH 5/6] removed ctest dependencies on forcing.nc --- components/omega/configs/Default.yml | 2 +- components/omega/test/ocn/ForcingTest.cpp | 41 +++++++++++++---------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 4dcb82cd86b1..94aa1a3b3daf 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -139,7 +139,7 @@ Omega: Mode: read Precision: double Freq: 1 - FreqUnits: OnStartup + FreqUnits: Never UseStartEnd: false Contents: - Forcing diff --git a/components/omega/test/ocn/ForcingTest.cpp b/components/omega/test/ocn/ForcingTest.cpp index 20217e1c1db1..c520f3ffa5e7 100644 --- a/components/omega/test/ocn/ForcingTest.cpp +++ b/components/omega/test/ocn/ForcingTest.cpp @@ -62,17 +62,17 @@ struct TestSetupSphere { }; #ifdef FORCING_TEST_PLANE -constexpr Geometry Geom = Geometry::Planar; -constexpr char DefaultMeshFile[] = "OmegaPlanarMesh.nc"; -constexpr char DefaultForcingFile[] = "forcingPlanar.nc"; -using TestSetup = TestSetupPlane; +constexpr Geometry Geom = Geometry::Planar; +constexpr char DefaultMeshFile[] = "OmegaPlanarMesh.nc"; +using TestSetup = TestSetupPlane; #else -constexpr Geometry Geom = Geometry::Spherical; -constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc"; -constexpr char DefaultForcingFile[] = "forcingSphere.nc"; -using TestSetup = TestSetupSphere; +constexpr Geometry Geom = Geometry::Spherical; +constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc"; +using TestSetup = TestSetupSphere; #endif +Forcing *TestForcing = nullptr; + int testSfcStressForcingVars(Real RTol) { int Err = 0; TestSetup Setup; @@ -146,17 +146,22 @@ int initForcingTest(const std::string &MeshFile) { Field::init(ModelClock); IOStream::init(ModelClock); - // Select the case-specific forcing file before Forcing::init() performs - // startup stream reads. - IOStream::changeFilename("Forcing", DefaultForcingFile); - Err = Halo::init(); if (Err != 0) { ABORT_ERROR("ForcingTest: error initializing default halo"); } HorzMesh::init(ModelClock); - Forcing::init(); + + const HorzMesh *DefMesh = HorzMesh::getDefault(); + Halo *DefHalo = Halo::getDefault(); + TestForcing = Forcing::create("Default", DefMesh, DefHalo); + if (TestForcing == nullptr) { + ABORT_ERROR("ForcingTest: failed creating default forcing instance"); + } + + Config *OmegaConfig = Config::getOmegaConfig(); + TestForcing->readConfigOptions(OmegaConfig); return 0; } @@ -174,12 +179,12 @@ void finalizeForcingTest() { } int testForcingInitAndConfig() { - // Verify Forcing::init consumes Omega.SfcStress config and maps InterpType. + // Verify forcing setup consumes Omega.SfcStress config and maps InterpType. int Err = 0; - Forcing *DefForcing = Forcing::getDefault(); + Forcing *DefForcing = TestForcing; if (DefForcing == nullptr) { - LOG_ERROR("ForcingTest: default forcing instance is null"); + LOG_ERROR("ForcingTest: test forcing instance is null"); return 1; } @@ -209,7 +214,7 @@ int testForcingInitAndConfig() { } if (DefForcing->SfcStressForcing.InterpChoice != ExpectedChoice) { - LOG_ERROR("ForcingTest: InterpChoice mismatch after Forcing::init"); + LOG_ERROR("ForcingTest: InterpChoice mismatch after forcing setup"); Err++; } @@ -226,7 +231,7 @@ int testForcingComputeAll() { int Err = 0; const HorzMesh *Mesh = HorzMesh::getDefault(); - Forcing *DefForcing = Forcing::getDefault(); + Forcing *DefForcing = TestForcing; if (Mesh == nullptr || DefForcing == nullptr) { LOG_ERROR("ForcingTest: missing mesh or forcing for compute test"); return 1; From 169102581ae8ef18fc726713781cee7ca2001a8c Mon Sep 17 00:00:00 2001 From: Alice Barthel Date: Tue, 7 Jul 2026 17:34:07 -0700 Subject: [PATCH 6/6] update to FillValue and fields --- components/omega/src/ocn/Forcing.cpp | 4 ---- components/omega/src/ocn/forcingVars/SfcStressForcingVars.cpp | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/components/omega/src/ocn/Forcing.cpp b/components/omega/src/ocn/Forcing.cpp index 8dfd536d9f5a..42643dba5385 100644 --- a/components/omega/src/ocn/Forcing.cpp +++ b/components/omega/src/ocn/Forcing.cpp @@ -170,10 +170,6 @@ I4 Forcing::exchangeHalo() const { void Forcing::readStreamIntoArrays() { Error Err; - Real FillValueReal = -999._Real; - deepCopy(SfcStressForcing.ZonalStressCell, FillValueReal); - deepCopy(SfcStressForcing.MeridStressCell, FillValueReal); - std::string StreamName = "Forcing"; // Attempt to read stream; if unavailable, log and fall back to zero forcing. diff --git a/components/omega/src/ocn/forcingVars/SfcStressForcingVars.cpp b/components/omega/src/ocn/forcingVars/SfcStressForcingVars.cpp index f61cee3ccc26..17656f6ba340 100644 --- a/components/omega/src/ocn/forcingVars/SfcStressForcingVars.cpp +++ b/components/omega/src/ocn/forcingVars/SfcStressForcingVars.cpp @@ -17,9 +17,7 @@ SfcStressForcingVars::SfcStressForcingVars(const std::string &Suffix, void SfcStressForcingVars::registerFields( const std::string &MeshName // name of horizontal mesh ) const { - - const Real FillValue = -9.99e30; - int NDims = 1; + int NDims = 1; std::vector DimNames(NDims); std::string DimSuffix; if (MeshName == "Default") {