diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 0776bcf98..06debbbed 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -23,6 +23,7 @@ #include "include/ModelMetadata.hpp" #include "include/ParallelNetcdfFile.hpp" #include "include/Xios.hpp" +#include "include/gridNames.hpp" #include #include @@ -54,11 +55,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" } }; @@ -85,9 +84,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 +96,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 " @@ -504,83 +501,111 @@ void Xios::setupDomains() // Set domain extents based on model metadata size_t counter = 0; for (ModelArray::Dimension dim : ModelArray::typeDimensions[type]) { - const std::string domainName = ModelArray::definedDimensions[dim].name; + const std::string dimName = ModelArray::definedDimensions[dim].altName; if (counter == 0) { + int ni_glo; + int ni; + int ibegin; if (dim == ModelArray::Dimension::X) { - cxios_set_domain_ni_glo(domain, metadata.getGlobalExtentX()); - cxios_set_domain_ni(domain, metadata.getLocalExtentX()); - cxios_set_domain_ibegin(domain, metadata.getLocalCornerX()); + ni_glo = metadata.getGlobalExtentX(); + ni = metadata.getLocalExtentX(); + ibegin = metadata.getLocalCornerX(); } else if (dim == ModelArray::Dimension::XVERTEX) { - cxios_set_domain_ni_glo(domain, metadata.getGlobalExtentX() + 1); - cxios_set_domain_ni(domain, metadata.getLocalExtentX() + 1); - cxios_set_domain_ibegin(domain, metadata.getLocalCornerX()); + ni_glo = metadata.getGlobalExtentX() + 1; + ni = metadata.getLocalExtentX() + 1; + ibegin = metadata.getLocalCornerX(); } else if (dim == ModelArray::Dimension::XCG) { - cxios_set_domain_ni_glo(domain, CGDEGREE * metadata.getGlobalExtentX() + 1); - cxios_set_domain_ni(domain, CGDEGREE * metadata.getLocalExtentX() + 1); - cxios_set_domain_ibegin(domain, CGDEGREE * metadata.getLocalCornerX()); + ni_glo = CGDEGREE * metadata.getGlobalExtentX() + 1; + ni = CGDEGREE * metadata.getLocalExtentX() + 1; + ibegin = CGDEGREE * metadata.getLocalCornerX(); } else { throw std::runtime_error( - "Xios: Could not set domain extents based on dimension '" - + ModelArray::definedDimensions.at(dim).name + "'"); + "Xios: Could not set domain extents based on dimension '" + dimName + "'"); } + cxios_set_domain_ni_glo(domain, ni_glo); if (!cxios_is_defined_domain_ni_glo(domain)) { throw std::runtime_error( "Xios: Failed to set global x-size for domain '" + domainId + "'"); } + cxios_set_domain_ni(domain, ni); if (!cxios_is_defined_domain_ni(domain)) { throw std::runtime_error( "Xios: Failed to set local x-size for domain '" + domainId + "'"); } + cxios_set_domain_ibegin(domain, ibegin); if (!cxios_is_defined_domain_ibegin(domain)) { throw std::runtime_error( "Xios: Failed to set local starting x-index for domain '" + domainId + "'"); } - cxios_set_domain_dim_i_name(domain, domainName.c_str(), domainName.length()); + std::vector lonvalue; + for (int i = 0; i < ni; i++) { + lonvalue.push_back(ibegin + i); + } + cxios_set_domain_lonvalue_1d(domain, lonvalue.data(), &ni); + if (!cxios_is_defined_domain_lonvalue_1d(domain)) { + throw std::runtime_error( + "Xios: Failed to set local x-indices for domain '" + domainId + "'"); + } + cxios_set_domain_dim_i_name(domain, dimName.c_str(), dimName.length()); if (!cxios_is_defined_domain_dim_i_name(domain)) { throw std::runtime_error( "Xios: Failed to set x-coordinate name for domain '" + domainId + "'"); } - cxios_set_domain_lon_name(domain, domainName.c_str(), domainName.length()); + cxios_set_domain_lon_name(domain, dimName.c_str(), dimName.length()); if (!cxios_is_defined_domain_lon_name(domain)) { throw std::runtime_error( "Xios: Failed to set longitude name for domain '" + domainId + "'"); } } else if (counter == 1) { + int nj_glo; + int nj; + int jbegin; if (dim == ModelArray::Dimension::Y) { - cxios_set_domain_nj_glo(domain, metadata.getGlobalExtentY()); - cxios_set_domain_nj(domain, metadata.getLocalExtentY()); - cxios_set_domain_jbegin(domain, metadata.getLocalCornerY()); + nj_glo = metadata.getGlobalExtentY(); + nj = metadata.getLocalExtentY(); + jbegin = metadata.getLocalCornerY(); } else if (dim == ModelArray::Dimension::YVERTEX) { - cxios_set_domain_nj_glo(domain, metadata.getGlobalExtentY() + 1); - cxios_set_domain_nj(domain, metadata.getLocalExtentY() + 1); - cxios_set_domain_jbegin(domain, metadata.getLocalCornerY()); + nj_glo = metadata.getGlobalExtentY() + 1; + nj = metadata.getLocalExtentY() + 1; + jbegin = metadata.getLocalCornerY(); } else if (dim == ModelArray::Dimension::YCG) { - cxios_set_domain_nj_glo(domain, CGDEGREE * metadata.getGlobalExtentY() + 1); - cxios_set_domain_nj(domain, CGDEGREE * metadata.getLocalExtentY() + 1); - cxios_set_domain_jbegin(domain, CGDEGREE * metadata.getLocalCornerY()); + nj_glo = CGDEGREE * metadata.getGlobalExtentY() + 1; + nj = CGDEGREE * metadata.getLocalExtentY() + 1; + jbegin = CGDEGREE * metadata.getLocalCornerY(); } else { throw std::runtime_error( - "Xios: Could not set domain extents based on dimension '" - + ModelArray::definedDimensions.at(dim).name + "'"); + "Xios: Could not set domain extents based on dimension '" + dimName + "'"); } + cxios_set_domain_nj_glo(domain, nj_glo); if (!cxios_is_defined_domain_nj_glo(domain)) { throw std::runtime_error( "Xios: Failed to set global y-size for domain '" + domainId + "'"); } + cxios_set_domain_nj(domain, nj); if (!cxios_is_defined_domain_nj(domain)) { throw std::runtime_error( "Xios: Failed to set local y-size for domain '" + domainId + "'"); } + cxios_set_domain_jbegin(domain, jbegin); if (!cxios_is_defined_domain_jbegin(domain)) { throw std::runtime_error( "Xios: Failed to set local starting y-index for domain '" + domainId + "'"); } - cxios_set_domain_dim_j_name(domain, domainName.c_str(), domainName.length()); + std::vector latvalue; + for (int j = 0; j < nj; j++) { + latvalue.push_back(jbegin + j); + } + cxios_set_domain_latvalue_1d(domain, latvalue.data(), &nj); + if (!cxios_is_defined_domain_latvalue_1d(domain)) { + throw std::runtime_error( + "Xios: Failed to set local y-indices for domain '" + domainId + "'"); + } + cxios_set_domain_dim_j_name(domain, dimName.c_str(), dimName.length()); if (!cxios_is_defined_domain_dim_j_name(domain)) { throw std::runtime_error( "Xios: Failed to set y-coordinate name for domain '" + domainId + "'"); } - cxios_set_domain_lat_name(domain, domainName.c_str(), domainName.length()); + cxios_set_domain_lat_name(domain, dimName.c_str(), dimName.length()); if (!cxios_is_defined_domain_lat_name(domain)) { throw std::runtime_error( "Xios: Failed to set latitude name for domain '" + domainId + "'"); @@ -770,11 +795,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)) { @@ -1032,6 +1062,9 @@ xios::CFile* Xios::getFile(const std::string fileId) */ void Xios::createFile(const std::string fileId, const int fieldType) { + ModelMetadata& metadata = ModelMetadata::getInstance(); + + // Create the file xios::CFile* file = NULL; bool exists; cxios_file_valid_id(&exists, fileId.c_str(), fileId.length()); @@ -1047,62 +1080,81 @@ void Xios::createFile(const std::string fileId, const int fieldType) throw std::runtime_error("Xios: Failed to create file '" + fileId + "'"); } - // Set file name - cxios_set_file_name(file, fileId.c_str(), fileId.length()); - if (!cxios_is_defined_file_name(file)) { - throw std::runtime_error("Xios: Failed to set name for file '" + fileId + "'"); - } - - // Determine whether the file is configured for reading or writing - bool readAccess = (fieldType == INPUT_RESTART || fieldType == FORCING); - bool writeAccess = (fieldType == OUTPUT_RESTART || fieldType == DIAGNOSTIC); - - // 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 + // Hard-code 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 + "'"); } - - // 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; + 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 + "'"); } - // 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"); + // Hard-code 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 + "'"); } - - // Set the file mode and some defaults - if (readAccess) { - setFileMode(fileId, "read"); - } else { - setFileMode(fileId, "write"); + 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 + // Get the names, modes, and fieldIds associated with the file + std::string filename; + std::string mode; std::set fieldIds; + bool readAccess; if (fieldType == INPUT_RESTART) { + filename = metadata.initialFileName; + mode = "read"; + readAccess = true; fieldIds = configGetInputRestartFieldNames(); } else if (fieldType == OUTPUT_RESTART) { + filename = metadata.finalFileName; + mode = "write"; + readAccess = false; fieldIds = configGetOutputRestartFieldNames(); } else if (fieldType == FORCING) { + filename = forcingFilename; + mode = "read"; + readAccess = true; fieldIds = configGetForcingFieldNames(); } else if (fieldType == DIAGNOSTIC) { + filename = diagnosticFilename; + mode = "write"; + readAccess = false; fieldIds = configGetDiagnosticFieldNames(); } + // Check that the filename and fileId are consistent + if (filename.find(fileId) == std::string::npos) { + throw std::runtime_error( + "Xios: File '" + fileId + "' inconsistent with filename '" + filename + "'"); + } + + // Set the file mode to 'read' or 'write', as appropriate + 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 file name, removing any format strings + // NOTE: Everything starting from the first '%' to the file extension is removed + if (filename.find("%") != std::string::npos) { + filename.erase(filename.find("%"), filename.find(".nc")); + } + cxios_set_file_name(file, filename.c_str(), filename.length()); + if (!cxios_is_defined_file_name(file)) { + throw std::runtime_error("Xios: Failed to set name for file '" + fileId + "'"); + } + // Set the file output frequency - std::string periodStr; - ModelMetadata& metadata = ModelMetadata::getInstance(); if (fieldType == INPUT_RESTART || fieldType == OUTPUT_RESTART) { - setFileOutputFreq(fileId, metadata.restartPeriod); + setFileOutputFreq(fileId, metadata.restartPeriod, fieldType); } else { std::string periodStr; if (fieldType == FORCING) { @@ -1112,39 +1164,17 @@ void Xios::createFile(const std::string fileId, const int fieldType) = Configured::getConfiguration(keyMap.at(DIAGNOSTIC_PERIOD_KEY), std::string()); } if (periodStr.empty() || periodStr == "0") { - setFileOutputFreq(fileId, metadata.runLength()); + setFileOutputFreq(fileId, metadata.runLength(), fieldType); } else { - setFileOutputFreq(fileId, Duration(periodStr)); + setFileOutputFreq(fileId, Duration(periodStr), fieldType); } } - // 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()); - } - 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 + "'"); - } - } - } - - // XiosOutput.field_names, XiosInput.field_names, XiosDiagnostic.field_names, or - // XiosForcing.field_names entries in the config. + // Loop over all field names in the config section corresponding to the file for (std::string fieldId : fieldIds) { createField(fieldId); fileAddField(fileId, fieldId); + // TODO: Refactor to allow a field to be both read and written setFieldReadAccess(fieldId, readAccess); // Set field name @@ -1157,30 +1187,16 @@ void Xios::createFile(const std::string fileId, const int fieldType) } /*! - * Set the type of a file with a given ID + * @brief Set the output frequency 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 + * @details In the case of outputs, the file splitting frequency is also set to coincide with the + * output frequency. * - * @param the file ID - * @param output frequency to set + * @param the file ID + * @param output frequency to set + * @param enum indicating field type */ -void Xios::setFileOutputFreq(const std::string fileId, const Duration freq) +void Xios::setFileOutputFreq(const std::string fileId, const Duration freq, const int fieldType) { xios::CFile* file = getFile(fileId); if (cxios_is_defined_file_output_freq(file)) { @@ -1190,59 +1206,21 @@ void Xios::setFileOutputFreq(const std::string fileId, const Duration 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 + "'"); + if (fieldType == OUTPUT_RESTART || fieldType == DIAGNOSTIC) { + // For output files, align the file splitting frequency with the output frequency + 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(freq)); + 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 + const std::string split_freq_format = "%y-%mo-%dT%h:%mi:%sZ"; + cxios_set_file_split_freq_format( + file, split_freq_format.c_str(), split_freq_format.length()); } - char cStr[cStrLen]; - cxios_get_file_type(file, cStr, cStrLen); - return convertCStrToCppStr(cStr, cStrLen); } /*! @@ -1262,44 +1240,6 @@ Duration Xios::getFileOutputFreq(const std::string fileId) 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 * @@ -1337,7 +1277,6 @@ 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(); // Get forcing and diganostic file IDs from the configuration @@ -1348,9 +1287,16 @@ void Xios::setupFiles() diagnosticFileId = ((std::filesystem::path)diagnosticFilename).filename().replace_extension(); // Create files for any non-empty file IDs - for (auto entry : fileMap) { - const std::string fileId = entry.second; + for (auto& entry : fileMap) { + const std::string& fileId = entry.second; if (!fileId.empty()) { + for (auto& other : fileMap) { + if ((entry.first != other.first) && (entry.second == other.second)) { + throw std::runtime_error("Xios: File '" + fileId + "' appears in multiple" + + " configuration sections. This is not yet supported in the XIOS I/O" + + " implementation."); + } + } createFile(fileId, entry.first); } } diff --git a/core/src/include/Xios.hpp b/core/src/include/Xios.hpp index 2e70949cc..ca128dd4e 100644 --- a/core/src/include/Xios.hpp +++ b/core/src/include/Xios.hpp @@ -88,25 +88,19 @@ class Xios : public Configured { /* 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); + void setFileOutputFreq( + const std::string fileId, const Duration outputFreq, const int fieldType); 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, @@ -189,7 +183,6 @@ 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 inputFileId; std::string diagnosticFilename; diff --git a/core/src/include/xios_c_interface.hpp b/core/src/include/xios_c_interface.hpp index 845e3c294..619f7a6f7 100644 --- a/core/src/include/xios_c_interface.hpp +++ b/core/src/include/xios_c_interface.hpp @@ -90,10 +90,14 @@ bool cxios_is_defined_domain_lat_name(xios::CDomain* domain_hdl); bool cxios_is_defined_domain_lon_name(xios::CDomain* domain_hdl); bool cxios_is_defined_domain_ni_glo(xios::CDomain* domain_hdl); bool cxios_is_defined_domain_nj_glo(xios::CDomain* domain_hdl); +void cxios_set_domain_lonvalue_1d(xios::CDomain* domain_hdl, double* data, const int* ni); +void cxios_set_domain_latvalue_1d(xios::CDomain* domain_hdl, double* data, const int* nj); bool cxios_is_defined_domain_ni(xios::CDomain* domain_hdl); bool cxios_is_defined_domain_nj(xios::CDomain* domain_hdl); bool cxios_is_defined_domain_ibegin(xios::CDomain* domain_hdl); bool cxios_is_defined_domain_jbegin(xios::CDomain* domain_hdl); +bool cxios_is_defined_domain_lonvalue_1d(xios::CDomain* domain_hdl); +bool cxios_is_defined_domain_latvalue_1d(xios::CDomain* domain_hdl); // grid group methods void cxios_gridgroup_handle_create(xios::CGridGroup** _ret, const char* _id, int _id_len); @@ -142,16 +146,16 @@ 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_set_file_split_freq_format( + xios::CFile* file_hdl, const char* split_freq_format, int split_freq_format_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); 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/src/iodef.xml.jinja b/core/src/iodef.xml.jinja index 18b3db77f..3b93c85cb 100644 --- a/core/src/iodef.xml.jinja +++ b/core/src/iodef.xml.jinja @@ -9,9 +9,9 @@ - - - + + + diff --git a/core/test/CMakeLists.txt b/core/test/CMakeLists.txt index e84ca786d..0be5f0dde 100644 --- a/core/test/CMakeLists.txt +++ b/core/test/CMakeLists.txt @@ -217,18 +217,6 @@ 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) - target_include_directories( - testXiosFile_MPI2 - PRIVATE - ${PHYSICS_INCLUDE_DIRS} - "${MODEL_INCLUDE_DIR}" - "${XIOS_INCLUDE_LIST}" - "${ModulesRoot}/StructureModule" - ) - target_link_libraries(testXiosFile_MPI2 PRIVATE nextsimlib doctest::doctest) - add_executable(testXiosRead_MPI2 "XiosRead_test.cpp" "MainMPI.cpp") target_compile_definitions(testXiosRead_MPI2 PRIVATE @@ -271,6 +259,9 @@ if(ENABLE_MPI) ) add_mpi_test("${XIOS_TESTS}") + + # Ensure XiosWrite_test is run before XiosRead_test + set_tests_properties(testXiosRead_MPI2 PROPERTIES DEPENDS testXiosWrite_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/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index 3dcbdab59..9dd52149a 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -20,7 +20,8 @@ #include const std::string testFilesDir = TEST_FILES_DIR; -const std::string restartFilename = testFilesDir + "/xios_test_input.nc"; +const std::string inputFilename + = testFilesDir + "/restart_2023-03-17T17:11:00Z-2023-03-17T20:10:59Z.nc"; const std::string forcingFilename = testFilesDir + "/xios_test_forcing.nc"; static const int DGCOMP = 6; @@ -42,8 +43,7 @@ MPI_TEST_CASE("TestXiosRead", 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 = " << restartFilename << std::endl; - config << "restart_period = P0-0T01:30:00" << std::endl; + config << "init_file = " << inputFilename << std::endl; config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; config << "[XiosInput]" << std::endl; config << "field_names = " << maskName << "," << coordsName << "," << hiceName << "," @@ -79,8 +79,11 @@ MPI_TEST_CASE("TestXiosRead", 2) xiosHandler.close_context_definition(); - // Check the input files exists - REQUIRE(std::filesystem::exists(restartFilename)); + // Check the input files exist + if (!std::filesystem::exists(inputFilename)) { + throw std::runtime_error( + "XiosRead_test: Input file not found. Did you run XiosWrite_test?"); + } REQUIRE(std::filesystem::exists(forcingFilename)); // Check calendar step is zero initially @@ -93,7 +96,7 @@ MPI_TEST_CASE("TestXiosRead", 2) REQUIRE(ModelArray::size(ModelArray::Dimension::DG) == DGCOMP); // Read restarts from file and check they take the expected values - ModelState restarts = grid.getModelState(restartFilename); + ModelState restarts = grid.getModelState(inputFilename); int rank; MPI_Comm_rank(test_comm, &rank); for (auto& entry : restarts.data) { @@ -169,6 +172,13 @@ MPI_TEST_CASE("TestXiosRead", 2) REQUIRE(xiosHandler.getCalendarStep() == ts + 1); } + if (rank == 0) { + std::filesystem::remove("restart_2023-03-17T17:11:00Z-2023-03-17T20:10:59Z.nc"); + std::filesystem::remove("restart_2023-03-17T20:11:00Z-2023-03-17T23:10:59Z.nc"); + 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/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index d83382976..34bf900bd 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -20,9 +20,9 @@ #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"; +const std::string diagnosticFilename = testFilesDir + "/diagnostic%Y-%m-%dT%H:%M:%SZ.nc"; static const int DGCOMP = 6; static const int DGSTRESSCOMP = 8; @@ -43,19 +43,17 @@ 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; - 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)); @@ -107,48 +105,12 @@ 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(); @@ -162,12 +124,50 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Simulate 4 iterations (timesteps) ModelMetadata& metadata = ModelMetadata::getInstance(); Duration timestep = metadata.stepLength(); + int rank; + MPI_Comm_rank(test_comm, &rank); 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 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) { + 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)); + } + } + } + // Update diagnostics for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { @@ -191,18 +191,15 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Write out diagnostics and then restarts pio->writeDiagnosticTime(diagnostics, diagnosticFilename); - grid.dumpModelState(restarts, restartOutputFilename, true); + grid.dumpModelState(restarts, restartFilename, true); } - // Check the files have indeed been created then remove it - 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_diagnostic.nc")); - if (rank == 0) { - std::filesystem::remove("xios_test_output_20230317171100-20230317201059.nc"); - std::filesystem::remove("xios_test_output_20230317201100-20230317231059.nc"); - std::filesystem::remove("xios_test_diagnostic.nc"); - } + // Check the files have indeed been created + // NOTE: We don't remove them because they are used in XiosRead_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")); + 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();