diff --git a/components/omega/doc/devGuide/TimeStepping.md b/components/omega/doc/devGuide/TimeStepping.md index 5ad80b4b6748..a9ff915f008c 100644 --- a/components/omega/doc/devGuide/TimeStepping.md +++ b/components/omega/doc/devGuide/TimeStepping.md @@ -20,12 +20,14 @@ enum class TimeStepperType { ForwardBackward, RungeKutta4, RungeKutta2 }; The `TimeStepper` class is a base class for all Omega time steppers. It stores as members common data needed by every time stepper. These include a name and time stepping method as well as some time tracking variables, including -StartTime, StopTime, TimeStep, a Clock and an EndAlarm. Its `doStep` method -defines the interface that every time stepper needs to implement. In addition to this -method, it provides a number of other methods that can divided into two groups. -The first group is for general time stepper management. The second group is -utility functions that provide common functionality that can be re-used in -implementation of different time steppers. +a Clock, TimeStep, StartTime, StopTime (optional) and an EndAlarm (optional). +The StopTime and EndAlarm are optional, because in coupled E3SM simulations the +components do not know the StopTime and therefore no EndAlarm can be created. +Its `doStep` method defines the interface that every time stepper needs to +implement. In addition to this method, it provides a number of other methods +that can divided into two groups. The first group is for general time stepper +management. The second group is utility functions that provide common +functionality that can be re-used in implementation of different time steppers. ### Do step method The `doStep` method is the main method of every time stepper class. It exists @@ -62,14 +64,26 @@ retrieved at any time using: TimeStepper* DefTimeStepper = TimeStepper::getDefault(); ``` +In standalone simulations, `TimeStepper::init1();` will read the StartTime from +the configuration file. For coupled simulations the StartTime is provided by +coupler, overriding the value defined in the configuration file. +In this case, the first phase of time stepper initialization would look like: +```c++ +// coupled simulations have a start time but no stop time +TimeInitParams TimeParams{StartTime, std::nullopt}; + +// initialize TimeStepper with coupler provided start time +TimeStepper::init1(TimeParams); +``` + #### Creation of non-default time steppers A non-default time stepper can be created from a string `Name`, time stepper type `Type`, `TimeStep`, `StartTime`, `EndTime`, tendencies `Tend`, auxiliary state `AuxState`, horizontal mesh `Mesh`, and halo layer `MyHalo` ```c++ -TimeStepper* NewTimeStepper = TimeStepper::create(Name, Type, StartTime, - EndTime, TimeStep, Tend, AuxState, Mesh, MyHalo); +TimeStepper* NewTimeStepper = TimeStepper::create(Name, Type, TimeStep, + StartTime, EndTime, Tend, AuxState, Mesh, MyHalo); ``` For convenience, this returns a pointer to the newly created time stepper. Given its name, a pointer to a named time stepper can be obtained at any time diff --git a/components/omega/src/ocn/OceanDriver.h b/components/omega/src/ocn/OceanDriver.h index 1f405c6df2e2..e0c09d77d06d 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" @@ -34,6 +35,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..b5c822655073 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -102,59 +102,9 @@ int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator 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) { - - // 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(); - // Initialize IOStreams - this does not yet validate the contents - // of each file, only creates streams from Config - IOStream::init(ModelClock); - - IO::init(Comm); - Field::init(ModelClock); - Decomp::init(); - - Err = Halo::init(); - if (Err != 0) { - ABORT_ERROR("ocnInit: Error initializing default halo"); - } - - HorzMesh::init(); - VertCoord::init(); - Tracers::init(); - VertAdv::init(); - AuxiliaryState::init(); - Eos::init(); - PressureGrad::init(); - Tendencies::init(); - - // Validate SurfaceTracerRestoring configuration - Tendencies *DefTend = Tendencies::getDefault(); - if (DefTend->SurfaceTracerRestoring.Enabled && - DefTend->SurfaceTracerRestoring.NTracersToRestore == 0) { - ABORT_ERROR("OceanInit: SurfaceTracerRestoring is enabled but " - "TracersToRestore is empty"); - } - - TimeStepper::init2(); - - Err = OceanState::init(); - if (Err != 0) { - ABORT_ERROR("ocnInit: Error initializing default state"); - } - // Now that all fields have been defined, validate all the streams // contents bool StreamsValid = IOStream::validateAll(); @@ -212,7 +162,74 @@ int initOmegaModules(MPI_Comm Comm) { return Err; -} // end initOmegaModules +} // end ocnInit + +// Call init routines for remaining Omega modules +// 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; + + TimeStepper *DefStepper = TimeStepper::getDefault(); + Clock *ModelClock = DefStepper->getClock(); + + // Initialize IOStreams - this does not yet validate the contents + // of each file, only creates streams from Config + IOStream::init(ModelClock); + + IO::init(Comm); + Field::init(ModelClock); + Decomp::init(); + + Err = Halo::init(); + if (Err != 0) { + ABORT_ERROR("ocnInit: Error initializing default halo"); + } + + HorzMesh::init(); + VertCoord::init(); + Tracers::init(); + VertAdv::init(); + AuxiliaryState::init(); + Eos::init(); + PressureGrad::init(); + Tendencies::init(); + + // Validate SurfaceTracerRestoring configuration + Tendencies *DefTend = Tendencies::getDefault(); + if (DefTend->SurfaceTracerRestoring.Enabled && + DefTend->SurfaceTracerRestoring.NTracersToRestore == 0) { + ABORT_ERROR("OceanInit: SurfaceTracerRestoring is enabled but " + "TracersToRestore is empty"); + } + + TimeStepper::init2(); + + Err = OceanState::init(); + if (Err != 0) { + ABORT_ERROR("ocnInit: Error initializing default state"); + } + + return Err; + +} // end initOmegaModulesImpl + +int initOmegaModules(MPI_Comm Comm) { + // Initialize the default time stepper (phase 1) that includes the + // calendar, model clock and start/stop times and alarms with all options + // read from the config file + TimeStepper::init1(); + return initOmegaModulesImpl(Comm); +} + +int initOmegaModules(MPI_Comm Comm, const TimeInitParams &TParams) { + // Initialize time stepper (phase 1) using coupler provided time parameters + // Calendar should have already been initalized + 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 8717b46e3757..095508dc6e5d 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/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"); }