From 6230c93e18d41ef8aa5ce0c1390701502f79ed68 Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Fri, 22 Nov 2024 12:50:51 -0600 Subject: [PATCH 1/5] Adds a CF-compliant unlimited time dimension - adds support for an unlimited time dimension - adds the CF-compliant time field with appropriate units - adds additional Field properties to support time-dependent fields - extends the support for non-distributed field IO into IOStream - updates documentation This still only supports one time slice per file. A subsequent modification will add support for multiple slices in a file. --- components/omega/doc/devGuide/Field.md | 31 ++- components/omega/src/base/IO.h | 3 + components/omega/src/infra/Field.cpp | 44 +++- components/omega/src/infra/Field.h | 27 ++- components/omega/src/infra/IOStream.cpp | 207 ++++++++++++++----- components/omega/src/infra/IOStream.h | 10 +- components/omega/src/infra/TimeMgr.h | 5 + components/omega/src/ocn/OceanInit.cpp | 2 +- components/omega/test/infra/FieldTest.cpp | 8 +- components/omega/test/infra/IOStreamTest.cpp | 13 +- components/omega/test/ocn/StateTest.cpp | 2 +- 11 files changed, 288 insertions(+), 64 deletions(-) diff --git a/components/omega/doc/devGuide/Field.md b/components/omega/doc/devGuide/Field.md index 85609f5fe1f0..38928a0dd8a0 100644 --- a/components/omega/doc/devGuide/Field.md +++ b/components/omega/doc/devGuide/Field.md @@ -39,7 +39,8 @@ Fields are created with standard metadata using ValidMax, ///< [in] max valid field value field data) FillValue, ///< [in] scalar used for undefined entries NumDims, ///< [in] number of dimensions (int) - Dimensions ///< dim names for each dim (vector of strings) + Dimensions, ///< [in] dim names (vector of strings) + InTimeDependent ///< [in] (opt, default true) if time varying ); ``` This interface enforces a list of required metadata. If a CF standard name does @@ -49,8 +50,15 @@ for some intermediate calculations or unique analyses. If there is no restriction on valid range, an appropriately large range should be provided for the data type. Similarly, if a FillValue is not being used, a very unique number should be supplied to prevent accidentally treating valid data as a -FillValue. Actual field data stored in an array is attached in a separate -call as described below. Fields without a data array can be created with: +FillValue. The optional TimeDependent argument can be omitted and is assumed +to be true by default. Fields with this attribute will be output with the +unlimited time dimension added. Time should not be added explicitly in the +dimension list since it will be added during I/O. Fields that do not change +with time should include this argument with the value false so that the time +dimension is not added. Actual field data stored in an array is attached in a +separate call as described below. Scalar fields can be added by setting the +NumDims to zero (the DimNames is then ignored). Scalar data is attached using +a 1D array with size 1. Fields without a data array can be created with: ```c++ std::shared_ptr MyField = Field::create(FieldName ///< [in] Name of field @@ -108,9 +116,10 @@ captured correctly. If the location of the data changes (eg the time level changes and the pointer points to a different time slice), the data must be updated by calling the attach routine to replace the pointer to the new location. It is up to the developer to insert the appropriate call to reattach -the data. The attach function primarily sets the pointer to the data location -but it also sets the data type of the variable and its memory location using -two enum classes: +the data. As mentioned previously, scalar data should be attached using the +appropriate 1D HostArray with a size of 1. The attach function primarily sets +the pointer to the data location but it also sets the data type of the variable +and its memory location using two enum classes: ```c++ enum class FieldType {Unknown, I4, I8, R4, R8}; enum class FieldMemLoc {Unknown, Device, Host, Both}; @@ -155,7 +164,15 @@ The dimension information can be retrieved using: int Err = MyField->getDimNames(MyDimNames); ``` Once the dimension names have been retrieved, the Dimension class API can be -used to extract further dimension information. +used to extract further dimension information. Two other field quantities +can be retrieved, but are used only by the IOStream capability: +```c++ + bool IsTimeDependent = MyField->isTimeDependent(); + bool IsDistributed = MyField->isDistributed(); +``` +The first determines whether the unlimited time dimension should be added +during IO operations. The second determines whether any of the dimensions +are distributed across MPI tasks so that parallel IO is required. The data and metadata stored in a field can be retrieved using several functions. To retrieve a pointer to the full Field, use: diff --git a/components/omega/src/base/IO.h b/components/omega/src/base/IO.h index fd5bde1af6b5..83ed72a3f6c2 100644 --- a/components/omega/src/base/IO.h +++ b/components/omega/src/base/IO.h @@ -50,6 +50,9 @@ namespace IO { /// ID for global metadata (ie metadata not associated with a variable) constexpr int GlobalID = PIO_GLOBAL; +/// Length for unlimited dimensions +constexpr int Unlimited = PIO_UNLIMITED; + /// Choice of parallel IO rearranger algorithm enum Rearranger { RearrBox = PIO_REARR_BOX, ///< box rearranger (default) diff --git a/components/omega/src/infra/Field.cpp b/components/omega/src/infra/Field.cpp index ffe46705d22e..dd1e3efaffaf 100644 --- a/components/omega/src/infra/Field.cpp +++ b/components/omega/src/infra/Field.cpp @@ -16,6 +16,7 @@ #include "Field.h" #include "DataTypes.h" #include "Dimension.h" +#include "IO.h" #include "Logging.h" #include #include @@ -30,7 +31,8 @@ std::map> FieldGroup::AllGroups; //------------------------------------------------------------------------------ // Initializes the fields for global code and simulation metadata -int Field::init() { +int Field::init(const Clock *ModelClock // [in] default model clock +) { int Err = 0; @@ -39,6 +41,24 @@ int Field::init() { std::shared_ptr CodeField = create(CodeMeta); std::shared_ptr SimField = create(SimMeta); + // Define an unlimited time dimension for many time-dependent fields + // for CF-compliant output + std::shared_ptr TimeDim = + Dimension::create("time", IO::Unlimited); + + // Define a time field with required metadata for CF-compliant output + // It is defined here as a scalar field but the time axis will be added + // during IO + TimeInstant StartTime = ModelClock->getStartTime(); + std::string StartTimeStr = StartTime.getString(4, 0, " "); + std::string UnitString = "seconds since " + StartTimeStr; + CalendarKind CalKind = Calendar::getKind(); + std::string CalName = CalendarCFName[CalKind]; + std::vector DimNames; // empty dim names vector + std::shared_ptr TimeField = + create("time", "time", UnitString, "time", 0.0, 1.e20, 0.0, 0, DimNames); + TimeField->addMetadata("calendar", CalName); + return Err; } @@ -70,7 +90,8 @@ Field::create(const std::string &FieldName, // [in] Name of variable/field const std::any ValidMax, // [in] max valid field value const std::any FillValue, // [in] scalar for undefined entries const int NumDims, // [in] number of dimensions - const std::vector &Dimensions // [in] dim names + const std::vector &Dimensions, // [in] dim names + bool InTimeDependent // [in] flag for time dependent field ) { // Check to make sure a field of that name has not already been defined @@ -107,16 +128,24 @@ Field::create(const std::string &FieldName, // [in] Name of variable/field ThisField->FieldMeta["FillValue"] = FillValue; ThisField->FieldMeta["_FillValue"] = FillValue; + // Set the time-dependent flag + ThisField->TimeDependent = InTimeDependent; + // Number of dimensions for the field ThisField->NDims = NumDims; // Dimension names for retrieval of dimension info // These must be in the same index order as the stored data + // Also determine whether this is a distributed field - true if any of + // the dimensions are distributed. + ThisField->Distributed = false; ThisField->DimNames; if (NumDims > 0) { ThisField->DimNames.resize(NumDims); for (int I = 0; I < NumDims; ++I) { ThisField->DimNames[I] = Dimensions[I]; + if (Dimension::isDistributedDim(Dimensions[I])) + ThisField->Distributed = true; } } @@ -300,6 +329,17 @@ bool Field::isFieldOnHost(const std::string &FieldName // [in] name of field // Returns the number of dimensions for the field int Field::getNumDims() const { return NDims; } +//------------------------------------------------------------------------------ +// Determines whether the field is time dependent and requires the unlimited +// time dimension during IO +bool Field::isTimeDependent() const { return TimeDependent; } + +//------------------------------------------------------------------------------ +// Determinse whether a field is distributed across tasks or whether a copy +// is entirely local. This is needed to determine whether a parallel IO or +// a non-distributed IO will be used. +bool Field::isDistributed() const { return Distributed; } + //------------------------------------------------------------------------------ // Returns a vector of dimension names associated with each dimension // of an array field. Returns an error code. diff --git a/components/omega/src/infra/Field.h b/components/omega/src/infra/Field.h index 643d4dd6483c..93a8ac998485 100644 --- a/components/omega/src/infra/Field.h +++ b/components/omega/src/infra/Field.h @@ -19,6 +19,7 @@ #include "DataTypes.h" #include "Dimension.h" #include "Logging.h" +#include "TimeMgr.h" #include #include #include @@ -107,6 +108,14 @@ class Field { /// Location of data FieldMemLoc MemLoc; + /// Flag for whether this is a time-dependent field that needs the + /// Unlimited time dimension added during IO + bool TimeDependent; + + /// Flag for whether this is a field that is distributed across tasks + /// or whether it is entirely local + bool Distributed; + /// Data attached to this field. This will be a pointer to the Kokkos /// array holding the data. We use a void pointer to manage all the /// various types and cast to the appropriate type when needed. @@ -117,7 +126,9 @@ class Field { // Initialization //--------------------------------------------------------------------------- /// Initializes the fields for global code and simulation metadata - static int init(); + /// It also initializes the unlimited time dimension needed by most fields + static int init(const Clock *ModelClock ///< [in] the default model clock + ); //--------------------------------------------------------------------------- // Create/destroy/query fields @@ -142,7 +153,8 @@ class Field { const std::any ValidMax, ///< [in] max valid field value const std::any FillValue, ///< [in] scalar for undefined entries const int NumDims, ///< [in] number of dimensions - const std::vector &Dimensions ///< dim names for each dim + const std::vector &Dimensions, ///< [in] dim names + const bool InTimeDependent = true ///< [in] opt flag for unlim time ); //--------------------------------------------------------------------------- @@ -222,6 +234,17 @@ class Field { std::vector &Dimensions ///< [out] list of dimensions ) const; + //--------------------------------------------------------------------------- + // Query for other properties + /// Determine whether this is a time-dependent field that requires the + /// unlimited time dimension for IO + bool isTimeDependent() const; + + /// Determine whether this is a distributed field or whether it is entirely + /// local. This is needed to determine whether IO uses parallel read/write + /// or an undistributed read/write. + bool isDistributed() const; + //--------------------------------------------------------------------------- // Metadata functions //--------------------------------------------------------------------------- diff --git a/components/omega/src/infra/IOStream.cpp b/components/omega/src/infra/IOStream.cpp index c245bd2aa43f..40e964bd87b5 100644 --- a/components/omega/src/infra/IOStream.cpp +++ b/components/omega/src/infra/IOStream.cpp @@ -598,6 +598,11 @@ int IOStream::create(const std::string &StreamName, //< [in] name of stream return Err; } + // If this is a write stream, add the time field for CF compliant + // time information + if (NewStream->Mode == IO::ModeWrite) + NewStream->addField("time"); + // The contents are stored as an ordered set so we use the addField // interface to add each name. Note that in this context, the field // name can also be a group name. Group names are expanded during the @@ -643,6 +648,10 @@ int IOStream::defineAllDims( // For input files, we read the DimID from the file if (Mode == IO::ModeRead) { + // skip reading the unlimited time dimension + if (DimName == "time") + continue; + // If dimension not found, only generate a warning since there // may be some dimensions that are not required I4 InLength; @@ -689,15 +698,50 @@ int IOStream::defineAllDims( } // End defineAllDims +//------------------------------------------------------------------------------ +// Retrieves field size and dim lengths for non-distributed fields +// (distributed fields get this information from computeDecomp) +void IOStream::getFieldSize( + std::shared_ptr FieldPtr, // [in] pointer to Field + int &LocalSize, // [out] size of local array + std::vector &DimLengths // [out] vector of local dim lengths +) { + + // Retrieve some basic field information + std::string FieldName = FieldPtr->getName(); + int NDims = FieldPtr->getNumDims(); + if (NDims == 0) { // scalar field + LocalSize = 1; + DimLengths[0] = 1; + return; + } else if (NDims < 1) { + LOG_ERROR("Invalid number of dimensions for Field {}", FieldName); + return; + } + + std::vector DimNames(NDims); + int Err = FieldPtr->getDimNames(DimNames); + if (Err != 0) { + LOG_ERROR("Error retrieving dimension names for Field {}", FieldName); + return; + } + + LocalSize = 1; + for (int IDim = 0; IDim < NDims; ++IDim) { + std::string DimName = DimNames[IDim]; + std::shared_ptr ThisDim = Dimension::get(DimName); + DimLengths[IDim] = ThisDim->getLengthLocal(); + LocalSize *= DimLengths[IDim]; + } +} //------------------------------------------------------------------------------ // Computes the parallel decomposition (offsets) for a field needed for parallel // I/O. Return error code and also Decomp ID and array size for field. int IOStream::computeDecomp( - std::shared_ptr FieldPtr, // [in] pointer to Field - std::map &AllDimIDs, // [in] dimension IDs - int &DecompID, // [out] ID assigned to the decomposition - int &LocalSize, // [out] size of local array - std::vector &DimLengths // [out] vector of local dim lengths + std::shared_ptr FieldPtr, // [in] pointer to Field + int &DecompID, // [out] ID assigned to the decomposition + int &LocalSize, // [out] size of local array + std::vector &DimLengths // [out] vector of local dim lengths ) { int Err = 0; @@ -706,7 +750,7 @@ int IOStream::computeDecomp( std::string FieldName = FieldPtr->getName(); IO::IODataType MyIOType = getFieldIOType(FieldPtr); int NDims = FieldPtr->getNumDims(); - if (NDims < 1) { + if (NDims < 0) { LOG_ERROR("Invalid number of dimensions for Field {}", FieldName); Err = 1; return Err; @@ -906,22 +950,34 @@ int IOStream::writeFieldData( // Retrieve some basic field information std::string FieldName = FieldPtr->getName(); bool OnHost = FieldPtr->isOnHost(); + bool IsDistributed = FieldPtr->isDistributed(); + bool IsTimeDependent = FieldPtr->isTimeDependent(); FieldType MyType = FieldPtr->getType(); int NDims = FieldPtr->getNumDims(); - if (NDims < 1) { + if (NDims < 0) { LOG_ERROR("Invalid number of dimensions for Field {}", FieldName); Err = 2; return Err; } - // Create the decomposition needed for parallel I/O + // Create the decomposition needed for parallel I/O or if not decomposed + // get the relevant size information int MyDecompID; int LocSize; - std::vector DimLengths(NDims); - Err = computeDecomp(FieldPtr, AllDimIDs, MyDecompID, LocSize, DimLengths); - if (Err != 0) { - LOG_ERROR("Error computing decomposition for Field {}", FieldName); - return Err; + int NDimsTmp = std::max(NDims, 1); + std::vector DimLengths(NDimsTmp); + if (IsDistributed) { + Err = computeDecomp(FieldPtr, MyDecompID, LocSize, DimLengths); + if (Err != 0) { + LOG_ERROR("Error computing decomposition for Field {}", FieldName); + return Err; + } + } else { // Get dimension lengths + IOStream::getFieldSize(FieldPtr, LocSize, DimLengths); + // Scalar data stored as an array with size 1 so reset the local NDims + // to pick this up + if (NDims == 0) + ++NDims; } // Extract and write the array of data based on the type, dimension and @@ -1541,21 +1597,45 @@ int IOStream::writeFieldData( } // end switch data type - // Write the data - Err = OMEGA::IO::writeArray(DataPtr, LocSize, FillValPtr, FileID, MyDecompID, - FieldID); - if (Err != 0) { - LOG_ERROR("Error writing data array for field {} in stream {}", FieldName, - Name); - return Err; + // If this variable has an unlimited time dimension, set the frame/record + // number + if (IsTimeDependent) { + // currently always 0 but will be updated once support for multiple + // records is added + int Frame = 0; + Err = PIOc_setframe(FileID, FieldID, Frame); + if (Err != 0) { + LOG_ERROR("Error setting frame for unlimited time"); + return Err; + } } - // Clean up the decomp - Err = OMEGA::IO::destroyDecomp(MyDecompID); - if (Err != 0) { - LOG_ERROR("Error destroying decomp for field {} in stream {}", FieldName, - Name); - return Err; + // Write the data + if (IsDistributed) { + Err = OMEGA::IO::writeArray(DataPtr, LocSize, FillValPtr, FileID, + MyDecompID, FieldID); + if (Err != 0) { + LOG_ERROR("Error writing data array for field {} in stream {}", + FieldName, Name); + return Err; + } + + // Clean up the decomp + Err = OMEGA::IO::destroyDecomp(MyDecompID); + if (Err != 0) { + LOG_ERROR("Error destroying decomp for field {} in stream {}", + FieldName, Name); + return Err; + } + + } else { + Err = OMEGA::IO::writeNDVar(DataPtr, FileID, FieldID); + if (Err != 0) { + LOG_ERROR( + "Error writing non-distributed data for field {} in stream {}", + FieldName, Name); + return Err; + } } return Err; @@ -1581,22 +1661,34 @@ int IOStream::readFieldData( std::string OldFieldName = FieldName; OldFieldName[0] = std::tolower(OldFieldName[0]); bool OnHost = FieldPtr->isOnHost(); + bool IsDistributed = FieldPtr->isDistributed(); + bool IsTimeDependent = FieldPtr->isTimeDependent(); FieldType MyType = FieldPtr->getType(); int NDims = FieldPtr->getNumDims(); - if (NDims < 1) { + if (NDims < 0) { LOG_ERROR("Invalid number of dimensions for Field {}", FieldName); Err = 1; return Err; } - // Compute the parallel decomposition + // Create the decomposition needed for parallel I/O or if not decomposed + // get the relevant size information int DecompID; int LocSize; - std::vector DimLengths(NDims); - Err = computeDecomp(FieldPtr, AllDimIDs, DecompID, LocSize, DimLengths); - if (Err != 0) { - LOG_ERROR("Error computing decomposition for Field {}", FieldName); - return Err; + int NDimsTmp = std::min(NDims, 1); + std::vector DimLengths(NDimsTmp); + if (IsDistributed) { + Err = computeDecomp(FieldPtr, DecompID, LocSize, DimLengths); + if (Err != 0) { + LOG_ERROR("Error computing decomposition for Field {}", FieldName); + return Err; + } + } else { // Get dimension lengths + IOStream::getFieldSize(FieldPtr, LocSize, DimLengths); + // Scalar data stored as an array with size 1 so reset the local NDims + // to pick this up + if (NDims == 0) + ++NDims; } // The IO routines require a pointer to a contiguous memory on the host @@ -1624,11 +1716,20 @@ int IOStream::readFieldData( } // read data into vector - Err = IO::readArray(DataPtr, LocSize, FieldName, FileID, DecompID, FieldID); + if (IsDistributed) { + Err = + IO::readArray(DataPtr, LocSize, FieldName, FileID, DecompID, FieldID); + } else { + Err = IO::readNDVar(DataPtr, FieldName, FileID, FieldID); + } if (Err != 0) { // For back compatibility, try to read again with old field name - Err = IO::readArray(DataPtr, LocSize, OldFieldName, FileID, DecompID, - FieldID); + if (IsDistributed) { + Err = IO::readArray(DataPtr, LocSize, OldFieldName, FileID, DecompID, + FieldID); + } else { + Err = IO::readNDVar(DataPtr, OldFieldName, FileID, FieldID); + } if (Err == 0) { LOG_INFO("Ignore PIO error for field {} ", FieldName); LOG_INFO("Found field under old name {} ", OldFieldName); @@ -2394,6 +2495,15 @@ int IOStream::writeStream( TimeInstant SimTime = ModelClock->getCurrentTime(); std::string SimTimeStr = SimTime.getString(4, 0, "_"); + // Update the time field with elapsed time since simulation start + TimeInstant StartTime = ModelClock->getStartTime(); + TimeInterval ElapsedTime = SimTime - StartTime; + R8 ElapsedTimeR8; + Err = ElapsedTime.get(ElapsedTimeR8, TimeUnits::Seconds); + HostArray1DR8 OutTime("OutTime", 1); + OutTime(0) = ElapsedTimeR8; + Err = Field::attachFieldData("time", OutTime); + // Reset alarms and flags if (OnStartup) OnStartup = false; @@ -2463,18 +2573,23 @@ int IOStream::writeStream( // Retrieve the dimensions for this field and determine dim IDs NDims = ThisField->getNumDims(); - if (NDims < 1) { - LOG_ERROR("Invalid number of dimensions for Field {}", FieldName); - Err = 2; - return Err; + if (NDims > 0) { + DimNames.resize(NDims); + FieldDims.resize(NDims); + Err = ThisField->getDimNames(DimNames); + if (Err != 0) { + LOG_ERROR("Error retrieving dimension names for Field {}", + FieldName); + return Err; + } } - DimNames.resize(NDims); - FieldDims.resize(NDims); - Err = ThisField->getDimNames(DimNames); - if (Err != 0) { - LOG_ERROR("Error retrieving dimension names for Field {}", FieldName); - return Err; + // If this is a time-dependent field, we insert the unlimited time + // dimension as the first dimension (for field definition only) + if (ThisField->isTimeDependent()) { + ++NDims; + DimNames.insert(DimNames.begin(), "time"); } + // Get the dim IDs for (int IDim = 0; IDim < NDims; ++IDim) { std::string DimName = DimNames[IDim]; FieldDims[IDim] = AllDimIDs[DimName]; diff --git a/components/omega/src/infra/IOStream.h b/components/omega/src/infra/IOStream.h index 3eeeb947615a..65f216df52bd 100644 --- a/components/omega/src/infra/IOStream.h +++ b/components/omega/src/infra/IOStream.h @@ -84,13 +84,19 @@ class IOStream { /// Computes the parallel decomposition (offsets) for a field. /// Needed for parallel I/O int computeDecomp( - std::shared_ptr FieldPtr, ///< [in] field - std::map &AllDimIDs, ///< [in] dimension IDs + std::shared_ptr FieldPtr, ///< [in] field int &DecompID, ///< [out] ID assigned to the defined decomposition I4 &LocalSize, ///< [out] size of the local array for this field std::vector &DimLengths // [out] local dim lengths ); + /// Retrieves field size information for non-distributed fields + /// (distributed fields get this info from computeDecomp) + void getFieldSize(std::shared_ptr FieldPtr, ///< [in] field + I4 &LocalSize, ///< [out] size of the local array + std::vector &DimLengths ///< [out] local dim lengths + ); + /// Private function that performs most of the stream read - called by the /// public read method int readStream( diff --git a/components/omega/src/infra/TimeMgr.h b/components/omega/src/infra/TimeMgr.h index 1aaaa5f21260..a9c4504d82e2 100644 --- a/components/omega/src/infra/TimeMgr.h +++ b/components/omega/src/infra/TimeMgr.h @@ -82,6 +82,11 @@ const std::string CalendarKindName[NUM_SUPPORTED_CALENDARS] = { "Gregorian", "No Leap", "Julian", "Julian Day", "Modified Julian Day", "360 Day", "Custom", "No Calendar", "Invalid"}; +/// Standard CF-compliant name associated with each supported calendar type +const std::string CalendarCFName[NUM_SUPPORTED_CALENDARS] = { + "gregorian", "noleap", "julian", "julian_day", "modified_julian_day", + "360_day", "custom", "none", "invalid"}; + /// The TimeFrac class includes the core representation, functions and /// operators for time within OMEGA. /// diff --git a/components/omega/src/ocn/OceanInit.cpp b/components/omega/src/ocn/OceanInit.cpp index d2606c41b1be..c727e86c1805 100644 --- a/components/omega/src/ocn/OceanInit.cpp +++ b/components/omega/src/ocn/OceanInit.cpp @@ -91,7 +91,7 @@ int initOmegaModules(MPI_Comm Comm) { return Err; } - Err = Field::init(); + Err = Field::init(ModelClock); if (Err != 0) { LOG_CRITICAL("ocnInit: Error initializing Fields"); return Err; diff --git a/components/omega/test/infra/FieldTest.cpp b/components/omega/test/infra/FieldTest.cpp index dbae3c7e2cfb..7d1455a88ff6 100644 --- a/components/omega/test/infra/FieldTest.cpp +++ b/components/omega/test/infra/FieldTest.cpp @@ -18,6 +18,7 @@ #include "Logging.h" #include "MachEnv.h" #include "OmegaKokkos.h" +#include "TimeMgr.h" #include "mpi.h" #include @@ -128,8 +129,13 @@ int initFieldTest() { std::shared_ptr StuffDim = Dimension::create("NStuff", NVertLevels); + // Create a model clock for time info + TimeInstant SimStartTime(0001, 1, 1, 0, 0, 0.0); + TimeInterval TimeStep(2, TimeUnits::Hours); + Clock *ModelClock = new Clock(SimStartTime, TimeStep); + // Initialize Field class - creates Model and Sim metadata fields - int Err1 = Field::init(); + int Err1 = Field::init(ModelClock); TstEval("Field initialization", Err1, ErrRef, Err); // Add some global (Model and Simulation) metadata diff --git a/components/omega/test/infra/IOStreamTest.cpp b/components/omega/test/infra/IOStreamTest.cpp index 9253129cee8d..c45135a2ade4 100644 --- a/components/omega/test/infra/IOStreamTest.cpp +++ b/components/omega/test/infra/IOStreamTest.cpp @@ -95,12 +95,22 @@ int initIOStreamTest(Clock *&ModelClock // Model clock Decomp::init(); Decomp *DefDecomp = Decomp::getDefault(); + // Initialize time stepper for time levels and clock + Err1 = TimeStepper::init1(); + TestEval("Ocean time step initialization", Err1, ErrRef, Err); + // Initialize Halo updates Halo::init(); OMEGA::Halo *DefHalo = OMEGA::Halo::getDefault(); + // Override the input time stepper clock with a clock more suitable + // for testing streams + TimeInstant SimStartTime(0001, 1, 1, 0, 0, 0.0); + TimeInterval TimeStep(2, TimeUnits::Hours); + ModelClock = new Clock(SimStartTime, TimeStep); + // Initialize Field - Err1 = Field::init(); + Err1 = Field::init(ModelClock); TestEval("IO Field initialization", Err1, ErrRef, Err); // Initialize IOStreams @@ -211,7 +221,6 @@ int main(int argc, char **argv) { TimeInstant StopTime(0002, 1, 1, 0, 0, 0.0); Alarm StopAlarm("Stop Time", StopTime); Err1 = ModelClock->attachAlarm(&StopAlarm); - TestEval("Attach stop alarm", Err1, ErrRef, Err); // Overwrite // Step forward in time and write files if it is time diff --git a/components/omega/test/ocn/StateTest.cpp b/components/omega/test/ocn/StateTest.cpp index a0cd792330cf..9cbac6bfbdf8 100644 --- a/components/omega/test/ocn/StateTest.cpp +++ b/components/omega/test/ocn/StateTest.cpp @@ -77,7 +77,7 @@ int initStateTest() { } // Initialize Field infrastructure - Err = Field::init(); + Err = Field::init(ModelClock); if (Err != 0) { LOG_CRITICAL("State: Error initializing Fields"); return Err; From ff91b18b51d97198ab3c9dda6d5070b77bff6dc9 Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Fri, 22 Nov 2024 13:49:06 -0600 Subject: [PATCH 2/5] fix bug introduced during rebase --- components/omega/test/infra/IOStreamTest.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/components/omega/test/infra/IOStreamTest.cpp b/components/omega/test/infra/IOStreamTest.cpp index c45135a2ade4..e5f7f310f40d 100644 --- a/components/omega/test/infra/IOStreamTest.cpp +++ b/components/omega/test/infra/IOStreamTest.cpp @@ -95,20 +95,10 @@ int initIOStreamTest(Clock *&ModelClock // Model clock Decomp::init(); Decomp *DefDecomp = Decomp::getDefault(); - // Initialize time stepper for time levels and clock - Err1 = TimeStepper::init1(); - TestEval("Ocean time step initialization", Err1, ErrRef, Err); - // Initialize Halo updates Halo::init(); OMEGA::Halo *DefHalo = OMEGA::Halo::getDefault(); - // Override the input time stepper clock with a clock more suitable - // for testing streams - TimeInstant SimStartTime(0001, 1, 1, 0, 0, 0.0); - TimeInterval TimeStep(2, TimeUnits::Hours); - ModelClock = new Clock(SimStartTime, TimeStep); - // Initialize Field Err1 = Field::init(ModelClock); TestEval("IO Field initialization", Err1, ErrRef, Err); From ecd2ce1d2841a3a26ea8d117443c113390925b20 Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Wed, 27 Nov 2024 10:12:47 -0600 Subject: [PATCH 3/5] removed In from argument name - no need to disambiguate --- components/omega/src/infra/Field.cpp | 4 ++-- components/omega/src/infra/Field.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/omega/src/infra/Field.cpp b/components/omega/src/infra/Field.cpp index dd1e3efaffaf..9a165960db13 100644 --- a/components/omega/src/infra/Field.cpp +++ b/components/omega/src/infra/Field.cpp @@ -91,7 +91,7 @@ Field::create(const std::string &FieldName, // [in] Name of variable/field const std::any FillValue, // [in] scalar for undefined entries const int NumDims, // [in] number of dimensions const std::vector &Dimensions, // [in] dim names - bool InTimeDependent // [in] flag for time dependent field + bool TimeDependent // [in] flag for time dependent field ) { // Check to make sure a field of that name has not already been defined @@ -129,7 +129,7 @@ Field::create(const std::string &FieldName, // [in] Name of variable/field ThisField->FieldMeta["_FillValue"] = FillValue; // Set the time-dependent flag - ThisField->TimeDependent = InTimeDependent; + ThisField->TimeDependent = TimeDependent; // Number of dimensions for the field ThisField->NDims = NumDims; diff --git a/components/omega/src/infra/Field.h b/components/omega/src/infra/Field.h index 93a8ac998485..dae6e81aa019 100644 --- a/components/omega/src/infra/Field.h +++ b/components/omega/src/infra/Field.h @@ -154,7 +154,7 @@ class Field { const std::any FillValue, ///< [in] scalar for undefined entries const int NumDims, ///< [in] number of dimensions const std::vector &Dimensions, ///< [in] dim names - const bool InTimeDependent = true ///< [in] opt flag for unlim time + const bool TimeDependent = true ///< [in] opt flag for unlim time ); //--------------------------------------------------------------------------- From 0e593af58a726e72fa6399181af2827ae51e760a Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Wed, 27 Nov 2024 10:32:20 -0600 Subject: [PATCH 4/5] minor clarification in documentation --- components/omega/doc/devGuide/Field.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/omega/doc/devGuide/Field.md b/components/omega/doc/devGuide/Field.md index 38928a0dd8a0..a0c58542f3ef 100644 --- a/components/omega/doc/devGuide/Field.md +++ b/components/omega/doc/devGuide/Field.md @@ -57,7 +57,8 @@ dimension list since it will be added during I/O. Fields that do not change with time should include this argument with the value false so that the time dimension is not added. Actual field data stored in an array is attached in a separate call as described below. Scalar fields can be added by setting the -NumDims to zero (the DimNames is then ignored). Scalar data is attached using +NumDims to zero (the Dimensions vector is ignored but an empty vector +must still be supplied in the argument list). Scalar data is attached using a 1D array with size 1. Fields without a data array can be created with: ```c++ std::shared_ptr MyField = From 46141c38785cae78c4db63a39bebb90e92e5bbee Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Mon, 2 Dec 2024 11:26:52 -0600 Subject: [PATCH 5/5] Changed FillValue for the CF time field --- components/omega/src/infra/Field.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/omega/src/infra/Field.cpp b/components/omega/src/infra/Field.cpp index 9a165960db13..13e35b5aa625 100644 --- a/components/omega/src/infra/Field.cpp +++ b/components/omega/src/infra/Field.cpp @@ -55,8 +55,8 @@ int Field::init(const Clock *ModelClock // [in] default model clock CalendarKind CalKind = Calendar::getKind(); std::string CalName = CalendarCFName[CalKind]; std::vector DimNames; // empty dim names vector - std::shared_ptr TimeField = - create("time", "time", UnitString, "time", 0.0, 1.e20, 0.0, 0, DimNames); + std::shared_ptr TimeField = create("time", "time", UnitString, "time", + 0.0, 1.e20, -9.99e30, 0, DimNames); TimeField->addMetadata("calendar", CalName); return Err;