From 24513594371bcf1fc4bf865d414e2ac9efe411d3 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 18 Nov 2025 13:09:29 +0000 Subject: [PATCH 01/16] Ensure write test comes before read test --- core/test/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/test/CMakeLists.txt b/core/test/CMakeLists.txt index e84ca786d..2963c8c62 100644 --- a/core/test/CMakeLists.txt +++ b/core/test/CMakeLists.txt @@ -271,6 +271,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" From 5474f917c1f463a6b78c56fc3fad7962bd11f7a8 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 18 Nov 2025 13:14:44 +0000 Subject: [PATCH 02/16] Have the read test delete the files from the write test; disable file splitting for now --- core/test/XiosRead_test.cpp | 8 ++++++++ core/test/XiosWrite_test.cpp | 17 ++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index 3dcbdab59..b7d619f51 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -169,6 +169,14 @@ MPI_TEST_CASE("TestXiosRead", 2) REQUIRE(xiosHandler.getCalendarStep() == ts + 1); } + if (rank == 0) { + // TODO: Re-enable file splitting (#898) + // std::filesystem::remove("xios_test_output_20230317171100-20230317201059.nc"); + // std::filesystem::remove("xios_test_output_20230317201100-20230317231059.nc"); + std::filesystem::remove("xios_test_output.nc"); + std::filesystem::remove("xios_test_diagnostic.nc"); + } + xiosHandler.context_finalize(); Finalizer::finalize(); } diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index d83382976..558432bbe 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -51,7 +51,8 @@ MPI_TEST_CASE("TestXiosWrite", 2) 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; + // TODO: Re-enable file splitting (#898) + // config << "split_period = P0-0T03:00:00" << std::endl; config << "[XiosDiagnostic]" << std::endl; config << "filename = " << diagnosticFilename << std::endl; config << "field_names = " << hsnowName << std::endl; @@ -194,15 +195,13 @@ MPI_TEST_CASE("TestXiosWrite", 2) grid.dumpModelState(restarts, restartOutputFilename, 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")); + // Check the files have indeed been created + // NOTE: We don't remove them because they are used in XiosRead_test + // TODO: Re-enable file splitting (#898) + // REQUIRE(std::filesystem::exists("xios_test_output_20230317171100-20230317201059.nc")); + // REQUIRE(std::filesystem::exists("xios_test_output_20230317201100-20230317231059.nc")); + REQUIRE(std::filesystem::exists("xios_test_output.nc")); REQUIRE(std::filesystem::exists("xios_test_diagnostic.nc")); - 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"); - } xiosHandler.context_finalize(); Finalizer::finalize(); From 57eaef7c3423b883df2b82dea09b166bf9e3fbc2 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 18 Nov 2025 15:32:40 +0000 Subject: [PATCH 03/16] Fix dimension names --- core/src/iodef.xml.jinja | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 @@ - - - + + + From 1555ee20383b17a94ccb6a5d5a1bd4e2d9e4c4ce Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 24 Nov 2025 14:41:25 +0000 Subject: [PATCH 04/16] Set domain lon/lat values as indices --- core/src/Xios.cpp | 66 +++++++++++++++++++-------- core/src/include/xios_c_interface.hpp | 4 ++ 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 0776bcf98..a80bfdf3d 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -506,35 +506,50 @@ void Xios::setupDomains() for (ModelArray::Dimension dim : ModelArray::typeDimensions[type]) { const std::string domainName = ModelArray::definedDimensions[dim].name; 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 + "'"); } + 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 + "'"); } + 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, domainName.c_str(), domainName.length()); if (!cxios_is_defined_domain_dim_i_name(domain)) { throw std::runtime_error( @@ -546,35 +561,50 @@ void Xios::setupDomains() "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 + "'"); } + 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 + "'"); } + 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, domainName.c_str(), domainName.length()); if (!cxios_is_defined_domain_dim_j_name(domain)) { throw std::runtime_error( diff --git a/core/src/include/xios_c_interface.hpp b/core/src/include/xios_c_interface.hpp index 845e3c294..e9a84a5c9 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); From 84d0c95fc9e8699631348a3ca18cb900e437aac7 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 24 Nov 2025 14:49:12 +0000 Subject: [PATCH 05/16] Have read test read output of write test --- core/test/XiosRead_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index b7d619f51..5838dae51 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -20,7 +20,7 @@ #include const std::string testFilesDir = TEST_FILES_DIR; -const std::string restartFilename = testFilesDir + "/xios_test_input.nc"; +const std::string restartFilename = testFilesDir + "/xios_test_output.nc"; const std::string forcingFilename = testFilesDir + "/xios_test_forcing.nc"; static const int DGCOMP = 6; From 94a05c415f949a8cbb214772bf56f5b96062a8cf Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 24 Nov 2025 15:45:22 +0000 Subject: [PATCH 06/16] Clearer error for missing restart file --- core/test/XiosRead_test.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index 5838dae51..539269462 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -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(restartFilename)) { + 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 From c16977a9e36738265f6418c9127ff6bf338086ea Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 24 Nov 2025 16:22:35 +0000 Subject: [PATCH 07/16] Use altName for dims --- core/src/Xios.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index a80bfdf3d..442d42e3c 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -504,7 +504,7 @@ 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; @@ -523,8 +523,7 @@ void Xios::setupDomains() 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)) { @@ -550,12 +549,12 @@ void Xios::setupDomains() throw std::runtime_error( "Xios: Failed to set local x-indices for domain '" + domainId + "'"); } - cxios_set_domain_dim_i_name(domain, domainName.c_str(), domainName.length()); + 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 + "'"); @@ -578,8 +577,7 @@ void Xios::setupDomains() 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)) { @@ -605,12 +603,12 @@ void Xios::setupDomains() throw std::runtime_error( "Xios: Failed to set local y-indices for domain '" + domainId + "'"); } - cxios_set_domain_dim_j_name(domain, domainName.c_str(), domainName.length()); + 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 + "'"); From 5296d983a8fa7d58b6976ff343593bc3739917f2 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Wed, 1 Oct 2025 16:28:41 +0100 Subject: [PATCH 08/16] Only write landmask once --- core/src/Xios.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 442d42e3c..242adb828 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 @@ -798,11 +799,13 @@ 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) { + if (configGetInputRestartFieldNames().count(fieldId) > 0 || fieldId == maskName) { + // Restarts are read "once" and the mask is fixed so only written once operation = "once"; } else { + // Otherwise, read/write all timesteps without post-processing operation = "instant"; } if (cxios_is_defined_field_operation(field)) { From 042eee85a9eb566cca6bfc518c29cc972dba73c7 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Mon, 10 Nov 2025 17:15:06 +0000 Subject: [PATCH 09/16] Write diagnostics with averaging --- core/src/Xios.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 242adb828..e1f503184 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -804,6 +804,9 @@ void Xios::createField(const std::string fieldId) if (configGetInputRestartFieldNames().count(fieldId) > 0 || fieldId == maskName) { // Restarts are read "once" and the mask is fixed so only written 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"; From 7786fb40c7a9a2d4b91b22aeb0b1af40d09e7620 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 25 Nov 2025 13:14:59 +0000 Subject: [PATCH 10/16] Don't write the mask 'once' Otherwise, it will be zeroed in file splits after the first --- core/src/Xios.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index e1f503184..5d3d49f37 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -801,8 +801,8 @@ void Xios::createField(const std::string fieldId) // Set the operation type std::string operation; - if (configGetInputRestartFieldNames().count(fieldId) > 0 || fieldId == maskName) { - // Restarts are read "once" and the mask is fixed so only written once + 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 From ce5116f7811fbffa2f3a9a63356a9c129e469f6e Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 25 Nov 2025 13:23:35 +0000 Subject: [PATCH 11/16] Make XiosWrite_test output fields time-dependent --- core/test/XiosRead_test.cpp | 13 +++--- core/test/XiosWrite_test.cpp | 83 ++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index 539269462..ba8b65d16 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_output.nc"; +const std::string restartFilename + = testFilesDir + "/xios_test_output_20230317171100-20230317201059.nc"; const std::string forcingFilename = testFilesDir + "/xios_test_forcing.nc"; static const int DGCOMP = 6; @@ -43,7 +44,6 @@ MPI_TEST_CASE("TestXiosRead", 2) config << "stop = 2023-03-17T23:11:00Z" << std::endl; config << "time_step = P0-0T01:30:00" << std::endl; config << "init_file = " << restartFilename << std::endl; - config << "restart_period = P0-0T01:30:00" << std::endl; config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; config << "[XiosInput]" << std::endl; config << "field_names = " << maskName << "," << coordsName << "," << hiceName << "," @@ -173,11 +173,10 @@ MPI_TEST_CASE("TestXiosRead", 2) } if (rank == 0) { - // TODO: Re-enable file splitting (#898) - // std::filesystem::remove("xios_test_output_20230317171100-20230317201059.nc"); - // std::filesystem::remove("xios_test_output_20230317201100-20230317231059.nc"); - std::filesystem::remove("xios_test_output.nc"); - std::filesystem::remove("xios_test_diagnostic.nc"); + std::filesystem::remove("xios_test_output_20230317171100-20230317201059.nc"); + std::filesystem::remove("xios_test_output_20230317201100-20230317231059.nc"); + std::filesystem::remove("xios_test_diagnostic_20230317171100-20230317201059.nc"); + std::filesystem::remove("xios_test_diagnostic_20230317201100-20230317231059.nc"); } xiosHandler.context_finalize(); diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index 558432bbe..68bd810e2 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -108,48 +108,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(); @@ -163,12 +127,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) { @@ -197,11 +199,10 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Check the files have indeed been created // NOTE: We don't remove them because they are used in XiosRead_test - // TODO: Re-enable file splitting (#898) - // REQUIRE(std::filesystem::exists("xios_test_output_20230317171100-20230317201059.nc")); - // REQUIRE(std::filesystem::exists("xios_test_output_20230317201100-20230317231059.nc")); - REQUIRE(std::filesystem::exists("xios_test_output.nc")); - REQUIRE(std::filesystem::exists("xios_test_diagnostic.nc")); + 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_20230317171100-20230317201059.nc")); + REQUIRE(std::filesystem::exists("xios_test_diagnostic_20230317201100-20230317231059.nc")); xiosHandler.context_finalize(); Finalizer::finalize(); From daea40229dd2981dbe5deca8f7263a01d4c2d899 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 25 Nov 2025 13:33:48 +0000 Subject: [PATCH 12/16] Align outputFreq and splitFreq --- core/src/Xios.cpp | 53 ++++++++++++++---------------------- core/src/include/Xios.hpp | 5 ++-- core/test/XiosFile_test.cpp | 10 +++---- core/test/XiosWrite_test.cpp | 7 ++--- 4 files changed, 30 insertions(+), 45 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 5d3d49f37..560e56e4a 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -55,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" } }; @@ -1133,10 +1131,9 @@ void Xios::createFile(const std::string fileId, const int fieldType) } // 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) { @@ -1146,31 +1143,9 @@ 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)); - } - } - - // 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 + "'"); - } + setFileOutputFreq(fileId, Duration(periodStr), fieldType); } } @@ -1209,12 +1184,16 @@ void Xios::setFileType(const std::string fileId, const std::string fileType) } /*! - * Set the output frequency of a file with a given ID + * @brief Set the output frequency of a file with a given ID * - * @param the file ID - * @param output frequency to set + * @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 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)) { @@ -1224,6 +1203,16 @@ 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 + "'"); } + if (fieldType == OUTPUT_RESTART || fieldType == DIAGNOSTIC) { + 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 + "'"); + } + } } /*! diff --git a/core/src/include/Xios.hpp b/core/src/include/Xios.hpp index 2e70949cc..510b9ab30 100644 --- a/core/src/include/Xios.hpp +++ b/core/src/include/Xios.hpp @@ -89,7 +89,8 @@ 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 setFileOutputFreq( + const std::string fileId, const Duration outputFreq, const int fieldType); void setFileParAccess(const std::string fileId, const std::string parAccess); std::string getFileType(const std::string fileId); Duration getFileOutputFreq(const std::string fileId); @@ -101,12 +102,10 @@ class Xios : public Configured { 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, diff --git a/core/test/XiosFile_test.cpp b/core/test/XiosFile_test.cpp index bae28fd67..6c88d83b8 100644 --- a/core/test/XiosFile_test.cpp +++ b/core/test/XiosFile_test.cpp @@ -120,19 +120,19 @@ MPI_TEST_CASE("TestXiosFile", 2) 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")); + xiosHandler.setFileOutputFreq(yearId, Duration("P1-0T00:00:00"), xiosHandler.OUTPUT_RESTART); REQUIRE(xiosHandler.getFileOutputFreq(yearId).seconds() == 365 * 24 * 60 * 60); xiosHandler.createFile(dayId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(dayId, Duration("P0-1T00:00:00")); + xiosHandler.setFileOutputFreq(dayId, Duration("P0-1T00:00:00"), xiosHandler.OUTPUT_RESTART); REQUIRE(xiosHandler.getFileOutputFreq(dayId).seconds() == 24 * 60 * 60); xiosHandler.createFile(hourId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(hourId, Duration("P0-0T01:00:00")); + xiosHandler.setFileOutputFreq(hourId, Duration("P0-0T01:00:00"), xiosHandler.OUTPUT_RESTART); REQUIRE(xiosHandler.getFileOutputFreq(hourId).seconds() == 60 * 60); xiosHandler.createFile(minuteId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(minuteId, Duration("P0-0T00:01:00")); + xiosHandler.setFileOutputFreq(minuteId, Duration("P0-0T00:01:00"), xiosHandler.OUTPUT_RESTART); REQUIRE(xiosHandler.getFileOutputFreq(minuteId).seconds() == 60); xiosHandler.createFile(secondId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(secondId, Duration("P0-0T00:00:01")); + xiosHandler.setFileOutputFreq(secondId, Duration("P0-0T00:00:01"), xiosHandler.OUTPUT_RESTART); REQUIRE(xiosHandler.getFileOutputFreq(secondId).seconds() == 1); xiosHandler.close_context_definition(); diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index 68bd810e2..de2b461c3 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -46,17 +46,14 @@ MPI_TEST_CASE("TestXiosWrite", 2) config << "init_file = " << restartInputFilename << std::endl; config << "restart_file = " << restartOutputFilename << std::endl; config << "partition_file = xios_test_partition_metadata_2.nc" << std::endl; - config << "restart_period = P0-0T01:30:00" << std::endl; + config << "restart_period = P0-0T03:00:00" << std::endl; config << "[XiosOutput]" << std::endl; config << "field_names = " << maskName << "," << coordsName << "," << hiceName << "," << ticeName << "," << uName << std::endl; - config << "period = P0-0T01:30:00" << std::endl; - // TODO: Re-enable file splitting (#898) - // config << "split_period = P0-0T03:00:00" << std::endl; config << "[XiosDiagnostic]" << std::endl; config << "filename = " << diagnosticFilename << std::endl; config << "field_names = " << hsnowName << std::endl; - config << "period = P0-0T01:30:00" << std::endl; + config << "period = P0-0T03:00:00" << std::endl; std::unique_ptr pcstream(new std::stringstream(config.str())); Configurator::addStream(std::move(pcstream)); From f73566bd8cfd94811ca92b76a3dac2063960d262 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 25 Nov 2025 16:01:02 +0000 Subject: [PATCH 13/16] Use consistent date format in split files --- core/src/Xios.cpp | 5 +++++ core/src/include/xios_c_interface.hpp | 3 +++ core/test/XiosRead_test.cpp | 10 +++++----- core/test/XiosWrite_test.cpp | 12 ++++++------ 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 560e56e4a..fe8d25b51 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -1204,6 +1204,7 @@ void Xios::setFileOutputFreq(const std::string fileId, const Duration freq, cons throw std::runtime_error("Xios: Failed to set output frequency 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 + "'"); } @@ -1212,6 +1213,10 @@ void Xios::setFileOutputFreq(const std::string fileId, const Duration freq, cons 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()); } } diff --git a/core/src/include/xios_c_interface.hpp b/core/src/include/xios_c_interface.hpp index e9a84a5c9..202d4a852 100644 --- a/core/src/include/xios_c_interface.hpp +++ b/core/src/include/xios_c_interface.hpp @@ -146,6 +146,8 @@ 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_set_file_split_freq_format( + xios::CFile* file_hdl, const char* split_freq_format, int split_freq_format_size); void cxios_get_file_type(xios::CFile* file_hdl, char* type, int type_size); void cxios_get_file_output_freq(xios::CFile* file_hdl, cxios_duration* output_freq_c); void cxios_get_file_mode(xios::CFile* file_hdl, char* mode, int mode_size); @@ -156,6 +158,7 @@ bool cxios_is_defined_file_output_freq(xios::CFile* file_hdl); bool cxios_is_defined_file_split_freq(xios::CFile* file_hdl); bool cxios_is_defined_file_mode(xios::CFile* file_hdl); bool cxios_is_defined_file_par_access(xios::CFile* file_hdl); +bool cxios_is_defined_file_split_freq_format(xios::CFile* file_hdl); void cxios_xml_tree_add_fieldtofile( xios::CFile* file, xios::CField** field, const char* _id, int _id_len); diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index ba8b65d16..6db533c12 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -21,7 +21,7 @@ const std::string testFilesDir = TEST_FILES_DIR; const std::string restartFilename - = testFilesDir + "/xios_test_output_20230317171100-20230317201059.nc"; + = 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; @@ -173,10 +173,10 @@ MPI_TEST_CASE("TestXiosRead", 2) } 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_20230317171100-20230317201059.nc"); - std::filesystem::remove("xios_test_diagnostic_20230317201100-20230317231059.nc"); + 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(); diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index de2b461c3..b28a7e9d5 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -21,8 +21,8 @@ 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 restartOutputFilename = testFilesDir + "/restart.nc"; +const std::string diagnosticFilename = testFilesDir + "/diagnostic.nc"; static const int DGCOMP = 6; static const int DGSTRESSCOMP = 8; @@ -196,10 +196,10 @@ MPI_TEST_CASE("TestXiosWrite", 2) // Check the files have indeed been created // NOTE: We don't remove them because they are used in XiosRead_test - 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_20230317171100-20230317201059.nc")); - REQUIRE(std::filesystem::exists("xios_test_diagnostic_20230317201100-20230317231059.nc")); + 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(); From 3e886cd3835caed7d17f161c811840bd97101e11 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 25 Nov 2025 17:08:31 +0000 Subject: [PATCH 14/16] Handle patterns in filenames --- core/src/Xios.cpp | 48 +++++++++++++++++++++--------------- core/test/XiosRead_test.cpp | 8 +++--- core/test/XiosWrite_test.cpp | 12 ++++----- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index fe8d25b51..4f60d02bc 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -84,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." }, @@ -97,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 " @@ -1064,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()); @@ -1079,8 +1080,29 @@ 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()); + // Get the fieldIds and filenames + std::set fieldIds; + std::string filename; + if (fieldType == INPUT_RESTART) { + fieldIds = configGetInputRestartFieldNames(); + filename = metadata.initialFileName; + } else if (fieldType == OUTPUT_RESTART) { + fieldIds = configGetOutputRestartFieldNames(); + filename = metadata.finalFileName; + } else if (fieldType == FORCING) { + fieldIds = configGetForcingFieldNames(); + filename = forcingFilename; + } else if (fieldType == DIAGNOSTIC) { + fieldIds = configGetDiagnosticFieldNames(); + filename = diagnosticFilename; + } + + // 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 + "'"); } @@ -1118,20 +1140,7 @@ void Xios::createFile(const std::string fileId, const int fieldType) setFileType(fileId, "one_file"); setFileParAccess(fileId, "collective"); - // Get the fieldIds - std::set fieldIds; - if (fieldType == INPUT_RESTART) { - fieldIds = configGetInputRestartFieldNames(); - } else if (fieldType == OUTPUT_RESTART) { - fieldIds = configGetOutputRestartFieldNames(); - } else if (fieldType == FORCING) { - fieldIds = configGetForcingFieldNames(); - } else if (fieldType == DIAGNOSTIC) { - fieldIds = configGetDiagnosticFieldNames(); - } - // Set the file output frequency - ModelMetadata& metadata = ModelMetadata::getInstance(); if (fieldType == INPUT_RESTART || fieldType == OUTPUT_RESTART) { setFileOutputFreq(fileId, metadata.restartPeriod, fieldType); } else { @@ -1365,7 +1374,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 diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index 6db533c12..9dd52149a 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -20,7 +20,7 @@ #include const std::string testFilesDir = TEST_FILES_DIR; -const std::string restartFilename +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"; @@ -43,7 +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 << "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 << "," @@ -80,7 +80,7 @@ MPI_TEST_CASE("TestXiosRead", 2) xiosHandler.close_context_definition(); // Check the input files exist - if (!std::filesystem::exists(restartFilename)) { + if (!std::filesystem::exists(inputFilename)) { throw std::runtime_error( "XiosRead_test: Input file not found. Did you run XiosWrite_test?"); } @@ -96,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) { diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index b28a7e9d5..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 + "/restart.nc"; -const std::string diagnosticFilename = testFilesDir + "/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,8 +43,8 @@ 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-0T03:00:00" << std::endl; config << "[XiosOutput]" << std::endl; @@ -191,7 +191,7 @@ 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 From 322f0e4167309fefa011d70ed4fe43c4c7e702a7 Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 25 Nov 2025 17:25:14 +0000 Subject: [PATCH 15/16] Overhaul createFile; drop XiosFile_test --- core/src/Xios.cpp | 188 +++++++------------------- core/src/include/Xios.hpp | 6 - core/src/include/xios_c_interface.hpp | 3 - core/test/CMakeLists.txt | 12 -- core/test/XiosFile_test.cpp | 142 ------------------- 5 files changed, 48 insertions(+), 303 deletions(-) delete mode 100644 core/test/XiosFile_test.cpp diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 4f60d02bc..a0fcee5e3 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -1080,21 +1080,61 @@ void Xios::createFile(const std::string fileId, const int fieldType) throw std::runtime_error("Xios: Failed to create file '" + fileId + "'"); } - // Get the fieldIds and filenames - std::set fieldIds; + // 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 + "'"); + } + 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 + "'"); + } + + // 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 + "'"); + } + 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 names, modes, and fieldIds associated with the file std::string filename; + std::string mode; + std::set fieldIds; if (fieldType == INPUT_RESTART) { - fieldIds = configGetInputRestartFieldNames(); filename = metadata.initialFileName; + mode = "read"; + fieldIds = configGetInputRestartFieldNames(); } else if (fieldType == OUTPUT_RESTART) { - fieldIds = configGetOutputRestartFieldNames(); filename = metadata.finalFileName; + mode = "write"; + fieldIds = configGetOutputRestartFieldNames(); } else if (fieldType == FORCING) { - fieldIds = configGetForcingFieldNames(); filename = forcingFilename; + mode = "read"; + fieldIds = configGetForcingFieldNames(); } else if (fieldType == DIAGNOSTIC) { - fieldIds = configGetDiagnosticFieldNames(); filename = diagnosticFilename; + mode = "write"; + 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 @@ -1107,39 +1147,16 @@ void Xios::createFile(const std::string fileId, const int fieldType) throw std::runtime_error("Xios: Failed to set name for file '" + fileId + "'"); } - // Determine whether the file is configured for reading or writing + // Check that the filename is not used for both reading and writing + // FIXME: This check will no longer ever be triggered 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 } - // 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; - } - - // Check that the filename is in the XiosOutput or XiosInput config section - if (!(readAccess || writeAccess)) { - throw std::runtime_error("Xios: File '" + fileId - + "' cannot be found in the model, XiosDiagnostic, or XiosForcing config sections"); - } - - // Set the file mode and some defaults - if (readAccess) { - setFileMode(fileId, "read"); - } else { - setFileMode(fileId, "write"); - } - setFileType(fileId, "one_file"); - setFileParAccess(fileId, "collective"); - // Set the file output frequency if (fieldType == INPUT_RESTART || fieldType == OUTPUT_RESTART) { setFileOutputFreq(fileId, metadata.restartPeriod, fieldType); @@ -1174,24 +1191,6 @@ void Xios::createFile(const std::string fileId, const int fieldType) } } -/*! - * Set the type of a file with a given ID - * - * @param the file ID - * @param file type to set - */ -void Xios::setFileType(const std::string fileId, const std::string fileType) -{ - xios::CFile* file = getFile(fileId); - if (cxios_is_defined_file_type(file)) { - Logged::warning("Xios: Overwriting type for file '" + fileId + "'"); - } - cxios_set_file_type(file, fileType.c_str(), fileType.length()); - if (!cxios_is_defined_file_type(file)) { - throw std::runtime_error("Xios: Failed to set type for file '" + fileId + "'"); - } -} - /*! * @brief Set the output frequency of a file with a given ID * @@ -1229,59 +1228,6 @@ void Xios::setFileOutputFreq(const std::string fileId, const Duration freq, cons } } -/*! - * Set the mode of a file with a given ID - * - * @param the file ID - * @param file mode to set - */ -void Xios::setFileMode(const std::string fileId, const std::string mode) -{ - xios::CFile* file = getFile(fileId); - if (cxios_is_defined_file_mode(file)) { - Logged::warning("Xios: Overwriting mode for file '" + fileId + "'"); - } - cxios_set_file_mode(file, mode.c_str(), mode.length()); - if (!cxios_is_defined_file_mode(file)) { - throw std::runtime_error("Xios: Failed to set mode for file '" + fileId + "'"); - } -} - -/*! - * Set the parallel access mode of a file with a given ID - * - * @param the file ID - * @param parallel access mode to set - */ -void Xios::setFileParAccess(const std::string fileId, const std::string parAccess) -{ - xios::CFile* file = getFile(fileId); - if (cxios_is_defined_file_par_access(file)) { - Logged::warning("Xios: Overwriting parallel access for file '" + fileId + "'"); - } - cxios_set_file_par_access(file, parAccess.c_str(), parAccess.length()); - if (!cxios_is_defined_file_par_access(file)) { - throw std::runtime_error("Xios: Failed to set parallel access for file '" + fileId + "'"); - } -} - -/*! - * Get the type of a file with a given ID - * - * @param the file ID - * @return type of the corresponding file - */ -std::string Xios::getFileType(const std::string fileId) -{ - xios::CFile* file = getFile(fileId); - if (!cxios_is_defined_file_type(file)) { - throw std::runtime_error("Xios: Undefined type for file '" + fileId + "'"); - } - char cStr[cStrLen]; - cxios_get_file_type(file, cStr, cStrLen); - return convertCStrToCppStr(cStr, cStrLen); -} - /*! * Get the output frequency of a file with a given ID * @@ -1299,44 +1245,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 * diff --git a/core/src/include/Xios.hpp b/core/src/include/Xios.hpp index 510b9ab30..ca128dd4e 100644 --- a/core/src/include/Xios.hpp +++ b/core/src/include/Xios.hpp @@ -88,14 +88,9 @@ 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, const int fieldType); - void setFileParAccess(const std::string fileId, const std::string parAccess); - std::string getFileType(const std::string fileId); Duration getFileOutputFreq(const std::string fileId); - std::string getFileMode(const std::string fileId); - std::string getFileParAccess(const std::string fileId); void fileAddField(const std::string fileId, const std::string fieldId); std::vector fileGetFieldIds(const std::string fileId); @@ -188,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 202d4a852..619f7a6f7 100644 --- a/core/src/include/xios_c_interface.hpp +++ b/core/src/include/xios_c_interface.hpp @@ -148,10 +148,7 @@ 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_set_file_split_freq_format( xios::CFile* file_hdl, const char* split_freq_format, int split_freq_format_size); -void cxios_get_file_type(xios::CFile* file_hdl, char* type, int type_size); void cxios_get_file_output_freq(xios::CFile* file_hdl, cxios_duration* output_freq_c); -void cxios_get_file_mode(xios::CFile* file_hdl, char* mode, int mode_size); -void cxios_get_file_par_access(xios::CFile* file_hdl, char* par_access, int par_access_size); 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); diff --git a/core/test/CMakeLists.txt b/core/test/CMakeLists.txt index 2963c8c62..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 diff --git a/core/test/XiosFile_test.cpp b/core/test/XiosFile_test.cpp deleted file mode 100644 index 6c88d83b8..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"), xiosHandler.OUTPUT_RESTART); - REQUIRE(xiosHandler.getFileOutputFreq(yearId).seconds() == 365 * 24 * 60 * 60); - xiosHandler.createFile(dayId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(dayId, Duration("P0-1T00:00:00"), xiosHandler.OUTPUT_RESTART); - REQUIRE(xiosHandler.getFileOutputFreq(dayId).seconds() == 24 * 60 * 60); - xiosHandler.createFile(hourId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(hourId, Duration("P0-0T01:00:00"), xiosHandler.OUTPUT_RESTART); - REQUIRE(xiosHandler.getFileOutputFreq(hourId).seconds() == 60 * 60); - xiosHandler.createFile(minuteId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(minuteId, Duration("P0-0T00:01:00"), xiosHandler.OUTPUT_RESTART); - REQUIRE(xiosHandler.getFileOutputFreq(minuteId).seconds() == 60); - xiosHandler.createFile(secondId, xiosHandler.OUTPUT_RESTART); - xiosHandler.setFileOutputFreq(secondId, Duration("P0-0T00:00:01"), xiosHandler.OUTPUT_RESTART); - REQUIRE(xiosHandler.getFileOutputFreq(secondId).seconds() == 1); - - xiosHandler.close_context_definition(); - xiosHandler.context_finalize(); - Finalizer::finalize(); -} -} From 33e0a3eb495a7e59ea80d11c52ce2816fc407c1f Mon Sep 17 00:00:00 2001 From: Joe Wallwork Date: Tue, 25 Nov 2025 17:33:05 +0000 Subject: [PATCH 16/16] Pull file uniqueness check out of createFile --- core/src/Xios.cpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index a0fcee5e3..06debbbed 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -1104,21 +1104,26 @@ void Xios::createFile(const std::string fileId, const int fieldType) 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(); } @@ -1147,16 +1152,6 @@ void Xios::createFile(const std::string fileId, const int fieldType) throw std::runtime_error("Xios: Failed to set name for file '" + fileId + "'"); } - // Check that the filename is not used for both reading and writing - // FIXME: This check will no longer ever be triggered - bool readAccess = (fieldType == INPUT_RESTART || fieldType == FORCING); - bool writeAccess = (fieldType == OUTPUT_RESTART || fieldType == DIAGNOSTIC); - if (readAccess && writeAccess) { - throw std::runtime_error("Xios: File '" + fileId + "' configured for both reading and" - + " writing. This is not yet supported in the XIOS I/O implementation."); - // TODO: Refactor to allow a field to be both read and written - } - // Set the file output frequency if (fieldType == INPUT_RESTART || fieldType == OUTPUT_RESTART) { setFileOutputFreq(fileId, metadata.restartPeriod, fieldType); @@ -1175,11 +1170,11 @@ void Xios::createFile(const std::string fileId, const int fieldType) } } - // 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 @@ -1292,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); } }