From 93ea58ecdc30440064257e09aeb3a8843f9ba805 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 13:13:26 +0100 Subject: [PATCH 01/10] Add XiosDiagnostic section --- core/src/Xios.cpp | 80 +++++++++++++++++++++++++++++---------- core/src/include/Xios.hpp | 13 +++++-- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 21d0afcc0..022d418b3 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -43,6 +43,7 @@ namespace Nextsim { static const std::string xOutputPfx = "XiosOutput"; static const std::string xInputPfx = "XiosInput"; +static const std::string xDiagonsticPfx = "XiosDiagnostic"; static const std::string xForcingPfx = "XiosForcing"; static const std::map keyMap = { { Xios::ENABLED_KEY, "xios.enable" }, { Xios::STARTTIME_KEY, "model.start" }, @@ -53,6 +54,9 @@ static const std::map keyMap { Xios::INPUT_RESTARTPERIOD_KEY, xInputPfx + ".period" }, { Xios::INPUT_RESTARTFILE_KEY, xInputPfx + ".filename" }, { Xios::INPUT_FIELD_NAMES_KEY, xInputPfx + ".field_names" }, + { Xios::DIAGNOSTIC_PERIOD_KEY, xDiagonsticPfx + ".period" }, + { Xios::DIAGNOSTIC_FILE_KEY, xDiagonsticPfx + ".filename" }, + { Xios::DIAGNOSTIC_FIELD_NAMES_KEY, xDiagonsticPfx + ".field_names" }, { Xios::FORCING_PERIOD_KEY, xForcingPfx + ".period" }, { Xios::FORCING_FILE_KEY, xForcingPfx + ".filename" }, { Xios::FORCING_FIELD_NAMES_KEY, xForcingPfx + ".field_names" } }; @@ -80,18 +84,26 @@ Xios::Xios(const std::string contextid, const std::string calendartype) // Create the input and output files (if found in the config) if (firstTime) { - istringstream(Configured::getConfiguration(keyMap.at(INPUT_RESTARTFILE_KEY), std::string())) - >> inputFilename; - inputFileId = ((std::filesystem::path)inputFilename).replace_extension(); istringstream( Configured::getConfiguration(keyMap.at(OUTPUT_RESTARTFILE_KEY), std::string())) >> outputFilename; outputFileId = ((std::filesystem::path)outputFilename).replace_extension(); + istringstream(Configured::getConfiguration(keyMap.at(INPUT_RESTARTFILE_KEY), std::string())) + >> inputFilename; + inputFileId = ((std::filesystem::path)inputFilename).replace_extension(); + istringstream(Configured::getConfiguration(keyMap.at(DIAGNOSTIC_FILE_KEY), std::string())) + >> diagnosticFilename; + diagnosticFileId = ((std::filesystem::path)diagnosticFilename).replace_extension(); istringstream(Configured::getConfiguration(keyMap.at(FORCING_FILE_KEY), std::string())) >> forcingFilename; forcingFileId = ((std::filesystem::path)forcingFilename).replace_extension(); // TODO: Account for separate restart and forcing files (#929) + if (diagnosticFilename.length() > 0 && outputFilename.length() > 0 + && outputFilename != diagnosticFilename) { + throw std::runtime_error( + "Xios: Separate diagnostic and restart files not yet supported"); + } if (forcingFilename.length() > 0 && inputFilename.length() > 0 && inputFilename != forcingFilename) { throw std::runtime_error("Xios: Separate forcing and restart files not yet supported"); @@ -136,6 +148,17 @@ Xios::HelpMap& Xios::getHelpText(HelpMap& map, bool getAll) "-DENABLE_XIOS=ON, passing the path to your XIOS installation with " "-Dxios_DIR=/path/to/xios." }, }; + map["XiosOutput"] = { + { keyMap.at(OUTPUT_RESTARTPERIOD_KEY), ConfigType::STRING, {}, "0", "", + "The period between restart file outputs, formatted as an ISO8601 " + "duration (P prefix) or number of seconds. A value of zero " + "ensures no intermediate restart files are written." }, + { keyMap.at(OUTPUT_RESTARTFILE_KEY), ConfigType::STRING, {}, "", "", + // TODO: Support format "restart%Y-%m-%dT%H:%M:%SZ.nc" (#898) + "The file name to be used for output." }, + { keyMap.at(OUTPUT_FIELD_NAMES_KEY), ConfigType::STRING, {}, "", "", + "Comma-separated list of field names to be written to the output file." }, + }; map["XiosInput"] = { { keyMap.at(INPUT_RESTARTPERIOD_KEY), ConfigType::STRING, {}, "0", "", "The period between restart file outputs expected in a file to be read, formatted as " @@ -147,22 +170,23 @@ Xios::HelpMap& Xios::getHelpText(HelpMap& map, bool getAll) { keyMap.at(INPUT_FIELD_NAMES_KEY), ConfigType::STRING, {}, "", "", "Comma-separated list of field names to be read from the input file." }, }; - map["XiosOutput"] = { - { keyMap.at(OUTPUT_RESTARTPERIOD_KEY), ConfigType::STRING, {}, "0", "", - "The period between restart file outputs, formatted as an ISO8601 " - "duration (P prefix) or number of seconds. A value of zero " - "ensures no intermediate restart files are written." }, - { keyMap.at(OUTPUT_RESTARTFILE_KEY), ConfigType::STRING, {}, "", "", + map["XiosDiagnostic"] = { + { keyMap.at(DIAGNOSTIC_PERIOD_KEY), ConfigType::STRING, {}, "0", "", + "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 output." }, - { keyMap.at(OUTPUT_FIELD_NAMES_KEY), ConfigType::STRING, {}, "", "", - "Comma-separated list of field names to be written to the output file." }, + "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." }, }; map["XiosForcing"] = { { keyMap.at(FORCING_PERIOD_KEY), ConfigType::STRING, {}, "0", "", "The period between forcing 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 restart files." }, + "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." }, @@ -993,15 +1017,33 @@ std::set Xios::configGetInputFieldNames() } // Extract the field_names entry from the XiosOutput section of the config. -std::set Xios::configGetOutputFieldNames() +std::set Xios::configGetOutputRestartFieldNames() { - // TODO: Account for diagnostics (#917) std::string fieldsStr; istringstream(Configured::getConfiguration(keyMap.at(OUTPUT_FIELD_NAMES_KEY), std::string())) >> fieldsStr; return str2set(fieldsStr); } +// Extract the field_names entry from the XiosDiagnostic section of the config. +std::set Xios::configGetDiagnosticFieldNames() +{ + std::string fieldsStr; + istringstream( + Configured::getConfiguration(keyMap.at(DIAGNOSTIC_FIELD_NAMES_KEY), std::string())) + >> fieldsStr; + return str2set(fieldsStr); +} + +// Extract the field_names entry from the XiosOutput and XiosDiagnostic sections of the config. +std::set Xios::configGetOutputFieldNames() +{ + std::set restartFieldNames = configGetOutputRestartFieldNames(); + std::set diagnosticFieldNames = configGetDiagnosticFieldNames(); + restartFieldNames.insert(diagnosticFieldNames.begin(), diagnosticFieldNames.end()); + return restartFieldNames; +} + /*! * Extract the field_names entry from the XIOS config. * @@ -1271,7 +1313,7 @@ void Xios::createFile(const std::string fileId) bool readAccess = ((inputFileId.length() > 0) && (inputFileId == fileId)); // TODO: Account for separate restart and forcing files (#929) bool writeAccess = ((outputFileId.length() > 0) && (outputFileId == fileId)); - // TODO: Account for diagnostics (#917) + // TODO: Account for separate restart and diagnostics files (#929) // Check that the filename is not in both the XiosOutput and XiosInput config sections if (readAccess && writeAccess) { @@ -1313,7 +1355,7 @@ void Xios::createFile(const std::string fileId) istringstream( Configured::getConfiguration(keyMap.at(OUTPUT_RESTARTPERIOD_KEY), std::string())) >> periodStr; - // TODO: Account for diagnostics (#917) + // TODO: Account for diagnostics period being different to restart period (#929) } if (periodStr.length() == 0 || periodStr == "0") { setFileOutputFreq(fileId, stopTime - startTime); @@ -1322,8 +1364,8 @@ void Xios::createFile(const std::string fileId) } // Create all fields found in the config based off the field names found in the - // XiosInput.field_names, XiosOutput.field_names, or XiosForcing.field_names entries in the - // config. + // XiosOutput.field_names, XiosInput.field_names, XiosDiagnostic.field_names, or + // XiosForcing.field_names entries in the config. for (std::string fieldId : configGetFieldNames(readAccess)) { createField(fieldId); fileAddField(fileId, fieldId); diff --git a/core/src/include/Xios.hpp b/core/src/include/Xios.hpp index c05fc6178..a33fc0d98 100644 --- a/core/src/include/Xios.hpp +++ b/core/src/include/Xios.hpp @@ -147,6 +147,9 @@ class Xios : public Configured { INPUT_RESTARTPERIOD_KEY, INPUT_RESTARTFILE_KEY, INPUT_FIELD_NAMES_KEY, + DIAGNOSTIC_PERIOD_KEY, + DIAGNOSTIC_FILE_KEY, + DIAGNOSTIC_FIELD_NAMES_KEY, FORCING_PERIOD_KEY, FORCING_FILE_KEY, FORCING_FIELD_NAMES_KEY, @@ -193,9 +196,11 @@ class Xios : public Configured { xios::CFieldGroup* getFieldGroup(); xios::CField* getField(const std::string fieldId); void setFieldReadAccess(const std::string fieldId, const bool readAccess); + std::set configGetOutputRestartFieldNames(); std::set configGetInputRestartFieldNames(); - std::set configGetInputFieldNames(); + std::set configGetDiagnosticFieldNames(); std::set configGetOutputFieldNames(); + std::set configGetInputFieldNames(); std::set configGetFieldNames(const bool readAccess); bool configCheckField(const std::string fieldId, const bool readAccess); @@ -207,10 +212,12 @@ class Xios : public Configured { xios::CFileGroup* getFileGroup(); xios::CFile* getFile(const std::string fileId); void setFileMode(const std::string fileId, const std::string mode); - std::string inputFilename; - std::string inputFileId; std::string outputFilename; std::string outputFileId; + std::string inputFilename; + std::string inputFileId; + std::string diagnosticFilename; + std::string diagnosticFileId; std::string forcingFilename; std::string forcingFileId; From f134caffc26e611075ef07c17b79159a1d66e075 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 13:14:48 +0100 Subject: [PATCH 02/10] Mention XiosDiagnostic in XIOS docs --- docs/xios.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/xios.rst b/docs/xios.rst index e9a5ffa01..be539c063 100644 --- a/docs/xios.rst +++ b/docs/xios.rst @@ -37,11 +37,12 @@ configure the calendar used by XIOS and take the following default values. time_step = P0-0T01:00:00 Files and fields to be read and written to files are configured via the -``XiosInput``, ``XiosOutput``, and ``XiosForcing`` sections, where the first two -refer to restarts. These sections all accept the same entries for period, -filename, and field names. For example, we could set the following values, which -would specify two fields labelled ``field_A`` and ``field_B``, which are written -to file ``my_output_file.nc`` every two (simulated) hours. +``XiosOutput``, ``XiosInput``, ``XiosDiagnostic``, and ``XiosForcing`` sections, +where the first two refer to restarts. These sections all accept the same +entries for period, filename, and field names. For example, we could set the +following values, which would specify two fields labelled ``field_A`` and +``field_B``, which are written to file ``my_output_file.nc`` every two +(simulated) hours. .. code-block:: [XiosOutput] @@ -50,8 +51,8 @@ to file ``my_output_file.nc`` every two (simulated) hours. field_names = field_A,field_B The ``field_names`` entry may contain a single field name or a comma-separated -list. Note that all of the ``XiosInput``, ``XiosOutput``, and ``XiosForcing`` -sections are optional. +list. Note that all of the ``XiosOutput``, ``XiosInput``, ``XiosDiagnostic``, +and ``XiosForcing`` sections are optional. Developer notes ^^^^^^^^^^^^^^^ From c506fbdb2fd41cacf9d5f8cafa146a56b517c932 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 13:57:58 +0100 Subject: [PATCH 03/10] Update XIOS write test to consider diagnostics --- core/test/CMakeLists.txt | 6 +++- core/test/XiosWrite_test.cpp | 54 +++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/core/test/CMakeLists.txt b/core/test/CMakeLists.txt index 6361b7fe8..3e2570d22 100644 --- a/core/test/CMakeLists.txt +++ b/core/test/CMakeLists.txt @@ -132,7 +132,11 @@ if(ENABLE_MPI) target_link_libraries(testXiosRead_MPI2 PRIVATE nextsimlib doctest::doctest) add_executable(testXiosWrite_MPI2 "XiosWrite_test.cpp" "MainMPI.cpp") - target_compile_definitions(testXiosWrite_MPI2 PRIVATE USE_XIOS) + target_compile_definitions(testXiosWrite_MPI2 + PRIVATE + USE_XIOS + TEST_FILES_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\" + ) target_include_directories( testXiosWrite_MPI2 PRIVATE "${MODEL_INCLUDE_DIR}" "${XIOS_INCLUDE_LIST}" "${ModulesRoot}/StructureModule" diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index 829d4bc0c..2bd1f91da 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -19,6 +19,10 @@ #include +const std::string testFilesDir = TEST_FILES_DIR; +const std::string filename = "xios_test_output.nc"; +const std::string filepath = testFilesDir + "/" + filename; + namespace Nextsim { /*! @@ -36,8 +40,12 @@ MPI_TEST_CASE("TestXiosWrite", 2) config << "start = 2023-03-17T17:11:00Z" << std::endl; config << "time_step = P0-0T01:30:00" << std::endl; config << "[XiosOutput]" << std::endl; - config << "filename = xios_test_output.nc" << std::endl; - config << "field_names = hice" << std::endl; + config << "filename = " << filename << std::endl; + config << "field_names = " << hiceName << std::endl; + config << "[XiosDiagnostic]" << std::endl; + // TODO: Account for separate restart and diagnostics files (#929) + config << "filename = " << filename << std::endl; + config << "field_names = " << uName << std::endl; std::unique_ptr pcstream(new std::stringstream(config.str())); Configurator::addStream(std::move(pcstream)); @@ -68,14 +76,17 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Create a field on the grid // NOTE: Fields are created when the XIOS handler is constructed // NOTE: The 2D grid is created along with the 2D domain - xiosHandler.setFieldOperation(hiceName, "instant"); - xiosHandler.setFieldGridRef(hiceName, "grid_2D"); Duration timestep = xiosHandler.getCalendarTimestep(); - xiosHandler.setFieldFreqOffset(hiceName, timestep); + for (std::string fieldId : { hiceName, uName }) { + xiosHandler.setFieldOperation(fieldId, "instant"); + xiosHandler.setFieldGridRef(fieldId, "grid_2D"); + xiosHandler.setFieldFreqOffset(fieldId, timestep); + } - // Set file split frequency + // Set file split frequency for restarts // NOTE: Files are created when the XIOS handler is constructed const std::string fileId = "xios_test_output"; + // TODO: Account for separate restart and diagnostics files (#929) xiosHandler.setFileSplitFreq(fileId, Duration("P0-0T03:00:00")); xiosHandler.close_context_definition(); @@ -88,12 +99,8 @@ MPI_TEST_CASE("TestXiosWrite", 2) hice(i, j) = 1.0 * (i + nx * j); } } - - // Setup ModelState with field above - ModelState state = { { - { hiceName, hice }, - }, - {} }; + HField u(ModelArray::Type::H); + u.resize(); // Check calendar step is zero initially REQUIRE(xiosHandler.getCalendarStep() == 0); @@ -104,10 +111,31 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Simulate 4 iterations (timesteps) metadata.setTime(xiosHandler.getCalendarStart()); 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); - grid.dumpModelState(state, metadata, "xios_test_output", true); + + // Update diagnostics + for (size_t j = 0; j < ny; ++j) { + for (size_t i = 0; i < nx; ++i) { + u(i, j) = ts; + } + } + + // Set up two ModelStates: one for restarts and one for diagnostics + ModelState restarts = { { + { hiceName, hice }, + }, + {} }; + ModelState diagnostics = { { + { uName, u }, + }, + {} }; + + // Write out diagnostics and then restarts + pio->writeDiagnosticTime(diagnostics, metadata, filepath); + grid.dumpModelState(restarts, metadata, "xios_test_output", true); } // Check the files have indeed been created then remove it From 827c90f542f7d0d60c850b52575790342b1ffd08 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 13:58:07 +0100 Subject: [PATCH 04/10] Similar updates for XiosRead_test --- core/test/XiosRead_test.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index 085e50edb..e8a8d7f7d 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 filename = testFilesDir + "/xios_test_input.nc"; +const std::string filename = "xios_test_input.nc"; +const std::string filepath = testFilesDir + "/" + filename; namespace Nextsim { @@ -39,11 +40,11 @@ MPI_TEST_CASE("TestXiosRead", 2) config << "start = 2023-03-17T17:11:00Z" << std::endl; config << "time_step = P0-0T01:30:00" << std::endl; config << "[XiosInput]" << std::endl; - config << "filename = xios_test_input.nc" << std::endl; + config << "filename = " << filename << std::endl; config << "field_names = " << hiceName << std::endl; config << "[XiosForcing]" << std::endl; // TODO: Account for separate restart and forcing files (#929) - config << "filename = xios_test_input.nc" << std::endl; + config << "filename = " << filename << std::endl; config << "field_names = " << uName << std::endl; std::unique_ptr pcstream(new std::stringstream(config.str())); Configurator::addStream(std::move(pcstream)); @@ -67,12 +68,13 @@ MPI_TEST_CASE("TestXiosRead", 2) // Create fields on the two grids // NOTE: Fields are created when the XIOS handler is constructed // NOTE: The 2D grid is created along with the 2D domain - xiosHandler.setFieldOperation(hiceName, "instant"); - xiosHandler.setFieldGridRef(hiceName, "grid_2D"); - xiosHandler.setFieldOperation(uName, "instant"); - xiosHandler.setFieldGridRef(uName, "grid_2D"); + for (std::string fieldId : { hiceName, uName }) { + xiosHandler.setFieldOperation(fieldId, "instant"); + xiosHandler.setFieldGridRef(fieldId, "grid_2D"); + } Duration timestep = xiosHandler.getCalendarTimestep(); xiosHandler.setFieldFreqOffset(hiceName, timestep); + // xiosHandler.setFieldFreqOffset(uName, timestep); // FIXME: Why does this cause a stall? xiosHandler.close_context_definition(); @@ -84,7 +86,7 @@ MPI_TEST_CASE("TestXiosRead", 2) REQUIRE(xiosHandler.getCalendarStep() == 0); // Check the input file exists - REQUIRE(std::filesystem::exists(filename)); + REQUIRE(std::filesystem::exists(filepath)); // Deduce the local lengths of the two dimensions const size_t nx = ModelArray::definedDimensions.at(ModelArray::Dimension::X).localLength; @@ -98,7 +100,7 @@ MPI_TEST_CASE("TestXiosRead", 2) // Read forcings from file and check they take the expected values TimePoint time = xiosHandler.getCurrentDate(); - ModelState forcings = pio->readForcingTimeStatic(forcingFieldNames, time, filename); + ModelState forcings = pio->readForcingTimeStatic(forcingFieldNames, time, filepath); for (auto& entry : forcings.data) { for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { @@ -112,7 +114,7 @@ MPI_TEST_CASE("TestXiosRead", 2) REQUIRE(xiosHandler.getCalendarStep() == ts); // Read restarts from file and check they take the expected values - ModelState restarts = grid.getModelState(filename, metadata); + ModelState restarts = grid.getModelState(filepath, metadata); for (auto& entry : restarts.data) { for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { From 7b915a15f05bf831fb01ea83d4c7a1a4d407a2e1 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 14:47:46 +0100 Subject: [PATCH 05/10] XIOS implementation of writeDiagnosticTime --- core/src/ParaGridIO_Xios.cpp | 211 ++------------------------------ core/src/include/ParaGridIO.hpp | 8 +- 2 files changed, 19 insertions(+), 200 deletions(-) diff --git a/core/src/ParaGridIO_Xios.cpp b/core/src/ParaGridIO_Xios.cpp index 401ae36ff..4867a12bd 100644 --- a/core/src/ParaGridIO_Xios.cpp +++ b/core/src/ParaGridIO_Xios.cpp @@ -28,48 +28,8 @@ namespace Nextsim { -// TODO: XIOS implementation ParaGridIO::ParaGridIO(ParametricGrid& grid) : IParaGridIO(grid) - , openFilesAndIndices(getOpenFilesAndIndices()) - , dimensionKeys({ - // clang-format off - // Accept post-May 2024 (xdim, ydim, zdim) dimension names and pre-May 2024 (x, y, z) - { "yx", ModelArray::Type::H }, - { "ydimxdim", ModelArray::Type::H }, - { "yxdg_comp", ModelArray::Type::DG }, - { "ydimxdimdg_comp", ModelArray::Type::DG }, - { "yxdgstress_comp", ModelArray::Type::DGSTRESS }, - { "ydimxdimdgstress_comp", ModelArray::Type::DGSTRESS }, - { "ycgxcg", ModelArray::Type::CG }, - { "yvertexxvertexncoords", ModelArray::Type::VERTEX }, - // clang-format on - }) - , isDG({ - // clang-format off - { ModelArray::Dimension::X, false }, - { ModelArray::Dimension::Y, false }, - { ModelArray::Dimension::XCG, true }, - { ModelArray::Dimension::YCG, true }, - { ModelArray::Dimension::DG, true }, - { ModelArray::Dimension::DGSTRESS, true }, - // NCOORDS is a number of components, but not in the same way as the DG components. - { ModelArray::Dimension::NCOORDS, false }, - // clang-format on - }) - , dimCompMap({ - // clang-format off - { ModelArray::componentMap.at(ModelArray::Type::DG), ModelArray::Type::DG }, - { ModelArray::componentMap.at(ModelArray::Type::DGSTRESS), ModelArray::Type::DGSTRESS }, - { ModelArray::componentMap.at(ModelArray::Type::VERTEX), ModelArray::Type::VERTEX }, - // clang-format on - }) -{ - Logged::warning("XIOS integration has not yet been completed"); - static bool doneOnce = doOnce(); -} - -bool ParaGridIO::doOnce() { Xios& xiosHandler = Xios::getInstance(); // NOTE: getInstance will call the constructor for the Xios handler class the first time it is @@ -78,15 +38,6 @@ bool ParaGridIO::doOnce() // entries are set in the config. // * Create all fields found in the config based off the field names found in the // XiosInput.field_names and XiosOutput.field_names entries in the config. - - // TODO: Register XIOS finalization and drop the following in that case. - // Register the finalization function here - Finalizer::registerUnique(closeAllFiles); - // Since it should only ever run once, do further one-off initialization: allow distant - // classes to close files via a callback. - FileCallbackCloser::onClose(close); - - return true; } ParaGridIO::~ParaGridIO() = default; @@ -163,6 +114,7 @@ void ParaGridIO::dumpModelState( // Assume that all fields in the supplied ModelState are necessary, and so write them to // file. + // TODO: Check they are indeed restarts for (auto entry : state.data) { const std::string fieldId = entry.first; if (xiosHandler.getFieldReadAccess(fieldId)) { @@ -176,158 +128,21 @@ void ParaGridIO::dumpModelState( void ParaGridIO::writeDiagnosticTime( const ModelState& state, const ModelMetadata& meta, const std::string& filePath) { - // TODO: XIOS implementation - - bool isNew = openFilesAndIndices.count(filePath) <= 0; - size_t nt = (isNew) ? 0 : ++openFilesAndIndices.at(filePath).second; - if (isNew) { - // Open a new file and emplace it in the map of open files. - // Set the initial time to be zero (assigned above) - // Piecewise construction is necessary to correctly construct the file handle/time index - // pair - openFilesAndIndices.emplace(std::piecewise_construct, std::make_tuple(filePath), - std::forward_as_tuple(std::piecewise_construct, - std::forward_as_tuple(filePath, netCDF::NcFile::replace, meta.mpiComm), - std::forward_as_tuple(nt))); - } - // Get the file handle - NetCDFFileType& ncFile = openFilesAndIndices.at(filePath).first; - - if (isNew) { - // Write the common structure and time metadata - CommonRestartMetadata::writeStructureType(ncFile, meta); - } - // Get the unlimited time dimension, creating it if necessary - netCDF::NcDim timeDim = (isNew) ? ncFile.addDim(timeName) : ncFile.getDim(timeName); - - // All of the dimensions defined by the data at a particular timestep. - std::map ncFromMAMap; - for (auto entry : ModelArray::definedDimensions) { - ModelArray::Dimension dim = entry.first; - size_t dimSz = (dimCompMap.count(dim)) ? ModelArray::nComponents(dimCompMap.at(dim)) - : dimSz = entry.second.globalLength; - ncFromMAMap[dim] - = (isNew) ? ncFile.addDim(entry.second.name, dimSz) : ncFile.getDim(entry.second.name); - } - - // Also create the sets of dimensions to be connected to the data fields - std::map> dimMap; - // Create the index and size arrays - std::map> startMap; - std::map> countMap; - for (auto entry : ModelArray::typeDimensions) { - ModelArray::Type type = entry.first; - std::vector ncDims; - std::vector start; - std::vector count; - - // Everything that has components needs that dimension, too - if (ModelArray::hasDoF(type)) { - if (type == ModelArray::Type::VERTEX && !isNew) - continue; - auto ncomps = ModelArray::nComponents(type); - auto dim = ModelArray::componentMap.at(type); - ncDims.push_back(ncFromMAMap.at(dim)); - start.push_back(0); - count.push_back(ncomps); - } - for (auto dt : entry.second) { - auto dim = ModelArray::definedDimensions.at(dt); - ncDims.push_back(ncFromMAMap.at(dt)); - start.push_back(dim.start); - count.push_back(dim.localLength); - } - - // Deal with VERTEX in each case - // Add the time dimension for all types that are not VERTEX - if (type != ModelArray::Type::VERTEX) { - ncDims.push_back(timeDim); - start.push_back(nt); - count.push_back(1UL); - } else if (!isNew) { - // For VERTEX in an existing file, there is nothing more to be done - continue; - } - - std::reverse(ncDims.begin(), ncDims.end()); - std::reverse(start.begin(), start.end()); - std::reverse(count.begin(), count.end()); - - dimMap[type] = ncDims; - startMap[type] = start; - countMap[type] = count; - } - - // Create a special timeless set of dimensions for the landmask - std::vector maskDims; - std::vector maskIndexes; - std::vector maskExtents; - if (isNew) { - for (ModelArray::Dimension& maDim : ModelArray::typeDimensions.at(ModelArray::Type::H)) { - maskDims.push_back(ncFromMAMap.at(maDim)); - } - maskIndexes = { 0, 0 }; - maskExtents = { ModelArray::definedDimensions - .at(ModelArray::typeDimensions.at(ModelArray::Type::H)[0]) - .localLength, - ModelArray::definedDimensions.at(ModelArray::typeDimensions.at(ModelArray::Type::H)[1]) - .localLength }; - } - - // Put the time axis variable - std::vector timeDimVec = { timeDim }; - netCDF::NcVar timeVar( - (isNew) ? ncFile.addVar(timeName, netCDF::ncDouble, timeDimVec) : ncFile.getVar(timeName)); - double secondsSinceEpoch = (meta.time() - TimePoint()).seconds(); - netCDF::setVariableCollective(timeVar, ncFile); - timeVar.putVar({ nt }, { 1 }, &secondsSinceEpoch); - - if (isNew) - timeVar.putAtt("units", "seconds since 1970-01-01 00:00:00"); + Xios& xiosHandler = Xios::getInstance(); - // Write the data + // Assume that all fields in the supplied ModelState are necessary, and so write them to + // file. + // TODO: Check they are indeed diagnostics for (auto entry : state.data) { - ModelArray::Type type = entry.second.getType(); - // Skip timeless fields (mask, coordinates) on existing files - if (!isNew && (entry.first == maskName || type == ModelArray::Type::VERTEX)) - continue; - if (entry.first == maskName) { - // Land mask in a new file (since it was skipped above in existing files) - netCDF::NcVar var(ncFile.addVar(maskName, netCDF::ncDouble, maskDims)); - // No missing data - netCDF::setVariableCollective(var, ncFile); - var.putVar(maskIndexes, maskExtents, entry.second.getData()); - - } else { - std::vector& ncDims = dimMap.at(type); - // Get the variable object, either creating a new one or getting the existing one - netCDF::NcVar var((isNew) ? ncFile.addVar(entry.first, netCDF::ncDouble, ncDims) - : ncFile.getVar(entry.first)); - if (isNew) - var.putAtt(mdiName, netCDF::ncDouble, MissingData::value()); - netCDF::setVariableCollective(var, ncFile); - var.putVar(startMap.at(type), countMap.at(type), entry.second.getData()); - } - } -} - -// TODO: This method will likely be dropped in the XIOS implementation -void ParaGridIO::close(const std::string& filePath) -{ - if (getOpenFilesAndIndices().count(filePath) > 0) { - getOpenFilesAndIndices().at(filePath).first.close(); - getOpenFilesAndIndices().erase(filePath); - } -} - -// TODO: This method will likely be dropped in the XIOS implementation -void ParaGridIO::closeAllFiles() -{ - std::cout << "ParaGridIO::closeAllFiles: closing " << getOpenFilesAndIndices().size() - << " files" << std::endl; - while (getOpenFilesAndIndices().size() > 0) { - close(getOpenFilesAndIndices().begin()->first); + const std::string fieldId = entry.first; + if (xiosHandler.getFieldReadAccess(fieldId)) { + throw std::runtime_error("ParaGridIO::writeDiagnosticTime: field " + fieldId + + " is not configured for writing, but is being written to file."); + }; + xiosHandler.write(fieldId, entry.second); } + // TODO: Create a special timeless set of dimensions for the landmask + // TODO: Put the time axis variable } } /* namespace Nextsim */ diff --git a/core/src/include/ParaGridIO.hpp b/core/src/include/ParaGridIO.hpp index a8596db91..ed569b1d3 100644 --- a/core/src/include/ParaGridIO.hpp +++ b/core/src/include/ParaGridIO.hpp @@ -77,6 +77,7 @@ class ParaGridIO : public ParametricGrid::IParaGridIO { void writeDiagnosticTime( const ModelState& state, const ModelMetadata& meta, const std::string& filePath) override; +#ifndef USE_XIOS /*! * Closes an open diagnostic file. Does nothing when provided with a * restart file name. @@ -84,17 +85,19 @@ class ParaGridIO : public ParametricGrid::IParaGridIO { * @param filePath The path to the file to be closed. */ static void close(const std::string& filePath); +#endif static ModelState readForcingTimeStatic( const std::set& forcings, const TimePoint& time, const std::string& filePath); private: - typedef std::map> FileAndIndexMap; - ParaGridIO() = delete; ParaGridIO(const ParaGridIO& other) = delete; ParaGridIO& operator=(const ParaGridIO& other) = delete; +#ifndef USE_XIOS + typedef std::map> FileAndIndexMap; + const std::map dimensionKeys; const std::map isDG; @@ -117,6 +120,7 @@ class ParaGridIO : public ParametricGrid::IParaGridIO { //! Performs some one-time initialization for the class. Returns true. static bool doOnce(); +#endif }; } /* namespace Nextsim */ From 95f5dd46e542628fe5f74d24713279730e03fe0a Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 15:37:26 +0100 Subject: [PATCH 06/10] Tidying up --- core/src/ParaGridIO_Xios.cpp | 2 +- core/test/XiosRead_test.cpp | 12 +++++++----- core/test/XiosWrite_test.cpp | 15 +++++++++------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/core/src/ParaGridIO_Xios.cpp b/core/src/ParaGridIO_Xios.cpp index 4867a12bd..fb12f460a 100644 --- a/core/src/ParaGridIO_Xios.cpp +++ b/core/src/ParaGridIO_Xios.cpp @@ -141,7 +141,7 @@ void ParaGridIO::writeDiagnosticTime( }; xiosHandler.write(fieldId, entry.second); } - // TODO: Create a special timeless set of dimensions for the landmask + // TODO: Create a special timeless set of dimensions for the landmask - use 'once' operation // TODO: Put the time axis variable } diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index e8a8d7f7d..cc6f0823c 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -42,10 +42,12 @@ MPI_TEST_CASE("TestXiosRead", 2) config << "[XiosInput]" << std::endl; config << "filename = " << filename << std::endl; config << "field_names = " << hiceName << std::endl; - config << "[XiosForcing]" << std::endl; + config << "period = P0-0T01:30:00" << std::endl; // TODO: Account for separate restart and forcing files (#929) + config << "[XiosForcing]" << std::endl; config << "filename = " << filename << std::endl; config << "field_names = " << uName << std::endl; + config << "period = P0-0T01:30:00" << std::endl; std::unique_ptr pcstream(new std::stringstream(config.str())); Configurator::addStream(std::move(pcstream)); @@ -82,18 +84,18 @@ MPI_TEST_CASE("TestXiosRead", 2) HField hice(ModelArray::Type::H); hice.resize(); - // Check calendar step is zero initially - REQUIRE(xiosHandler.getCalendarStep() == 0); - // Check the input file exists REQUIRE(std::filesystem::exists(filepath)); + // Check calendar step is zero initially + metadata.setTime(xiosHandler.getCalendarStart()); + REQUIRE(xiosHandler.getCalendarStep() == 0); + // Deduce the local lengths of the two dimensions const size_t nx = ModelArray::definedDimensions.at(ModelArray::Dimension::X).localLength; const size_t ny = ModelArray::definedDimensions.at(ModelArray::Dimension::Y).localLength; // Simulate 4 iterations (timesteps) - metadata.setTime(xiosHandler.getCalendarStart()); // TODO: Avoid making configGetForcingFieldNames public? auto forcingFieldNames = xiosHandler.configGetForcingFieldNames(); for (int ts = 1; ts <= 4; ts++) { diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index 2bd1f91da..811f81481 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -38,14 +38,17 @@ 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 << "[XiosOutput]" << std::endl; config << "filename = " << filename << std::endl; config << "field_names = " << hiceName << std::endl; - config << "[XiosDiagnostic]" << std::endl; + config << "period = P0-0T01:30:00" << std::endl; // TODO: Account for separate restart and diagnostics files (#929) + config << "[XiosDiagnostic]" << std::endl; config << "filename = " << filename << std::endl; config << "field_names = " << uName << std::endl; + config << "period = P0-0T01:30:00" << std::endl; std::unique_ptr pcstream(new std::stringstream(config.str())); Configurator::addStream(std::move(pcstream)); @@ -102,14 +105,14 @@ MPI_TEST_CASE("TestXiosWrite", 2) HField u(ModelArray::Type::H); u.resize(); - // Check calendar step is zero initially - REQUIRE(xiosHandler.getCalendarStep() == 0); - // Check a file with the expected name doesn't exist yet REQUIRE_FALSE(std::filesystem::exists("xios_test_output*.nc")); - // Simulate 4 iterations (timesteps) + // Check calendar step is zero initially metadata.setTime(xiosHandler.getCalendarStart()); + REQUIRE(xiosHandler.getCalendarStep() == 0); + + // Simulate 4 iterations (timesteps) for (int ts = 1; ts <= 4; ts++) { // Update the current timestep and verify it's updated in XIOS @@ -135,7 +138,7 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Write out diagnostics and then restarts pio->writeDiagnosticTime(diagnostics, metadata, filepath); - grid.dumpModelState(restarts, metadata, "xios_test_output", true); + grid.dumpModelState(restarts, metadata, filepath, true); } // Check the files have indeed been created then remove it From 1032cdce29c200469efc60c9367d55552ada2d30 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 15:54:21 +0100 Subject: [PATCH 07/10] No need to setFieldFreqOffset --- core/test/XiosRead_test.cpp | 12 +++++------- core/test/XiosWrite_test.cpp | 3 +-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index cc6f0823c..9aef50c03 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -74,9 +74,6 @@ MPI_TEST_CASE("TestXiosRead", 2) xiosHandler.setFieldOperation(fieldId, "instant"); xiosHandler.setFieldGridRef(fieldId, "grid_2D"); } - Duration timestep = xiosHandler.getCalendarTimestep(); - xiosHandler.setFieldFreqOffset(hiceName, timestep); - // xiosHandler.setFieldFreqOffset(uName, timestep); // FIXME: Why does this cause a stall? xiosHandler.close_context_definition(); @@ -96,6 +93,7 @@ MPI_TEST_CASE("TestXiosRead", 2) const size_t ny = ModelArray::definedDimensions.at(ModelArray::Dimension::Y).localLength; // Simulate 4 iterations (timesteps) + Duration timestep = xiosHandler.getCalendarTimestep(); // TODO: Avoid making configGetForcingFieldNames public? auto forcingFieldNames = xiosHandler.configGetForcingFieldNames(); for (int ts = 1; ts <= 4; ts++) { @@ -111,10 +109,6 @@ MPI_TEST_CASE("TestXiosRead", 2) } } - // Update the current timestep and verify it's updated in XIOS - metadata.incrementTime(timestep); - REQUIRE(xiosHandler.getCalendarStep() == ts); - // Read restarts from file and check they take the expected values ModelState restarts = grid.getModelState(filepath, metadata); for (auto& entry : restarts.data) { @@ -124,6 +118,10 @@ MPI_TEST_CASE("TestXiosRead", 2) } } } + + // Update the current timestep and verify it's updated in XIOS + metadata.incrementTime(timestep); + REQUIRE(xiosHandler.getCalendarStep() == ts); } xiosHandler.context_finalize(); Finalizer::finalize(); diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index 811f81481..f75fc80be 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -79,11 +79,9 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Create a field on the grid // NOTE: Fields are created when the XIOS handler is constructed // NOTE: The 2D grid is created along with the 2D domain - Duration timestep = xiosHandler.getCalendarTimestep(); for (std::string fieldId : { hiceName, uName }) { xiosHandler.setFieldOperation(fieldId, "instant"); xiosHandler.setFieldGridRef(fieldId, "grid_2D"); - xiosHandler.setFieldFreqOffset(fieldId, timestep); } // Set file split frequency for restarts @@ -113,6 +111,7 @@ MPI_TEST_CASE("TestXiosWrite", 2) REQUIRE(xiosHandler.getCalendarStep() == 0); // Simulate 4 iterations (timesteps) + Duration timestep = xiosHandler.getCalendarTimestep(); for (int ts = 1; ts <= 4; ts++) { // Update the current timestep and verify it's updated in XIOS From 913397d6c7b36760d4db2e32bd206d886841163d Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 16:18:14 +0100 Subject: [PATCH 08/10] Address TODOs; consistent checks before reading and writing --- core/src/ParaGridIO_Xios.cpp | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/core/src/ParaGridIO_Xios.cpp b/core/src/ParaGridIO_Xios.cpp index fb12f460a..b2facc834 100644 --- a/core/src/ParaGridIO_Xios.cpp +++ b/core/src/ParaGridIO_Xios.cpp @@ -55,12 +55,17 @@ ModelState ParaGridIO::getModelState(const std::string& filePath, ModelMetadata& state.merge(ModelState { { { fieldId, field } }, {} }); } // Assume that all fields in the supplied ModelState are necessary, and so read them from file. + std::set restartFieldIds = xiosHandler.configGetInputRestartFieldNames(); for (auto& entry : state.data) { const std::string fieldId = entry.first; if (!xiosHandler.getFieldReadAccess(fieldId)) { throw std::runtime_error("ParaGridIO::getModelState: field " + fieldId + " is not configured for reading, but is being read from file."); }; + if (restartFieldIds.count(fieldId) == 0) { + throw std::runtime_error( + "ParaGridIO::getModelState: field " + fieldId + " is not configured as a restart."); + } xiosHandler.read(fieldId, entry.second); } return state; @@ -86,10 +91,14 @@ ModelState ParaGridIO::readForcingTimeStatic( const bool readAccess = true; std::set forcingFieldIds = xiosHandler.configGetForcingFieldNames(); for (const std::string& fieldId : forcings) { - if (forcingFieldIds.count(fieldId) == 0 || !xiosHandler.getFieldReadAccess(fieldId)) { + if (!xiosHandler.getFieldReadAccess(fieldId)) { throw std::runtime_error("ParaGridIO::readForcingTimeStatic: forcing " + fieldId + " is not configured for reading, but is being read from file."); } + if (forcingFieldIds.count(fieldId) == 0) { + throw std::runtime_error("ParaGridIO::readForcingTimeStatic: field " + fieldId + + " is not configured as a forcing."); + } // ASSUME all forcings are HFields: finite volume fields on the same // grid as ice thickness HField field(ModelArray::Type::H); @@ -112,15 +121,18 @@ void ParaGridIO::dumpModelState( { Xios& xiosHandler = Xios::getInstance(); - // Assume that all fields in the supplied ModelState are necessary, and so write them to - // file. - // TODO: Check they are indeed restarts + // Assume that all fields in the supplied ModelState are necessary, and so write them to file. + std::set restartFieldIds = xiosHandler.configGetOutputRestartFieldNames(); for (auto entry : state.data) { const std::string fieldId = entry.first; if (xiosHandler.getFieldReadAccess(fieldId)) { throw std::runtime_error("ParaGridIO::dumpModelState: field " + fieldId + " is not configured for writing, but is being written to file."); }; + if (restartFieldIds.count(fieldId) == 0) { + throw std::runtime_error("ParaGridIO::dumpModelState: field " + fieldId + + " is not configured as a restart."); + } xiosHandler.write(fieldId, entry.second); } } @@ -130,19 +142,20 @@ void ParaGridIO::writeDiagnosticTime( { Xios& xiosHandler = Xios::getInstance(); - // Assume that all fields in the supplied ModelState are necessary, and so write them to - // file. - // TODO: Check they are indeed diagnostics + // Assume that all fields in the supplied ModelState are necessary, and so write them to file. + std::set diagnosticFieldIds = xiosHandler.configGetDiagnosticFieldNames(); for (auto entry : state.data) { const std::string fieldId = entry.first; if (xiosHandler.getFieldReadAccess(fieldId)) { throw std::runtime_error("ParaGridIO::writeDiagnosticTime: field " + fieldId + " is not configured for writing, but is being written to file."); }; + if (diagnosticFieldIds.count(fieldId) == 0) { + throw std::runtime_error("ParaGridIO::writeDiagnosticTime: field " + fieldId + + " is not configured as a diagnostic."); + } xiosHandler.write(fieldId, entry.second); } - // TODO: Create a special timeless set of dimensions for the landmask - use 'once' operation - // TODO: Put the time axis variable } } /* namespace Nextsim */ From be9d5d89b1995dde77deebc6917d61e8e1abe435 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Thu, 2 Oct 2025 14:30:34 +0100 Subject: [PATCH 09/10] Post-merge fixes --- core/test/XiosWrite_test.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index e8a6c9c6a..9ca00c824 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -88,6 +88,7 @@ MPI_TEST_CASE("TestXiosWrite", 2) xiosHandler.setFieldType(maskName, ModelArray::Type::H); xiosHandler.setFieldType(coordsName, ModelArray::Type::VERTEX); xiosHandler.setFieldType(hiceName, ModelArray::Type::DG); + xiosHandler.setFieldType(uName, ModelArray::Type::H); // Set file split frequency for restarts // NOTE: Files are created when the XIOS handler is constructed @@ -163,8 +164,8 @@ MPI_TEST_CASE("TestXiosWrite", 2) {} }; // Write out diagnostics and then restarts - pio->writeDiagnosticTime(diagnostics, metadata, filepath); - grid.dumpModelState(restarts, metadata, filepath, true); + pio->writeDiagnosticTime(diagnostics, filepath); + grid.dumpModelState(restarts, filepath, true); } // Check the files have indeed been created then remove it From 0e09b2cd1546378323f5f8548146b70d1fababf3 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 13 Oct 2025 16:28:18 +0100 Subject: [PATCH 10/10] Check filenames in ParaGridIO_Xios --- core/src/ParaGridIO_Xios.cpp | 21 +++++++++++++++++++++ core/src/Xios.cpp | 9 +++++---- core/test/XiosRead_test.cpp | 7 +++---- core/test/XiosWrite_test.cpp | 7 +++---- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/core/src/ParaGridIO_Xios.cpp b/core/src/ParaGridIO_Xios.cpp index b50577143..dffd08878 100644 --- a/core/src/ParaGridIO_Xios.cpp +++ b/core/src/ParaGridIO_Xios.cpp @@ -49,6 +49,11 @@ ModelState ParaGridIO::getModelState(const std::string& filePath) ModelState state; Xios& xiosHandler = Xios::getInstance(); + if (xiosHandler.inputFilename != filePath) { + throw std::runtime_error("ParaGridIO::getModelState: file path '" + filePath + + "' is inconsistent with XiosInput.filename '" + xiosHandler.inputFilename + "'"); + } + // Get all variables in the file and load them into a new ModelState const bool readAccess = true; for (std::string fieldId : xiosHandler.configGetInputRestartFieldNames()) { @@ -94,6 +99,11 @@ ModelState ParaGridIO::readForcingTimeStatic( ModelState state; Xios& xiosHandler = Xios::getInstance(); + if (xiosHandler.forcingFilename != filePath) { + throw std::runtime_error("ParaGridIO::readForcingTimeStatic: file path '" + filePath + + "' is inconsistent with XiosForcing.filename '" + xiosHandler.forcingFilename + "'"); + } + // Increment the XIOS calendar until it reaches the requested time while (xiosHandler.getCurrentDate() < time) { xiosHandler.incrementCalendar(); @@ -137,6 +147,11 @@ void ParaGridIO::dumpModelState(const ModelState& state, const std::string& file { Xios& xiosHandler = Xios::getInstance(); + if (xiosHandler.outputFilename != filePath) { + throw std::runtime_error("ParaGridIO::dumpModelState: file path '" + filePath + + "' is inconsistent with XiosOutput.filename '" + xiosHandler.outputFilename + "'"); + } + // Assume that all fields in the supplied ModelState are necessary, and so write them to file. std::set restartFieldIds = xiosHandler.configGetOutputRestartFieldNames(); for (auto entry : state.data) { @@ -157,6 +172,12 @@ void ParaGridIO::writeDiagnosticTime(const ModelState& state, const std::string& { Xios& xiosHandler = Xios::getInstance(); + if (xiosHandler.diagnosticFilename != filePath) { + throw std::runtime_error("ParaGridIO::writeDiagnosticTime: file path '" + filePath + + "' is inconsistent with XiosDiagnostic.filename '" + xiosHandler.diagnosticFilename + + "'"); + } + // Assume that all fields in the supplied ModelState are necessary, and so write them to file. std::set diagnosticFieldIds = xiosHandler.configGetDiagnosticFieldNames(); for (auto entry : state.data) { diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index e4e96d1cf..b443cb6b1 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -88,16 +88,17 @@ Xios::Xios(const std::string contextid, const std::string calendartype) istringstream( Configured::getConfiguration(keyMap.at(OUTPUT_RESTARTFILE_KEY), std::string())) >> outputFilename; - outputFileId = ((std::filesystem::path)outputFilename).replace_extension(); + outputFileId = ((std::filesystem::path)outputFilename).filename().replace_extension(); istringstream(Configured::getConfiguration(keyMap.at(INPUT_RESTARTFILE_KEY), std::string())) >> inputFilename; - inputFileId = ((std::filesystem::path)inputFilename).replace_extension(); + inputFileId = ((std::filesystem::path)inputFilename).filename().replace_extension(); istringstream(Configured::getConfiguration(keyMap.at(DIAGNOSTIC_FILE_KEY), std::string())) >> diagnosticFilename; - diagnosticFileId = ((std::filesystem::path)diagnosticFilename).replace_extension(); + diagnosticFileId + = ((std::filesystem::path)diagnosticFilename).filename().replace_extension(); istringstream(Configured::getConfiguration(keyMap.at(FORCING_FILE_KEY), std::string())) >> forcingFilename; - forcingFileId = ((std::filesystem::path)forcingFilename).replace_extension(); + forcingFileId = ((std::filesystem::path)forcingFilename).filename().replace_extension(); // TODO: Account for separate restart and forcing files (#929) if (diagnosticFilename.length() > 0 && outputFilename.length() > 0 diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index 8980dbfb7..df04f8e47 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -20,8 +20,7 @@ #include const std::string testFilesDir = TEST_FILES_DIR; -const std::string filename = "xios_test_input.nc"; -const std::string filepath = testFilesDir + "/" + filename; +const std::string filename = testFilesDir + "/xios_test_input.nc"; static const int DG = 3; @@ -80,7 +79,7 @@ MPI_TEST_CASE("TestXiosRead", 2) xiosHandler.close_context_definition(); // Check the input file exists - REQUIRE(std::filesystem::exists(filepath)); + REQUIRE(std::filesystem::exists(filename)); // Check calendar step is zero initially metadata.setTime(xiosHandler.getCalendarStart()); @@ -99,7 +98,7 @@ MPI_TEST_CASE("TestXiosRead", 2) // Read forcings from file and check they take the expected values TimePoint time = xiosHandler.getCurrentDate(); - ModelState forcings = pio->readForcingTimeStatic(forcingFieldNames, time, filepath); + ModelState forcings = pio->readForcingTimeStatic(forcingFieldNames, time, filename); for (auto& entry : forcings.data) { for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index 9ca00c824..c3e1eb40f 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -20,8 +20,7 @@ #include const std::string testFilesDir = TEST_FILES_DIR; -const std::string filename = "xios_test_output.nc"; -const std::string filepath = testFilesDir + "/" + filename; +const std::string filename = testFilesDir + "/xios_test_output.nc"; static const int DG = 3; @@ -164,8 +163,8 @@ MPI_TEST_CASE("TestXiosWrite", 2) {} }; // Write out diagnostics and then restarts - pio->writeDiagnosticTime(diagnostics, filepath); - grid.dumpModelState(restarts, filepath, true); + pio->writeDiagnosticTime(diagnostics, filename); + grid.dumpModelState(restarts, filename, true); } // Check the files have indeed been created then remove it