diff --git a/components/omega/src/ocn/OceanDriver.h b/components/omega/src/ocn/OceanDriver.h index 1f405c6df2e2..8cf0ede850a0 100644 --- a/components/omega/src/ocn/OceanDriver.h +++ b/components/omega/src/ocn/OceanDriver.h @@ -13,6 +13,7 @@ #include "Config.h" #include "TimeMgr.h" +#include "TimeStepper.h" #include "mpi.h" @@ -25,6 +26,14 @@ bool printTimingAllRanks(); /// for each Omega module int ocnInit(MPI_Comm Comm); +/// Initialize Omega with coupler provided parameters +int ocnInit(MPI_Comm Comm, ///< [in] ocean MPI communicator + const int OcnId, ///< [in] mct comp id for ocean + const std::string &ConfigFile, ///< [in] path to yaml config file + const std::string &LogFile, ///< [in] path to log file + const TimeInstant &StartTime ///< [in] simulation start time +); + /// Advance the model from starting from CurrTime until EndAlarm rings int ocnRun(TimeInstant &CurrTime); @@ -34,6 +43,9 @@ int ocnFinalize(const TimeInstant &CurrTime); /// Initialize Omega modules needed to run ocean model int initOmegaModules(MPI_Comm Comm); +/// Initialize Omega modules with coupler-provided time parameters +int initOmegaModules(MPI_Comm Comm, const TimeInitParams &TParams); + } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/OceanInit.cpp b/components/omega/src/ocn/OceanInit.cpp index 4d54e8903e9f..f4ebba8bbbab 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -106,15 +106,48 @@ int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator } // end ocnInit +int ocnInit(MPI_Comm Comm, ///< [in] ocean MPI communicator + const int OcnId, ///< [in] mct comp id for ocean + const std::string &ConfigFile, ///< [in] path to yaml config file + const std::string &LogFile, ///< [in] path to log file + const TimeInstant &StartTime ///< [in] simulation start time +) { + + I4 Err = 0; // return error code + + // Init the default machine environment based on input MPI communicator + MachEnv::init(Comm); + MachEnv *DefEnv = MachEnv::getDefault(); + + // Initialize Omega logging with coupler provided log file name + initLogging(DefEnv, LogFile); + + // Read config file into Config object + Config("Omega"); + Config::readAll(ConfigFile); + Config *OmegaConfig = Config::getOmegaConfig(); + + readTimingConfig(); + + // coupler decides the stop time + TimeInitParams TimeParams{StartTime, std::nullopt}; + + // initialize remaining Omega modules + Err = initOmegaModules(Comm, TimeParams); + if (Err != 0) + ABORT_ERROR("ocnInit: Error initializing Omega modules"); + + return Err; + +} // end ocnInit + // Call init routines for remaining Omega modules -int initOmegaModules(MPI_Comm Comm) { +// Internal helper — all module init after TimeStepper::init1 is called. +// Called by both initOmegaModules overloads. +static int initOmegaModulesImpl(MPI_Comm Comm) { - // error and return codes int Err = 0; - // Initialize the default time stepper (phase 1) that includes the - // calendar, model clock and start/stop times and alarms - TimeStepper::init1(); TimeStepper *DefStepper = TimeStepper::getDefault(); Clock *ModelClock = DefStepper->getClock(); @@ -212,7 +245,17 @@ int initOmegaModules(MPI_Comm Comm) { return Err; -} // end initOmegaModules +} // end initOmegaModulesImpl + +int initOmegaModules(MPI_Comm Comm) { + TimeStepper::init1(); + return initOmegaModulesImpl(Comm); +} + +int initOmegaModules(MPI_Comm Comm, const TimeInitParams &TParams) { + TimeStepper::init1(TParams); + return initOmegaModulesImpl(Comm); +} } // end namespace OMEGA //===----------------------------------------------------------------------===// diff --git a/components/omega/src/ocn/OceanRun.cpp b/components/omega/src/ocn/OceanRun.cpp index 7653e5b4c1eb..53c27545e09d 100644 --- a/components/omega/src/ocn/OceanRun.cpp +++ b/components/omega/src/ocn/OceanRun.cpp @@ -23,6 +23,9 @@ int ocnRun(TimeInstant &CurrTime ///< [inout] current sim time OceanState *DefOceanState = OceanState::getDefault(); TimeStepper *DefTimeStepper = TimeStepper::getDefault(); + // EndAlarm must be set before calling ocnRun + OMEGA_REQUIRE(DefTimeStepper->hasEndAlarm(), "ocnRun: no EndAlarm"); + // get simulation time and other time info Clock *OmegaClock = DefTimeStepper->getClock(); Alarm *EndAlarm = DefTimeStepper->getEndAlarm(); diff --git a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp index e72719a3b2f4..b5abd4b423ce 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp @@ -15,12 +15,12 @@ namespace OMEGA { // Mostly passes relevant info to the base constructor. ForwardBackwardStepper::ForwardBackwardStepper( const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping - const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep ///< [in] time step - ) - : TimeStepper(InName, TimeStepperType::ForwardBackward, 2, InStartTime, - InStopTime, InTimeStep) {} + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime) + : TimeStepper(InName, TimeStepperType::ForwardBackward, 2, InTimeStep, + InStartTime, InStopTime) {} //------------------------------------------------------------------------------ // Advance the state by one step of the forward-backward scheme diff --git a/components/omega/src/timeStepping/ForwardBackwardStepper.h b/components/omega/src/timeStepping/ForwardBackwardStepper.h index 5429bad63276..6aa10e68b26b 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.h +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.h @@ -16,10 +16,10 @@ class ForwardBackwardStepper : public TimeStepper { /// fills with some time information. Data pointers are added later. ForwardBackwardStepper( const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping - const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep ///< [in] time step - ); + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime = std::nullopt); /// Advance the state by one step of the forward-backward scheme void doStep(OceanState *State, ///< [inout] model state diff --git a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp index 4bfefe1633eb..359bd9a6766f 100644 --- a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp @@ -15,12 +15,12 @@ namespace OMEGA { // Mostly just passes info to the base constructor. RungeKutta2Stepper::RungeKutta2Stepper( const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping - const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep ///< [in] time step - ) - : TimeStepper(InName, TimeStepperType::RungeKutta2, 2, InStartTime, - InStopTime, InTimeStep) {} + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime) + : TimeStepper(InName, TimeStepperType::RungeKutta2, 2, InTimeStep, + InStartTime, InStopTime) {} //------------------------------------------------------------------------------ // Advance the state by one step of the midpoint Runge Kutta scheme diff --git a/components/omega/src/timeStepping/RungeKutta2Stepper.h b/components/omega/src/timeStepping/RungeKutta2Stepper.h index 1d935fc84dbd..e7d7d5301c9f 100644 --- a/components/omega/src/timeStepping/RungeKutta2Stepper.h +++ b/components/omega/src/timeStepping/RungeKutta2Stepper.h @@ -18,10 +18,10 @@ class RungeKutta2Stepper : public TimeStepper { /// fills with some time information. Data pointers are added later. RungeKutta2Stepper( const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping - const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep ///< [in] time step - ); + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime = std::nullopt); /// Advance the state by one step of the midpoint Runge Kutta scheme void doStep(OceanState *State, ///< [inout] model state diff --git a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp index bfa8255c821d..a6ea4a3aab6a 100644 --- a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp @@ -15,12 +15,12 @@ namespace OMEGA { // Uses the base constructor and adds some coefficients. RungeKutta4Stepper::RungeKutta4Stepper( const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping - const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep ///< [in] time step - ) - : TimeStepper(InName, TimeStepperType::RungeKutta4, 2, InStartTime, - InStopTime, InTimeStep) { + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime) + : TimeStepper(InName, TimeStepperType::RungeKutta4, 2, InTimeStep, + InStartTime, InStopTime) { RKA[0] = 0; RKA[1] = 1. / 2; diff --git a/components/omega/src/timeStepping/RungeKutta4Stepper.h b/components/omega/src/timeStepping/RungeKutta4Stepper.h index 8a67b44d91a0..3f3a67b8c5d9 100644 --- a/components/omega/src/timeStepping/RungeKutta4Stepper.h +++ b/components/omega/src/timeStepping/RungeKutta4Stepper.h @@ -17,10 +17,10 @@ class RungeKutta4Stepper : public TimeStepper { /// fills with some time information. Data pointers are added later. RungeKutta4Stepper( const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping - const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep ///< [in] time step - ); + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime = std::nullopt); /// Advance the state by one step of the fourth-order Runge Kutta scheme void doStep(OceanState *State, ///< [inout] model state diff --git a/components/omega/src/timeStepping/TimeStepper.cpp b/components/omega/src/timeStepping/TimeStepper.cpp index 7ee026c384b7..7ea29d2943d3 100644 --- a/components/omega/src/timeStepping/TimeStepper.cpp +++ b/components/omega/src/timeStepping/TimeStepper.cpp @@ -47,29 +47,31 @@ TimeStepperType getTimeStepperFromStr(const std::string &InString) { //------------------------------------------------------------------------------ // Constructors and creation methods. -// Constructor creates a new instance and fills in the time -// related data. attachData function is used to add the data pointers +/// Constructor creates a new instance and fills in the time +/// related data. attachData function is used to add the data pointers TimeStepper::TimeStepper( - const std::string &InName, // [in] name of time stepper - TimeStepperType InType, // [in] type (time stepping method) - I4 InNTimeLevels, // [in] num time levels needed by method - const TimeInstant &InStartTime, // [in] start time for time stepping - const TimeInstant &InStopTime, // [in] stop time for time stepping - const TimeInterval &InTimeStep // [in] time step - ) + const std::string &InName, ///< [in] name of time stepper + TimeStepperType InType, ///< [in] type (time stepping method) + I4 InNTimeLevels, ///< [in] num time levels for method + const TimeInterval &InTimeStep, ///< [in] time step + const TimeInstant &InStartTime, ///< [in] start time for time stepping + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime) : Name(InName), Type(InType), NTimeLevels(InNTimeLevels), - StartTime(InStartTime), StopTime(InStopTime), TimeStep(InTimeStep) { + TimeStep(InTimeStep), StartTime(InStartTime), StopTime(InStopTime) { // Most variables initialized via initializer list // Set up clock associated with this time stepper StepClock = std::make_unique(Clock(InStartTime, InTimeStep)); - // Create an EndAlarm associated with the StopTime - std::string AlarmName = "EndAlarm"; - if (InName != "Default") - AlarmName += InName; - EndAlarm = std::make_unique(Alarm(AlarmName, InStopTime)); - StepClock->attachAlarm(EndAlarm.get()); + if (InStopTime.has_value()) { + // Create an EndAlarm associated with the StopTime + std::string AlarmName = "EndAlarm"; + if (InName != "Default") + AlarmName += InName; + EndAlarm = std::make_unique(Alarm(AlarmName, *InStopTime)); + StepClock->attachAlarm(EndAlarm.get()); + } } //------------------------------------------------------------------------------ @@ -77,9 +79,9 @@ TimeStepper::TimeStepper( TimeStepper *TimeStepper::create( const std::string &InName, ///< [in] name of time stepper TimeStepperType InType, ///< [in] type (time stepping method) + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep, ///< [in] time step Tendencies *InTend, ///< [in] ptr to tendencies AuxiliaryState *InAuxState, ///< [in] ptr to aux state variables HorzMesh *InMesh, ///< [in] ptr to mesh information @@ -102,16 +104,16 @@ TimeStepper *TimeStepper::create( // additional info (NTimeLevels) switch (InType) { case TimeStepperType::ForwardBackward: - NewTimeStepper = new ForwardBackwardStepper(InName, InStartTime, - InStopTime, InTimeStep); + NewTimeStepper = new ForwardBackwardStepper(InName, InTimeStep, + InStartTime, InStopTime); break; case TimeStepperType::RungeKutta4: NewTimeStepper = - new RungeKutta4Stepper(InName, InStartTime, InStopTime, InTimeStep); + new RungeKutta4Stepper(InName, InTimeStep, InStartTime, InStopTime); break; case TimeStepperType::RungeKutta2: NewTimeStepper = - new RungeKutta2Stepper(InName, InStartTime, InStopTime, InTimeStep); + new RungeKutta2Stepper(InName, InTimeStep, InStartTime, InStopTime); break; case TimeStepperType::Invalid: ABORT_ERROR("Invalid time stepping method"); @@ -135,10 +137,10 @@ TimeStepper *TimeStepper::create( TimeStepper *TimeStepper::create( const std::string &InName, // [in] name of time stepper TimeStepperType InType, // [in] type (time stepping method) + TimeInterval &InTimeStep, // [in] time step TimeInstant &InStartTime, // [in] start time for time stepping - TimeInstant &InStopTime, // [in] stop time for time stepping - TimeInterval &InTimeStep // [in] time step -) { + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime) { // Check for duplicates if (AllTimeSteppers.find(InName) != AllTimeSteppers.end()) { @@ -153,16 +155,16 @@ TimeStepper *TimeStepper::create( // Call specific constructor with time info switch (InType) { case TimeStepperType::ForwardBackward: - NewTimeStepper = new ForwardBackwardStepper(InName, InStartTime, - InStopTime, InTimeStep); + NewTimeStepper = new ForwardBackwardStepper(InName, InTimeStep, + InStartTime, InStopTime); break; case TimeStepperType::RungeKutta4: NewTimeStepper = - new RungeKutta4Stepper(InName, InStartTime, InStopTime, InTimeStep); + new RungeKutta4Stepper(InName, InTimeStep, InStartTime, InStopTime); break; case TimeStepperType::RungeKutta2: NewTimeStepper = - new RungeKutta2Stepper(InName, InStartTime, InStopTime, InTimeStep); + new RungeKutta2Stepper(InName, InTimeStep, InStartTime, InStopTime); break; case TimeStepperType::Invalid: ABORT_ERROR("Invalid time stepping method"); @@ -226,7 +228,7 @@ void TimeStepper::clear() { // Initialize the default time stepper in two phases // Begin initialization of the default time stepper (phase 1) -// This is primarily the time information. +// This is primarily the time information read directly from the config file void TimeStepper::init1() { Error Err; // error code - default to success @@ -237,24 +239,12 @@ void TimeStepper::init1() { Err = OmegaConfig->get(TimeIntConfig); CHECK_ERROR_ABORT(Err, "TimeIntegration group not found in Config"); - // Must initialize the calendar first + // Must initialize the calendar first, before any TimeInstant objects std::string CalendarStr; Err += TimeIntConfig.get("CalendarType", CalendarStr); CHECK_ERROR_ABORT(Err, "CalendarType not found in TimeIntegration Config"); Calendar::init(CalendarStr); - // Initialize choice of time stepper - std::string TimeStepperStr; - Err += TimeIntConfig.get("TimeStepper", TimeStepperStr); - CHECK_ERROR_ABORT(Err, "TimeStepper not found in TimeIntegration Config"); - TimeStepperType TimeStepperChoice = getTimeStepperFromStr(TimeStepperStr); - - // Initialize time step - std::string TimeStepStr; - Err += TimeIntConfig.get("TimeStep", TimeStepStr); - CHECK_ERROR_ABORT(Err, "TimeStep not found in TimeIntegration Config"); - TimeInterval TimeStep(TimeStepStr); - // Initialize start time std::string StartTimeStr; Err = TimeIntConfig.get("StartTime", StartTimeStr); @@ -298,11 +288,47 @@ void TimeStepper::init1() { StopTime = StopTime2; } + TimeInitParams TimeParams{StartTime, StopTime}; + + init1(TimeParams); +} + +void TimeStepper::init1(const TimeInitParams &TimeParams) { + + // Calendar must be initialized before this is called — the no-arg init1() + // does this internally. In coupled mode, the caller (ocnInit) is responsible + // for calling Calendar::init() before constructing TimeInitParams. + OMEGA_REQUIRE(Calendar::isDefined(), + "Calendar must be initialized before TimeStepper::init1"); + + Error Err; // error code - default to success + + // TimeStepper and TimeStep are always read from the Config + Config *OmegaConfig = Config::getOmegaConfig(); + Config TimeIntConfig("TimeIntegration"); + Err = OmegaConfig->get(TimeIntConfig); + CHECK_ERROR_ABORT(Err, "TimeIntegration group not found in Config"); + + // Initialize choice of time stepper + std::string TimeStepperStr; + Err += TimeIntConfig.get("TimeStepper", TimeStepperStr); + CHECK_ERROR_ABORT(Err, "TimeStepper not found in TimeIntegration Config"); + TimeStepperType TimeStepperChoice = getTimeStepperFromStr(TimeStepperStr); + + // Initialize time step + std::string TimeStepStr; + Err += TimeIntConfig.get("TimeStep", TimeStepStr); + CHECK_ERROR_ABORT(Err, "TimeStep not found in TimeIntegration Config"); + TimeInterval TimeStep(TimeStepStr); + + // Local non-const copies required by the 2-phase create() signature + TimeInstant StartTime = TimeParams.StartTime; + // Now that all the inputs are defined, create the default time stepper // Use the partial creation function for only the time info. Data // pointers will be attached in phase 2 initialization - TimeStepper::DefaultTimeStepper = - create("Default", TimeStepperChoice, StartTime, StopTime, TimeStep); + TimeStepper::DefaultTimeStepper = create( + "Default", TimeStepperChoice, TimeStep, StartTime, TimeParams.StopTime); } //------------------------------------------------------------------------------ @@ -370,11 +396,14 @@ TimeInterval TimeStepper::getTimeStep() const { return TimeStep; } TimeInstant TimeStepper::getStartTime() const { return StartTime; } // Get stop time from instance -TimeInstant TimeStepper::getStopTime() const { return StopTime; } +std::optional TimeStepper::getStopTime() const { return StopTime; } // Get clock (ptr) from instance Clock *TimeStepper::getClock() { return StepClock.get(); } +// Check if time stepper has an end alarm (i.e. if stop time is defined) +bool TimeStepper::hasEndAlarm() const { return EndAlarm != nullptr; } + // Get end alarm (ptr) from instance Alarm *TimeStepper::getEndAlarm() { return EndAlarm.get(); } diff --git a/components/omega/src/timeStepping/TimeStepper.h b/components/omega/src/timeStepping/TimeStepper.h index 6e4d581e2e7f..c31b1c952248 100644 --- a/components/omega/src/timeStepping/TimeStepper.h +++ b/components/omega/src/timeStepping/TimeStepper.h @@ -49,6 +49,7 @@ #include #include +#include #include namespace OMEGA { @@ -62,6 +63,16 @@ enum class TimeStepperType { Invalid }; +/// Parameters required by TimeStepper::init1 for time setup. +/// In standalone mode all fields are populated from config. +/// In coupled mode, StartTime comes from the coupler and StopTime is absent +/// (the coupler controls run length externally). +/// Calendar::init() must be called before constructing this struct. +struct TimeInitParams { + TimeInstant StartTime; ///< Simulation start time + std::optional StopTime; ///< Absent in coupled mode +}; + //------------------------------------------------------------------------------ // Utility routine /// Translate string for time stepper type into enum @@ -86,6 +97,7 @@ class TimeStepper { /// 1st phase of Initialization for the default time stepper static void init1(); + static void init1(const TimeInitParams &TimeParams); /// 2nd phase of Initialization for the default time stepper static void init2(); @@ -94,9 +106,9 @@ class TimeStepper { static TimeStepper * create(const std::string &InName, ///< [in] name of time stepper TimeStepperType InType, ///< [in] type (time stepping method) + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep, ///< [in] time step Tendencies *InTend, ///< [in] ptr to tendencies AuxiliaryState *InAuxState, ///< [in] ptr to aux state variables HorzMesh *InMesh, ///< [in] ptr to mesh information @@ -110,10 +122,10 @@ class TimeStepper { static TimeStepper * create(const std::string &InName, ///< [in] name of time stepper TimeStepperType InType, ///< [in] type (time stepping method) + TimeInterval &InTimeStep, ///< [in] time step TimeInstant &InStartTime, ///< [in] start time for time stepping - TimeInstant &InStopTime, ///< [in] stop time for time stepping - TimeInterval &InTimeStep ///< [in] time step - ); + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime = std::nullopt); /// For 2-step creation, this attaches all the data pointers to an instance /// once the data and tendencies have been created. @@ -157,7 +169,10 @@ class TimeStepper { TimeInstant getStartTime() const; /// Get stop time - TimeInstant getStopTime() const; + std::optional getStopTime() const; + + /// Check if time stepper has an end alarm (i.e. if stop time is defined) + bool hasEndAlarm() const; /// Get a pointer to the clock Clock *getClock(); @@ -256,7 +271,7 @@ class TimeStepper { TimeInstant StartTime; /// Stop time - TimeInstant StopTime; + std::optional StopTime; // std::nullopt in coupled mode /// Alarm that rings at StopTime std::unique_ptr EndAlarm; @@ -281,10 +296,10 @@ class TimeStepper { const std::string &InName, ///< [in] name of time stepper TimeStepperType InType, ///< [in] type (time stepping method) I4 InNTimeLevels, ///< [in] num time levels for method + const TimeInterval &InTimeStep, ///< [in] time step const TimeInstant &InStartTime, ///< [in] start time for time stepping - const TimeInstant &InStopTime, ///< [in] stop time for time stepping - const TimeInterval &InTimeStep ///< [in] time step - ); + ///< [in] stop time for time stepping, missing in coupled mode + std::optional InStopTime = std::nullopt); // Disable copy constructor TimeStepper(const TimeStepper &) = delete; diff --git a/components/omega/test/CMakeLists.txt b/components/omega/test/CMakeLists.txt index 301a04ede49c..860fa4607fc5 100644 --- a/components/omega/test/CMakeLists.txt +++ b/components/omega/test/CMakeLists.txt @@ -448,6 +448,13 @@ add_omega_test( "-n;8" ) +add_omega_test( + COUPLED_DRIVER_TEST + testCoupledDriver.exe + drivers/CoupledDriverTest.cpp + "-n;8" +) + ################## # Tracers test ################## diff --git a/components/omega/test/drivers/CoupledDriverTest.cpp b/components/omega/test/drivers/CoupledDriverTest.cpp new file mode 100644 index 000000000000..e31d8b8b580f --- /dev/null +++ b/components/omega/test/drivers/CoupledDriverTest.cpp @@ -0,0 +1,120 @@ +//===-- Test driver for Omega coupled driver -----------------*- C++ -*-====// +// +/// \file +/// \brief Test driver for Omega coupled driver +/// +/// This driver tests the coupled initialization path where the calendar and +/// start time are provided externally (as they would be from the MCT coupler). +/// It confirms that initialization succeeds without a StopTime/EndAlarm, that +/// the model can be stepped forward, and that finalization exits cleanly. +/// +// +//===-----------------------------------------------------------------------===/ + +#include "IOStream.h" +#include "OceanDriver.h" +#include "OceanState.h" +#include "OmegaKokkos.h" +#include "Pacer.h" +#include "TimeMgr.h" +#include "TimeStepper.h" +#include + +//------------------------------------------------------------------------------ +// The test driver for the coupled driver +// +int main(int argc, char *argv[]) { + + OMEGA::I4 ErrAll; + OMEGA::I4 ErrCurr; + OMEGA::I4 ErrFinalize; + + MPI_Init(&argc, &argv); // initialize MPI + Kokkos::initialize(); // initialize Kokkos + Pacer::initialize(MPI_COMM_WORLD); + Pacer::setPrefix("Omega:"); + + // Mock arguments coming from the coupled driver + int OcnId = 0; + std::string CalendarKindStr = "No Leap"; + std::string ConfigFile = "omega.yml"; + std::string LogFile = "ocn.log"; + int CurrYMD = 10101; // "0001-01-01" in YMD formatk + int CurrTOD = 0; // "00:00:00" in TOD format + + OMEGA::I4 Year = (CurrYMD / 100) / 100; + OMEGA::I4 Month = (CurrYMD / 100) % 100; + OMEGA::I4 Day = CurrYMD % 100; + OMEGA::I4 Hour = (CurrTOD / 60) / 60; + OMEGA::I4 Minute = (CurrTOD / 60) % 60; + OMEGA::R8 Second = CurrTOD % 60; + + // In coupled mode the coupler owns the calendar and start time. + // These must be initialized before ocnInit is called. + OMEGA::Calendar::init("No Leap"); + OMEGA::TimeInstant StartTime(Year, Month, Day, Hour, Minute, Second); + + Pacer::start("Init", 0); + ErrCurr = + OMEGA::ocnInit(MPI_COMM_WORLD, OcnId, ConfigFile, LogFile, StartTime); + if (ErrCurr == 0) { + LOG_INFO("CoupledDriverTest: Omega initialize PASS"); + } else { + LOG_INFO("CoupledDriverTest: Omega initialize FAIL"); + } + Pacer::stop("Init", 0); + + // Verify the stepper has no EndAlarm — in coupled mode the coupler + // controls run length, not an internal alarm. + OMEGA::TimeStepper *DefStepper = OMEGA::TimeStepper::getDefault(); + OMEGA::Clock *ModelClock = DefStepper->getClock(); + OMEGA::TimeInstant CurrTime = ModelClock->getCurrentTime(); + + if (ErrCurr == 0 && DefStepper->hasEndAlarm()) { + LOG_ERROR("CoupledDriverTest: hasEndAlarm() should be false in coupled " + "mode"); + ErrCurr++; + } + + // Step the model forward a few times to simulate coupling intervals. + // ocnRun(CurrTime, NextCouplingTime) will replace this loop once + // the coupled ocnRun overload is implemented. + Pacer::start("RunLoop", 0); + if (ErrCurr == 0) { + OMEGA::OceanState *DefState = OMEGA::OceanState::getDefault(); + for (int Step = 0; Step < 3; ++Step) { + DefStepper->doStep(DefState, CurrTime); + OMEGA::IOStream::writeAll(ModelClock); + } + LOG_INFO("CoupledDriverTest: Omega model run PASS"); + } else { + LOG_INFO("CoupledDriverTest: Omega model run FAIL"); + } + Pacer::stop("RunLoop", 0); + + Pacer::start("Finalize", 0); + ErrFinalize = OMEGA::ocnFinalize(CurrTime); + if (ErrFinalize == 0) { + LOG_INFO("CoupledDriverTest: Omega finalize PASS"); + } else { + LOG_INFO("CoupledDriverTest: Omega finalize FAIL"); + } + Pacer::stop("Finalize", 0); + + ErrAll = abs(ErrCurr) + abs(ErrFinalize); + if (ErrAll == 0) { + LOG_INFO("CoupledDriverTest: Successful completion"); + } + + Pacer::print("omega_coupled_driver_test", OMEGA::printTimingAllRanks()); + Pacer::finalize(); + + Kokkos::finalize(); + MPI_Finalize(); + + if (ErrAll >= 256) + ErrAll = 255; + return ErrAll; + +} // end of main +//===-----------------------------------------------------------------------===/ diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 27d4cdb75c67..557fc3601e54 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -319,7 +319,7 @@ int testTimeStepper(const std::string &Name, TimeStepperType Type, TimeInterval TimeStepTI(BaseTimeStepSeconds, TimeUnits::Seconds); auto *TestTimeStepper = TimeStepper::create( - "TestTimeStepper", Type, TimeStart, TimeEndTI, TimeStepTI, + "TestTimeStepper", Type, TimeStepTI, TimeStart, TimeEndTI, TestTendencies, TestAuxState, DefMesh, DefVCoord, DefHalo); if (!TestTimeStepper) { @@ -370,6 +370,57 @@ int testTimeStepper(const std::string &Name, TimeStepperType Type, return Err; } +int testOptionalStopTime(const std::string &Name, TimeStepperType Type) { + int Err = 0; + + auto *DefMesh = HorzMesh::getDefault(); + auto *DefVCoord = VertCoord::getDefault(); + auto *DefHalo = Halo::getDefault(); + auto *TestAuxState = AuxiliaryState::get("TestAuxState"); + auto *TestTendencies = Tendencies::get("TestTendencies"); + + TimeInstant TimeStart(0, 0, 0, 0, 0, 0); + TimeInterval TimeStep(0.2, TimeUnits::Seconds); + + // 2-phase create without StopTime — used by the coupled driver + auto *Stepper = + TimeStepper::create("CoupledTestStepper", Type, TimeStep, TimeStart); + + if (!Stepper) { + Err++; + LOG_ERROR("TimeStepperTest: error creating test stepper StopTime {}", + Name); + return Err; + } + + if (Stepper->hasEndAlarm()) { + Err++; + LOG_ERROR("TimeStepperTest: {}: hasEndAlarm() should be false without " + "StopTime", + Name); + } + + if (Stepper->getStopTime().has_value()) { + Err++; + LOG_ERROR("TimeStepperTest: {}: getStopTime() should return nullopt " + "without StopTime", + Name); + } + + // Verify doStep works without an EndAlarm (coupled run loop never uses it) + Stepper->attachData(TestTendencies, TestAuxState, DefMesh, DefVCoord, + DefHalo); + auto *State = OceanState::get("TestState"); + initState(); + TimeInstant CurTime = TimeStart; + Stepper->doStep(State, CurTime); + Stepper->doStep(State, CurTime); + + TimeStepper::erase("CoupledTestStepper"); + + return Err; +} + int timeStepperTest(const std::string &MeshFile = "OmegaMesh.nc") { int Err = initTimeStepperTest(MeshFile); @@ -395,6 +446,11 @@ int timeStepperTest(const std::string &MeshFile = "OmegaMesh.nc") { Err += testTimeStepper("RungeKutta2", TimeStepperType::RungeKutta2, ExpectedOrder, ATol); + // Verify stepper operates correctly without StopTime/EndAlarm + + Err += testOptionalStopTime("ForwardBackward", + TimeStepperType::ForwardBackward); + if (Err == 0) { LOG_INFO("TimeStepperTest: Successful completion"); }