diff --git a/core/src/ModelMetadata.cpp b/core/src/ModelMetadata.cpp index 7c4e147ec..07667b797 100644 --- a/core/src/ModelMetadata.cpp +++ b/core/src/ModelMetadata.cpp @@ -196,6 +196,21 @@ void ModelMetadata::setDimensionsFromFile(const std::string& filename) if (dim.isNull()) { dim = ncFile.getDim(dimensionSpec.altName); } +#ifdef USE_XIOS + // Account for the fact that XIOS writes dimensions differently if only one + // discretisation is written out. If the dimension is still null at this point then we + // assume that the only discretisation used is HDomain-based, i.e., HField, DGField, or + // DGSField. + if (dim.isNull()) { + if (dimensionSpec.name == "x_dim") { + dim = ncFile.getDim("x"); + } else if (dimensionSpec.name == "y_dim") { + dim = ncFile.getDim("y"); + } else { + continue; + } + } +#endif // If we didn't find a dimension with the dimensions name or altName, throw. if (dim.isNull()) { throw std::out_of_range( diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 63fa0106c..6f7c30f0f 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -54,11 +54,9 @@ static const std::string xForcingPfx = "XiosForcing"; static const std::map keyMap = { { Xios::ENABLED_KEY, "xios.enable" }, { Xios::OUTPUT_FIELD_NAMES_KEY, xOutputPfx + ".field_names" }, { Xios::INPUT_FIELD_NAMES_KEY, xInputPfx + ".field_names" }, - { Xios::OUTPUT_SPLITFREQ_KEY, xOutputPfx + ".split_period" }, { Xios::DIAGNOSTIC_PERIOD_KEY, xDiagnosticPfx + ".period" }, { Xios::DIAGNOSTIC_FILE_KEY, xDiagnosticPfx + ".filename" }, { Xios::DIAGNOSTIC_FIELD_NAMES_KEY, xDiagnosticPfx + ".field_names" }, - { Xios::DIAGNOSTIC_SPLITFREQ_KEY, xDiagnosticPfx + ".split_period" }, { Xios::FORCING_PERIOD_KEY, xForcingPfx + ".period" }, { Xios::FORCING_FILE_KEY, xForcingPfx + ".filename" }, { Xios::FORCING_FIELD_NAMES_KEY, xForcingPfx + ".field_names" } }; @@ -73,8 +71,8 @@ Xios::HelpMap& Xios::getHelpText(HelpMap& map, bool getAll) "-Dxios_DIR=/path/to/xios." }, }; map["XiosOutput"] = { - { keyMap.at(OUTPUT_FIELD_NAMES_KEY), ConfigType::STRING, {}, "", "", - "Comma-separated list of field names to be written to the output file." }, + { keyMap.at(OUTPUT_FIELD_NAMES_KEY), ConfigType::STRING, {}, "restart%Y-%m-%dT%H:%M:%SZ.nc", + "", "Comma-separated list of field names to be written to the output file." }, }; map["XiosInput"] = { { keyMap.at(INPUT_FIELD_NAMES_KEY), ConfigType::STRING, {}, "", "", @@ -85,9 +83,8 @@ Xios::HelpMap& Xios::getHelpText(HelpMap& map, bool getAll) "The period between diagnostics file outputs expected in a file to be " "read, formatted as an ISO8601 duration (P prefix) or number of " "seconds. A value of zero assumes no intermediate diagnostics files." }, - { keyMap.at(DIAGNOSTIC_FILE_KEY), ConfigType::STRING, {}, "", "", - // TODO: Support format "restart%Y-%m-%dT%H:%M:%SZ.nc" (#898) - "The file name to be used for diagnostics." }, + { keyMap.at(DIAGNOSTIC_FILE_KEY), ConfigType::STRING, {}, "diagnostic%Y-%m-%dT%H:%M:%SZ.nc", + "", "The file name to be used for diagnostics." }, { keyMap.at(DIAGNOSTIC_FIELD_NAMES_KEY), ConfigType::STRING, {}, "", "", "Comma-separated list of field names to be read from the diagnostics " "file." }, @@ -98,7 +95,6 @@ Xios::HelpMap& Xios::getHelpText(HelpMap& map, bool getAll) "read, formatted as an ISO8601 duration (P prefix) or number of " "seconds. A value of zero assumes no intermediate forcing files." }, { keyMap.at(FORCING_FILE_KEY), ConfigType::STRING, {}, "", "", - // TODO: Support format "restart%Y-%m-%dT%H:%M:%SZ.nc" (#898) "The file name to be used for forcings." }, { keyMap.at(FORCING_FIELD_NAMES_KEY), ConfigType::STRING, {}, "", "", "Comma-separated list of field names to be read from the forcings " @@ -794,11 +790,16 @@ void Xios::createField(const std::string& fieldId) throw std::runtime_error("Xios: Failed to create field '" + fieldId + "'"); } - // Restarts are read "once", everything else is written "instant"ly (no averaging) + // Set the operation type std::string operation; if (configGetInputRestartFieldNames().count(fieldId) > 0) { + // Restarts are read "once" operation = "once"; + } else if (configGetDiagnosticFieldNames().count(fieldId) > 0) { + // Diagonstics are averaged over the diagnostic output period + operation = "average"; } else { + // Otherwise, read/write all timesteps without post-processing operation = "instant"; } if (cxios_is_defined_field_operation(field)) { @@ -932,10 +933,10 @@ ModelArray::Type Xios::getFieldType(const std::string& fieldId) { return fieldTy * @param the field ID * @param ModelArray::Type used for the corresponding field */ -void Xios::setFieldType(const std::string& fieldId, const ModelArray::Type& type) +void Xios::setFieldType(const std::string& fieldId, const ModelArray::Type& fieldType) { - fieldTypes[fieldId] = type; - setFieldGridRef(fieldId, gridIds[type]); + fieldTypes[fieldId] = fieldType; + setFieldGridRef(fieldId, gridIds[fieldType]); } /*! @@ -957,10 +958,13 @@ void Xios::setupFields() // Create map for field types const std::map dimensionKeys = { + { "yx", ModelArray::Type::H }, { "ydimxdim", ModelArray::Type::H }, { "y_dimx_dim", ModelArray::Type::H }, + { "yxdg_comp", ModelArray::Type::DG }, { "ydimxdimdg_comp", ModelArray::Type::DG }, { "y_dimx_dimdg_comp", ModelArray::Type::DG }, + { "yxdgstress_comp", ModelArray::Type::DGSTRESS }, { "ydimxdimdgstress_comp", ModelArray::Type::DGSTRESS }, { "y_dimx_dimdgstress_comp", ModelArray::Type::DGSTRESS }, { "y_cgx_cg", ModelArray::Type::CG }, @@ -998,8 +1002,7 @@ void Xios::setupFields() if (!dimensionKeys.count(dimKey)) { continue; } - const ModelArray::Type& type = dimensionKeys.at(dimKey); - setFieldType(fieldId, type); + setFieldType(fieldId, dimensionKeys.at(dimKey)); } ncFile.close(); } catch (const netCDF::exceptions::NcException& nce) { @@ -1051,10 +1054,28 @@ xios::CFile* Xios::getFile(const std::string& fileId) * Create a file with some ID * * @param the file ID - * @param enum indicating field type */ -void Xios::createFile(const std::string& fileId, const int& fieldType) +void Xios::createFile(const std::string& fileId) { + if (!(fileId == outputFileId || fileId == inputFileId || fileId == diagnosticFileId + || fileId == forcingFileId)) { + throw std::runtime_error("Xios::createFile: Invalid fileId '" + fileId + "'"); + } + + // Deduce the field type + int ioType = -1; + for (const auto& [ioTypeOther, fileIdOther] : fileMap) { + if (fileId == fileIdOther) { + ioType = ioTypeOther; + break; + } + } + if (ioType == -1) { + throw std::runtime_error( + "Xios::createFile: Could not deduce file type for file '" + fileId + "'"); + } + + // Create the file xios::CFile* file = NULL; bool exists; cxios_file_valid_id(&exists, fileId.c_str(), fileId.length()); @@ -1077,94 +1098,118 @@ void Xios::createFile(const std::string& fileId, const int& fieldType) } // Determine whether the file is configured for reading or writing - bool readAccess = (fieldType == INPUT_RESTART || fieldType == FORCING); - bool writeAccess = (fieldType == OUTPUT_RESTART || fieldType == DIAGNOSTIC); + bool readAccess = (ioType == INPUT_RESTART || ioType == FORCING); - // Check that the filename is not used for both reading and writing - if (readAccess && writeAccess) { - throw std::runtime_error("Xios: File '" + fileId + "' configured for both reading and" - + " writing. This is not yet supported in the XIOS I/O implementation."); - // TODO: Refactor to allow a field to be both read and written + // Set the file mode + std::string fileMode; + if (readAccess) { + fileMode = "read"; + } else { + fileMode = "write"; } - - // Terminate early for special unit test cases, for which IDs start with 'unittest' - if (fileId.rfind("unittest", 0) == 0) { - Logged::warning("Xios: Special 'unittest' ID found; skipping automated setup. Are you sure " - "you want to do this?"); - return; + if (cxios_is_defined_file_mode(file)) { + Logged::warning("Xios: Overwriting mode for file '" + fileId + "'"); + } + cxios_set_file_mode(file, fileMode.c_str(), fileMode.length()); + if (!cxios_is_defined_file_mode(file)) { + throw std::runtime_error("Xios: Failed to set mode for file '" + fileId + "'"); } - // Check that the filename is in the XiosOutput or XiosInput config section - if (!(readAccess || writeAccess)) { - throw std::runtime_error("Xios: File '" + fileId - + "' cannot be found in the model, XiosDiagnostic, or XiosForcing config sections"); + // Set the file type to one_file + const std::string fileType = "one_file"; + if (cxios_is_defined_file_type(file)) { + Logged::warning("Xios: Overwriting type for file '" + fileId + "'"); + } + cxios_set_file_type(file, fileType.c_str(), fileType.length()); + if (!cxios_is_defined_file_type(file)) { + throw std::runtime_error("Xios: Failed to set type for file '" + fileId + "'"); } - // Set the file mode and some defaults - if (readAccess) { - setFileMode(fileId, "read"); - } else { - setFileMode(fileId, "write"); + // Set the file parallel access to collective + const std::string parAccess = "collective"; + if (cxios_is_defined_file_par_access(file)) { + Logged::warning("Xios: Overwriting parallel access for file '" + fileId + "'"); + } + cxios_set_file_par_access(file, parAccess.c_str(), parAccess.length()); + if (!cxios_is_defined_file_par_access(file)) { + throw std::runtime_error("Xios: Failed to set parallel access for file '" + fileId + "'"); } - setFileType(fileId, "one_file"); - setFileParAccess(fileId, "collective"); // Get the fieldIds std::set fieldIds; - if (fieldType == INPUT_RESTART) { + if (ioType == INPUT_RESTART) { fieldIds = configGetInputRestartFieldNames(); - } else if (fieldType == OUTPUT_RESTART) { + } else if (ioType == OUTPUT_RESTART) { fieldIds = configGetOutputRestartFieldNames(); - } else if (fieldType == FORCING) { + } else if (ioType == FORCING) { fieldIds = configGetForcingFieldNames(); - } else if (fieldType == DIAGNOSTIC) { + } else if (ioType == DIAGNOSTIC) { fieldIds = configGetDiagnosticFieldNames(); } - // Set the file output frequency - std::string periodStr; + // Determine the file output frequency + cxios_duration outputFreq; ModelMetadata& metadata = ModelMetadata::getInstance(); - if (fieldType == INPUT_RESTART || fieldType == OUTPUT_RESTART) { - setFileOutputFreq(fileId, metadata.restartPeriod); + if (ioType == INPUT_RESTART || ioType == OUTPUT_RESTART) { + outputFreq = convertDurationToXios(metadata.restartPeriod); } else { std::string periodStr; - if (fieldType == FORCING) { + if (ioType == FORCING) { periodStr = Configured::getConfiguration(keyMap.at(FORCING_PERIOD_KEY), std::string()); } else { periodStr = Configured::getConfiguration(keyMap.at(DIAGNOSTIC_PERIOD_KEY), std::string()); + cxios_set_file_split_freq(file, convertDurationToXios(Duration(periodStr))); } if (periodStr.empty() || periodStr == "0") { - setFileOutputFreq(fileId, metadata.runLength()); + outputFreq = convertDurationToXios(metadata.runLength()); } else { - setFileOutputFreq(fileId, Duration(periodStr)); + outputFreq = convertDurationToXios(Duration(periodStr)); } } - // Set the output file splitting frequency - if (fieldType == OUTPUT_RESTART || fieldType == DIAGNOSTIC) { - std::string splitStr; - if (fieldType == OUTPUT_RESTART) { - splitStr = Configured::getConfiguration(keyMap.at(OUTPUT_SPLITFREQ_KEY), std::string()); - } else if (fieldType == DIAGNOSTIC) { - splitStr - = Configured::getConfiguration(keyMap.at(DIAGNOSTIC_SPLITFREQ_KEY), std::string()); + // Set the file output frequency + if (cxios_is_defined_file_output_freq(file)) { + Logged::warning("Xios: Overwriting output frequency for file '" + fileId + "'"); + } + cxios_set_file_output_freq(file, outputFreq); + if (!cxios_is_defined_file_output_freq(file)) { + throw std::runtime_error("Xios: Failed to set output frequency for file '" + fileId + "'"); + } + + // Set the file split frequency to coincide with the output frequency for output files + if (!readAccess) { + if (cxios_is_defined_file_split_freq(file)) { + Logged::warning("Xios: Split frequency already set for file '" + fileId + "'"); } - if (!splitStr.empty()) { - xios::CFile* file = getFile(fileId); - if (cxios_is_defined_file_split_freq(file)) { - Logged::warning("Xios: Split frequency already set for file '" + fileId + "'"); - } - cxios_set_file_split_freq(file, convertDurationToXios(Duration(splitStr))); - if (!cxios_is_defined_file_split_freq(file)) { - throw std::runtime_error( - "Xios: Failed to set split frequency for file '" + fileId + "'"); + cxios_set_file_split_freq(file, outputFreq); + if (!cxios_is_defined_file_split_freq(file)) { + throw std::runtime_error( + "Xios: Failed to set split frequency for file '" + fileId + "'"); + } + + // Set format string for file splitting, converting characters as expected by XIOS + std::string split_freq_format; + if (ioType == OUTPUT_RESTART) { + split_freq_format = outputFormatStr; + } else { + split_freq_format = diagnosticFormatStr; + } + for (const auto& [from, to] : formatStrMap) { + auto pos = split_freq_format.find(from); + if (pos != std::string::npos) { + split_freq_format.replace(pos, from.length(), to); } } + cxios_set_file_split_freq_format( + file, split_freq_format.c_str(), split_freq_format.length()); + if (!cxios_is_defined_file_split_freq_format(file)) { + throw std::runtime_error( + "Xios: Failed to set split frequency format for file '" + fileId + "'"); + } } - // XiosOutput.field_names, XiosInput.field_names, XiosDiagnostic.field_names, or - // XiosForcing.field_names entries in the config. + // Loop over field_names entries in the config for (const std::string& fieldId : fieldIds) { createField(fieldId); fileAddField(fileId, fieldId); @@ -1179,150 +1224,6 @@ void Xios::createFile(const std::string& fileId, const int& fieldType) } } -/*! - * Set the type of a file with a given ID - * - * @param the file ID - * @param file type to set - */ -void Xios::setFileType(const std::string& fileId, const std::string& fileType) -{ - xios::CFile* file = getFile(fileId); - if (cxios_is_defined_file_type(file)) { - Logged::warning("Xios: Overwriting type for file '" + fileId + "'"); - } - cxios_set_file_type(file, fileType.c_str(), fileType.length()); - if (!cxios_is_defined_file_type(file)) { - throw std::runtime_error("Xios: Failed to set type for file '" + fileId + "'"); - } -} - -/*! - * Set the output frequency of a file with a given ID - * - * @param the file ID - * @param output frequency to set - */ -void Xios::setFileOutputFreq(const std::string& fileId, const Duration& freq) -{ - xios::CFile* file = getFile(fileId); - if (cxios_is_defined_file_output_freq(file)) { - Logged::warning("Xios: Overwriting output frequency for file '" + fileId + "'"); - } - cxios_set_file_output_freq(file, convertDurationToXios(freq)); - if (!cxios_is_defined_file_output_freq(file)) { - throw std::runtime_error("Xios: Failed to set output frequency for file '" + fileId + "'"); - } -} - -/*! - * Set the mode of a file with a given ID - * - * @param the file ID - * @param file mode to set - */ -void Xios::setFileMode(const std::string& fileId, const std::string& mode) -{ - xios::CFile* file = getFile(fileId); - if (cxios_is_defined_file_mode(file)) { - Logged::warning("Xios: Overwriting mode for file '" + fileId + "'"); - } - cxios_set_file_mode(file, mode.c_str(), mode.length()); - if (!cxios_is_defined_file_mode(file)) { - throw std::runtime_error("Xios: Failed to set mode for file '" + fileId + "'"); - } -} - -/*! - * Set the parallel access mode of a file with a given ID - * - * @param the file ID - * @param parallel access mode to set - */ -void Xios::setFileParAccess(const std::string& fileId, const std::string& parAccess) -{ - xios::CFile* file = getFile(fileId); - if (cxios_is_defined_file_par_access(file)) { - Logged::warning("Xios: Overwriting parallel access for file '" + fileId + "'"); - } - cxios_set_file_par_access(file, parAccess.c_str(), parAccess.length()); - if (!cxios_is_defined_file_par_access(file)) { - throw std::runtime_error("Xios: Failed to set parallel access for file '" + fileId + "'"); - } -} - -/*! - * Get the type of a file with a given ID - * - * @param the file ID - * @return type of the corresponding file - */ -std::string Xios::getFileType(const std::string& fileId) -{ - xios::CFile* file = getFile(fileId); - if (!cxios_is_defined_file_type(file)) { - throw std::runtime_error("Xios: Undefined type for file '" + fileId + "'"); - } - char cStr[cStrLen]; - cxios_get_file_type(file, cStr, cStrLen); - return convertCStrToCppStr(cStr, cStrLen); -} - -/*! - * Get the output frequency of a file with a given ID - * - * @param the file ID - * @return the corresponding output frequency - */ -Duration Xios::getFileOutputFreq(const std::string& fileId) -{ - xios::CFile* file = getFile(fileId); - if (!cxios_is_defined_file_output_freq(file)) { - throw std::runtime_error("Xios: Undefined output frequency for file '" + fileId + "'"); - } - cxios_duration duration; - cxios_get_file_output_freq(file, &duration); - return convertDurationFromXios(duration); -} - -/*! - * Get the mode of a file with a given ID - * - * @param the file ID - * @return mode of the corresponding file - */ -std::string Xios::getFileMode(const std::string& fileId) -{ - xios::CFile* file = getFile(fileId); - if (!cxios_is_defined_file_mode(file)) { - throw std::runtime_error("Xios: Undefined mode for file '" + fileId + "'"); - } - char cStr[cStrLen]; - cxios_get_file_mode(file, cStr, cStrLen); - std::string mode(cStr, cStrLen); - boost::algorithm::trim_right(mode); - return mode; -} - -/*! - * Get the parallel access mode of a file with a given ID - * - * @param the file ID - * @return parallel access mode of the corresponding file - */ -std::string Xios::getFileParAccess(const std::string& fileId) -{ - xios::CFile* file = getFile(fileId); - if (!cxios_is_defined_file_par_access(file)) { - throw std::runtime_error("Xios: Undefined parallel access for file '" + fileId + "'"); - } - char cStr[cStrLen]; - cxios_get_file_par_access(file, cStr, cStrLen); - std::string parAccess(cStr, cStrLen); - boost::algorithm::trim_right(parAccess); - return parAccess; -} - /*! * Get all field IDs associated with a given file * @@ -1360,20 +1261,57 @@ void Xios::setupFiles() // Get restart file IDs from the configuration inputFileId = ((std::filesystem::path)metadata.initialFileName).filename().replace_extension(); - // TODO: Properly support format "restart%Y-%m-%dT%H:%M:%SZ.nc" (#898) outputFileId = ((std::filesystem::path)metadata.finalFileName).filename().replace_extension(); + if (outputFileId.find("%") != std::string::npos) { + outputFormatStr = outputFileId.substr(outputFileId.find("%"), outputFileId.find(".nc")); + outputFileId.erase(outputFileId.find("%"), outputFileId.length()); + } + if (!inputFileId.empty() && inputFileId == outputFileId) { + throw std::runtime_error("Xios::setupFiles: Input and restart file names must differ."); + } - // Get forcing and diganostic file IDs from the configuration + // Get forcing file name and ID from the configuration forcingFilename = Configured::getConfiguration(keyMap.at(FORCING_FILE_KEY), std::string()); forcingFileId = ((std::filesystem::path)forcingFilename).filename().replace_extension(); + if (!forcingFileId.empty()) { + if (inputFileId == forcingFileId) { + throw std::runtime_error("Xios::setupFiles: Input and forcing file names must differ."); + } + if (outputFileId == forcingFileId) { + throw std::runtime_error( + "Xios::setupFiles: Restart and forcing file names must differ."); + } + } + + // Get diagnostic file name and ID from the configuration diagnosticFilename = Configured::getConfiguration(keyMap.at(DIAGNOSTIC_FILE_KEY), std::string()); diagnosticFileId = ((std::filesystem::path)diagnosticFilename).filename().replace_extension(); + if (diagnosticFileId.find("%") != std::string::npos) { + diagnosticFormatStr + = diagnosticFileId.substr(diagnosticFileId.find("%"), diagnosticFileId.find(".nc")); + diagnosticFileId.erase(diagnosticFileId.find("%"), diagnosticFileId.length()); + } + if (!diagnosticFileId.empty()) { + if (inputFileId == diagnosticFileId) { + throw std::runtime_error( + "Xios::setupFiles: Input and diagnostic file names must differ."); + } + if (outputFileId == diagnosticFileId) { + throw std::runtime_error( + "Xios::setupFiles: Restart and diagnostic file names must differ."); + } + if (forcingFileId == diagnosticFileId) { + throw std::runtime_error( + "Xios::setupFiles: Forcing and diagnostic file names must differ."); + } + } // Create files for any non-empty file IDs - for (const auto& [fileType, fileId] : fileMap) { + for (const std::string& fileId : + { outputFileId, inputFileId, diagnosticFileId, forcingFileId }) { if (!fileId.empty()) { - createFile(fileId, fileType); + createFile(fileId); } } } diff --git a/core/src/include/Xios.hpp b/core/src/include/Xios.hpp index 71cef9f4d..fa4eab414 100644 --- a/core/src/include/Xios.hpp +++ b/core/src/include/Xios.hpp @@ -83,40 +83,18 @@ class Xios : public Configured { ModelArray::Type getFieldType(const std::string& fieldId); void setFieldType(const std::string& fieldId, const ModelArray::Type& type); - /* File */ - void createFile(const std::string& fileId, const int& fieldType); - void setFileType(const std::string& fileId, const std::string& fileType); - void setFileOutputFreq(const std::string& fileId, const Duration& outputFreq); - void setFileParAccess(const std::string& fileId, const std::string& parAccess); - std::string getFileType(const std::string& fileId); - Duration getFileOutputFreq(const std::string& fileId); - std::string getFileMode(const std::string& fileId); - std::string getFileParAccess(const std::string& fileId); - void fileAddField(const std::string& fileId, const std::string& fieldId); - std::vector fileGetFieldIds(const std::string& fileId); - enum { ENABLED_KEY, OUTPUT_FIELD_NAMES_KEY, - OUTPUT_SPLITFREQ_KEY, INPUT_FIELD_NAMES_KEY, DIAGNOSTIC_PERIOD_KEY, DIAGNOSTIC_FILE_KEY, DIAGNOSTIC_FIELD_NAMES_KEY, - DIAGNOSTIC_SPLITFREQ_KEY, FORCING_PERIOD_KEY, FORCING_FILE_KEY, FORCING_FIELD_NAMES_KEY, }; - // TODO: Make the following enum private - enum { - OUTPUT_RESTART, - INPUT_RESTART, - DIAGNOSTIC, - FORCING, - }; - protected: bool isConfigured; @@ -192,13 +170,20 @@ class Xios : public Configured { /* File */ xios::CFileGroup* getFileGroup(); xios::CFile* getFile(const std::string& fileId); - void setFileMode(const std::string& fileId, const std::string& mode); std::string outputFileId; + std::string outputFormatStr = "%y-%mo-%dT%h:%mi:%sZ"; std::string inputFileId; std::string diagnosticFilename; std::string diagnosticFileId; + std::string diagnosticFormatStr = "%y-%mo-%dT%h:%mi:%sZ"; std::string forcingFilename; std::string forcingFileId; + enum { + OUTPUT_RESTART, + INPUT_RESTART, + DIAGNOSTIC, + FORCING, + }; const std::map fileMap = { { OUTPUT_RESTART, outputFileId }, { INPUT_RESTART, inputFileId }, @@ -206,6 +191,16 @@ class Xios : public Configured { { FORCING, forcingFileId }, }; void setupFiles(); + void createFile(const std::string& fileId); + void fileAddField(const std::string& fileId, const std::string& fieldId); + std::vector fileGetFieldIds(const std::string& fileId); + const std::map formatStrMap = { + { "%Y", "%y" }, + { "%m-", "%mo-" }, + { "%H", "%h" }, + { "%M", "%mi" }, + { "%S", "%s" }, + }; /* I/O */ void read(const std::string& fieldId, ModelArray& modelarray); diff --git a/core/src/include/xios_c_interface.hpp b/core/src/include/xios_c_interface.hpp index e9a84a5c9..3e1c46bc9 100644 --- a/core/src/include/xios_c_interface.hpp +++ b/core/src/include/xios_c_interface.hpp @@ -146,16 +146,15 @@ void cxios_set_file_output_freq(xios::CFile* file_hdl, cxios_duration output_fre void cxios_set_file_split_freq(xios::CFile* file_hdl, cxios_duration split_freq_c); void cxios_set_file_mode(xios::CFile* file_hdl, const char* mode, int mode_size); void cxios_set_file_par_access(xios::CFile* file_hdl, const char* par_access, int par_access_size); -void cxios_get_file_type(xios::CFile* file_hdl, char* type, int type_size); -void cxios_get_file_output_freq(xios::CFile* file_hdl, cxios_duration* output_freq_c); -void cxios_get_file_mode(xios::CFile* file_hdl, char* mode, int mode_size); -void cxios_get_file_par_access(xios::CFile* file_hdl, char* par_access, int par_access_size); +void cxios_set_file_split_freq_format( + xios::CFile* file_hdl, const char* split_freq_format, int split_freq_format_size); bool cxios_is_defined_file_name(xios::CFile* file_hdl); bool cxios_is_defined_file_type(xios::CFile* file_hdl); bool cxios_is_defined_file_output_freq(xios::CFile* file_hdl); bool cxios_is_defined_file_split_freq(xios::CFile* file_hdl); bool cxios_is_defined_file_mode(xios::CFile* file_hdl); bool cxios_is_defined_file_par_access(xios::CFile* file_hdl); +bool cxios_is_defined_file_split_freq_format(xios::CFile* file_hdl); void cxios_xml_tree_add_fieldtofile( xios::CFile* file, xios::CField** field, const char* _id, int _id_len); diff --git a/core/test/CMakeLists.txt b/core/test/CMakeLists.txt index 08d3d6dce..8fc98d746 100644 --- a/core/test/CMakeLists.txt +++ b/core/test/CMakeLists.txt @@ -217,63 +217,103 @@ if(ENABLE_MPI) ) target_link_libraries(testXiosField_MPI3 PRIVATE nextsimlib doctest::doctest) - add_executable(testXiosFile_MPI2 "XiosFile_test.cpp" "MainMPI.cpp") - target_compile_definitions(testXiosFile_MPI2 PRIVATE USE_XIOS) + add_executable(testXiosReadForcing_MPI2 "XiosReadForcing_test.cpp" "MainMPI.cpp") + target_compile_definitions(testXiosReadForcing_MPI2 + PRIVATE + USE_XIOS + TEST_FILES_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" + ) + target_include_directories( + testXiosReadForcing_MPI2 + PRIVATE + ${PHYSICS_INCLUDE_DIRS} + "${MODEL_INCLUDE_DIR}" + "${XIOS_INCLUDE_LIST}" + "${ModulesRoot}/StructureModule" + ) + target_link_libraries(testXiosReadForcing_MPI2 PRIVATE nextsimlib doctest::doctest) + + add_executable(testXiosReadDiagnostic_MPI2 "XiosReadDiagnostic_test.cpp" "MainMPI.cpp") + target_compile_definitions(testXiosReadDiagnostic_MPI2 + PRIVATE + USE_XIOS + TEST_FILES_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" + ) + target_include_directories( + testXiosReadDiagnostic_MPI2 + PRIVATE + ${PHYSICS_INCLUDE_DIRS} + "${MODEL_INCLUDE_DIR}" + "${XIOS_INCLUDE_LIST}" + "${ModulesRoot}/StructureModule" + ) + target_link_libraries(testXiosReadDiagnostic_MPI2 PRIVATE nextsimlib doctest::doctest) + + add_executable(testXiosReadRestart_MPI2 "XiosReadRestart_test.cpp" "MainMPI.cpp") + target_compile_definitions(testXiosReadRestart_MPI2 + PRIVATE + USE_XIOS + TEST_FILES_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" + ) target_include_directories( - testXiosFile_MPI2 + testXiosReadRestart_MPI2 PRIVATE ${PHYSICS_INCLUDE_DIRS} "${MODEL_INCLUDE_DIR}" "${XIOS_INCLUDE_LIST}" "${ModulesRoot}/StructureModule" ) - target_link_libraries(testXiosFile_MPI2 PRIVATE nextsimlib doctest::doctest) + target_link_libraries(testXiosReadRestart_MPI2 PRIVATE nextsimlib doctest::doctest) - add_executable(testXiosRead_MPI2 "XiosRead_test.cpp" "MainMPI.cpp") - target_compile_definitions(testXiosRead_MPI2 + add_executable(testXiosWriteDiagnostic_MPI2 "XiosWriteDiagnostic_test.cpp" "MainMPI.cpp") + target_compile_definitions(testXiosWriteDiagnostic_MPI2 PRIVATE USE_XIOS TEST_FILES_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" ) target_include_directories( - testXiosRead_MPI2 + testXiosWriteDiagnostic_MPI2 PRIVATE ${PHYSICS_INCLUDE_DIRS} "${MODEL_INCLUDE_DIR}" "${XIOS_INCLUDE_LIST}" "${ModulesRoot}/StructureModule" ) - target_link_libraries(testXiosRead_MPI2 PRIVATE nextsimlib doctest::doctest) + target_link_libraries(testXiosWriteDiagnostic_MPI2 PRIVATE nextsimlib doctest::doctest) - add_executable(testXiosWrite_MPI2 "XiosWrite_test.cpp" "MainMPI.cpp") - target_compile_definitions(testXiosWrite_MPI2 + add_executable(testXiosWriteRestart_MPI2 "XiosWriteRestart_test.cpp" "MainMPI.cpp") + target_compile_definitions(testXiosWriteRestart_MPI2 PRIVATE USE_XIOS TEST_FILES_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" ) target_include_directories( - testXiosWrite_MPI2 + testXiosWriteRestart_MPI2 PRIVATE ${PHYSICS_INCLUDE_DIRS} "${MODEL_INCLUDE_DIR}" "${XIOS_INCLUDE_LIST}" "${ModulesRoot}/StructureModule" ) - target_link_libraries(testXiosWrite_MPI2 PRIVATE nextsimlib doctest::doctest) + target_link_libraries(testXiosWriteRestart_MPI2 PRIVATE nextsimlib doctest::doctest) set(XIOS_TESTS testXiosCalendar_MPI1 testXiosAxis_MPI3 testXiosField_MPI3 - testXiosFile_MPI2 - testXiosRead_MPI2 - testXiosWrite_MPI2 + testXiosReadForcing_MPI2 + testXiosReadDiagnostic_MPI2 + testXiosReadRestart_MPI2 + testXiosWriteDiagnostic_MPI2 + testXiosWriteRestart_MPI2 ) add_mpi_test("${XIOS_TESTS}") - # Ensure XiosWrite_test is run before XiosRead_test - set_tests_properties(testXiosRead_MPI2 PROPERTIES DEPENDS testXiosWrite_MPI2) + # Ensure XiosWrite_test are run before XiosRead_test + set_tests_properties(testXiosReadRestart_MPI2 PROPERTIES DEPENDS testXiosWriteRestart_MPI2) + set_tests_properties(testXiosReadDiagnostic_MPI2 + PROPERTIES DEPENDS testXiosWriteDiagnostic_MPI2) else() add_executable(testHaloExchangeCB_MPI3 "HaloExchangeCB_test.cpp" diff --git a/core/test/XiosFile_test.cpp b/core/test/XiosFile_test.cpp deleted file mode 100644 index bae28fd67..000000000 --- a/core/test/XiosFile_test.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/*! - * @author Joe Wallwork - * @author Adeleke Bankole - * @brief Tests for XIOS file - * @details - * This test is designed to test file functionality of the C++ interface - * for XIOS. - */ -#include -#undef INFO - -#include "StructureModule/include/ParametricGrid.hpp" -#include "include/Configurator.hpp" -#include "include/Finalizer.hpp" -#include "include/Model.hpp" -#include "include/ModelMPI.hpp" -#include "include/NextsimModule.hpp" -#include "include/ParaGridIO.hpp" -#include "include/Xios.hpp" - -using namespace doctest; - -namespace Nextsim { - -/*! - * TestXiosFile - * - * This function tests the file functionality of the C++ interface for XIOS. It - * needs to be run with 2 ranks i.e., - * - * `mpirun -n 2 ./testXiosFile_MPI2` - * - */ -MPI_TEST_CASE("TestXiosFile", 2) -{ - std::stringstream config; - config << "[model]" << std::endl; - config << "start = 2023-03-17T17:11:00Z" << std::endl; - config << "stop = 2023-03-17T18:41:00Z" << std::endl; - config << "time_step = P0-0T01:30:00" << std::endl; - config << "init_file = xios_test_input.nc" << std::endl; - config << "restart_file = xios_test_output.nc" << std::endl; - config << "restart_period = P0-0T03:00:00" << std::endl; - config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; - config << "[XiosInput]" << std::endl; - config << "field_names = mask" << std::endl; - config << "[XiosOutput]" << std::endl; - config << "field_names = hice" << std::endl; - std::unique_ptr pcstream(new std::stringstream(config.str())); - Configurator::addStream(std::move(pcstream)); - - // Create ModelMPI instance based off the test communicator - auto& modelMPI = ModelMPI::getInstance(test_comm); - - // Create a Model and configure it so that time options are parsed - Model model; - model.configureRestarts(); - model.configureTime(); // TODO: Use Model.configure to parse restart files this way, too? - - // Get the Xios singleton instance and check it's initialized - // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this - // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to - // Model::configureTime() above. - Xios& xiosHandler = Xios::getInstance(); - - // Create ParametricGrid and ParaGridIO instances - // NOTE: XIOS axes, domains, and grids are created by the ParaGridIO constructor - Module::setImplementation("Nextsim::ParametricGrid"); - ParametricGrid grid; - ParaGridIO* pio = new ParaGridIO(grid); - grid.setIO(pio); - - // Associate fields with grids - // NOTE: fields are automatically created along with files - xiosHandler.setFieldType("mask", ModelArray::Type::H); - xiosHandler.setFieldType("hice", ModelArray::Type::DG); - - // --- Tests for file API - const std::string inFileId = "xios_test_input"; - const std::string outFileId = "xios_test_output"; - REQUIRE_THROWS_WITH( - xiosHandler.getFileType("unittest_undef"), "Xios: Undefined file 'unittest_undef'"); - // File creation - // NOTE: This is called based on the XiosInput.filename and XiosOutput.filename entries upon - // initialization - REQUIRE_THROWS_WITH(xiosHandler.createFile(outFileId, xiosHandler.OUTPUT_RESTART), - "Xios: File 'xios_test_output' already exists"); - // File type - // NOTE: This is to "one_file" when createFile is called - REQUIRE(xiosHandler.getFileType(outFileId) == "one_file"); - // Output frequency - // NOTE: This is set based off the XiosInput.period and XiosOutput.period entries when a file - // is created - REQUIRE(xiosHandler.getFileOutputFreq(outFileId).seconds() == 3.0 * 60 * 60); - // File mode - // NOTE: setFileMode is set based off the XiosInput.filename and XiosOutput.filename entries - // when a file is created - REQUIRE(xiosHandler.getFileMode(inFileId) == "read"); - REQUIRE(xiosHandler.getFileMode(outFileId) == "write"); - // File parallel access mode - // NOTE: setFileParAccess is is to "collective" when a file is created for reading - REQUIRE(xiosHandler.getFileParAccess(inFileId) == "collective"); - // File add field - // NOTE: fileAddField is triggered by a call to createFile, which parses the config to create - // all the corresponding fields and then associated them with the file - std::vector inputIds = xiosHandler.fileGetFieldIds(inFileId); - REQUIRE(inputIds.size() == 1); - REQUIRE(inputIds[0] == "mask"); - std::vector outputIds = xiosHandler.fileGetFieldIds(outFileId); - REQUIRE(outputIds.size() == 1); - REQUIRE(outputIds[0] == "hice"); - - // Create a new file for each time unit to check more thoroughly that XIOS interprets output - // frequency correctly. - // (If we reused the same file then the XIOS interface would raise warnings.) - const std::string prefix = "unittest"; - const std::string yearId = prefix + "_year"; - const std::string dayId = prefix + "_day"; - const std::string hourId = prefix + "_hour"; - const std::string minuteId = prefix + "_minute"; - const std::string secondId = prefix + "_second"; - xiosHandler.createFile(yearId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(yearId, Duration("P1-0T00:00:00")); - REQUIRE(xiosHandler.getFileOutputFreq(yearId).seconds() == 365 * 24 * 60 * 60); - xiosHandler.createFile(dayId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(dayId, Duration("P0-1T00:00:00")); - REQUIRE(xiosHandler.getFileOutputFreq(dayId).seconds() == 24 * 60 * 60); - xiosHandler.createFile(hourId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(hourId, Duration("P0-0T01:00:00")); - REQUIRE(xiosHandler.getFileOutputFreq(hourId).seconds() == 60 * 60); - xiosHandler.createFile(minuteId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(minuteId, Duration("P0-0T00:01:00")); - REQUIRE(xiosHandler.getFileOutputFreq(minuteId).seconds() == 60); - xiosHandler.createFile(secondId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(secondId, Duration("P0-0T00:00:01")); - REQUIRE(xiosHandler.getFileOutputFreq(secondId).seconds() == 1); - - xiosHandler.close_context_definition(); - xiosHandler.context_finalize(); - Finalizer::finalize(); -} -} diff --git a/core/test/XiosReadDiagnostic_test.cpp b/core/test/XiosReadDiagnostic_test.cpp new file mode 100644 index 000000000..775d48581 --- /dev/null +++ b/core/test/XiosReadDiagnostic_test.cpp @@ -0,0 +1,110 @@ +/*! + * @author Joe Wallwork + * @brief Tests for checking the contents of diagnostic files. + * @details Diagnostics files should apply time-averaging. + */ +#include +#undef INFO + +#include "StructureModule/include/ParametricGrid.hpp" +#include "include/Finalizer.hpp" +#include "include/Model.hpp" +#include "include/ModelMPI.hpp" +#include "include/NextsimModule.hpp" +#include "include/ParaGridIO.hpp" +#include "include/Xios.hpp" +#include "include/gridNames.hpp" + +#include + +const std::string testFilesDir = TEST_FILES_DIR; +const std::string diagnosticFilename + = testFilesDir + "/diagnostic_2023-03-17T17:11:00Z-2023-03-17T20:10:59Z.nc"; + +static const int DGCOMP = 6; +static const int DGSTRESSCOMP = 8; +static const int CGDEGREE = 2; + +namespace Nextsim { + +/*! + * TestXiosReadDiagnostic + * + * 1. Test the file reading functionality via `getModelState`. + * 2. Test the file writing functionality via `writeDiagnosticTime` in the sense of checking that + * time-averaging is applied. + */ +MPI_TEST_CASE("TestXiosReadDiagnostic", 2) +{ + std::stringstream config; + config << "[model]" << std::endl; + config << "start = 2023-03-17T17:11:00Z" << std::endl; + config << "stop = 2023-03-17T23:11:00Z" << std::endl; + config << "time_step = P0-0T01:30:00" << std::endl; + config << "init_file = " << diagnosticFilename << std::endl; + config << "restart_period = P0-0T03:00:00" << std::endl; + config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; + config << "[XiosInput]" << std::endl; + config << "field_names = " << hsnowName << std::endl; + std::unique_ptr pcstream(new std::stringstream(config.str())); + Configurator::addStream(std::move(pcstream)); + + // Create ModelMPI instance based off the test communicator + auto& modelMPI = ModelMPI::getInstance(test_comm); + + // Create a Model and configure it so that time options are parsed + // TODO: Use Model.configure for consistency with the rest of the model + Model model; + model.configureRestarts(); + model.configureTime(); + + // Get the Xios singleton instance and check it's initialized + // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this + // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to + // Model::configureTime() above. + Xios& xiosHandler = Xios::getInstance(); + + // Create ParametricGrid and ParaGridIO instances + // NOTE: XIOS axes, domains, and grids are created by the ParaGridIO constructor + Module::setImplementation("Nextsim::ParametricGrid"); + ParametricGrid grid; + ParaGridIO* pio = new ParaGridIO(grid); + grid.setIO(pio); + + xiosHandler.close_context_definition(); + + // Check the input file exists + if (!std::filesystem::exists(diagnosticFilename)) { + throw std::runtime_error( + "XiosReadDiagnostic_test: Input file not found. Did you run XiosWrite_test?"); + } + + // Check calendar step is zero initially + REQUIRE(xiosHandler.getCalendarStep() == 0); + + // Deduce the local lengths of the two dimensions + const size_t nx = ModelArray::size(ModelArray::Dimension::X); + const size_t ny = ModelArray::size(ModelArray::Dimension::Y); + + // Read restarts from file and check they take the expected values + for (const auto [fieldName, modelarray] : grid.getModelState(diagnosticFilename).data) { + REQUIRE(fieldName == hsnowName); + for (size_t j = 0; j < ny; ++j) { + for (size_t i = 0; i < nx; ++i) { + REQUIRE(modelarray(i, j) == doctest::Approx(0.15)); + } + } + } + + // Remove the diagnostic files + int rank; + MPI_Comm_rank(test_comm, &rank); + if (rank == 0) { + std::filesystem::remove("diagnostic_2023-03-17T17:11:00Z-2023-03-17T20:10:59Z.nc"); + std::filesystem::remove("diagnostic_2023-03-17T20:11:00Z-2023-03-17T23:10:59Z.nc"); + } + + xiosHandler.context_finalize(); + Finalizer::finalize(); +} +} diff --git a/core/test/XiosReadForcing_test.cpp b/core/test/XiosReadForcing_test.cpp new file mode 100644 index 000000000..e4f1c4175 --- /dev/null +++ b/core/test/XiosReadForcing_test.cpp @@ -0,0 +1,115 @@ +/*! + * @author Joe Wallwork + * @brief Tests for XIOS functionality for reading forcings + * @details The functionality of reading forcings via XIOS is tested. + */ +#include +#undef INFO + +#include "StructureModule/include/ParametricGrid.hpp" +#include "include/Finalizer.hpp" +#include "include/Model.hpp" +#include "include/ModelMPI.hpp" +#include "include/NextsimModule.hpp" +#include "include/ParaGridIO.hpp" +#include "include/Xios.hpp" +#include "include/gridNames.hpp" + +#include + +const std::string testFilesDir = TEST_FILES_DIR; +const std::string inputFilename = testFilesDir + "/xios_test_input.nc"; +const std::string forcingFilename = testFilesDir + "/xios_test_forcing.nc"; + +static const int DGCOMP = 6; +static const int DGSTRESSCOMP = 8; +static const int CGDEGREE = 2; + +namespace Nextsim { + +/*! + * TestXiosReadForcing + * + * Test reading of restarts via `readForcingTimeStatic`. + */ +MPI_TEST_CASE("TestXiosReadForcing", 2) +{ + std::stringstream config; + config << "[model]" << std::endl; + config << "start = 2023-03-17T17:11:00Z" << std::endl; + config << "stop = 2023-03-17T23:11:00Z" << std::endl; + config << "time_step = P0-0T01:30:00" << std::endl; + config << "init_file = " << inputFilename << std::endl; + config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; + config << "[XiosForcing]" << std::endl; + config << "filename = " << forcingFilename << std::endl; + config << "field_names = " << hsnowName << std::endl; + config << "period = P0-0T01:30:00" << std::endl; + std::unique_ptr pcstream(new std::stringstream(config.str())); + Configurator::addStream(std::move(pcstream)); + + // Create ModelMPI instance based off the test communicator + auto& modelMPI = ModelMPI::getInstance(test_comm); + + // Create a Model and configure it so that time options are parsed + // TODO: Use Model.configure for consistency with the rest of the model + Model model; + model.configureRestarts(); + model.configureTime(); + + // Get the Xios singleton instance and check it's initialized + // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this + // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to + // Model::configureTime() above. + Xios& xiosHandler = Xios::getInstance(); + + // Create ParametricGrid and ParaGridIO instances + // NOTE: XIOS axes, domains, and grids are created by the ParaGridIO constructor + Module::setImplementation("Nextsim::ParametricGrid"); + ParametricGrid grid; + ParaGridIO* pio = new ParaGridIO(grid); + grid.setIO(pio); + + xiosHandler.close_context_definition(); + + // Check the input file exists + REQUIRE(std::filesystem::exists(forcingFilename)); + + // Check calendar step is zero initially + REQUIRE(xiosHandler.getCalendarStep() == 0); + + // Deduce the local lengths of the two dimensions + const size_t nx = ModelArray::size(ModelArray::Dimension::X); + const size_t ny = ModelArray::size(ModelArray::Dimension::Y); + REQUIRE(ModelArray::nComponents(ModelArray::Type::DG) == DGCOMP); + REQUIRE(ModelArray::size(ModelArray::Dimension::DG) == DGCOMP); + + // Simulate 4 iterations (timesteps), reading forcing data at each + ModelMetadata& metadata = ModelMetadata::getInstance(); + const Duration& timestep = metadata.stepLength(); + // TODO: Avoid making configGetForcingFieldNames public? + auto forcingFieldNames = xiosHandler.configGetForcingFieldNames(); + for (int ts = 0; ts <= 4; ts++) { + + // Read forcings from file and check they take the expected values + const TimePoint& time = xiosHandler.getCurrentDate(); + const ModelState forcings + = pio->readForcingTimeStatic(forcingFieldNames, time, forcingFilename); + for (const auto& [fieldName, modelarray] : forcings.data) { + REQUIRE(fieldName == hsnowName); + for (size_t j = 0; j < ny; ++j) { + for (size_t i = 0; i < nx; ++i) { + REQUIRE(modelarray(i, j) == doctest::Approx(0.1 * ts)); + } + } + } + + // Update the current timestep and verify it's updated in XIOS + metadata.incrementTime(timestep); + REQUIRE(xiosHandler.getCalendarStep() == ts + 1); + } + + xiosHandler.context_finalize(); + Finalizer::finalize(); +} +} diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosReadRestart_test.cpp similarity index 62% rename from core/test/XiosRead_test.cpp rename to core/test/XiosReadRestart_test.cpp index f7385ac1c..56141708b 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosReadRestart_test.cpp @@ -1,9 +1,7 @@ /*! * @author Joe Wallwork - * @brief Tests for XIOS read functionality - * @details - * This test is designed to test the file reading functionality of the C++ - * interface for XIOS. + * @brief Tests for XIOS functionality for reading restart files. + * @details The functionality of reading restarts via XIOS is tested. */ #include #undef INFO @@ -20,8 +18,8 @@ #include const std::string testFilesDir = TEST_FILES_DIR; -const std::string restartFilename = testFilesDir + "/xios_test_output.nc"; -const std::string forcingFilename = testFilesDir + "/xios_test_forcing.nc"; +const std::string restartFilename + = testFilesDir + "/restart_2023-03-17T17:11:00Z-2023-03-17T20:10:59Z.nc"; static const int DGCOMP = 6; static const int DGSTRESSCOMP = 8; @@ -30,12 +28,11 @@ static const int CGDEGREE = 2; namespace Nextsim { /*! - * TestXiosRead + * TestXiosReadRestart * - * This function tests the file reading functionality of the C++ interface for XIOS for fields with - * two and three spatial dimensions. The test runs with two MPI ranks. + * Test reading of restarts via `getModelState`. */ -MPI_TEST_CASE("TestXiosRead", 2) +MPI_TEST_CASE("TestXiosReadRestart", 2) { std::stringstream config; config << "[model]" << std::endl; @@ -43,15 +40,11 @@ MPI_TEST_CASE("TestXiosRead", 2) config << "stop = 2023-03-17T23:11:00Z" << std::endl; config << "time_step = P0-0T01:30:00" << std::endl; config << "init_file = " << restartFilename << std::endl; - config << "restart_period = P0-0T01:30:00" << std::endl; + config << "restart_period = P0-0T03:00:00" << std::endl; config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; config << "[XiosInput]" << std::endl; config << "field_names = " << maskName << "," << coordsName << "," << hiceName << "," << ticeName << "," << uName << std::endl; - config << "[XiosForcing]" << std::endl; - config << "filename = " << forcingFilename << std::endl; - config << "field_names = " << hsnowName << std::endl; - config << "period = P0-0T01:30:00" << std::endl; std::unique_ptr pcstream(new std::stringstream(config.str())); Configurator::addStream(std::move(pcstream)); @@ -79,12 +72,11 @@ MPI_TEST_CASE("TestXiosRead", 2) xiosHandler.close_context_definition(); - // Check the input files exist + // Check the input file exists if (!std::filesystem::exists(restartFilename)) { throw std::runtime_error( - "XiosRead_test: Input file not found. Did you run XiosWrite_test?"); + "XiosReadRestart_test: Input file not found. Did you run XiosWrite_test?"); } - REQUIRE(std::filesystem::exists(forcingFilename)); // Check calendar step is zero initially REQUIRE(xiosHandler.getCalendarStep() == 0); @@ -98,6 +90,7 @@ MPI_TEST_CASE("TestXiosRead", 2) // Read restarts from file and check they take the expected values int rank; MPI_Comm_rank(test_comm, &rank); + float ts = 2; // Corresponds to 2023-03-17T20:10:59Z for (const auto [fieldName, modelarray] : grid.getModelState(restartFilename).data) { if (fieldName == maskName) { for (size_t j = 0; j < ny; ++j) { @@ -108,19 +101,22 @@ MPI_TEST_CASE("TestXiosRead", 2) } else if (fieldName == coordsName) { for (size_t j = 0; j < ny + 1; ++j) { for (size_t i = 0; i < nx + 1; ++i) { + float expected_x; if (rank == 0) { - REQUIRE(modelarray.components({ i, j })[0] == doctest::Approx(i)); + expected_x = ts * i; } else { - REQUIRE(modelarray.components({ i, j })[0] == doctest::Approx(i + 2)); + expected_x = ts * (i + 2); } - REQUIRE(modelarray.components({ i, j })[1] == doctest::Approx(j)); + const float expected_y = ts * j; + REQUIRE(modelarray.components({ i, j })[0] == doctest::Approx(expected_x)); + REQUIRE(modelarray.components({ i, j })[1] == doctest::Approx(expected_y)); } } } else if (fieldName == hiceName) { for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { for (size_t d = 0; d < DGCOMP; ++d) { - float expected = 1.0 * (d + DGCOMP * (i + nx * j)); + const float expected = ts * (d + DGCOMP * (i + nx * j)); REQUIRE(modelarray.components({ i, j })[d] == doctest::Approx(expected)); } } @@ -129,7 +125,7 @@ MPI_TEST_CASE("TestXiosRead", 2) for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { for (size_t d = 0; d < DGSTRESSCOMP; ++d) { - float expected = 2.0 * (d + DGSTRESSCOMP * (i + nx * j)); + const float expected = 2.0 * ts * (d + DGSTRESSCOMP * (i + nx * j)); REQUIRE(modelarray.components({ i, j })[d] == doctest::Approx(expected)); } } @@ -137,47 +133,22 @@ MPI_TEST_CASE("TestXiosRead", 2) } else if (fieldName == uName) { for (size_t j = 0; j < CGDEGREE * ny + 1; ++j) { for (size_t i = 0; i < CGDEGREE * nx + 1; ++i) { + float expected; if (rank == 0) { - REQUIRE(modelarray(i, j) == doctest::Approx((i + 1) * (j + 1))); + expected = ts * (i + 1) * (j + 1); } else { - REQUIRE(modelarray(i, j) == doctest::Approx((i + 5) * (j + 1))); + expected = ts * (i + 5) * (j + 1); } + REQUIRE(modelarray(i, j) == doctest::Approx(expected)); } } } } - // Simulate 4 iterations (timesteps), reading forcing data at each - ModelMetadata& metadata = ModelMetadata::getInstance(); - const Duration& timestep = metadata.stepLength(); - // TODO: Avoid making configGetForcingFieldNames public? - auto forcingFieldNames = xiosHandler.configGetForcingFieldNames(); - for (int ts = 0; ts <= 4; ts++) { - - // Read forcings from file and check they take the expected values - const TimePoint& time = xiosHandler.getCurrentDate(); - const ModelState forcings - = pio->readForcingTimeStatic(forcingFieldNames, time, forcingFilename); - for (const auto& [fieldName, modelarray] : forcings.data) { - REQUIRE(fieldName == hsnowName); - for (size_t j = 0; j < ny; ++j) { - for (size_t i = 0; i < nx; ++i) { - REQUIRE(modelarray(i, j) == doctest::Approx(0.1 * ts)); - } - } - } - - // Update the current timestep and verify it's updated in XIOS - metadata.incrementTime(timestep); - REQUIRE(xiosHandler.getCalendarStep() == ts + 1); - } - + // Remove the restart files if (rank == 0) { - // TODO: Re-enable file splitting (#898) - // std::filesystem::remove("xios_test_output_20230317171100-20230317201059.nc"); - // std::filesystem::remove("xios_test_output_20230317201100-20230317231059.nc"); - std::filesystem::remove("xios_test_output.nc"); - std::filesystem::remove("xios_test_diagnostic.nc"); + std::filesystem::remove("restart_20230317171100-20230317201059.nc"); + std::filesystem::remove("restart_20230317201100-20230317231059.nc"); } xiosHandler.context_finalize(); diff --git a/core/test/XiosWriteDiagnostic_test.cpp b/core/test/XiosWriteDiagnostic_test.cpp new file mode 100644 index 000000000..675c6397a --- /dev/null +++ b/core/test/XiosWriteDiagnostic_test.cpp @@ -0,0 +1,145 @@ +/*! + * @author Joe Wallwork + * @brief Tests for XIOS functionality for writing diagnostic files. + * @details The functionality of writing diagnostics via XIOS is tested. + */ +#include +#undef INFO + +#include "StructureModule/include/ParametricGrid.hpp" +#include "include/Finalizer.hpp" +#include "include/Model.hpp" +#include "include/ModelMPI.hpp" +#include "include/NextsimModule.hpp" +#include "include/ParaGridIO.hpp" +#include "include/Xios.hpp" +#include "include/gridNames.hpp" + +#include + +const std::string testFilesDir = TEST_FILES_DIR; +const std::string inputFilename = testFilesDir + "/xios_test_input.nc"; +const std::string diagnosticFilename = testFilesDir + "/diagnostic%Y-%m-%dT%H:%M:%SZ.nc"; + +static const int DGCOMP = 6; +static const int DGSTRESSCOMP = 8; +static const int CGDEGREE = 2; + +namespace Nextsim { + +/*! + * TestXiosWrite + * + * Test writing of diagnostics via `writeDiagnosticTime`. + */ +MPI_TEST_CASE("TestXiosWrite", 2) +{ + std::stringstream config; + config << "[model]" << std::endl; + config << "start = 2023-03-17T17:11:00Z" << std::endl; + config << "stop = 2023-03-17T23:11:00Z" << std::endl; + config << "time_step = P0-0T01:30:00" << std::endl; + config << "init_file = " << inputFilename << std::endl; + config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; + config << "restart_period = P0-0T03:00:00" << std::endl; + config << "[XiosDiagnostic]" << std::endl; + config << "filename = " << diagnosticFilename << std::endl; + config << "field_names = " << hsnowName << std::endl; + config << "period = P0-0T03:00:00" << std::endl; + std::unique_ptr pcstream(new std::stringstream(config.str())); + Configurator::addStream(std::move(pcstream)); + + // Create ModelMPI instance based off the test communicator + auto& modelMPI = ModelMPI::getInstance(test_comm); + + // Create a Model and configure it so that time options are parsed + // TODO: Use Model.configure for consistency with the rest of the model + Model model; + model.configureRestarts(); + model.configureTime(); + + // Get the Xios singleton instance and check it's initialized + // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this + // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to + // Model::configureTime() above. + Xios& xiosHandler = Xios::getInstance(); + + // Set ModelArray dimensions + const size_t nx = ModelArray::size(ModelArray::Dimension::X); + const size_t ny = ModelArray::size(ModelArray::Dimension::Y); + REQUIRE(ModelArray::nComponents(ModelArray::Type::DG) == DGCOMP); + REQUIRE(ModelArray::size(ModelArray::Dimension::DG) == DGCOMP); + + // Create ParametricGrid and ParaGridIO instances + // NOTE: XIOS axes, domains, and grids are created by the ParaGridIO constructor + Module::setImplementation("Nextsim::ParametricGrid"); + ParametricGrid grid; + ParaGridIO* pio = new ParaGridIO(grid); + grid.setIO(pio); + + // Set field type for diagnostics + xiosHandler.setFieldType(hsnowName, ModelArray::Type::H); + + xiosHandler.close_context_definition(); + + // Create some fake data to test writing methods + HField mask(ModelArray::Type::H); + mask.resize(); + for (size_t j = 0; j < ny; ++j) { + for (size_t i = 0; i < nx; ++i) { + mask(i, j) = j >= 1 ? 1.0 : 0.0; + } + } + VertexField coordinates(ModelArray::Type::VERTEX); + coordinates.resize(); + DGField hice(ModelArray::Type::DG); + hice.resize(); + DGSField tice(ModelArray::Type::DGSTRESS); + tice.resize(); + CGField uice(ModelArray::Type::CG); + uice.resize(); + HField hsnow(ModelArray::Type::H); + hsnow.resize(); + + // Check files with the expected names don't exist yet + REQUIRE_FALSE(std::filesystem::exists("diagnostic*.nc")); + + // Check calendar step is zero initially + REQUIRE(xiosHandler.getCalendarStep() == 0); + + // Simulate 4 iterations (timesteps) + ModelMetadata& metadata = ModelMetadata::getInstance(); + const Duration& timestep = metadata.stepLength(); + int rank; + MPI_Comm_rank(test_comm, &rank); + for (int ts = 0; ts <= 4; ts++) { + + // Update diagnostics + for (size_t j = 0; j < ny; ++j) { + for (size_t i = 0; i < nx; ++i) { + hsnow(i, j) = 0.1 * ts; + } + } + + // Set up ModelStates for diagnostics and write out + ModelState diagnostics = { { + { hsnowName, hsnow }, + }, + {} }; + pio->writeDiagnosticTime(diagnostics, diagnosticFilename); + + // Update the current timestep and verify it's updated in XIOS + metadata.incrementTime(timestep); + REQUIRE(xiosHandler.getCalendarStep() == ts + 1); + } + + // Check the files have indeed been created + // NOTE: Don't remove them because their contents are checked in XiosReadDiagnostic_test + REQUIRE(std::filesystem::exists("diagnostic_2023-03-17T17:11:00Z-2023-03-17T20:10:59Z.nc")); + REQUIRE(std::filesystem::exists("diagnostic_2023-03-17T20:11:00Z-2023-03-17T23:10:59Z.nc")); + + xiosHandler.context_finalize(); + Finalizer::finalize(); +} + +} diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWriteRestart_test.cpp similarity index 56% rename from core/test/XiosWrite_test.cpp rename to core/test/XiosWriteRestart_test.cpp index 5a6069a47..88e327044 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWriteRestart_test.cpp @@ -1,9 +1,7 @@ /*! * @author Joe Wallwork - * @brief Tests for XIOS write functionality - * @details - * This test is designed to test the file writing functionality of the C++ - * interface for XIOS. + * @brief Tests for XIOS functionality for writing restart files. + * @details The functionality of writing restarts via XIOS is tested. */ #include #undef INFO @@ -20,9 +18,8 @@ #include const std::string testFilesDir = TEST_FILES_DIR; -const std::string restartInputFilename = testFilesDir + "/xios_test_input.nc"; -const std::string restartOutputFilename = testFilesDir + "/xios_test_output.nc"; -const std::string diagnosticFilename = testFilesDir + "/xios_test_diagnostic.nc"; +const std::string inputFilename = testFilesDir + "/xios_test_input.nc"; +const std::string restartFilename = testFilesDir + "/restart%Y-%m-%dT%H:%M:%SZ.nc"; static const int DGCOMP = 6; static const int DGSTRESSCOMP = 8; @@ -33,8 +30,7 @@ namespace Nextsim { /*! * TestXiosWrite * - * This function tests the file writing functionality of the C++ interface for XIOS for fields with - * two and three spatial dimensions. The test runs with two MPI ranks. + * Test writing of restarts via `dumpModelState`. */ MPI_TEST_CASE("TestXiosWrite", 2) { @@ -43,20 +39,14 @@ MPI_TEST_CASE("TestXiosWrite", 2) config << "start = 2023-03-17T17:11:00Z" << std::endl; config << "stop = 2023-03-17T23:11:00Z" << std::endl; config << "time_step = P0-0T01:30:00" << std::endl; - config << "init_file = " << restartInputFilename << std::endl; - config << "restart_file = " << restartOutputFilename << std::endl; + config << "init_file = " << inputFilename << std::endl; + config << "restart_file = " << restartFilename << std::endl; config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; - config << "restart_period = P0-0T01:30:00" << std::endl; + config << "restart_period = P0-0T03:00:00" << std::endl; config << "[XiosOutput]" << std::endl; config << "field_names = " << maskName << "," << coordsName << "," << hiceName << "," << ticeName << "," << uName << std::endl; - config << "period = P0-0T01:30:00" << std::endl; - // TODO: Re-enable file splitting (#898) - // config << "split_period = P0-0T03:00:00" << std::endl; - config << "[XiosDiagnostic]" << std::endl; - config << "filename = " << diagnosticFilename << std::endl; - config << "field_names = " << hsnowName << std::endl; - config << "period = P0-0T01:30:00" << std::endl; + config << "period = P0-0T03:00:00" << std::endl; std::unique_ptr pcstream(new std::stringstream(config.str())); Configurator::addStream(std::move(pcstream)); @@ -88,13 +78,12 @@ MPI_TEST_CASE("TestXiosWrite", 2) ParaGridIO* pio = new ParaGridIO(grid); grid.setIO(pio); - // Set field types + // Set field types for restarts xiosHandler.setFieldType(maskName, ModelArray::Type::H); xiosHandler.setFieldType(coordsName, ModelArray::Type::VERTEX); xiosHandler.setFieldType(hiceName, ModelArray::Type::DG); xiosHandler.setFieldType(ticeName, ModelArray::Type::DGSTRESS); xiosHandler.setFieldType(uName, ModelArray::Type::CG); - xiosHandler.setFieldType(hsnowName, ModelArray::Type::H); xiosHandler.close_context_definition(); @@ -108,54 +97,17 @@ MPI_TEST_CASE("TestXiosWrite", 2) } VertexField coordinates(ModelArray::Type::VERTEX); coordinates.resize(); - int rank; - MPI_Comm_rank(test_comm, &rank); - for (size_t j = 0; j < ny + 1; ++j) { - for (size_t i = 0; i < nx + 1; ++i) { - if (rank == 0) { - coordinates.components({ i, j })[0] = (double)i; - coordinates.components({ i, j })[1] = (double)j; - } else { - coordinates.components({ i, j })[0] = (double)(i + 2); - coordinates.components({ i, j })[1] = (double)j; - } - } - } DGField hice(ModelArray::Type::DG); hice.resize(); - for (size_t j = 0; j < ny; ++j) { - for (size_t i = 0; i < nx; ++i) { - for (size_t d = 0; d < DGCOMP; ++d) { - hice.components({ i, j })[d] = 1.0 * (d + DGCOMP * (i + nx * j)); - } - } - } DGSField tice(ModelArray::Type::DGSTRESS); tice.resize(); - for (size_t j = 0; j < ny; ++j) { - for (size_t i = 0; i < nx; ++i) { - for (size_t d = 0; d < DGSTRESSCOMP; ++d) { - tice.components({ i, j })[d] = 2.0 * (d + DGSTRESSCOMP * (i + nx * j)); - } - } - } CGField uice(ModelArray::Type::CG); uice.resize(); - for (size_t j = 0; j < CGDEGREE * ny + 1; ++j) { - for (size_t i = 0; i < CGDEGREE * nx + 1; ++i) { - if (rank == 0) { - uice(i, j) = (double)((i + 1) * (j + 1)); - } else { - uice(i, j) = (double)((i + 5) * (j + 1)); - } - } - } HField hsnow(ModelArray::Type::H); hsnow.resize(); // Check files with the expected names don't exist yet - REQUIRE_FALSE(std::filesystem::exists("xios_test_output*.nc")); - REQUIRE_FALSE(std::filesystem::exists("xios_test_diagnostic*.nc")); + REQUIRE_FALSE(std::filesystem::exists("restart*.nc")); // Check calendar step is zero initially REQUIRE(xiosHandler.getCalendarStep() == 0); @@ -163,20 +115,47 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Simulate 4 iterations (timesteps) ModelMetadata& metadata = ModelMetadata::getInstance(); const Duration& timestep = metadata.stepLength(); - for (int ts = 1; ts <= 4; ts++) { - - // Update the current timestep and verify it's updated in XIOS - metadata.incrementTime(timestep); - REQUIRE(xiosHandler.getCalendarStep() == ts); - - // Update diagnostics + int rank; + MPI_Comm_rank(test_comm, &rank); + for (int ts = 0; ts <= 4; ts++) { + + // Update restart fields + for (size_t j = 0; j < ny + 1; ++j) { + for (size_t i = 0; i < nx + 1; ++i) { + if (rank == 0) { + coordinates.components({ i, j })[0] = 1.0 * ts * i; + coordinates.components({ i, j })[1] = 1.0 * ts * j; + } else { + coordinates.components({ i, j })[0] = 1.0 * ts * (i + 2); + coordinates.components({ i, j })[1] = 1.0 * ts * j; + } + } + } for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { - hsnow(i, j) = 0.1 * ts; + for (size_t d = 0; d < DGCOMP; ++d) { + hice.components({ i, j })[d] = 1.0 * ts * (d + DGCOMP * (i + nx * j)); + } + } + } + for (size_t j = 0; j < CGDEGREE * ny + 1; ++j) { + for (size_t i = 0; i < CGDEGREE * nx + 1; ++i) { + if (rank == 0) { + uice(i, j) = 1.0 * ts * ((i + 1) * (j + 1)); + } else { + uice(i, j) = 1.0 * ts * ((i + 5) * (j + 1)); + } + } + } + for (size_t j = 0; j < ny; ++j) { + for (size_t i = 0; i < nx; ++i) { + for (size_t d = 0; d < DGSTRESSCOMP; ++d) { + tice.components({ i, j })[d] = 2.0 * ts * (d + DGSTRESSCOMP * (i + nx * j)); + } } } - // Set up two ModelStates: one for restarts and one for diagnostics + // Set up ModelState for restarts and write out ModelState restarts = { { { maskName, mask }, { coordsName, coordinates }, @@ -185,23 +164,17 @@ MPI_TEST_CASE("TestXiosWrite", 2) { uName, uice }, }, {} }; - ModelState diagnostics = { { - { hsnowName, hsnow }, - }, - {} }; + grid.dumpModelState(restarts, restartFilename, true); - // Write out diagnostics and then restarts - pio->writeDiagnosticTime(diagnostics, diagnosticFilename); - grid.dumpModelState(restarts, restartOutputFilename, true); + // Update the current timestep and verify it's updated in XIOS + metadata.incrementTime(timestep); + REQUIRE(xiosHandler.getCalendarStep() == ts + 1); } // Check the files have indeed been created - // NOTE: We don't remove them because they are used in XiosRead_test - // TODO: Re-enable file splitting (#898) - // REQUIRE(std::filesystem::exists("xios_test_output_20230317171100-20230317201059.nc")); - // REQUIRE(std::filesystem::exists("xios_test_output_20230317201100-20230317231059.nc")); - REQUIRE(std::filesystem::exists("xios_test_output.nc")); - REQUIRE(std::filesystem::exists("xios_test_diagnostic.nc")); + // NOTE: Don't remove them because their contents are checked in XiosReadRestart_test + REQUIRE(std::filesystem::exists("restart_2023-03-17T17:11:00Z-2023-03-17T20:10:59Z.nc")); + REQUIRE(std::filesystem::exists("restart_2023-03-17T20:11:00Z-2023-03-17T23:10:59Z.nc")); xiosHandler.context_finalize(); Finalizer::finalize(); diff --git a/core/test/xios_test_forcing.cdl b/core/test/xios_test_forcing.cdl index 4f6f1f754..5b5e20747 100644 --- a/core/test/xios_test_forcing.cdl +++ b/core/test/xios_test_forcing.cdl @@ -26,8 +26,8 @@ variables: time_instant:standard_name = "time" ; time_instant:long_name = "Time axis" ; time_instant:calendar = "gregorian" ; - time_instant:units = "seconds since 2020-01-23 00:08:15" ; - time_instant:time_origin = "2020-01-23 00:08:15" ; + time_instant:units = "seconds since 1970-01-01 00:00:00" ; + time_instant:time_origin = "1970-01-01 00:00:00" ; time_instant:bounds = "time_instant_bounds" ; double time_instant_bounds(time_counter, axis_nbounds) ; double time_counter(time_counter) ; @@ -35,8 +35,8 @@ variables: time_counter:standard_name = "time" ; time_counter:long_name = "Time axis" ; time_counter:calendar = "gregorian" ; - time_counter:units = "seconds since 2020-01-23 00:08:15" ; - time_counter:time_origin = "2020-01-23 00:08:15" ; + time_counter:units = "seconds since 1970-01-01 00:00:00" ; + time_counter:time_origin = "1970-01-01 00:00:00" ; time_counter:bounds = "time_counter_bounds" ; double time_counter_bounds(time_counter, axis_nbounds) ; float hsnow(time_counter, y_dim, x_dim) ; @@ -55,23 +55,23 @@ variables: data: - time_instant = 99334965, 99340365, 99345765, 99351165, 99356565 ; + time_instant = 1679074860, 1679078460, 1679082060, 1679085660, 1679089260 ; time_instant_bounds = - 99334965, 99334965, - 99340365, 99340365, - 99345765, 99345765, - 99351165, 99351165, - 99356565, 99356565 ; + 1679074860, 1679074860, + 1679078460, 1679078460, + 1679082060, 1679082060, + 1679085660, 1679085660, + 1679089260, 1679089260 ; - time_counter = 99334965, 99340365, 99345765, 99351165, 99356565 ; + time_counter = 1679074860, 1679078460, 1679082060, 1679085660, 1679089260 ; time_counter_bounds = - 99334965, 99334965, - 99340365, 99340365, - 99345765, 99345765, - 99351165, 99351165, - 99356565, 99356565 ; + 1679074860, 1679074860, + 1679078460, 1679078460, + 1679082060, 1679082060, + 1679085660, 1679085660, + 1679089260, 1679089260 ; hsnow = 0.0, 0.0, 0.0, 0.0, diff --git a/core/test/xios_test_input.cdl b/core/test/xios_test_input.cdl index 49720846d..94d3f785e 100644 --- a/core/test/xios_test_input.cdl +++ b/core/test/xios_test_input.cdl @@ -41,33 +41,18 @@ variables: y_cg:standard_name = "CG latitude" ; y_cg:long_name = "Continuous Galerkin latitude" ; y_cg:units = "degrees_north" ; - float DGAxis(dg_comp) ; - DGAxis:axis = "DG" ; - DGAxis:standard_name = "DG DoFs" ; - DGAxis:long_name = "Discontinuous Galerkin number of degrees of freedom" ; - DGAxis:units = "-" ; - float DGSAxis(dgstress_comp) ; - DGSAxis:axis = "DGS" ; - DGSAxis:standard_name = "DGS DoFs" ; - DGSAxis:long_name = "Discontinuous Galerkin stress component number of degrees of freedom" ; - DGSAxis:units = "-" ; - float VertexAxis(ncoords) ; - VertexAxis:axis = "DIM" ; - VertexAxis:standard_name = "Dimension" ; - VertexAxis:long_name = "Spatial dimension" ; - VertexAxis:units = "-" ; float coords(y_vertex, x_vertex, ncoords) ; coords:online_operation = "once" ; - coords:coordinates = "lat lon VertexAxis" ; + coords:coordinates = "lat lon ncoords" ; float hice(y_dim, x_dim, dg_comp) ; hice:online_operation = "once" ; - hice:coordinates = "lat lon DGAxis" ; + hice:coordinates = "lat lon dg_comp" ; float mask(y_dim, x_dim) ; mask:online_operation = "once" ; mask:coordinates = "lat lon" ; float tice(y_dim, x_dim, dgstress_comp) ; tice:online_operation = "once" ; - tice:coordinates = "lat lon DGSAxis" ; + tice:coordinates = "lat lon dgstress_comp" ; float u(y_cg, x_cg) ; u:online_operation = "once" ; u:coordinates = "lat lon" ; diff --git a/docs/xios.rst b/docs/xios.rst index aba2ea4a9..aece8dc34 100644 --- a/docs/xios.rst +++ b/docs/xios.rst @@ -117,13 +117,15 @@ The ``field_names`` entry may contain a single field name or a comma-separated list. Note that all of the ``XiosOutput``, ``XiosInput``, ``XiosDiagnostic``, and ``XiosForcing`` sections are optional. -When writing out files, the ``split_period`` option can be included in either of -the ``XiosOutput`` or ``XiosDiagnostic`` sections, which instructs XIOS to -create a separate output file for each such period. For a given period, a file -will be written with a name of the form -``_-.nc``, where ```` is the -user-provided file name and ```` and ```` are the start -and end of the associated period. +Restart and diagnostic file names may include format strings such as +``restart%Y-%m-%dT%H:%M:%SZ.nc`` or ``diagnostic%Y-%m-%dT%H:%M:%SZ.nc`` (in +fact, these are the defaults). When writing out restarts and diagnostics, a +separate file is produced for each restart period, with filename of the format +``_-.nc``, where ```` and +```` are the start and end of the associated period, written using the +provided format string. Restart files contain the state at the beginning of the +time window, whereas diagnostics files are averaged over the timesteps in the +window. As elsewhere in the model, the configuration values above are all parsed by calling the ``Model.configure`` member function. Since building with XIOS