From bda397e469c7d556108b90104cef27bc2e2f67ea Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Sun, 23 Aug 2026 10:55:35 -0500 Subject: [PATCH 1/3] adds start/stop options and fixes restart issues - adds a config stop option to support stopping at a time, after an interval, or on a signal from coupler - adds a start option to support start-up, continuation and branch runs - fixes some related clock and clock reset issues on restart that were impacting a number of situations, especially in streams for appended files - modifies buildnml for new start/stop options --- .../omega_buildnml/data/config_overrides.yaml | 4 +- .../cime_config/omega_buildnml/validate.py | 6 +- components/omega/configs/Default.yml | 21 +- components/omega/doc/devGuide/TimeStepping.md | 33 +- .../omega/doc/userGuide/TimeStepping.md | 48 ++- .../drivers/coupled/omega_cxx2f_interface.cpp | 8 +- components/omega/src/infra/TimeMgr.cpp | 38 +- components/omega/src/infra/TimeMgr.h | 6 +- components/omega/src/ocn/OceanDriver.h | 22 +- components/omega/src/ocn/OceanInit.cpp | 180 +++++---- components/omega/src/ocn/OceanRun.cpp | 3 - .../timeStepping/ForwardBackwardStepper.cpp | 16 +- .../src/timeStepping/ForwardBackwardStepper.h | 13 +- .../src/timeStepping/RungeKutta2Stepper.cpp | 16 +- .../src/timeStepping/RungeKutta2Stepper.h | 13 +- .../src/timeStepping/RungeKutta4Stepper.cpp | 16 +- .../src/timeStepping/RungeKutta4Stepper.h | 13 +- .../timeStepping/SplitExplicitRK2Stepper.cpp | 15 +- .../timeStepping/SplitExplicitRK2Stepper.h | 16 +- .../omega/src/timeStepping/TimeStepper.cpp | 380 +++++++++++++----- .../omega/src/timeStepping/TimeStepper.h | 179 ++++++--- .../test/timeStepping/TimeStepperTest.cpp | 34 +- 22 files changed, 724 insertions(+), 356 deletions(-) diff --git a/components/omega/cime_config/omega_buildnml/data/config_overrides.yaml b/components/omega/cime_config/omega_buildnml/data/config_overrides.yaml index 576fe761e77b..fc8b348eaac4 100644 --- a/components/omega/cime_config/omega_buildnml/data/config_overrides.yaml +++ b/components/omega/cime_config/omega_buildnml/data/config_overrides.yaml @@ -2,8 +2,8 @@ coupled: TimeIntegration: TimeStepper: SplitExplicitRK2 StartTime: 9999-12-31_00:00:00 - StopTime: 9999-12-31_00:00:00 - RunDuration: none + StopType: OnSignal + StopCriterion: 9999-12-31_00:00:00 Tendencies: SfcStressForcingTendencyEnable: true diff --git a/components/omega/cime_config/omega_buildnml/validate.py b/components/omega/cime_config/omega_buildnml/validate.py index 69afeb43de5b..67b2550647e2 100644 --- a/components/omega/cime_config/omega_buildnml/validate.py +++ b/components/omega/cime_config/omega_buildnml/validate.py @@ -41,10 +41,10 @@ BLOCKED_OPTIONS = frozenset( {f"IOStreams.{stream}" for stream in BLOCKED_STREAMS} | { - # start, stop, and duration are provided by the coupler at runtime + # start time is provided by the coupler at runtime "TimeIntegration.StartTime", - "TimeIntegration.StopTime", - "TimeIntegration.RunDuration", + # stop criterion is ignored for coupled sims + "TimeIntegration.StopCriterion", # calendar must agree with the CIME ``CALENDAR`` setting "TimeIntegration.CalendarType", # base IO task and rearranger are owned by the driver (CIME/shr_pio) diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index c45000e8249f..08a8ff939825 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -8,15 +8,16 @@ Omega: CalendarType: No Leap TimeStepper: Forward-Backward TimeStep: 0000_00:10:00 + StartType: StartUp + StartTime: 0001-01-01_00:00:00 + StopType: AtTime + StopCriterion: 0001-01-01_02:00:00 ModeSplitShare: BtrTimeStepper: Predictor-Corrector BtrTimeStep: 0000_00:00:20 NTimeStepIteration: 2 NBclCoriolisIteration: 2 ReinitSplitVelocity: false - StartTime: 0001-01-01_00:00:00 - StopTime: 0001-01-01_02:00:00 - RunDuration: none Decomp: HaloWidth: 3 DecompMethod: MetisKWay @@ -130,9 +131,8 @@ Omega: UseStartEnd: false Contents: - InitVertCoord - # InitialState should only be used when starting from scratch. - # For restart runs, the frequency units should be changed from - # "OnStartup" to "never" so that the initial state file is not read. + # InitialState is only read if the StartType is StartUp so this should + # reflect that case with FreqUnits "OnStartup" InitialState: UsePointerFile: false Filename: OmegaMesh.nc @@ -158,9 +158,8 @@ Omega: 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 - # for the first startup job. + # first startup job. This stream is only read if the StartType requires a + # restart (Continue or Branch) and this stream is ignored otherwise RestartRead: UsePointerFile: true PointerFilename: ocn.pointer @@ -168,9 +167,7 @@ Omega: Precision: double Freq: 1 FreqUnits: OnStartup - UseStartEnd: true - StartTime: 0001-01-01_00:00:01 - EndTime: 99999-12-31_00:00:00 + UseStartEnd: false Contents: - Restart RestartWrite: diff --git a/components/omega/doc/devGuide/TimeStepping.md b/components/omega/doc/devGuide/TimeStepping.md index 235709233a51..b3412ec87b5b 100644 --- a/components/omega/doc/devGuide/TimeStepping.md +++ b/components/omega/doc/devGuide/TimeStepping.md @@ -75,11 +75,8 @@ the configuration file. For coupled simulations the StartTime is provided by the 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); +// initialize TimeStepper with coupler provided start type and start time +TimeStepper::init1(StartType, StartTime); ``` #### Creation of non-default time steppers @@ -88,12 +85,22 @@ 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, TimeStep, - StartTime, EndTime, Tend, AuxState, Mesh, MyHalo); +TimeStepper* NewTimeStepper = TimeStepper::create(Name, Type, + Tend, AuxState, Mesh, MyHalo, TimeStep, StartType, StartTime, + StopType, EndTime, Duration); ``` -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 -by calling the static `get` method: +where the EndTime and Duration are optional arguments depending on StopType. + +The TimeStepper constructors are filled initially with a StopTime based on +the simulation CurrentTime. In many cases (including the default time stepper), +the constructor is called before the CurrentTime is reset on restart. For the +StartType Continue option, the StopTime and EndAlarm must be reset by calling +the ``MyStepper->resetEndAlarm()`` function once the current simulation time +has been updated from the restart metadata. + +For convenience, the create routine 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 by calling the static `get` method: ```c++ TimeStepper* NewTimeStepper = TimeStepper::get(Name); ``` @@ -114,11 +121,17 @@ TimeStepperType Type = Stepper->getType(); int NTimeLevels = Stepper->getNTimeLevels(); std::string Name = Stepper->getName(); TimeInterval TimeStep = Stepper->getTimeStep(); +TimeStepperStartType StartType = Stepper->getStartType(); TimeInstant StartTime = Stepper->getStartTime(); +TimeStepperStopType StopType = Stepper->getStopType(); TimeInstant StopTime = Stepper->getStopTime(); +TimeInterval Duration = Stepper->getDuration(); Clock *ModelClock = Stepper->getClock(); Alarm *EndAlarm = Stepper->getEndAlarm(); ``` +Note that StopTime, Duration and EndAlarm are set to appropriate values +no matter what the StopType is (though for the OnSignal option, they are +set far into the future). #### Removal of time steppers To erase a specific named time stepper use `erase` diff --git a/components/omega/doc/userGuide/TimeStepping.md b/components/omega/doc/userGuide/TimeStepping.md index c94c89500b12..fff05ce5a18a 100644 --- a/components/omega/doc/userGuide/TimeStepping.md +++ b/components/omega/doc/userGuide/TimeStepping.md @@ -9,25 +9,26 @@ of the Omega configuration file: CalendarType: No Leap TimeStepper: Forward-Backward TimeStep: 0000_00:10:00 + StartType: StartUp + StartTime: 0001-01-01_00:00:00 + StopType: AtTime + StopCriterion: 0001-01-01_02:00:00 ModeSplitShare: BtrTimeStepper: Predictor-Corrector BtrTimeStep: 0000_00:00:20 NTimeStepIteration: 2 NBclCoriolisIteration: 2 ReinitSplitVelocity: false - StartTime: 0001-01-01_00:00:00 - StopTime: 0001-01-01_02:00:00 - RunDuration: none ``` This configuration refers to the default time stepping used for the model dynamics (momentum and continuity equations). Additional time steppers can be used for other portions of the model (eg the barotropic mode or tracer transport) that may have different time steps and use different algorithms. -The Calendar choice is describe in the +The ``CalendarType`` choice is described in the [Time Management](#omega-user-time-manager) section. -The TimeStepper option refers to the numerical scheme used to advance the +The ``TimeStepper`` option refers to the numerical scheme used to advance the model in time. Omega implements a number of time-stepping schemes. The user can select the scheme they want in the configuration file. The following time steppers are currently available: @@ -43,7 +44,7 @@ The following time steppers are currently available: subgroup shown above. See [Split time stepping](#omega-user-split-time-stepping) for descriptions of these schemes and their configuration options. -The time step refers to the main model time step used to advance the solution +The ``TimeStep`` refers to the main model time step used to advance the solution forward. The time step is specified as a formatted string and can be provided in any of the following forms: @@ -55,22 +56,31 @@ in any of the following forms: Days, hours and minutes are optional but must be in order if included. Fractional seconds are optional. -The StartTime refers to the starting time for the simulation. It is in the +The ``StartOption`` can be one of three choices. The ``StartUp`` option is for +starting a solution from scratch from an initial state file. The ``Continue`` +option is for continuing a simulation from a restart file. The ``Branch`` +option will branch from an existing simulation by reading from the restart +file, but it will reset the clock to the ``StartTime``. + +The ``StartTime`` refers to the starting time for the full simulation (not the +current leg of an ongoing simulation). It is in the format ``yyyy-mm-day_hh:mm:ss`` for year, month, day, hour, minute, second. -This refers to the initial start time; for a longer simulation, the current -time will be modified by the restart file to update to the present time for -the current segment of the simulation. +If this is a continuation of an existing simulation, the current time for this +leg of the full simulation will be set by the restart file. -The simulation will be stopped either at a fixed StopTime if provided or -by the RunDuration. For shorter simulations, the StopTime can be used to -specify a specific time to stop. For longer simulations with multiple -segments that are restarted, the RunDuration should used and should be set -to fit within the queue time. The format for StopTime is the same as StartTime. -The format for RunDuration is the same as the TimeStep. +A ``StopType`` determines (with the ``StopCriterion`` below) how the simulation +will be stopped. There are three options. The ``AtTime`` option will stop the +simulation at a specific time and the ``StopCriterion`` holds that specific time +as described below. The ``AfterDuration`` option runs the simulation for a fixed +time interval and the ``StopCriterion`` is used to define that interval. A final +option called ``OnSignal`` is primarily for coupled simulations where the +simulation will stop after receiving a signal from the coupler. -Only one of the StopTime or RunDuration should be specified with the other -set to either an empty string or "none". If both are specified, the -RunDuration is used instead of the StopTime. +For the ``AtTime`` stop type, the ``StopCriterion`` must be a time instant in +the format ``yyyy-mm-dd_hh:mm:ss``. If the ``StopType`` is ``AfterDuration``, +the ``StopCriterion`` is a time interval in the format described above for the +time step (but typically ``dddd_hh:mm:ss``). For the ``OnSignal`` option the +``StopCriterion`` is ignored. ```{toctree} :hidden: diff --git a/components/omega/src/drivers/coupled/omega_cxx2f_interface.cpp b/components/omega/src/drivers/coupled/omega_cxx2f_interface.cpp index 9dcac1eb7d59..84e4131827bc 100644 --- a/components/omega/src/drivers/coupled/omega_cxx2f_interface.cpp +++ b/components/omega/src/drivers/coupled/omega_cxx2f_interface.cpp @@ -53,7 +53,7 @@ void omega_ocn_init1( const int OcnID, // [in] mct comp id for ocn mode const char *YamlConfigFile, // [in] yaml file name for ocean model const char *OcnLogFile, // [in] log file name for ocean model - const int StartType, // [in] 0=startup, 1=continue, 2=branch + const int InStartType, // [in] 0=startup, 1=continue, 2=branch const char *CalendarName, // [in] CIME calendar name const int RunStartYMD, // [in] run start date in YYYYMMDD const int RunStartTOD, // [in] run start time in seconds of day @@ -110,8 +110,8 @@ void omega_ocn_init1( Pacer::start("Init1", 0); - OMEGA::StartType StartTypeEnum = OMEGA::safeIntToStartType(StartType); - OMEGA::TimeInitParams TimeParams{StartTime, std::nullopt}; + OMEGA::TimeStepperStartType StartTypeEnum = + OMEGA::getTimeStepperStartTypeFromE3SM(InStartType); OMEGA::CouplingInitParams CouplingParams{ NCouplerImports, NCouplerExports, ImportIdxMap, ExportIdxMap, CouplingInterval, OMEGA::CouplingLayout::MCT}; @@ -123,7 +123,7 @@ void omega_ocn_init1( IOBaseTask, static_cast(IORearranger)}; OMEGA::ocnInit1(Comm, OcnID, YamlConfigFile, OcnLogFile, StartTypeEnum, - TimeParams, CouplingParams, IOParams); + StartTime, CouplingParams, IOParams); Pacer::stop("Init1", 0); diff --git a/components/omega/src/infra/TimeMgr.cpp b/components/omega/src/infra/TimeMgr.cpp index febc551d8d64..4553827cb2ec 100644 --- a/components/omega/src/infra/TimeMgr.cpp +++ b/components/omega/src/infra/TimeMgr.cpp @@ -3914,6 +3914,15 @@ bool Alarm::isRinging(void) { return Ringing; } // end Alarm::isRinging +//------------------------------------------------------------------------------ +// Alarm::isPeriodic - Check whether an alarm is a recurring alarm +// This function checks to see if an alarm is a recurring alarm by simply +// returning the periodic flag + +bool Alarm::isPeriodic(void) { return Periodic; } + +// end Alarm::isPeriodic + //------------------------------------------------------------------------------ // Alarm::updateStatus - Changes the alarm status based on current time // Checks whether the alarm should ring based on the current (or supplied) @@ -3932,7 +3941,7 @@ void Alarm::updateStatus(const TimeInstant CurrentTime // [in] current time //------------------------------------------------------------------------------ // Alarm::reset - Stops a ringing alarm and resets to a new alarm time -// Stops a ringing alarm and sets next a new ring time. If the alarm +// Stops a ringing alarm and sets a new ring time. If the alarm // is a periodic/interval alarm, the next ring time is set to be the // next interval boundary after the input time. If the alarm is a // single instance, the input time is used as the next alarm time. @@ -3943,17 +3952,20 @@ void Alarm::reset(const TimeInstant InTime // [in] new alarm time // stop the ringing alarm Ringing = false; - // if this is an interval alarm, find the next alarm time after - // the input time + // if this is an interval alarm, reset the alarm so it rings at the next + // interval after the input time if (Periodic) { - // first check that the input time is valid + // if the new time is before the ring time, move the alarm backward + // until the new time is within a ring interval if (InTime < RingTime) { - ABORT_ERROR("TimeMgr: Alarm::reset error - input time is less than the" - " current ring time"); + while (InTime < RingTimePrev) { + RingTime -= RingInterval; + RingTimePrev -= RingInterval; + } + // if the new time is after the ring time, move alarm forward until + // the next interval is greater than input time } else { - // now move forward in time until the next interval is greater - // than the input time while (RingTime <= InTime) { RingTimePrev = RingTime; RingTime += RingInterval; @@ -4075,6 +4087,8 @@ Clock::~Clock(void) { // Nothing to be done // Clock::setCurrentTime - Sets the current time // Sets the current time to an input value. Also must reset previous time and // next time to be consistent. Check that new time does not precede start time. +// Updates all one-time alarms and resets all periodic alarms based on the new +// time. void Clock::setCurrentTime( const TimeInstant InCurrTime // new value for current time @@ -4093,9 +4107,13 @@ void Clock::setCurrentTime( NextTime = CurrTime + TimeStep; } - // Update status of all attached alarms based on new time + // Reset periodic alarms and update the status of one-time alarms for (I4 N = 0; N < NumAlarms; ++N) { - Alarms[N]->updateStatus(CurrTime); + if (Alarms[N]->isPeriodic()) { + Alarms[N]->reset(CurrTime); + } else { + Alarms[N]->updateStatus(CurrTime); + } } } // end Clock::setCurrentTime diff --git a/components/omega/src/infra/TimeMgr.h b/components/omega/src/infra/TimeMgr.h index f17aa3fe4e4c..1224943329a7 100644 --- a/components/omega/src/infra/TimeMgr.h +++ b/components/omega/src/infra/TimeMgr.h @@ -773,9 +773,13 @@ class Alarm { ~Alarm(void); /// Check whether an alarm is ringing - /// \return true if alarm is ringing, false otherwise + /// Return true if alarm is ringing, false otherwise bool isRinging(void); + /// Check whether an alarm is a recurring alarm + /// Return true if periodic, false otherwise + bool isPeriodic(void); + /// Checks whether the alarm should ring based on the current /// (or supplied) time instant void updateStatus(const TimeInstant CurrentTime ///< [in] current time diff --git a/components/omega/src/ocn/OceanDriver.h b/components/omega/src/ocn/OceanDriver.h index 45a7122d98be..839c97a96716 100644 --- a/components/omega/src/ocn/OceanDriver.h +++ b/components/omega/src/ocn/OceanDriver.h @@ -21,12 +21,6 @@ namespace OMEGA { -/// enumeration for the different "start_type"s supported by the coupler -enum class StartType { StartUp, Continue, Branch }; - -/// Convienvence converter of an int to a StartType enum, with error checking -StartType safeIntToStartType(int val); - /// Should timing info be printed from all ranks bool printTimingAllRanks(); @@ -41,8 +35,8 @@ int ocnInit1( 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 StartType StartType, ///< [in] simulation start type - const TimeInitParams &TimeParams, ///< [in] time parameters + const TimeStepperStartType StartType, ///< [in] simulation start type + const TimeInstant &StartTime, ///< [in] simulation start time const CouplingInitParams &CouplingParams, ///< [in] coupling parameters const IO::IOInitParams &IOParams ///< [in] driver-owned IO params ); @@ -65,10 +59,14 @@ 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 and IO parameters -int initOmegaModules(MPI_Comm Comm, const TimeInitParams &TParams, - const CouplingInitParams &CParams, - const IO::IOInitParams &IOParams); +/// Initialize Omega modules with coupler-provided time parameters +int initOmegaModules( + MPI_Comm Comm, ///< [in] MPI communicator for ocn + TimeStepperStartType StartType, ///< [in] option for starting this leg + const TimeInstant &StartTime, ///< [in] start time for full simulation + const CouplingInitParams &CParams, ///< [in] struct with coupling params + const IO::IOInitParams &IOParams ///< [in] struct with IO parameters +); /// Update Halo/Host arrays with new state, auxiliary state, and tracer fields int initUpdateHaloAndHostArrays(); diff --git a/components/omega/src/ocn/OceanInit.cpp b/components/omega/src/ocn/OceanInit.cpp index a27cc3f3760a..eeed99dda568 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -39,19 +39,8 @@ namespace OMEGA { -// Convienvence converter of an int to a StartType enum, with error checking -StartType safeIntToStartType(int val) { - switch (val) { - case 0: - return StartType::StartUp; - case 1: - return StartType::Continue; - case 2: - return StartType::Branch; - default: - ABORT_ERROR("Invalid start type value: {}", val); - } -} +//------------------------------------------------------------------------------ +// Timing initialization routines namespace Timing { // Flag to determine if timing info should be printed from all ranks @@ -100,6 +89,7 @@ static void readTimingConfig(Config *OmegaConfig) { // the state is far enough along for initStateForTimeStepper static bool CoupledReadRestart = false; +//------------------------------------------------------------------------------ // Perform any time-stepper specific state initialization that can only be done // once the input has been read and the halos have been exchanged. For the // split-explicit stepper this separates the input velocity into its barotropic @@ -114,6 +104,8 @@ static void initStateForTimeStepper( DefStepper->initializeStateFromInput(DefState, ReadRestart); } +//------------------------------------------------------------------------------ +// Ocean initialization - standalone case int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator ) { @@ -146,41 +138,58 @@ int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator // Now that all fields have been defined, validate all the streams // contents bool StreamsValid = IOStream::validateAll(); - if (!StreamsValid) { + if (!StreamsValid) ABORT_ERROR("ocnInit: Error validating IO Streams"); - } // Initialize data from Restart or InitialState files - std::string SimTimeStr = " "; // create SimulationTime metadata std::shared_ptr SimField = Field::get(SimMeta); + std::string SimTimeStr = " "; SimField->addMetadata("SimulationTime", SimTimeStr); Error Err1; - Error Err2; - - // read from initial state if this is starting a new simulation - Metadata ReqMeta; // no requested metadata for initial state - Err1 = IOStream::read("InitialState", ModelClock, ReqMeta); - - // read restart if starting from restart - SimTimeStr = " "; - ReqMeta["SimulationTime"] = SimTimeStr; - Err2 = IOStream::read("RestartRead", ModelClock, ReqMeta); - - // One of the above two streams must be successful to initialize the - // state and other fields used in the model - if (Err1.isFail() and Err2.isFail()) { - CHECK_ERROR(Err1, "Errors encountered reading InitialState"); - CHECK_ERROR(Err2, "Errors encountered reading RestartRead"); - ABORT_ERROR("Error initializing ocean variables from input streams"); - } - // If reading from restart, reset the current time to the input time - SimTimeStr = std::any_cast(ReqMeta["SimulationTime"]); - const bool ReadRestart = SimTimeStr != " "; - if (ReadRestart) { + Metadata ReqMeta; // empty requested metadata from file + TimeStepperStartType StartType = DefStepper->getStartType(); + bool ReadRestart = false; + + // Read from either initial state stream or restart stream based + // on the start option + switch (StartType) { + + // Starting from scratch using an initial state + case (TimeStepperStartType::StartUp): + Err1 = IOStream::read("InitialState", ModelClock, ReqMeta); + CHECK_ERROR_ABORT(Err1, "Error reading InitialState file"); + break; + + // Continue simulation from a restart file and reset current time + // to the restart time read from restart metadata + case (TimeStepperStartType::Continue): { + ReqMeta["SimulationTime"] = SimTimeStr; // request current sim time + Err1 = IOStream::read("RestartRead", ModelClock, ReqMeta); + ReadRestart = true; + + // Reset the current time to the input time from restart file and + // update the end alarm and stop time. + SimTimeStr = std::any_cast(ReqMeta["SimulationTime"]); + if (SimTimeStr == " ") + ABORT_ERROR("Error reading current time from restart file"); TimeInstant NewCurrentTime(SimTimeStr); ModelClock->setCurrentTime(NewCurrentTime); - } + DefStepper->resetEndAlarm(); + } break; + + // Branch a simulation from a previous restart file but keep the + // simulation StartTime rather than the restart time + case (TimeStepperStartType::Branch): + Err1 = IOStream::read("RestartRead", ModelClock, ReqMeta); + CHECK_ERROR_ABORT(Err1, "Error reading restart file for branch run"); + ReadRestart = true; + break; + + default: + ABORT_ERROR("Unknown StartType in OcnInit"); + + } // end switch StartType // Update Halo/Host arrays with new state, auxiliary state, and tracer fields Err = initUpdateHaloAndHostArrays(); @@ -191,12 +200,14 @@ int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator return Err; } // end ocnInit +//------------------------------------------------------------------------------ +// Ocean initialization - coupling case int ocnInit1(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 StartType StartType, ///< [in] simulation start type - const TimeInitParams &TimeParams, ///< [in] simulation start time + const TimeStepperStartType StartType, ///< [in] sim start type + const TimeInstant &StartTime, ///< [in] simulation start time const CouplingInitParams &CouplingParams, ///< [in] coupler info const IO::IOInitParams &IOParams ///< [in] driver-owned IO params ) { @@ -215,7 +226,7 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator readTimingConfig(OmegaConfig); // initialize remaining Omega modules - Err = initOmegaModules(Comm, TimeParams, CouplingParams, IOParams); + Err = initOmegaModules(Comm, StartType, StartTime, CouplingParams, IOParams); if (Err != 0) ABORT_ERROR("ocnInit: Error initializing Omega modules"); @@ -229,38 +240,56 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator ABORT_ERROR("ocnInit: Error validating IO Streams"); } - Metadata ReqMeta; - CoupledReadRestart = StartType != StartType::StartUp; - if (StartType == StartType::StartUp) { - // read from initial state if this is starting a new simulation - Error IOError = IOStream::read("InitialState", ModelClock, ReqMeta); - if (IOError.isFail()) { - ABORT_ERROR("Errors encountered reading InitialState"); - } - } else if (StartType == StartType::Continue || - StartType == StartType::Branch) { - // read restart if starting from restart - ReqMeta["SimulationTime"] = std::string(" "); - Error IOError = IOStream::read("RestartRead", ModelClock, ReqMeta); - if (IOError.isFail()) { - ABORT_ERROR("Errors encountered reading RestartRead"); - } - - // Coupler only provides case start time, so on restart get the - // simulation time from the restart file - std::string SimTimeStr = - std::any_cast(ReqMeta["SimulationTime"]); - if (SimTimeStr == " ") { - ABORT_ERROR("RestartRead stream did not provide SimulationTime"); - } - - // Set the model clock to the simulation time read from the restart file + // Initialize data from Restart or InitialState files + std::string SimTimeStr = " "; // create SimulationTime metadata + std::shared_ptr SimField = Field::get(SimMeta); + SimField->addMetadata("SimulationTime", SimTimeStr); + Error Err1; + Metadata ReqMeta; // empty requested metadata from file + + // Read from either initial state stream or restart stream based + // on the start option + switch (StartType) { + + // Starting from scratch using an initial state + case (TimeStepperStartType::StartUp): + Err1 = IOStream::read("InitialState", ModelClock, ReqMeta); + CHECK_ERROR_ABORT(Err1, "Error reading InitialState file"); + CoupledReadRestart = false; + break; + + // Continue simulation from a restart file and reset current time + // to the restart time read from restart metadata + case (TimeStepperStartType::Continue): { + ReqMeta["SimulationTime"] = SimTimeStr; // request current sim time + Err1 = IOStream::read("RestartRead", ModelClock, ReqMeta); + CoupledReadRestart = true; + + // Reset the current time to the input time from restart file and + // update the end alarm and stop time. + SimTimeStr = std::any_cast(ReqMeta["SimulationTime"]); + if (SimTimeStr == " ") + ABORT_ERROR("Error reading current time from restart file"); TimeInstant NewCurrentTime(SimTimeStr); ModelClock->setCurrentTime(NewCurrentTime); - }; + DefStepper->resetEndAlarm(); + } break; + + // Branch a simulation from a previous restart file but keep the + // simulation StartTime rather than the restart time + case (TimeStepperStartType::Branch): + Err1 = IOStream::read("RestartRead", ModelClock, ReqMeta); + CHECK_ERROR_ABORT(Err1, "Error reading restart file for branch run"); + CoupledReadRestart = true; + break; + + default: + ABORT_ERROR("Unknown StartType in OcnInit"); + + } // end switch StartType // Advance clock one coupling interval, to be in sync with couplers clock - if (StartType == StartType::StartUp) { + if (StartType == TimeStepperStartType::StartUp) { SfcCoupling *DefCoupling = SfcCoupling::getDefault(); while (!DefCoupling->getCouplingAlarm()->isRinging()) { ModelClock->advance(); @@ -275,6 +304,7 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator return Err; } // end ocnInit1 +//------------------------------------------------------------------------------ // Coupled init phase 2: attach the coupler's MCT buffers and exchange the // initial coupled state; split from ocnInit1 since these buffers don't exist // until the coupler has sized/allocated them using Omega's decomposition @@ -296,6 +326,7 @@ void ocnInit2(const Real *CplToOcnData, Real *OcnToCplData) { // coupling interval, including the first, so nothing is missed. } // end ocnInit2 +//------------------------------------------------------------------------------ // Call init routines for remaining Omega modules // Internal helper — all module init after TimeStepper::init1 is called. // Called by both initOmegaModules overloads. @@ -338,6 +369,7 @@ static int initOmegaModulesImpl() { "TracersToRestore is empty"); } + // Add fields to time stepper TimeStepper::init2(); Err = OceanState::init(); @@ -351,6 +383,7 @@ static int initOmegaModulesImpl() { } // 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 @@ -360,13 +393,15 @@ int initOmegaModules(MPI_Comm Comm) { return initOmegaModulesImpl(); } -int initOmegaModules(MPI_Comm Comm, const TimeInitParams &TParams, +//------------------------------------------------------------------------------ +int initOmegaModules(MPI_Comm Comm, TimeStepperStartType StartType, + const TimeInstant &StartTime, const CouplingInitParams &CParams, const IO::IOInitParams &IOParams) { int Err = 0; // Initialize time stepper (phase 1) using coupler provided time parameters // Calendar should have already been initalized - TimeStepper::init1(TParams); + TimeStepper::init1(StartType, StartTime); IO::init(Comm, IOParams); Err = initOmegaModulesImpl(); SfcCoupling::init(CParams); @@ -374,6 +409,7 @@ int initOmegaModules(MPI_Comm Comm, const TimeInitParams &TParams, return Err; } +//------------------------------------------------------------------------------ int initUpdateHaloAndHostArrays() { // Update Halo/Host arrays with new state, auxiliary state, and tracer fields int Err = 0; diff --git a/components/omega/src/ocn/OceanRun.cpp b/components/omega/src/ocn/OceanRun.cpp index 6237988d400f..4ce98a2267ba 100644 --- a/components/omega/src/ocn/OceanRun.cpp +++ b/components/omega/src/ocn/OceanRun.cpp @@ -29,9 +29,6 @@ int ocnRun(TimeInstant &CurrTime ///< [inout] current sim time TimeStepper *DefTimeStepper = TimeStepper::getDefault(); Forcing *DefForcing = Forcing::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 b4deb43c5c9d..b4b2a91dde37 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.cpp +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.cpp @@ -15,13 +15,17 @@ namespace OMEGA { // fills with some time information. Data pointers are added later. // 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime) + const std::string &InName, // [in] name of time stepper + const TimeInterval &InTimeStep, // [in] time step + const TimeStepperStartType InStartType, // [in] option for starting + const TimeInstant &InStartTime, // [in] start time for full sim + const TimeStepperStopType InStopType, // [in] option for stopping + std::optional InStopTime, // [in] stop time if option AtTime + std::optional InDuration // [in] duration if AfterDuration + ) : TimeStepper(InName, TimeStepperType::ForwardBackward, 2, InTimeStep, - InStartTime, InStopTime) {} + InStartType, InStartTime, InStopType, InStopTime, + InDuration) {} //------------------------------------------------------------------------------ // 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 6aa10e68b26b..05d1026fe028 100644 --- a/components/omega/src/timeStepping/ForwardBackwardStepper.h +++ b/components/omega/src/timeStepping/ForwardBackwardStepper.h @@ -15,11 +15,14 @@ class ForwardBackwardStepper : public TimeStepper { /// Constructor creates an instance of a forward-backward stepper and /// 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime = std::nullopt); + const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step + const TimeStepperStartType InStartType, ///< [in] option for starting sim + const TimeInstant &InStartTime, ///< [in] start time for full sim + const TimeStepperStopType InStopType, ///< [in] option for stopping + std::optional InStopTime, ///< [in] stop time if AtTime + std::optional InDuration ///< [in] duration if AfterDur + ); /// 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 dcf33ad611a2..520a05b6066f 100644 --- a/components/omega/src/timeStepping/RungeKutta2Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta2Stepper.cpp @@ -15,13 +15,17 @@ namespace OMEGA { // fills with some time information. Data pointers are added later. // 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime) + const std::string &InName, // [in] name of time stepper + const TimeInterval &InTimeStep, // [in] time step + const TimeStepperStartType InStartType, // [in] option for starting + const TimeInstant &InStartTime, // [in] start time for full sim + const TimeStepperStopType InStopType, // [in] option for stopping + std::optional InStopTime, // [in] stop time if option AtTime + std::optional InDuration // [in] duration if AfterDuration + ) : TimeStepper(InName, TimeStepperType::RungeKutta2, 2, InTimeStep, - InStartTime, InStopTime) {} + InStartType, InStartTime, InStopType, InStopTime, + InDuration) {} //------------------------------------------------------------------------------ // 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 e7d7d5301c9f..c18a30b8c676 100644 --- a/components/omega/src/timeStepping/RungeKutta2Stepper.h +++ b/components/omega/src/timeStepping/RungeKutta2Stepper.h @@ -17,11 +17,14 @@ class RungeKutta2Stepper : public TimeStepper { /// Constructor creates an instance of a midpoint Runge Kutta stepper and /// 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime = std::nullopt); + const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step + const TimeStepperStartType InStartType, ///< [in] option for starting + const TimeInstant &InStartTime, ///< [in] start time for full sim + const TimeStepperStopType InStopType, ///< [in] option for stopping + std::optional InStopTime, ///< [in] stop time if opt AtTime + std::optional InDuration ///< [in] duration if AfterDur + ); /// 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 abd83b03043e..3123faec3135 100644 --- a/components/omega/src/timeStepping/RungeKutta4Stepper.cpp +++ b/components/omega/src/timeStepping/RungeKutta4Stepper.cpp @@ -15,13 +15,17 @@ namespace OMEGA { // fills with some time information. Data pointers are added later. // 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime) + const std::string &InName, // [in] name of time stepper + const TimeInterval &InTimeStep, // [in] time step + const TimeStepperStartType InStartType, // [in] option for starting + const TimeInstant &InStartTime, // [in] start time for full sim + const TimeStepperStopType InStopType, // [in] option for stopping + std::optional InStopTime, // [in] stop time if option AtTime + std::optional InDuration // [in] duration if AfterDuration + ) : TimeStepper(InName, TimeStepperType::RungeKutta4, 2, InTimeStep, - InStartTime, InStopTime) { + InStartType, InStartTime, InStopType, InStopTime, + InDuration) { RKA[0] = 0; RKA[1] = 1. / 2; diff --git a/components/omega/src/timeStepping/RungeKutta4Stepper.h b/components/omega/src/timeStepping/RungeKutta4Stepper.h index 16efa1acd9ba..78dcb18a2d96 100644 --- a/components/omega/src/timeStepping/RungeKutta4Stepper.h +++ b/components/omega/src/timeStepping/RungeKutta4Stepper.h @@ -16,11 +16,14 @@ class RungeKutta4Stepper : public TimeStepper { /// Constructor creates an instance of a fourth order Runge Kutta stepper and /// 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime = std::nullopt); + const std::string &InName, ///< [in] name of time stepper + const TimeInterval &InTimeStep, ///< [in] time step + const TimeStepperStartType InStartType, ///< [in] option for starting + const TimeInstant &InStartTime, ///< [in] start time for full sim + const TimeStepperStopType InStopType, ///< [in] option for stopping + std::optional InStopTime, ///< [in] stop time if AtTime + std::optional InDuration ///< [in] duration if AfterDur + ); /// 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/SplitExplicitRK2Stepper.cpp b/components/omega/src/timeStepping/SplitExplicitRK2Stepper.cpp index 9660a9c53214..87e2711a5827 100644 --- a/components/omega/src/timeStepping/SplitExplicitRK2Stepper.cpp +++ b/components/omega/src/timeStepping/SplitExplicitRK2Stepper.cpp @@ -18,10 +18,17 @@ namespace OMEGA { //------------------------------------------------------------------------------ SplitExplicitRK2Stepper::SplitExplicitRK2Stepper( - const std::string &InName, TimeStepperType InType, - const TimeInterval &InTimeStep, const TimeInstant &InStartTime, - std::optional InStopTime) - : TimeStepper(InName, InType, 2, InTimeStep, InStartTime, InStopTime), + const std::string &InName, // [in] name of time stepper + TimeStepperType InType, // [in] type for SE variants + const TimeInterval &InTimeStep, // [in] time step + const TimeStepperStartType InStartType, // [in] option for starting + const TimeInstant &InStartTime, // [in] start time for full sim + const TimeStepperStopType InStopType, // [in] option for stopping + std::optional InStopTime, // [in] stop time if option AtTime + std::optional InDuration // [in] duration if AfterDuration + ) + : TimeStepper(InName, InType, 2, InTimeStep, InStartType, InStartTime, + InStopType, InStopTime, InDuration), SEConfig(SplitExplicitInit::readConfigOptions( InTimeStep, InType == TimeStepperType::UnsplitRK2)) {} diff --git a/components/omega/src/timeStepping/SplitExplicitRK2Stepper.h b/components/omega/src/timeStepping/SplitExplicitRK2Stepper.h index c24e1bac50a8..ef6a96e5730f 100644 --- a/components/omega/src/timeStepping/SplitExplicitRK2Stepper.h +++ b/components/omega/src/timeStepping/SplitExplicitRK2Stepper.h @@ -16,13 +16,15 @@ namespace OMEGA { class SplitExplicitRK2Stepper : public TimeStepper { public: SplitExplicitRK2Stepper( - const std::string &InName, ///< [in] name of time stepper - ///< [in] SplitExplicitRK2 or UnsplitRK2 - TimeStepperType InType, - 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 = std::nullopt); + const std::string &InName, ///< [in] name of time stepper + TimeStepperType InType, ///< [in] SplitExplicit type + const TimeInterval &InTimeStep, ///< [in] time step + const TimeStepperStartType InStartType, ///< [in] option for starting sim + const TimeInstant &InStartTime, ///< [in] start time for full sim + const TimeStepperStopType InStopType, ///< [in] option for stopping + std::optional InStopTime, ///< [in] stop time if AtTime + std::optional InDuration ///< [in] duration if AfterDur + ); /// Indicate that this is a split time stepper bool isSplit() const override; diff --git a/components/omega/src/timeStepping/TimeStepper.cpp b/components/omega/src/timeStepping/TimeStepper.cpp index a480ba7b561d..1875f155be54 100644 --- a/components/omega/src/timeStepping/TimeStepper.cpp +++ b/components/omega/src/timeStepping/TimeStepper.cpp @@ -28,6 +28,7 @@ PrescribeStateType TimeStepper::DefaultPrescribeVelocityMode = //------------------------------------------------------------------------------ // utility functions +//------------------------------------------------------------------------------ // convert string into TimeStepperType enum TimeStepperType getTimeStepperFromStr(const std::string &InString) { @@ -54,6 +55,82 @@ TimeStepperType getTimeStepperFromStr(const std::string &InString) { return TimeStepperChoice; } +//------------------------------------------------------------------------------ +// convert string into StartType enum +TimeStepperStartType +getTimeStepperStartTypeFromStr(const std::string &InString) { + + // Convert input string to lowercase for easier and more robust comparison + std::string StartStr = InString; + std::transform(StartStr.begin(), StartStr.end(), StartStr.begin(), + [](unsigned char c) { return std::tolower(c); }); + + TimeStepperStartType StartChoice; + if (StartStr == "startup" or StartStr == "init" or StartStr == "initial") { + StartChoice = TimeStepperStartType::StartUp; + } else if (StartStr == "continue" or StartStr == "restart") { + StartChoice = TimeStepperStartType::Continue; + } else if (StartStr == "branch") { + StartChoice = TimeStepperStartType::Branch; + } else { + StartChoice = TimeStepperStartType::Invalid; + ABORT_ERROR("Invalid StartType {}", InString); + } + + return StartChoice; +} + +//------------------------------------------------------------------------------ +// convert E3SM start type into StartType enum +TimeStepperStartType getTimeStepperStartTypeFromE3SM(const int E3SMOption) { + + // Translate the integer start option from the E3SM coupler to the + // internal enum + + TimeStepperStartType StartChoice; + switch (E3SMOption) { + case 0: + StartChoice = TimeStepperStartType::StartUp; + break; + case 1: + StartChoice = TimeStepperStartType::Continue; + break; + case 2: + StartChoice = TimeStepperStartType::Branch; + break; + default: + StartChoice = TimeStepperStartType::Invalid; + ABORT_ERROR("Invalid E3SM start type value: {}", E3SMOption); + } + return StartChoice; +} + +//------------------------------------------------------------------------------ +// convert string into StopType enum +TimeStepperStopType getTimeStepperStopTypeFromStr(const std::string &InString) { + + // Convert input string to lowercase for easier and more robust comparison + std::string StopStr = InString; + std::transform(StopStr.begin(), StopStr.end(), StopStr.begin(), + [](unsigned char c) { return std::tolower(c); }); + + TimeStepperStopType StopChoice; + if (StopStr == "attime") { + StopChoice = TimeStepperStopType::AtTime; + } else if (StopStr == "afterduration") { + StopChoice = TimeStepperStopType::AfterDuration; + } else if (StopStr == "onsignal") { + StopChoice = TimeStepperStopType::OnSignal; + } else { + StopChoice = TimeStepperStopType::Invalid; + ABORT_ERROR("Invalid StopType {}", InString); + } + + return StopChoice; +} + +//------------------------------------------------------------------------------ +// Convert string into PrescribeStateType enum PrescribeStateType getPrescribeThicknessTypeFromStr(const std::string &InString) { @@ -91,46 +168,104 @@ getPrescribeVelocityTypeFromStr(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 most of the time related +// data. The attachData function is used to add the data pointers once they +// are known. While an initial StopTime and EndAlarm are created here, they +// must be reset later if the initial time for the segment is updated (eg during +// the restart read). TimeStepper::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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime) + 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 TimeStepperStartType InStartType, // [in] option for starting + const TimeInstant &InStartTime, // [in] start time for full sim + const TimeStepperStopType InStopType, // [in] option for stopping + std::optional InStopTime, // [in] stop time if opt AtTime + std::optional InDuration + // [in] duration of simulation segment if StopType is AfterDuration + ) : Name(InName), Type(InType), NTimeLevels(InNTimeLevels), - TimeStep(InTimeStep), StartTime(InStartTime), StopTime(InStopTime) { - // Most variables initialized via initializer list + TimeStep(InTimeStep), StartType(InStartType), StartTime(InStartTime), + StopType(InStopType) { + // Many variables initialized via initializer list // Set up clock associated with this time stepper StepClock = std::make_unique(Clock(InStartTime, InTimeStep)); - 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)); + // Create an initial stop time and end alarm and attach the alarm to the + // StepClock. For the duration case, these are only valid for the first run + // segment and must be reset later after the current time is updated on + // restart. + + // Create alarm name based on TimeStepper instance name + std::string AlarmName = "EndAlarm"; + if (InName != "Default") + AlarmName += InName; + + switch (StopType) { + case TimeStepperStopType::AtTime: + if (InStopTime.has_value()) { // StopTime must be provided + StopTime = InStopTime.value(); + EndAlarm = std::make_unique(Alarm(AlarmName, StopTime)); + StepClock->attachAlarm(EndAlarm.get()); + Duration = StopTime - StartTime; + } else { + ABORT_ERROR("While creating TimeStepper {}, the StopType AtTime" + " was requested but a StopTime was not provided", + Name); + } + break; + case TimeStepperStopType::AfterDuration: + // This initialization of the end alarm is only valid for a simulation + // starting from scratch or branching with a time reset. + // If the simulation is being continued from a restart file, then + // these must be reset later using the resetEndAlarm function. + if (InDuration.has_value()) { // Duration must be provided + TimeInstant CurrentTime = StepClock->getCurrentTime(); + Duration = InDuration.value(); + StopTime = CurrentTime + Duration; + EndAlarm = std::make_unique(Alarm(AlarmName, StopTime)); + StepClock->attachAlarm(EndAlarm.get()); + } else { + ABORT_ERROR("While creating TimeStepper {}, the StopType" + " AfterDuration was requested but a Duration was not" + " provided", + Name); + } + break; + case TimeStepperStopType::OnSignal: + // Simulation will stop on an external signal so no StopTime + // or Duration are needed. Set to a large value or long future time. + Duration = TimeInterval(1.e16, TimeUnits::Seconds); + StopTime = TimeInstant("9999-12-31_00:00:00"); + EndAlarm = std::make_unique(Alarm(AlarmName, StopTime)); StepClock->attachAlarm(EndAlarm.get()); + break; + default: + ABORT_ERROR("Invalid StopType encountered creating TimeStepper {}", Name); } } //------------------------------------------------------------------------------ // Create a time stepper when all components are known +// Note that if StopType is AfterDuration, the StopTime and EndAlarm +// are computed based on the clock's current time and must be reset if +// the current time is reset (eg by reading a restart) using resetEndAlarm 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 - Tendencies *InTend, ///< [in] ptr to tendencies - AuxiliaryState *InAuxState, ///< [in] ptr to aux state variables - HorzMesh *InMesh, ///< [in] ptr to mesh information - VertCoord *InVCoord, ///< [in] ptr to vertical coordinate - Halo *InMeshHalo ///< [in] ptr to halos + const std::string &InName, // [in] name of time stepper + TimeStepperType InType, // [in] type (time stepping method) + Tendencies *InTend, // [in] ptr to tendencies + AuxiliaryState *InAuxState, // [in] ptr to aux state variables + HorzMesh *InMesh, // [in] ptr to mesh information + VertCoord *InVCoord, // [in] ptr to vertical coordinate + Halo *InMeshHalo, // [in] ptr to halos + const TimeInterval &InTimeStep, // [in] time step + const TimeStepperStartType InStartType, // [in] option to start sim + const TimeInstant &InStartTime, // [in] full simulation start time + const TimeStepperStopType InStopType, // [in] option to stop + std::optional InStopTime, // [in] stop time if option AtTime + std::optional InDuration // [in] duration if opt AfterDur ) { OMEGA_REQUIRE( @@ -152,7 +287,8 @@ TimeStepper *TimeStepper::create( // Start by calling the two-phase create function TimeStepper *NewTimeStepper = - create(InName, InType, InTimeStep, InStartTime, InStopTime); + create(InName, InType, InTimeStep, InStartType, InStartTime, InStopType, + InStopTime, InDuration); NewTimeStepper->PrescribeThicknessMode = DefaultPrescribeThicknessMode; NewTimeStepper->PrescribeVelocityMode = DefaultPrescribeVelocityMode; @@ -167,20 +303,25 @@ TimeStepper *TimeStepper::create( // Create a time stepper when time information is needed before state // and tendencies are defined. It creates an instance and only fills // the time information. Data pointers are attached later. +// Note that if StopType is AfterDuration, the StopTime and EndAlarm +// are computed based on the clock's current time and must be reset if +// the current time is reset (eg by reading a restart) using resetEndAlarm 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime) { + const std::string &InName, // [in] name of time stepper + TimeStepperType InType, // [in] type (time stepping method) + const TimeInterval &InTimeStep, // [in] time step + const TimeStepperStartType InStartType, // [in] option for starting + const TimeInstant &InStartTime, // [in] start time for full sim + const TimeStepperStopType InStopType, // [in] option for stopping + std::optional InStopTime, // [in] stop time option is AtTime + std::optional InDuration // [in] duration opt AfterDuration +) { // Check for duplicates if (AllTimeSteppers.find(InName) != AllTimeSteppers.end()) { - LOG_ERROR("Attempted to create a new TimeStepper with name {} but it " - "already exists", - InName); - return nullptr; + ABORT_ERROR("Attempted to create a new TimeStepper with name {} but it " + "already exists", + InName); } TimeStepper *NewTimeStepper; @@ -188,24 +329,28 @@ TimeStepper *TimeStepper::create( // Call specific constructor with time info switch (InType) { case TimeStepperType::ForwardBackward: - NewTimeStepper = new ForwardBackwardStepper(InName, InTimeStep, - InStartTime, InStopTime); + NewTimeStepper = new ForwardBackwardStepper( + InName, InTimeStep, InStartType, InStartTime, InStopType, InStopTime, + InDuration); break; case TimeStepperType::RungeKutta4: NewTimeStepper = - new RungeKutta4Stepper(InName, InTimeStep, InStartTime, InStopTime); + new RungeKutta4Stepper(InName, InTimeStep, InStartType, InStartTime, + InStopType, InStopTime, InDuration); break; case TimeStepperType::RungeKutta2: NewTimeStepper = - new RungeKutta2Stepper(InName, InTimeStep, InStartTime, InStopTime); + new RungeKutta2Stepper(InName, InTimeStep, InStartType, InStartTime, + InStopType, InStopTime, InDuration); break; // Both share an implementation; the type selects whether the barotropic // mode is split off (SplitFactor 1) or carried in the baroclinic solve // (SplitFactor 0). case TimeStepperType::SplitExplicitRK2: case TimeStepperType::UnsplitRK2: - NewTimeStepper = new SplitExplicitRK2Stepper(InName, InType, InTimeStep, - InStartTime, InStopTime); + NewTimeStepper = new SplitExplicitRK2Stepper( + InName, InType, InTimeStep, InStartType, InStartTime, InStopType, + InStopTime, InDuration); break; case TimeStepperType::Invalid: ABORT_ERROR("Invalid time stepping method"); @@ -269,10 +414,9 @@ 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 read directly from the config file +// 1st stage of two-stage initialization for the default time stepper +// This version with no input parameters is for standalone mode +// with parameters from configuration input void TimeStepper::init1() { Error Err; // error code - default to success @@ -290,49 +434,20 @@ void TimeStepper::init1() { CHECK_ERROR_ABORT(Err, "CalendarType not found in TimeIntegration Config"); Calendar::init(CalendarStr); + // Initialize start option + std::string StartTypeStr; + Err = TimeIntConfig.get("StartType", StartTypeStr); + CHECK_ERROR_ABORT(Err, "StartType not found in TimeIntegration Config"); + TimeStepperStartType InStartType = + getTimeStepperStartTypeFromStr(StartTypeStr); + // Initialize start time std::string StartTimeStr; Err = TimeIntConfig.get("StartTime", StartTimeStr); CHECK_ERROR_ABORT(Err, "StartTime not found in TimeIntConfig"); TimeInstant StartTime(StartTimeStr); - // Either the StopTime or RunDuration will be used to set the StopTime - std::string StopTimeStr; - std::string DurationStr; - Err += TimeIntConfig.get("StopTime", StopTimeStr); - Error Err1 = TimeIntConfig.get("RunDuration", DurationStr); - - // Check for empty or none strings for either choice - bool ValidStopTime = false; - bool ValidDuration = false; - if (Err.isSuccess()) { // stop time was read from config - ValidStopTime = true; - if (StopTimeStr == "" or StopTimeStr == " " or StopTimeStr == "none" or - StopTimeStr == "None") - ValidStopTime = false; - } - if (Err1.isSuccess()) { // duration was read from config - ValidDuration = true; - if (DurationStr == "" or DurationStr == " " or DurationStr == "none" or - DurationStr == "None") - ValidDuration = false; - } - if (!ValidStopTime and !ValidDuration) { - ABORT_ERROR("Either StopTime or RunDuration must be supplied in" - "TimeIntegration Config"); - } - - // Set stop time if a valid value is present. If both are present and - // valid, we use the RunDuration, so compute stop time based on that first - TimeInstant StopTime; - if (ValidDuration) { // valid RunDuration supplied - TimeInterval Duration(DurationStr); - StopTime = StartTime + Duration; - } else { // only valid StopTime supplied - TimeInstant StopTime2(StopTimeStr); - StopTime = StopTime2; - } - + // Extract Prescribe options from config Config StateConfig("State"); Error StateErr = OmegaConfig->get(StateConfig); if (StateErr.isSuccess()) { @@ -350,12 +465,15 @@ void TimeStepper::init1() { } } - TimeInitParams TimeParams{StartTime, StopTime}; - - init1(TimeParams); + init1(InStartType, StartTime); } -void TimeStepper::init1(const TimeInitParams &TimeParams) { +//------------------------------------------------------------------------------ +// 1st stage of two-stage initialization for the default time stepper +// This version with start parameters is for coupled mode but is also +// called by the init1 routine above to complete standalone init +void TimeStepper::init1(const TimeStepperStartType InStartType, + const TimeInstant &StartTime) { // Calendar must be initialized before this is called — the no-arg init1() // does this internally. In coupled mode, the caller (ocnInit) is responsible @@ -384,12 +502,48 @@ void TimeStepper::init1(const TimeInitParams &TimeParams) { CHECK_ERROR_ABORT(Err, "TimeStep not found in TimeIntegration Config"); TimeInterval TimeStep(TimeStepStr); + // Initialize the option for stopping the simulation + std::string StopTypeStr; + Err += TimeIntConfig.get("StopType", StopTypeStr); + CHECK_ERROR_ABORT(Err, "StopType not found in TimeIntegrationConfig"); + TimeStepperStopType InStopType = getTimeStepperStopTypeFromStr(StopTypeStr); + + // Depending on the stop option, extract the StopTime, Duration variables + // from the StopCriterion config string + std::string StopCriterionStr; + std::optional InStopTime = std::nullopt; + std::optional InDuration = std::nullopt; + switch (InStopType) { + case TimeStepperStopType::AtTime: { + // The StopCriterion string contains the StopTime string + Err += TimeIntConfig.get("StopCriterion", StopCriterionStr); + CHECK_ERROR_ABORT(Err, "StopCriterion not found in TimeIntConfig"); + TimeInstant StopTimeTmp(StopCriterionStr); // extract StopTime + InStopTime = StopTimeTmp; + break; + } + case TimeStepperStopType::AfterDuration: { + // The StopCriterion string contains the Duration string + Err += TimeIntConfig.get("StopCriterion", StopCriterionStr); + CHECK_ERROR_ABORT(Err, "StopCriterion not found in TimeIntConfig"); + TimeInterval DurationTmp(StopCriterionStr); // extract Duration + InDuration = DurationTmp; + break; + } + case TimeStepperStopType::OnSignal: + // Neither StopTime or Duration needed + break; + default: + ABORT_ERROR("Unknown StopType {} while initializing TimeStepper", + StopTypeStr); + } + // 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, TimeStep, TimeParams.StartTime, - TimeParams.StopTime); + create("Default", TimeStepperChoice, TimeStep, InStartType, StartTime, + InStopType, InStopTime, InDuration); } //------------------------------------------------------------------------------ @@ -473,23 +627,59 @@ int TimeStepper::getNTimeLevels() const { return NTimeLevels; } // Get time step TimeInterval TimeStepper::getTimeStep() const { return TimeStep; } +// Get start option from instance +TimeStepperStartType TimeStepper::getStartType() const { return StartType; } + // Get start time TimeInstant TimeStepper::getStartTime() const { return StartTime; } +// Get stop option from instance +TimeStepperStopType TimeStepper::getStopType() const { return StopType; } + // Get stop time from instance -std::optional TimeStepper::getStopTime() const { return StopTime; } +TimeInstant TimeStepper::getStopTime() const { return StopTime; } + +// Get duration of run segment +TimeInterval TimeStepper::getDuration() const { return Duration; } // 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(); } //------------------------------------------------------------------------------ // Update functions +//------------------------------------------------------------------------------ +// If the current segment start time is reset (eg by reading a restart), the +// relevant stop times and alarms must be reset. It is assumed that the +// ModelClock CurrentTime has been reset already and uses this updated time +// as the initial time for the duration. This only modifies StopTime and +// EndAlarm if StopType is AfterDuration. +void TimeStepper::resetEndAlarm() { + + // The end alarm is only reset from the StopType AfterDuration based on + // the new current time in the step clock + switch (StopType) { + case TimeStepperStopType::AtTime: + // Stop time is fixed, so no reset + break; + case TimeStepperStopType::AfterDuration: { + // We assume the CurrentTime has been updated and compute a new + // stop time and alarm based on that CurrentTime and Duration + TimeInstant CurrentTime = StepClock->getCurrentTime(); + StopTime = CurrentTime + Duration; + EndAlarm->reset(StopTime); + break; + } + case TimeStepperStopType::OnSignal: + // Simulation will stop on an external signal and EndTime has been + // set far in the future. No reset is needed. + break; + default: + ABORT_ERROR("Invalid StopType encountered creating TimeStepper {}", Name); + } +} //------------------------------------------------------------------------------ // Updates pseudo-thickness using tendency terms diff --git a/components/omega/src/timeStepping/TimeStepper.h b/components/omega/src/timeStepping/TimeStepper.h index 2775221bbafc..6ec668890153 100644 --- a/components/omega/src/timeStepping/TimeStepper.h +++ b/components/omega/src/timeStepping/TimeStepper.h @@ -23,6 +23,30 @@ /// TimeStepper: Forward-Backward /// # Time step to use, in form of DDDD_hh:mm:ss (days, hours, minutes, secs) /// TimeStep: 0000_00:10:00 +/// # Start type. Options are StartUp (for starting a simulation from scratch +/// # with an initial state), Continue (for continuing a simulation from a +/// # restart file, and Branch (for starting a simulation from a restart file +/// # but resetting the clock to the StartTime) +/// StartType: StartUp +/// # Start time of full simulation (YYYY-MM-DD_hh:mm:ss) +/// StartTime: 0001-01-01_00:00:00 +/// # StopType is a string that determines, together with the criterion +/// # below, when to stop the simulation. Supported options are AtTime (to +/// # stop at a specific time instant), AfterDuration (to stop after a +/// # specified time interval - typical of a production simulation when the +/// # simulation is advanced for a time that fits within a queue limit), and +/// # OnSignal (to stop based on an external alarm or signal - eg while +/// # coupling). +/// StopType: AtTime +/// # Stop criterion is a time string that determines, based on the +/// # StopType above, when to stop the simulation. If StopType is +/// # AtTime, this is a string formatted as YYYY-MM-DD_hh:mm:ss that +/// # specifies the time instant at which to stop. If StopType is +/// # AfterDuration, the string is any supported TimeInterval string +/// # (typically DDDD_HH:MM:SS) that represents the duration of the segment +/// # of a simulation. For the OnSignal StopType, the criterion string is +/// # ignored and not required. +/// StopCriterion: 0001-01-01_02:00:00 /// # Options shared by the split time steppers SplitExplicitRK2 and /// # UnsplitRK2. The subgroup is required whenever one of those steppers /// # is selected. BtrTimeStep is required within it for SplitExplicitRK2 @@ -39,16 +63,6 @@ /// NBclCoriolisIteration: 2 /// # Recompute the velocity split from NormalVelocity at every slow step /// ReinitSplitVelocity: false -/// # Start time of full simulation (YYYY-MM-DD_hh:mm:ss) -/// StartTime: 0001-01-01_00:00:00 -/// # Either stop time or run duration must be supplied with Duration -/// # used if both are present. Duration is also preferred for long runs -/// # that involve multiple job submissions with restarts so that the -/// # configuration does not need to be modified. -/// # Stop time for this run (YYYY-MM-DD_hh:mm:ss) -/// StopTime: 0001-01-01_02:00:00 -/// # Duration of this run segment in form dddd_hh:mm:ss -/// RunDuration: none /// \EndConfigInput // //===----------------------------------------------------------------------===// @@ -82,27 +96,48 @@ enum class TimeStepperType { Invalid }; +/// An enum for the time stepper start option +enum class TimeStepperStartType { + StartUp, // Simulation will start from an initial state at sim start time + Continue, // Simulation will start from a restart at the restart time + Branch, // Simulation will start from a restart but reset to start time + Invalid // Invalid or undefined stop option +}; + +/// An enum for the time stepper stop option +enum class TimeStepperStopType { + AtTime, // Simulation will stop at specified time + AfterDuration, // Simulation will stop after a specified time interval + OnSignal, // Simulation will stop based on an external signal/alarm + Invalid // Invalid or undefined stop option +}; + /// An enum describing how a state variable should be prescribed from the /// reference time level enum class PrescribeStateType { None, Init, NonDivergent, Divergent, 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 +// Utility routines /// Translate string for time stepper type into enum TimeStepperType getTimeStepperFromStr( const std::string &InString ///< [in] choice of time stepping method ); +/// Translate the input start option string to enum option +TimeStepperStartType getTimeStepperStartTypeFromStr( + const std::string &InString ///< [in] choice for starting simulation leg +); + +/// Translate the input E3SM start option integer to enum start option +TimeStepperStartType getTimeStepperStartTypeFromE3SM( + const int E3SMOpt ///< [in] E3SM integer start type +); + +/// Translate the input stop option string to enum option +TimeStepperStopType getTimeStepperStopTypeFromStr( + const std::string &InString ///< [in] choice for stopping simulation +); + /// Translate string for prescribe state type into enum PrescribeStateType getPrescribeStateTypeFromStr( const std::string &InString ///< [in] choice of prescribe method @@ -129,37 +164,66 @@ class TimeStepper { bool ///< [in] true if restart input initialized the state ) const {} - /// 1st phase of Initialization for the default time stepper + /// 1st stage of two-stage initialization for the default time stepper + /// This version with no input parameters is for standalone mode + /// with parameters from configuration input static void init1(); - static void init1(const TimeInitParams &TimeParams); + + /// 1st stage of two-stage initialization for the default time stepper + /// This version with start parameters is for coupled mode but is also + /// called by the init1 routine above to complete standalone init + static void + init1(TimeStepperStartType InStartType, ///< [in] start choice for this leg + const TimeInstant &StartTime); ///< [in] start time for full sim /// 2nd phase of Initialization for the default time stepper static void init2(); /// Create a time stepper when all components are known + /// Note that if StopType is AfterDuration, the StopTime and EndAlarm + /// are computed based on the clock's current time and must be reset if + /// the current time is reset (eg by reading a restart) using resetEndAlarm 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 Tendencies *InTend, ///< [in] ptr to tendencies AuxiliaryState *InAuxState, ///< [in] ptr to aux state variables HorzMesh *InMesh, ///< [in] ptr to mesh information VertCoord *InVCoord, ///< [in] ptr to vertical coordinate - Halo *InMeshHalo ///< [in] ptr to halos + Halo *InMeshHalo, ///< [in] ptr to halos + const TimeInterval &InTimeStep, ///< [in] time step + const TimeStepperStartType InStartType, ///< [in] option for start + const TimeInstant &InStartTime, ///< [in] full simulation start time + const TimeStepperStopType InStopType, ///< [in] option to stop + std::optional InStopTime = std::nullopt, + ///< [in] stop time if StopType is AtTime + std::optional InDuration = std::nullopt + ///< [in] duration of current run segment if StopType is AfterDuration ); /// Create a time stepper when time information is needed before state /// and tendencies are defined. It creates an instance and only fills /// the time information. Data pointers are attached later. + /// Note that if StopType is AfterDuration, the StopTime and EndAlarm + /// are computed based on the clock's current time and must be reset if + /// the current time is reset (eg by reading a restart) using resetEndAlarm 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime = std::nullopt); + create(const std::string &InName, ///< [in] name of time stepper + TimeStepperType InType, ///< [in] type (method) + const TimeInterval &InTimeStep, ///< [in] time step + const TimeStepperStartType InStartType, ///< [in] option for starting + const TimeInstant &InStartTime, ///< [in] full sim start time + const TimeStepperStopType InStopType, ///< [in] option for stopping + std::optional InStopTime = std::nullopt, + ///< [in] stop time if StopType is AtTime + std::optional InDuration = std::nullopt + ///< [in] duration of current run segment if StopType is AfterDuration + ); + + /// If the current segment start time is reset (eg by reading a restart), the + /// relevant stop times and alarms must be reset. This only modifies + /// StopTime and EndAlarm if StopType is AfterDuration + void resetEndAlarm(); /// For 2-step creation, this attaches all the data pointers to an instance /// once the data and tendencies have been created. @@ -199,14 +263,20 @@ class TimeStepper { /// Get time step TimeInterval getTimeStep() const; + /// Get start option + TimeStepperStartType getStartType() const; + /// Get start time TimeInstant getStartTime() const; + /// Get stop option + TimeStepperStopType getStopType() const; + /// Get stop time - std::optional getStopTime() const; + TimeInstant getStopTime() const; - /// Check if time stepper has an end alarm (i.e. if stop time is defined) - bool hasEndAlarm() const; + /// Get duration of run segment + TimeInterval getDuration() const; /// Get a pointer to the clock Clock *getClock(); @@ -334,17 +404,22 @@ class TimeStepper { /// Time step TimeInterval TimeStep; - /// Prescribe state configuration - PrescribeStateType PrescribeThicknessMode; - PrescribeStateType PrescribeVelocityMode; + /// Start option + TimeStepperStartType StartType; /// Start time TimeInstant StartTime; - /// Stop time - std::optional StopTime; // std::nullopt in coupled mode + /// Stop option + TimeStepperStopType StopType; + + /// Stop time for this run segment + TimeInstant StopTime; - /// Alarm that rings at StopTime + /// Duration of this run segment + TimeInterval Duration; + + /// Alarm that rings at StopTime if needed std::unique_ptr EndAlarm; /// Clock for this time stepper @@ -354,6 +429,10 @@ class TimeStepper { /// Number of doStep calls made on this instance since creation mutable I8 StepCount = 0; + /// Prescribe state configuration + PrescribeStateType PrescribeThicknessMode; + PrescribeStateType PrescribeVelocityMode; + // Pointers to objects needed by every time stepper Tendencies *Tend; /// Ptr to tendency terms AuxiliaryState *AuxState; /// Ptr to auxiliary state data @@ -364,16 +443,22 @@ class TimeStepper { /// Function for any method-specific modifications for the default stepper virtual void finalizeInit() {} - /// 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 most of the time + /// related data. The stop time is added later if needed and the attachData + /// function is used to add the data pointers 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 - ///< [in] stop time for time stepping, missing in coupled mode - std::optional InStopTime); + const TimeStepperStartType InStartType, ///< [in] option for starting + const TimeInstant &InStartTime, ///< [in] start time for full sim + const TimeStepperStopType InStopType, ///< [in] option for stopping + std::optional InStopTime = std::nullopt, + ///< [in] stop time if StopType is AtTime + std::optional InDuration = std::nullopt + ///< [in] duration of current run segment if StopType is AfterDuration + ); // Disable copy constructor TimeStepper(const TimeStepper &) = delete; diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index d1617f19fe85..0c8a8ca77500 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -154,6 +154,7 @@ int initTimeStepperTest(const std::string &mesh) { // Default init initLogging(DefEnv); + LOG_INFO("------ Time Stepper Unit Test ------"); // Open config file Config("Omega"); @@ -355,14 +356,17 @@ int testTimeStepper(const std::string &Name, TimeStepperType Type, // Set time information const TimeInstant TimeStart(0, 0, 0, 0, 0, 0); - const Real TimeEnd = 1; + TimeStepperStartType StartType = TimeStepperStartType::StartUp; + TimeStepperStopType StopType = TimeStepperStopType::AtTime; + const Real TimeEnd = 1; TimeInstant TimeEndTI(0, 0, 0, 0, 0, 1); TimeInterval TimeStepTI(BaseTimeStepSeconds, TimeUnits::Seconds); auto *TestTimeStepper = TimeStepper::create( - "TestTimeStepper", Type, TimeStepTI, TimeStart, TimeEndTI, - TestTendencies, TestAuxState, DefMesh, DefVCoord, DefHalo); + "TestTimeStepper", Type, TestTendencies, TestAuxState, DefMesh, + DefVCoord, DefHalo, TimeStepTI, StartType, TimeStart, StopType, + TimeEndTI); if (!TestTimeStepper) { Err++; @@ -428,8 +432,10 @@ int testOptionalStopTime(const std::string &Name, TimeStepperType Type) { TimeInterval TimeStep(0.2, TimeUnits::Seconds); // 2-phase create without StopTime — used by the coupled driver - auto *Stepper = - TimeStepper::create("CoupledTestStepper", Type, TimeStep, TimeStart); + TimeStepperStartType StartType = TimeStepperStartType::StartUp; + TimeStepperStopType StopType = TimeStepperStopType::OnSignal; + auto *Stepper = TimeStepper::create("CoupledTestStepper", Type, TimeStep, + StartType, TimeStart, StopType); if (!Stepper) { Err++; @@ -438,20 +444,6 @@ int testOptionalStopTime(const std::string &Name, TimeStepperType Type) { 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); - } - if (Stepper->getStepCount() != 0) { Err++; LOG_ERROR("TimeStepperTest: {}: getStepCount() should be 0 before any " @@ -535,7 +527,7 @@ int timeStepperTest(const std::string &MeshFile = "OmegaMesh.nc") { Err += testOptionalStopTime("UnsplitRK2", TimeStepperType::UnsplitRK2); if (Err == 0) { - LOG_INFO("TimeStepperTest: Successful completion"); + LOG_INFO("------ Time Stepper Unit Test Successful ------"); } finalizeTimeStepperTest(); @@ -552,8 +544,6 @@ int main(int argc, char *argv[]) { Pacer::initialize(MPI_COMM_WORLD); Pacer::setPrefix("Omega:"); - LOG_INFO("----- Time Stepper Unit Test -----"); - RetVal += timeStepperTest("OmegaSphereMesh.nc"); Pacer::finalize(); From 9776017f7062f0ed18531395791be230a3f5d3a6 Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Fri, 18 Sep 2026 12:27:02 -0500 Subject: [PATCH 2/3] bug fix for setting StopTime --- components/omega/src/timeStepping/TimeStepper.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/omega/src/timeStepping/TimeStepper.cpp b/components/omega/src/timeStepping/TimeStepper.cpp index 1875f155be54..ff9d4402f87d 100644 --- a/components/omega/src/timeStepping/TimeStepper.cpp +++ b/components/omega/src/timeStepping/TimeStepper.cpp @@ -234,14 +234,17 @@ TimeStepper::TimeStepper( Name); } break; - case TimeStepperStopType::OnSignal: + case TimeStepperStopType::OnSignal: { // Simulation will stop on an external signal so no StopTime - // or Duration are needed. Set to a large value or long future time. + // or Duration are needed. + std::string StopTimeStr = "9999-12-31_00:00:00"; + // Set Duration and StopTime with long future values for a dummy + // EndAlarm Duration = TimeInterval(1.e16, TimeUnits::Seconds); - StopTime = TimeInstant("9999-12-31_00:00:00"); + StopTime = TimeInstant(StopTimeStr); EndAlarm = std::make_unique(Alarm(AlarmName, StopTime)); StepClock->attachAlarm(EndAlarm.get()); - break; + } break; default: ABORT_ERROR("Invalid StopType encountered creating TimeStepper {}", Name); } From 5d5295938325e7ce359d861b51d4749fbd7ec8bc Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Wed, 30 Sep 2026 12:49:53 -0500 Subject: [PATCH 3/3] change of start/stop options to coupled for coupled sims - also changes calculation of stop time for coupled sims --- .../omega_buildnml/data/config_overrides.yaml | 3 +- .../cime_config/omega_buildnml/validate.py | 3 ++ components/omega/doc/devGuide/TimeStepping.md | 2 +- .../omega/doc/userGuide/TimeStepping.md | 19 ++++++++--- components/omega/src/ocn/OceanInit.cpp | 34 ++++++++++++------- .../omega/src/timeStepping/TimeStepper.cpp | 25 ++++++++------ .../omega/src/timeStepping/TimeStepper.h | 16 +++++---- .../test/timeStepping/TimeStepperTest.cpp | 2 +- 8 files changed, 68 insertions(+), 36 deletions(-) diff --git a/components/omega/cime_config/omega_buildnml/data/config_overrides.yaml b/components/omega/cime_config/omega_buildnml/data/config_overrides.yaml index fc8b348eaac4..cff973977294 100644 --- a/components/omega/cime_config/omega_buildnml/data/config_overrides.yaml +++ b/components/omega/cime_config/omega_buildnml/data/config_overrides.yaml @@ -1,8 +1,9 @@ coupled: TimeIntegration: TimeStepper: SplitExplicitRK2 + StartType: Coupled StartTime: 9999-12-31_00:00:00 - StopType: OnSignal + StopType: Coupled StopCriterion: 9999-12-31_00:00:00 Tendencies: diff --git a/components/omega/cime_config/omega_buildnml/validate.py b/components/omega/cime_config/omega_buildnml/validate.py index 67b2550647e2..33e8f49d14af 100644 --- a/components/omega/cime_config/omega_buildnml/validate.py +++ b/components/omega/cime_config/omega_buildnml/validate.py @@ -41,6 +41,9 @@ BLOCKED_OPTIONS = frozenset( {f"IOStreams.{stream}" for stream in BLOCKED_STREAMS} | { + # start and stop types must be coupled + "TimeIntegration.StartType", + "TimeIntegration.StopType", # start time is provided by the coupler at runtime "TimeIntegration.StartTime", # stop criterion is ignored for coupled sims diff --git a/components/omega/doc/devGuide/TimeStepping.md b/components/omega/doc/devGuide/TimeStepping.md index b3412ec87b5b..15d6cddf271f 100644 --- a/components/omega/doc/devGuide/TimeStepping.md +++ b/components/omega/doc/devGuide/TimeStepping.md @@ -130,7 +130,7 @@ Clock *ModelClock = Stepper->getClock(); Alarm *EndAlarm = Stepper->getEndAlarm(); ``` Note that StopTime, Duration and EndAlarm are set to appropriate values -no matter what the StopType is (though for the OnSignal option, they are +no matter what the StopType is (though for the Coupled option, they are set far into the future). #### Removal of time steppers diff --git a/components/omega/doc/userGuide/TimeStepping.md b/components/omega/doc/userGuide/TimeStepping.md index fff05ce5a18a..faf68e2d5aa3 100644 --- a/components/omega/doc/userGuide/TimeStepping.md +++ b/components/omega/doc/userGuide/TimeStepping.md @@ -56,11 +56,22 @@ in any of the following forms: Days, hours and minutes are optional but must be in order if included. Fractional seconds are optional. -The ``StartOption`` can be one of three choices. The ``StartUp`` option is for +The ``StartOption`` can be one of five choices. The ``StartUp`` option is for starting a solution from scratch from an initial state file. The ``Continue`` option is for continuing a simulation from a restart file. The ``Branch`` option will branch from an existing simulation by reading from the restart -file, but it will reset the clock to the ``StartTime``. +file and is identical to ``Continue`` except that some other simulation +metadata (eg case name or simulation name) can be changed. The ``Hybrid`` +option will continue a branch from a restart file but will reset the start +time and should only be used for the first step of a hybrid run, similar to the +``StartUp`` option. It is equivalent to using the ``StartUp`` option and +specifying the restart file as an init file. It is only provided in the event +a future mult-level time stepping scheme is used in which starting from a +restart with multiple time levels is an advantage over a fresh start. Finally, +a ``Coupled`` option specifies that the ``StartType`` is provided by the +coupler in a coupled configuration. The actual ``StartType`` used internally +is created by the E3SM CIME tools from a combination of the ``RUN_TYPE`` and +``CONTINUE`` options. The ``StartTime`` refers to the starting time for the full simulation (not the current leg of an ongoing simulation). It is in the @@ -73,13 +84,13 @@ will be stopped. There are three options. The ``AtTime`` option will stop the simulation at a specific time and the ``StopCriterion`` holds that specific time as described below. The ``AfterDuration`` option runs the simulation for a fixed time interval and the ``StopCriterion`` is used to define that interval. A final -option called ``OnSignal`` is primarily for coupled simulations where the +option called ``Coupled`` is primarily for coupled simulations where the simulation will stop after receiving a signal from the coupler. For the ``AtTime`` stop type, the ``StopCriterion`` must be a time instant in the format ``yyyy-mm-dd_hh:mm:ss``. If the ``StopType`` is ``AfterDuration``, the ``StopCriterion`` is a time interval in the format described above for the -time step (but typically ``dddd_hh:mm:ss``). For the ``OnSignal`` option the +time step (but typically ``dddd_hh:mm:ss``). For the ``Coupled`` option the ``StopCriterion`` is ignored. ```{toctree} diff --git a/components/omega/src/ocn/OceanInit.cpp b/components/omega/src/ocn/OceanInit.cpp index eeed99dda568..9adaac861692 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -156,14 +156,15 @@ int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator switch (StartType) { // Starting from scratch using an initial state - case (TimeStepperStartType::StartUp): + case TimeStepperStartType::StartUp: Err1 = IOStream::read("InitialState", ModelClock, ReqMeta); CHECK_ERROR_ABORT(Err1, "Error reading InitialState file"); break; // Continue simulation from a restart file and reset current time // to the restart time read from restart metadata - case (TimeStepperStartType::Continue): { + case TimeStepperStartType::Branch: + case TimeStepperStartType::Continue: { ReqMeta["SimulationTime"] = SimTimeStr; // request current sim time Err1 = IOStream::read("RestartRead", ModelClock, ReqMeta); ReadRestart = true; @@ -180,12 +181,18 @@ int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator // Branch a simulation from a previous restart file but keep the // simulation StartTime rather than the restart time - case (TimeStepperStartType::Branch): + case TimeStepperStartType::Hybrid: Err1 = IOStream::read("RestartRead", ModelClock, ReqMeta); CHECK_ERROR_ABORT(Err1, "Error reading restart file for branch run"); ReadRestart = true; break; + // If the type is coupler, we have reached here in error. For coupled + // simulations the input coupled start type is translated into one of the + // above, so we only reached here if it's coupled but in standalone mode + case TimeStepperStartType::Coupled: + ABORT_ERROR("Attempted use of Coupled StartType for Standalone run"); + default: ABORT_ERROR("Unknown StartType in OcnInit"); @@ -218,6 +225,8 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator OMEGA_REQUIRE(DefEnv, "Null default MachEnv pointer in ocnInit1"); // Read config file into Config object + // Note that most info comes from the coupler in this case, so only timing + // configuration is read from the actual config file Config("Omega"); Config::readAll(ConfigFile); Config *OmegaConfig = Config::getOmegaConfig(); @@ -252,7 +261,7 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator switch (StartType) { // Starting from scratch using an initial state - case (TimeStepperStartType::StartUp): + case TimeStepperStartType::StartUp: Err1 = IOStream::read("InitialState", ModelClock, ReqMeta); CHECK_ERROR_ABORT(Err1, "Error reading InitialState file"); CoupledReadRestart = false; @@ -260,7 +269,8 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator // Continue simulation from a restart file and reset current time // to the restart time read from restart metadata - case (TimeStepperStartType::Continue): { + case TimeStepperStartType::Branch: + case TimeStepperStartType::Continue: { ReqMeta["SimulationTime"] = SimTimeStr; // request current sim time Err1 = IOStream::read("RestartRead", ModelClock, ReqMeta); CoupledReadRestart = true; @@ -275,13 +285,11 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator DefStepper->resetEndAlarm(); } break; - // Branch a simulation from a previous restart file but keep the - // simulation StartTime rather than the restart time - case (TimeStepperStartType::Branch): - Err1 = IOStream::read("RestartRead", ModelClock, ReqMeta); - CHECK_ERROR_ABORT(Err1, "Error reading restart file for branch run"); - CoupledReadRestart = true; - break; + // For coupled simulations, these two options are translated elsewhere + // into one of the above options, so if we have reached here, it is an error + case TimeStepperStartType::Hybrid: + case TimeStepperStartType::Coupled: + ABORT_ERROR("Incorrect translation of StartType by coupled OcnInit"); default: ABORT_ERROR("Unknown StartType in OcnInit"); @@ -384,6 +392,7 @@ static int initOmegaModulesImpl() { } // end initOmegaModulesImpl //------------------------------------------------------------------------------ +// Initializes time stepper and other modules using Config inputs (standalone) 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 @@ -394,6 +403,7 @@ int initOmegaModules(MPI_Comm Comm) { } //------------------------------------------------------------------------------ +// Initializes time stepper and other modules using coupler provided inputs int initOmegaModules(MPI_Comm Comm, TimeStepperStartType StartType, const TimeInstant &StartTime, const CouplingInitParams &CParams, diff --git a/components/omega/src/timeStepping/TimeStepper.cpp b/components/omega/src/timeStepping/TimeStepper.cpp index ff9d4402f87d..1c9e60de5dc1 100644 --- a/components/omega/src/timeStepping/TimeStepper.cpp +++ b/components/omega/src/timeStepping/TimeStepper.cpp @@ -72,6 +72,10 @@ getTimeStepperStartTypeFromStr(const std::string &InString) { StartChoice = TimeStepperStartType::Continue; } else if (StartStr == "branch") { StartChoice = TimeStepperStartType::Branch; + } else if (StartStr == "hybrid") { + StartChoice = TimeStepperStartType::Hybrid; + } else if (StartStr == "coupled") { + StartChoice = TimeStepperStartType::Coupled; } else { StartChoice = TimeStepperStartType::Invalid; ABORT_ERROR("Invalid StartType {}", InString); @@ -85,7 +89,8 @@ getTimeStepperStartTypeFromStr(const std::string &InString) { TimeStepperStartType getTimeStepperStartTypeFromE3SM(const int E3SMOption) { // Translate the integer start option from the E3SM coupler to the - // internal enum + // internal enum. For coupled simulations, the run type and continue + // options have been translated by CIME into these three startup choices. TimeStepperStartType StartChoice; switch (E3SMOption) { @@ -119,8 +124,8 @@ TimeStepperStopType getTimeStepperStopTypeFromStr(const std::string &InString) { StopChoice = TimeStepperStopType::AtTime; } else if (StopStr == "afterduration") { StopChoice = TimeStepperStopType::AfterDuration; - } else if (StopStr == "onsignal") { - StopChoice = TimeStepperStopType::OnSignal; + } else if (StopStr == "coupled") { + StopChoice = TimeStepperStopType::Coupled; } else { StopChoice = TimeStepperStopType::Invalid; ABORT_ERROR("Invalid StopType {}", InString); @@ -234,14 +239,14 @@ TimeStepper::TimeStepper( Name); } break; - case TimeStepperStopType::OnSignal: { + case TimeStepperStopType::Coupled: { // Simulation will stop on an external signal so no StopTime // or Duration are needed. - std::string StopTimeStr = "9999-12-31_00:00:00"; - // Set Duration and StopTime with long future values for a dummy - // EndAlarm + TimeInstant CurrentTime = StepClock->getCurrentTime(); + // Set Duration and StopTime with long future + // values for a dummy EndAlarm Duration = TimeInterval(1.e16, TimeUnits::Seconds); - StopTime = TimeInstant(StopTimeStr); + StopTime = CurrentTime + Duration; EndAlarm = std::make_unique(Alarm(AlarmName, StopTime)); StepClock->attachAlarm(EndAlarm.get()); } break; @@ -533,7 +538,7 @@ void TimeStepper::init1(const TimeStepperStartType InStartType, InDuration = DurationTmp; break; } - case TimeStepperStopType::OnSignal: + case TimeStepperStopType::Coupled: // Neither StopTime or Duration needed break; default: @@ -675,7 +680,7 @@ void TimeStepper::resetEndAlarm() { EndAlarm->reset(StopTime); break; } - case TimeStepperStopType::OnSignal: + case TimeStepperStopType::Coupled: // Simulation will stop on an external signal and EndTime has been // set far in the future. No reset is needed. break; diff --git a/components/omega/src/timeStepping/TimeStepper.h b/components/omega/src/timeStepping/TimeStepper.h index 6ec668890153..f8346485182e 100644 --- a/components/omega/src/timeStepping/TimeStepper.h +++ b/components/omega/src/timeStepping/TimeStepper.h @@ -25,8 +25,9 @@ /// TimeStep: 0000_00:10:00 /// # Start type. Options are StartUp (for starting a simulation from scratch /// # with an initial state), Continue (for continuing a simulation from a -/// # restart file, and Branch (for starting a simulation from a restart file -/// # but resetting the clock to the StartTime) +/// # restart file, Branch (for starting a simulation from a restart file +/// # but possibly changing case name) or Hybrid (for starting a simulation +/// # from restart but changing the simulation start time to a new time) /// StartType: StartUp /// # Start time of full simulation (YYYY-MM-DD_hh:mm:ss) /// StartTime: 0001-01-01_00:00:00 @@ -35,8 +36,7 @@ /// # stop at a specific time instant), AfterDuration (to stop after a /// # specified time interval - typical of a production simulation when the /// # simulation is advanced for a time that fits within a queue limit), and -/// # OnSignal (to stop based on an external alarm or signal - eg while -/// # coupling). +/// # Coupled (to stop based on a signal from the coupled model driver). /// StopType: AtTime /// # Stop criterion is a time string that determines, based on the /// # StopType above, when to stop the simulation. If StopType is @@ -44,7 +44,7 @@ /// # specifies the time instant at which to stop. If StopType is /// # AfterDuration, the string is any supported TimeInterval string /// # (typically DDDD_HH:MM:SS) that represents the duration of the segment -/// # of a simulation. For the OnSignal StopType, the criterion string is +/// # of a simulation. For the Coupled StopType, the criterion string is /// # ignored and not required. /// StopCriterion: 0001-01-01_02:00:00 /// # Options shared by the split time steppers SplitExplicitRK2 and @@ -100,7 +100,9 @@ enum class TimeStepperType { enum class TimeStepperStartType { StartUp, // Simulation will start from an initial state at sim start time Continue, // Simulation will start from a restart at the restart time - Branch, // Simulation will start from a restart but reset to start time + Branch, // Identical to continue, though case or sim name can change + Hybrid, // Simulation will start from a restart but reset to start time + Coupled, // Omega will receive start type from coupler based on E3SM opts Invalid // Invalid or undefined stop option }; @@ -108,7 +110,7 @@ enum class TimeStepperStartType { enum class TimeStepperStopType { AtTime, // Simulation will stop at specified time AfterDuration, // Simulation will stop after a specified time interval - OnSignal, // Simulation will stop based on an external signal/alarm + Coupled, // Simulation will stop based on an a signal from coupler Invalid // Invalid or undefined stop option }; diff --git a/components/omega/test/timeStepping/TimeStepperTest.cpp b/components/omega/test/timeStepping/TimeStepperTest.cpp index 0c8a8ca77500..264029af4106 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -433,7 +433,7 @@ int testOptionalStopTime(const std::string &Name, TimeStepperType Type) { // 2-phase create without StopTime — used by the coupled driver TimeStepperStartType StartType = TimeStepperStartType::StartUp; - TimeStepperStopType StopType = TimeStepperStopType::OnSignal; + TimeStepperStopType StopType = TimeStepperStopType::Coupled; auto *Stepper = TimeStepper::create("CoupledTestStepper", Type, TimeStep, StartType, TimeStart, StopType);