diff --git a/components/omega/configs/Default.yml b/components/omega/configs/Default.yml index 9f589f9466cf..31f9afb2099a 100644 --- a/components/omega/configs/Default.yml +++ b/components/omega/configs/Default.yml @@ -8,9 +8,10 @@ Omega: CalendarType: No Leap TimeStepper: Forward-Backward TimeStep: 0000_00:10:00 + StartType: StartUp StartTime: 0001-01-01_00:00:00 - StopTime: 0001-01-01_02:00:00 - RunDuration: none + StopType: AtTime + StopCriterion: 0001-01-01_02:00:00 Decomp: HaloWidth: 3 DecompMethod: MetisKWay @@ -119,9 +120,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 @@ -147,9 +147,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 is only read of the StartType requires a restart + # so the options here reflect only that case and are ignored otherwise RestartRead: UsePointerFile: true PointerFilename: ocn.pointer @@ -157,9 +156,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 df7fc370abea..8c13c5a9bb66 100644 --- a/components/omega/doc/userGuide/TimeStepping.md +++ b/components/omega/doc/userGuide/TimeStepping.md @@ -9,9 +9,10 @@ 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 - StopTime: 0001-01-01_02:00:00 - RunDuration: none + StopType: AtTime + StopCriterion: 0001-01-01_02:00:00 ``` This configuration refers to the default time stepping used for the model dynamics (momentum and continuity equations). Additional time steppers can @@ -43,19 +44,29 @@ 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. -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 simulation where the simulation will stop +and 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. diff --git a/components/omega/src/drivers/coupled/omega_cxx2f_interface.cpp b/components/omega/src/drivers/coupled/omega_cxx2f_interface.cpp index 2347521336f6..26a2ed9a4558 100644 --- a/components/omega/src/drivers/coupled/omega_cxx2f_interface.cpp +++ b/components/omega/src/drivers/coupled/omega_cxx2f_interface.cpp @@ -48,7 +48,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 @@ -97,14 +97,14 @@ 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}; OMEGA::ocnInit1(Comm, OcnID, YamlConfigFile, OcnLogFile, StartTypeEnum, - TimeParams, CouplingParams); + StartTime, CouplingParams); 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 5410585f73ab..393de685aa78 100644 --- a/components/omega/src/ocn/OceanDriver.h +++ b/components/omega/src/ocn/OceanDriver.h @@ -20,12 +20,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(); @@ -40,8 +34,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 ); @@ -64,8 +58,12 @@ int ocnFinalize(const TimeInstant &CurrTime); int initOmegaModules(MPI_Comm Comm); /// Initialize Omega modules with coupler-provided time parameters -int initOmegaModules(MPI_Comm Comm, const TimeInitParams &TParams, - const CouplingInitParams &CParams); +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 +); /// 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 472725f07dc4..ba4a3fe45e7f 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 @@ -95,6 +84,8 @@ static void readTimingConfig(Config *OmegaConfig) { CHECK_ERROR_ABORT(Err, "Timing: PrintAllRanks not found in TimingConfig"); } +//------------------------------------------------------------------------------ +// Ocean initialization - standalone case int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator ) { @@ -127,40 +118,55 @@ 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"]); - if (SimTimeStr != " ") { + Metadata ReqMeta; // empty requested metadata from file + TimeStepperStartType StartType = DefStepper->getStartType(); + + // 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); + + // 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"); + 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(); @@ -168,13 +174,15 @@ int ocnInit(MPI_Comm Comm ///< [in] ocean MPI communicator return Err; } // end ocnInit -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 CouplingInitParams &CouplingParams ///< [in] coupler info +//------------------------------------------------------------------------------ +// 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 TimeStepperStartType StartType, // [in] start option + const TimeInstant &StartTime, // [in] simulation start time + const CouplingInitParams &CouplingParams // [in] coupler info ) { I4 Err = 0; // return error code @@ -191,7 +199,7 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator readTimingConfig(OmegaConfig); // initialize remaining Omega modules - Err = initOmegaModules(Comm, TimeParams, CouplingParams); + Err = initOmegaModules(Comm, StartType, StartTime, CouplingParams); if (Err != 0) ABORT_ERROR("ocnInit: Error initializing Omega modules"); @@ -205,37 +213,53 @@ int ocnInit1(MPI_Comm Comm, ///< [in] ocean MPI communicator ABORT_ERROR("ocnInit: Error validating IO Streams"); } - Metadata ReqMeta; - 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"); + 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); + + // 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"); + 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(); @@ -245,6 +269,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 @@ -259,6 +284,7 @@ int ocnInit2(const Real *CplToOcnData, Real *OcnToCplData) { return initUpdateHaloAndHostArrays(); } // end ocnInit2 +//------------------------------------------------------------------------------ // Call init routines for remaining Omega modules // Internal helper — all module init after TimeStepper::init1 is called. // Called by both initOmegaModules overloads. @@ -302,6 +328,7 @@ static int initOmegaModulesImpl(MPI_Comm Comm) { "TracersToRestore is empty"); } + // Add fields to time stepper TimeStepper::init2(); Err = OceanState::init(); @@ -315,6 +342,7 @@ static int initOmegaModulesImpl(MPI_Comm Comm) { } // 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 @@ -323,18 +351,21 @@ int initOmegaModules(MPI_Comm Comm) { return initOmegaModulesImpl(Comm); } -int initOmegaModules(MPI_Comm Comm, const TimeInitParams &TParams, +//------------------------------------------------------------------------------ +int initOmegaModules(MPI_Comm Comm, TimeStepperStartType StartType, + const TimeInstant &StartTime, const CouplingInitParams &CParams) { 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); Err = initOmegaModulesImpl(Comm); SfcCoupling::init(CParams); 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 65653bfbb3ec..8c2d8e9b1707 100644 --- a/components/omega/src/ocn/OceanRun.cpp +++ b/components/omega/src/ocn/OceanRun.cpp @@ -28,9 +28,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 e1bb57802b70..714b77fac918 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/TimeStepper.cpp b/components/omega/src/timeStepping/TimeStepper.cpp index ee3ee2dd6650..20f0fa68e495 100644 --- a/components/omega/src/timeStepping/TimeStepper.cpp +++ b/components/omega/src/timeStepping/TimeStepper.cpp @@ -27,6 +27,7 @@ PrescribeStateType TimeStepper::DefaultPrescribeVelocityMode = //------------------------------------------------------------------------------ // utility functions +//------------------------------------------------------------------------------ // convert string into TimeStepperType enum TimeStepperType getTimeStepperFromStr(const std::string &InString) { @@ -48,6 +49,79 @@ 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; + case 1: + StartChoice = TimeStepperStartType::Continue; + case 2: + StartChoice = TimeStepperStartType::Branch; + 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) { @@ -85,46 +159,103 @@ 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)); - StepClock->attachAlarm(EndAlarm.get()); + // 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 nearly infinite value + Duration = TimeInterval(1.e16, TimeUnits::Seconds); + StopTime = StartTime + Duration; + EndAlarm = std::make_unique(Alarm(AlarmName, StopTime)); + 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( @@ -146,7 +277,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; @@ -161,20 +293,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; @@ -182,16 +319,19 @@ 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; case TimeStepperType::Invalid: ABORT_ERROR("Invalid time stepping method"); @@ -276,49 +416,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()) { @@ -336,12 +447,12 @@ void TimeStepper::init1() { } } - TimeInitParams TimeParams{StartTime, StopTime}; - - init1(TimeParams); + init1(InStartType, StartTime); } -void TimeStepper::init1(const TimeInitParams &TimeParams) { +//------------------------------------------------------------------------------ +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 @@ -370,12 +481,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); } //------------------------------------------------------------------------------ @@ -413,10 +560,6 @@ void TimeStepper::changeTimeStep(const TimeInterval &TimeStepIn) { StepClock->changeTimeStep(TimeStepIn); } -//------------------------------------------------------------------------------ -// Get number of doStep calls made on this instance -I8 TimeStepper::getStepCount() const { return StepCount; } - //------------------------------------------------------------------------------ // Retrieval functions @@ -449,29 +592,65 @@ std::string TimeStepper::getName() const { return Name; } // Get time stepper type TimeStepperType TimeStepper::getType() const { return Type; } +// Get number of doStep calls made on this instance +I8 TimeStepper::getStepCount() const { return StepCount; } + // Get number of time level 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 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 so no StopTime + // or Duration or EndAlarm are 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 7e95019da641..6aa10b0c420d 100644 --- a/components/omega/src/timeStepping/TimeStepper.h +++ b/components/omega/src/timeStepping/TimeStepper.h @@ -23,16 +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 -/// # 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 +/// # 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 /// \EndConfigInput // //===----------------------------------------------------------------------===// @@ -64,27 +78,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 @@ -106,36 +141,63 @@ class TimeStepper { ) const = 0; /// 1st phase of Initialization for the default time stepper + /// This version with no input parameters is typically for standalone mode static void init1(); - static void init1(const TimeInitParams &TimeParams); + + /// 1st phase of Initialization for the default time stepper + /// This version with start parameters is typically for coupled mode + 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. @@ -175,14 +237,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(); @@ -307,17 +375,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 @@ -327,6 +400,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 @@ -337,16 +414,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 d7ff973893f9..2e8b659c85b6 100644 --- a/components/omega/test/timeStepping/TimeStepperTest.cpp +++ b/components/omega/test/timeStepping/TimeStepperTest.cpp @@ -145,6 +145,7 @@ int initTimeStepperTest(const std::string &mesh) { // Default init initLogging(DefEnv); + LOG_INFO("------ Time Stepper Unit Test ------"); // Open config file Config("Omega"); @@ -330,15 +331,18 @@ 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); const Real BaseTimeStepSeconds = 0.2; 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++; @@ -401,8 +405,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++; @@ -411,20 +417,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 " @@ -495,7 +487,7 @@ int timeStepperTest(const std::string &MeshFile = "OmegaMesh.nc") { TimeStepperType::ForwardBackward); if (Err == 0) { - LOG_INFO("TimeStepperTest: Successful completion"); + LOG_INFO("------ Time Stepper Unit Test Successful ------"); } finalizeTimeStepperTest(); @@ -512,8 +504,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();