diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 08164bdaa373..94aa1a3b3daf 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: Never + 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..42643dba5385 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,28 @@ I4 Forcing::exchangeHalo() const { return Err; } +// Read forcing fields from input stream. +// To be extended with time indexing later. +void Forcing::readStreamIntoArrays() { + Error Err; + + 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 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") { 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/ForcingTest.cpp b/components/omega/test/ocn/ForcingTest.cpp index 13e0bdc54388..c520f3ffa5e7 100644 --- a/components/omega/test/ocn/ForcingTest.cpp +++ b/components/omega/test/ocn/ForcingTest.cpp @@ -71,6 +71,8 @@ constexpr char DefaultMeshFile[] = "OmegaSphereMesh.nc"; using TestSetup = TestSetupSphere; #endif +Forcing *TestForcing = nullptr; + int testSfcStressForcingVars(Real RTol) { int Err = 0; TestSetup Setup; @@ -150,7 +152,16 @@ int initForcingTest(const std::string &MeshFile) { } 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; } @@ -168,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; } @@ -203,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++; } @@ -220,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; 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();