diff --git a/.github/workflows/test_suite.yml b/.github/workflows/test_suite.yml index f7c502653..8949342af 100644 --- a/.github/workflows/test_suite.yml +++ b/.github/workflows/test_suite.yml @@ -4,7 +4,6 @@ on: push: branches: [ main, develop ] pull_request: - branches: [ main, develop ] jobs: diff --git a/core/src/ModelMetadata.cpp b/core/src/ModelMetadata.cpp index f6f332672..27b6c1809 100644 --- a/core/src/ModelMetadata.cpp +++ b/core/src/ModelMetadata.cpp @@ -1,7 +1,7 @@ /*! * @file ModelMetadata.cpp * - * @date 10 Dec 2024 + * @date 02 Jan 2025 * @author Tim Spain */ @@ -10,6 +10,9 @@ #include "include/IStructure.hpp" #include "include/NextsimModule.hpp" #include "include/gridNames.hpp" +#ifdef USE_XIOS +#include "include/Xios.hpp" +#endif #ifdef USE_MPI #include @@ -108,10 +111,10 @@ void ModelMetadata::setTime(const TimePoint& time) { m_time = time; #ifdef USE_XIOS + Xios* xiosHandler = Xios::getInstance(); if (!xiosHandler->isInitialized()) { throw std::runtime_error("ModelMetadata: Xios handler has not been initialized"); } - xiosHandler->setCalendarStep(0); xiosHandler->setCalendarStart(time); #endif } @@ -120,6 +123,7 @@ void ModelMetadata::incrementTime(const Duration& step) { m_time += step; #ifdef USE_XIOS + Xios* xiosHandler = Xios::getInstance(); if (!xiosHandler->isInitialized()) { throw std::runtime_error("ModelMetadata: Xios handler has not been initialized"); } diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 91a08f3fe..7a2dc11f8 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -2,7 +2,7 @@ * @file Xios.cpp * @author Tom Meltzer * @author Joe Wallwork - * @date 10 Dec 2024 + * @date 02 Jan 2025 * @brief XIOS interface implementation * @details * @@ -62,7 +62,14 @@ Xios::Xios(const std::string dt, const std::string contextid, const std::string } //! Destructor -Xios::~Xios() { finalize(); } +Xios::~Xios() +{ + finalize(); + if (instancePtr != nullptr) { + delete instancePtr; + instancePtr = nullptr; + } +} //! Close XIOS context definition once xml config has been read and calendar settings updated void Xios::close_context_definition() @@ -1648,6 +1655,10 @@ void Xios::read(const std::string fieldId, ModelArray& modelarray) throw std::invalid_argument("Only ModelArrays of dimension 2, 3, or 4 are supported"); } } + +// Create the singleton instance +Xios* Xios::instancePtr = nullptr; + } #endif diff --git a/core/src/include/ModelMetadata.hpp b/core/src/include/ModelMetadata.hpp index e0b2c6419..26a17b703 100644 --- a/core/src/include/ModelMetadata.hpp +++ b/core/src/include/ModelMetadata.hpp @@ -1,7 +1,7 @@ /*! * @file ModelMetadata.hpp * - * @date 09 Dec 2024 + * @date 02 Jan 2025 * @author Tim Spain */ @@ -12,10 +12,6 @@ #include "include/ModelArray.hpp" #include "include/ModelState.hpp" #include "include/Time.hpp" -#ifdef USE_XIOS -#include "include/Xios.hpp" -#endif - #include #ifdef USE_MPI @@ -102,15 +98,6 @@ class ModelMetadata { int localCornerX, localCornerY, localExtentX, localExtentY, globalExtentX, globalExtentY; #endif -#ifdef USE_XIOS - /* - * @brief Set pointer to Xios handler instance. - * - * @param xiosPtr Pointer to set - */ - inline void setXiosHandler(Xios* xiosPtr) { xiosHandler = xiosPtr; } -#endif - private: TimePoint m_time; ConfigMap m_config; @@ -129,9 +116,6 @@ class ModelMetadata { #ifdef USE_MPI const std::string bboxName = "bounding_boxes"; #endif -#ifdef USE_XIOS - Xios* xiosHandler = nullptr; -#endif }; } /* namespace Nextsim */ diff --git a/core/src/include/Xios.hpp b/core/src/include/Xios.hpp index 4db7b6f47..7afffa34f 100644 --- a/core/src/include/Xios.hpp +++ b/core/src/include/Xios.hpp @@ -2,7 +2,7 @@ * @file Xios.hpp * @author Tom Meltzer * @author Joe Wallwork - * @date 10 Dec 2024 + * @date 02 Jan 2025 * @brief XIOS interface header * @details * @@ -30,12 +30,37 @@ namespace Nextsim { void enableXios(); class Xios : public Configured { -public: +private: + static Xios* instancePtr; + + // Private constructor Xios(const std::string dt = "P0-0T01:00:00", const std::string contextid = "nextSIM-DG", const std::string starttime = "1970-01-01T00:00:00Z", const std::string calendartype = "Gregorian"); + +public: + Xios(const Xios& xiosHandler) = delete; ~Xios(); + /* + * Define Xios handler Singleton + * + * NOTE: The arguments will only be used the first time this is called. + */ + inline static Xios* getInstance(const std::string dt = "P0-0T01:00:00", + const std::string contextId = "nextSIM-DG", + const std::string startTime = "1970-01-01T00:00:00Z", + const std::string calendarType = "Gregorian") + { + if (isNull()) { + instancePtr = new Xios(dt, contextId, startTime, calendarType); + } + return instancePtr; + }; + + /* Utility for checking if the singleton has been created */ + inline static bool isNull() { return (instancePtr == nullptr); } + /* Initialization and finalization */ void close_context_definition(); void context_finalize(); diff --git a/core/test/CMakeLists.txt b/core/test/CMakeLists.txt index 15ae0c2c3..37ba63939 100644 --- a/core/test/CMakeLists.txt +++ b/core/test/CMakeLists.txt @@ -87,13 +87,21 @@ if(ENABLE_MPI) ) target_link_libraries(testXiosFile_MPI2 PRIVATE nextsimlib doctest::doctest) - add_executable(testXiosReadWrite_MPI2 "XiosReadWrite_test.cpp" "MainMPI.cpp") - target_compile_definitions(testXiosReadWrite_MPI2 PRIVATE USE_XIOS) + add_executable(testXiosRead_MPI2 "XiosRead_test.cpp" "MainMPI.cpp") + target_compile_definitions(testXiosRead_MPI2 PRIVATE USE_XIOS) target_include_directories( - testXiosReadWrite_MPI2 + testXiosRead_MPI2 PRIVATE "${MODEL_INCLUDE_DIR}" "${XIOS_INCLUDE_LIST}" "${ModulesRoot}/StructureModule" ) - target_link_libraries(testXiosReadWrite_MPI2 PRIVATE nextsimlib doctest::doctest) + 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_include_directories( + testXiosWrite_MPI2 + PRIVATE "${MODEL_INCLUDE_DIR}" "${XIOS_INCLUDE_LIST}" "${ModulesRoot}/StructureModule" + ) + target_link_libraries(testXiosWrite_MPI2 PRIVATE nextsimlib doctest::doctest) else() add_executable(testRectGrid_MPI3 "RectGrid_test.cpp" "MainMPI.cpp") target_compile_definitions( diff --git a/core/test/ConfigOutput_test.cpp b/core/test/ConfigOutput_test.cpp index b06282205..8879c6356 100644 --- a/core/test/ConfigOutput_test.cpp +++ b/core/test/ConfigOutput_test.cpp @@ -1,7 +1,7 @@ /*! * @file ConfigOutput_test.cpp * - * @date 11 Dec 2024 + * @date 02 Jan 2025 * @author Tim Spain */ @@ -25,6 +25,9 @@ #include "include/NZLevels.hpp" #include "include/NextsimModule.hpp" #include "include/gridNames.hpp" +#ifdef USE_XIOS +#include "include/Xios.hpp" +#endif #include #include @@ -100,10 +103,9 @@ TEST_CASE("Test periodic output") ModelMetadata meta; #ifdef USE_XIOS enableXios(); - Xios xiosHandler("P0-0T01:00:00", "test1"); - xiosHandler.setCalendarOrigin(TimePoint("1970-01-01T00:00:00Z")); - meta.setXiosHandler(&xiosHandler); - xiosHandler.close_context_definition(); + Xios* xiosHandler = Xios::getInstance("P0-0T01:00:00", "test1"); + xiosHandler->setCalendarOrigin(TimePoint("1970-01-01T00:00:00Z")); + xiosHandler->close_context_definition(); #endif meta.setTime(TimePoint("2020-01-01T00:00:00Z")); diff --git a/core/test/ParaGrid_test.cpp b/core/test/ParaGrid_test.cpp index 8b598ec4d..ca87f3e65 100644 --- a/core/test/ParaGrid_test.cpp +++ b/core/test/ParaGrid_test.cpp @@ -1,7 +1,7 @@ /*! * @file ParaGrid_test.cpp * - * @date 10 Dec 2024 + * @date 02 Jan 2025 * @author Tim Spain */ @@ -23,6 +23,9 @@ #include "include/ParaGridIO.hpp" #include "include/ParametricGrid.hpp" #include "include/gridNames.hpp" +#ifdef USE_XIOS +#include "include/Xios.hpp" +#endif #include #include @@ -210,10 +213,9 @@ TEST_CASE("Write and read a ModelState-based ParaGrid restart file") ModelMetadata metadata; #ifdef USE_XIOS enableXios(); - Xios xiosHandler("P0-0T01:00:00", "test1a"); - xiosHandler.setCalendarOrigin(TimePoint("1970-01-01T00:00:00Z")); - metadata.setXiosHandler(&xiosHandler); - xiosHandler.close_context_definition(); + Xios* xiosHandler = Xios::getInstance("P0-0T01:00:00"); + xiosHandler->setCalendarOrigin(TimePoint("1970-01-01T00:00:00Z")); + xiosHandler->close_context_definition(); #endif metadata.setTime(TimePoint("2000-01-01T00:00:00Z")); // The coordinates are passed through the metadata object as affix @@ -258,9 +260,6 @@ TEST_CASE("Write and read a ModelState-based ParaGrid restart file") #ifdef USE_MPI ModelMetadata metadataIn(partitionFilename, test_comm); -#ifdef USE_XIOS - metadataIn.setXiosHandler(&xiosHandler); -#endif metadataIn.setTime(TimePoint(dateString)); ModelState ms = gridIn.getModelState(filename, metadataIn); #else @@ -413,10 +412,7 @@ TEST_CASE("Write a diagnostic ParaGrid file") ModelMetadata metadata; #ifdef USE_XIOS enableXios(); - Xios xiosHandler("P0-0T01:00:00", "test2"); - xiosHandler.setCalendarOrigin(TimePoint("1970-01-01T00:00:00Z")); - metadata.setXiosHandler(&xiosHandler); - xiosHandler.close_context_definition(); + Xios* xiosHandler = Xios::getInstance(); #endif metadata.setTime(TimePoint("2000-01-01T00:00:00Z")); // The coordinates are passed through the metadata object as affix @@ -556,10 +552,7 @@ TEST_CASE("Check an exception is thrown for an invalid file name") ModelMetadata metadataIn(partitionFilename, test_comm); #ifdef USE_XIOS enableXios(); - Xios xiosHandler("P0-0T01:00:00", "test4"); - xiosHandler.setCalendarOrigin(TimePoint("1970-01-01T00:00:00Z")); - metadataIn.setXiosHandler(&xiosHandler); - xiosHandler.close_context_definition(); + Xios* xiosHandler = Xios::getInstance(); #endif metadataIn.setTime(TimePoint(dateString)); REQUIRE_THROWS(state = gridIn.getModelState(longRandomFilename, metadataIn)); @@ -614,10 +607,7 @@ TEST_CASE("Check if a file with the old dimension names can be read") ModelMetadata metadata; #ifdef USE_XIOS enableXios(); - Xios xiosHandler("P0-0T01:00:00", "test5"); - xiosHandler.setCalendarOrigin(TimePoint("1970-01-01T00:00:00Z")); - metadata.setXiosHandler(&xiosHandler); - xiosHandler.close_context_definition(); + Xios* xiosHandler = Xios::getInstance(); #endif metadata.setMpiMetadata(test_comm); if (metadata.mpiMyRank == 0) { diff --git a/core/test/XiosAxis_test.cpp b/core/test/XiosAxis_test.cpp index 74e47ac9a..e2750b401 100644 --- a/core/test/XiosAxis_test.cpp +++ b/core/test/XiosAxis_test.cpp @@ -1,7 +1,7 @@ /*! * @file XiosAxis_test.cpp * @author Joe Wallwork - * @date 03 Dec 2024 + * @date 02 Jan 2025 * @brief Tests for XIOS axes * @details * This test is designed to test axis functionality of the C++ interface @@ -29,34 +29,34 @@ MPI_TEST_CASE("TestXiosAxis", 2) { enableXios(); - // Initialize an Xios instance called xios_handler - Xios xios_handler; - REQUIRE(xios_handler.isInitialized()); - REQUIRE(xios_handler.getClientMPISize() == 2); + // Get the Xios singleton instance and check it's initialized + Xios* xiosHandler = Xios::getInstance(); + REQUIRE(xiosHandler->isInitialized()); + REQUIRE(xiosHandler->getClientMPISize() == 2); // --- Tests for axis API const std::string axisId = { "axis_A" }; - REQUIRE_THROWS_WITH(xios_handler.getAxisSize(axisId), "Xios: Undefined axis 'axis_A'"); - REQUIRE_THROWS_WITH(xios_handler.getAxisValues(axisId), "Xios: Undefined axis 'axis_A'"); - xios_handler.createAxis(axisId); - REQUIRE_THROWS_WITH(xios_handler.createAxis(axisId), "Xios: Axis 'axis_A' already exists"); + REQUIRE_THROWS_WITH(xiosHandler->getAxisSize(axisId), "Xios: Undefined axis 'axis_A'"); + REQUIRE_THROWS_WITH(xiosHandler->getAxisValues(axisId), "Xios: Undefined axis 'axis_A'"); + xiosHandler->createAxis(axisId); + REQUIRE_THROWS_WITH(xiosHandler->createAxis(axisId), "Xios: Axis 'axis_A' already exists"); // Axis size - REQUIRE_THROWS_WITH(xios_handler.getAxisSize(axisId), "Xios: Undefined size for axis 'axis_A'"); + REQUIRE_THROWS_WITH(xiosHandler->getAxisSize(axisId), "Xios: Undefined size for axis 'axis_A'"); const size_t axisSize { 2 }; - xios_handler.setAxisSize(axisId, axisSize); - REQUIRE(xios_handler.getAxisSize(axisId) == axisSize); + xiosHandler->setAxisSize(axisId, axisSize); + REQUIRE(xiosHandler->getAxisSize(axisId) == axisSize); // Axis values REQUIRE_THROWS_WITH( - xios_handler.getAxisValues(axisId), "Xios: Undefined values for axis 'axis_A'"); - REQUIRE_THROWS_WITH(xios_handler.setAxisValues(axisId, { 0.0, 1.0, 2.0 }), + xiosHandler->getAxisValues(axisId), "Xios: Undefined values for axis 'axis_A'"); + REQUIRE_THROWS_WITH(xiosHandler->setAxisValues(axisId, { 0.0, 1.0, 2.0 }), "Xios: Size incompatible with values for axis 'axis_A'"); std::vector axisValues { 0.0, 1.0 }; - xios_handler.setAxisValues(axisId, axisValues); - std::vector axis_A = xios_handler.getAxisValues(axisId); + xiosHandler->setAxisValues(axisId, axisValues); + std::vector axis_A = xiosHandler->getAxisValues(axisId); REQUIRE(axis_A[0] == doctest::Approx(0.0)); REQUIRE(axis_A[1] == doctest::Approx(1.0)); - xios_handler.close_context_definition(); - xios_handler.context_finalize(); + xiosHandler->close_context_definition(); + xiosHandler->context_finalize(); } } diff --git a/core/test/XiosCalendar_test.cpp b/core/test/XiosCalendar_test.cpp index 6fe41dae5..a8436fc77 100644 --- a/core/test/XiosCalendar_test.cpp +++ b/core/test/XiosCalendar_test.cpp @@ -1,7 +1,7 @@ /*! * @file XiosCalendar_test.cpp * @author Joe Wallwork - * @date 10 Dec 2024 + * @date 02 Jan 2025 * @brief Tests for XIOS calandars * @details * This test is designed to test calendar functionality of the C++ interface @@ -29,45 +29,45 @@ MPI_TEST_CASE("TestXiosInitialization", 2) { enableXios(); - // Initialize an Xios instance called xios_handler - Xios xios_handler; - REQUIRE(xios_handler.isInitialized()); - REQUIRE(xios_handler.getClientMPISize() == 2); + // Get the Xios singleton instance and check it's initialized + Xios* xiosHandler = Xios::getInstance(); + REQUIRE(xiosHandler->isInitialized()); + REQUIRE(xiosHandler->getClientMPISize() == 2); // --- Tests for calendar API // Calendar type - REQUIRE(xios_handler.getCalendarType() == "Gregorian"); + REQUIRE(xiosHandler->getCalendarType() == "Gregorian"); // Calendar origin - REQUIRE(xios_handler.getCalendarOrigin().format() == "1970-01-01T00:00:00Z"); // Default + REQUIRE(xiosHandler->getCalendarOrigin().format() == "1970-01-01T00:00:00Z"); // Default TimePoint origin("2020-01-23T00:08:15Z"); - xios_handler.setCalendarOrigin(origin); - REQUIRE(origin == xios_handler.getCalendarOrigin()); + xiosHandler->setCalendarOrigin(origin); + REQUIRE(origin == xiosHandler->getCalendarOrigin()); REQUIRE(origin.format() == "2020-01-23T00:08:15Z"); // Calendar start - REQUIRE(xios_handler.getCalendarStart().format() == "1970-01-01T00:00:00Z"); // Default + REQUIRE(xiosHandler->getCalendarStart().format() == "1970-01-01T00:00:00Z"); // Default TimePoint start("2023-03-17T17:11:00Z"); - xios_handler.setCalendarStart(start); - REQUIRE(start == xios_handler.getCalendarStart()); + xiosHandler->setCalendarStart(start); + REQUIRE(start == xiosHandler->getCalendarStart()); REQUIRE(start.format() == "2023-03-17T17:11:00Z"); // Timestep - REQUIRE(xios_handler.getCalendarTimestep().seconds() == 3600.0); // Default + REQUIRE(xiosHandler->getCalendarTimestep().seconds() == 3600.0); // Default Duration timestep("P0-0T01:30:00"); REQUIRE(timestep.seconds() == 5400.0); - xios_handler.setCalendarTimestep(timestep); - REQUIRE(xios_handler.getCalendarTimestep().seconds() == 5400.0); + xiosHandler->setCalendarTimestep(timestep); + REQUIRE(xiosHandler->getCalendarTimestep().seconds() == 5400.0); - xios_handler.close_context_definition(); + xiosHandler->close_context_definition(); // --- Tests for getCurrentDate method - REQUIRE(xios_handler.getCalendarStep() == 0); - REQUIRE(xios_handler.getCurrentDate() == "2023-03-17T17:11:00Z"); - REQUIRE(xios_handler.getCurrentDate(false) == "2023-03-17 17:11:00"); + REQUIRE(xiosHandler->getCalendarStep() == 0); + REQUIRE(xiosHandler->getCurrentDate() == "2023-03-17T17:11:00Z"); + REQUIRE(xiosHandler->getCurrentDate(false) == "2023-03-17 17:11:00"); // -- Tests that the timestep is set up correctly - xios_handler.setCalendarStep(1); - REQUIRE(xios_handler.getCurrentDate() == "2023-03-17T18:41:00Z"); + xiosHandler->setCalendarStep(1); + REQUIRE(xiosHandler->getCurrentDate() == "2023-03-17T18:41:00Z"); - xios_handler.context_finalize(); + xiosHandler->context_finalize(); } } diff --git a/core/test/XiosDomain_test.cpp b/core/test/XiosDomain_test.cpp index 04f9cb17e..d9f3c174e 100644 --- a/core/test/XiosDomain_test.cpp +++ b/core/test/XiosDomain_test.cpp @@ -1,7 +1,7 @@ /*! * @file XiosDomain_test.cpp * @author Joe Wallwork - * @date 03 Dec 2024 + * @date 02 Jan 2025 * @brief Tests for XIOS domains * @details * This test is designed to test domain functionality of the C++ interface @@ -29,82 +29,82 @@ MPI_TEST_CASE("TestXiosDomain", 2) { enableXios(); - // Initialize an Xios instance called xios_handler - Xios xios_handler; - REQUIRE(xios_handler.isInitialized()); - const size_t size = xios_handler.getClientMPISize(); + // Get the Xios singleton instance and check it's initialized + Xios* xiosHandler = Xios::getInstance(); + REQUIRE(xiosHandler->isInitialized()); + const size_t size = xiosHandler->getClientMPISize(); REQUIRE(size == 2); - const size_t rank = xios_handler.getClientMPIRank(); + const size_t rank = xiosHandler->getClientMPIRank(); // --- Tests for domain API const std::string domainId = "domain_A"; - REQUIRE_THROWS_WITH(xios_handler.getDomainType(domainId), "Xios: Undefined domain 'domain_A'"); - xios_handler.createDomain(domainId); + REQUIRE_THROWS_WITH(xiosHandler->getDomainType(domainId), "Xios: Undefined domain 'domain_A'"); + xiosHandler->createDomain(domainId); REQUIRE_THROWS_WITH( - xios_handler.createDomain(domainId), "Xios: Domain 'domain_A' already exists"); + xiosHandler->createDomain(domainId), "Xios: Domain 'domain_A' already exists"); // Domain type REQUIRE_THROWS_WITH( - xios_handler.getDomainType(domainId), "Xios: Undefined type for domain 'domain_A'"); + xiosHandler->getDomainType(domainId), "Xios: Undefined type for domain 'domain_A'"); const std::string domainType = "rectilinear"; - xios_handler.setDomainType(domainId, domainType); - REQUIRE(xios_handler.getDomainType(domainId) == domainType); + xiosHandler->setDomainType(domainId, domainType); + REQUIRE(xiosHandler->getDomainType(domainId) == domainType); // Global number of points in x-direction - REQUIRE_THROWS_WITH(xios_handler.getDomainGlobalXSize(domainId), + REQUIRE_THROWS_WITH(xiosHandler->getDomainGlobalXSize(domainId), "Xios: Undefined global x-size for domain 'domain_A'"); const size_t nx_glo = 4; - xios_handler.setDomainGlobalXSize(domainId, nx_glo); - REQUIRE(xios_handler.getDomainGlobalXSize(domainId) == nx_glo); + xiosHandler->setDomainGlobalXSize(domainId, nx_glo); + REQUIRE(xiosHandler->getDomainGlobalXSize(domainId) == nx_glo); // Global number of points in y-direction - REQUIRE_THROWS_WITH(xios_handler.getDomainGlobalYSize(domainId), + REQUIRE_THROWS_WITH(xiosHandler->getDomainGlobalYSize(domainId), "Xios: Undefined global y-size for domain 'domain_A'"); const size_t ny_glo = 2; - xios_handler.setDomainGlobalYSize(domainId, ny_glo); - REQUIRE(xios_handler.getDomainGlobalYSize(domainId) == ny_glo); + xiosHandler->setDomainGlobalYSize(domainId, ny_glo); + REQUIRE(xiosHandler->getDomainGlobalYSize(domainId) == ny_glo); // Local number of points in x-direction - REQUIRE_THROWS_WITH(xios_handler.getDomainLocalXSize(domainId), + REQUIRE_THROWS_WITH(xiosHandler->getDomainLocalXSize(domainId), "Xios: Undefined local x-size for domain 'domain_A'"); const size_t nx = nx_glo / size; - xios_handler.setDomainLocalXSize(domainId, nx); - REQUIRE(xios_handler.getDomainLocalXSize(domainId) == nx); + xiosHandler->setDomainLocalXSize(domainId, nx); + REQUIRE(xiosHandler->getDomainLocalXSize(domainId) == nx); // Local number of points in y-direction - REQUIRE_THROWS_WITH(xios_handler.getDomainLocalYSize(domainId), + REQUIRE_THROWS_WITH(xiosHandler->getDomainLocalYSize(domainId), "Xios: Undefined local y-size for domain 'domain_A'"); const size_t ny = ny_glo; - xios_handler.setDomainLocalYSize(domainId, ny); - REQUIRE(xios_handler.getDomainLocalYSize(domainId) == ny); + xiosHandler->setDomainLocalYSize(domainId, ny); + REQUIRE(xiosHandler->getDomainLocalYSize(domainId) == ny); // Local starting x-index - REQUIRE_THROWS_WITH(xios_handler.getDomainLocalXStart(domainId), + REQUIRE_THROWS_WITH(xiosHandler->getDomainLocalXStart(domainId), "Xios: Undefined local starting x-index for domain 'domain_A'"); const size_t x0 = nx * rank; - xios_handler.setDomainLocalXStart(domainId, x0); - REQUIRE(xios_handler.getDomainLocalXStart(domainId) == x0); + xiosHandler->setDomainLocalXStart(domainId, x0); + REQUIRE(xiosHandler->getDomainLocalXStart(domainId) == x0); // Local starting y-index - REQUIRE_THROWS_WITH(xios_handler.getDomainLocalYStart(domainId), + REQUIRE_THROWS_WITH(xiosHandler->getDomainLocalYStart(domainId), "Xios: Undefined local starting y-index for domain 'domain_A'"); const size_t y0 = 0; - xios_handler.setDomainLocalYStart(domainId, y0); - REQUIRE(xios_handler.getDomainLocalYStart(domainId) == y0); + xiosHandler->setDomainLocalYStart(domainId, y0); + REQUIRE(xiosHandler->getDomainLocalYStart(domainId) == y0); // Local x-values - REQUIRE_THROWS_WITH(xios_handler.getDomainLocalXValues(domainId), + REQUIRE_THROWS_WITH(xiosHandler->getDomainLocalXValues(domainId), "Xios: Undefined local x-values for domain 'domain_A'"); - REQUIRE_THROWS_AS(xios_handler.getDomainLocalXValues(domainId), std::runtime_error); + REQUIRE_THROWS_AS(xiosHandler->getDomainLocalXValues(domainId), std::runtime_error); std::vector vx { -1.0 + rank, -0.5 + rank }; - xios_handler.setDomainLocalXValues(domainId, vx); - std::vector vxOut = xios_handler.getDomainLocalXValues(domainId); + xiosHandler->setDomainLocalXValues(domainId, vx); + std::vector vxOut = xiosHandler->getDomainLocalXValues(domainId); for (size_t i = 0; i < nx; i++) { REQUIRE(vxOut[i] == doctest::Approx(vx[i])); } // Local y-values - REQUIRE_THROWS_WITH(xios_handler.getDomainLocalYValues(domainId), + REQUIRE_THROWS_WITH(xiosHandler->getDomainLocalYValues(domainId), "Xios: Undefined local y-values for domain 'domain_A'"); std::vector vy { -1.0, 1.0 }; - xios_handler.setDomainLocalYValues(domainId, vy); - std::vector vyOut = xios_handler.getDomainLocalYValues(domainId); + xiosHandler->setDomainLocalYValues(domainId, vy); + std::vector vyOut = xiosHandler->getDomainLocalYValues(domainId); for (size_t j = 0; j < ny; j++) { REQUIRE(vyOut[j] == doctest::Approx(vy[j])); } - xios_handler.close_context_definition(); - xios_handler.context_finalize(); + xiosHandler->close_context_definition(); + xiosHandler->context_finalize(); } } diff --git a/core/test/XiosField_test.cpp b/core/test/XiosField_test.cpp index 6358470f9..f524e9044 100644 --- a/core/test/XiosField_test.cpp +++ b/core/test/XiosField_test.cpp @@ -1,7 +1,7 @@ /*! * @file XiosField_test.cpp * @author Joe Wallwork - * @date 03 Dec 2024 + * @date 02 Jan 2025 * @brief Tests for XIOS axes * @details * This test is designed to test axis functionality of the C++ interface @@ -29,55 +29,55 @@ MPI_TEST_CASE("TestXiosField", 2) { enableXios(); - // Initialize an Xios instance called xios_handler - Xios xios_handler; - REQUIRE(xios_handler.isInitialized()); - const size_t size = xios_handler.getClientMPISize(); + // Get the Xios singleton instance and check it's initialized + Xios* xiosHandler = Xios::getInstance(); + REQUIRE(xiosHandler->isInitialized()); + const size_t size = xiosHandler->getClientMPISize(); REQUIRE(size == 2); - const size_t rank = xios_handler.getClientMPIRank(); + const size_t rank = xiosHandler->getClientMPIRank(); // Create an axis with two points - xios_handler.createAxis("axis_A"); - xios_handler.setAxisValues("axis_A", { 0.0, 1.0 }); + xiosHandler->createAxis("axis_A"); + xiosHandler->setAxisValues("axis_A", { 0.0, 1.0 }); // Create a 1D grid comprised of the single axis - xios_handler.createGrid("grid_1D"); - xios_handler.gridAddAxis("grid_1D", "axis_A"); + xiosHandler->createGrid("grid_1D"); + xiosHandler->gridAddAxis("grid_1D", "axis_A"); // --- Tests for field API const std::string fieldId = "field_A"; - REQUIRE_THROWS_WITH(xios_handler.getFieldName(fieldId), "Xios: Undefined field 'field_A'"); - xios_handler.createField(fieldId); - REQUIRE_THROWS_WITH(xios_handler.createField(fieldId), "Xios: Field 'field_A' already exists"); + REQUIRE_THROWS_WITH(xiosHandler->getFieldName(fieldId), "Xios: Undefined field 'field_A'"); + xiosHandler->createField(fieldId); + REQUIRE_THROWS_WITH(xiosHandler->createField(fieldId), "Xios: Field 'field_A' already exists"); // Field name REQUIRE_THROWS_WITH( - xios_handler.getFieldName(fieldId), "Xios: Undefined name for field 'field_A'"); + xiosHandler->getFieldName(fieldId), "Xios: Undefined name for field 'field_A'"); const std::string fieldName = "test_field"; - xios_handler.setFieldName(fieldId, fieldName); - REQUIRE(xios_handler.getFieldName(fieldId) == fieldName); + xiosHandler->setFieldName(fieldId, fieldName); + REQUIRE(xiosHandler->getFieldName(fieldId) == fieldName); // Operation REQUIRE_THROWS_WITH( - xios_handler.getFieldOperation(fieldId), "Xios: Undefined operation for field 'field_A'"); + xiosHandler->getFieldOperation(fieldId), "Xios: Undefined operation for field 'field_A'"); const std::string operation = "instant"; - xios_handler.setFieldOperation(fieldId, operation); - REQUIRE(xios_handler.getFieldOperation(fieldId) == operation); + xiosHandler->setFieldOperation(fieldId, operation); + REQUIRE(xiosHandler->getFieldOperation(fieldId) == operation); // Grid reference - REQUIRE_THROWS_WITH(xios_handler.getFieldGridRef(fieldId), + REQUIRE_THROWS_WITH(xiosHandler->getFieldGridRef(fieldId), "Xios: Undefined grid reference for field 'field_A'"); const std::string gridRef = "grid_1D"; - xios_handler.setFieldGridRef(fieldId, gridRef); - REQUIRE(xios_handler.getFieldGridRef(fieldId) == gridRef); + xiosHandler->setFieldGridRef(fieldId, gridRef); + REQUIRE(xiosHandler->getFieldGridRef(fieldId) == gridRef); // Read access const bool readAccess(true); - xios_handler.setFieldReadAccess(fieldId, readAccess); - REQUIRE(xios_handler.getFieldReadAccess(fieldId)); + xiosHandler->setFieldReadAccess(fieldId, readAccess); + REQUIRE(xiosHandler->getFieldReadAccess(fieldId)); // Frequency offset - Duration freqOffset = xios_handler.getCalendarTimestep(); - xios_handler.setFieldFreqOffset(fieldId, freqOffset); + Duration freqOffset = xiosHandler->getCalendarTimestep(); + xiosHandler->setFieldFreqOffset(fieldId, freqOffset); // TODO: Overload == for Duration - REQUIRE(xios_handler.getFieldFreqOffset(fieldId).seconds() == freqOffset.seconds()); + REQUIRE(xiosHandler->getFieldFreqOffset(fieldId).seconds() == freqOffset.seconds()); - xios_handler.close_context_definition(); - xios_handler.context_finalize(); + xiosHandler->close_context_definition(); + xiosHandler->context_finalize(); } } diff --git a/core/test/XiosFile_test.cpp b/core/test/XiosFile_test.cpp index 34a59552d..0c5bc6d6a 100644 --- a/core/test/XiosFile_test.cpp +++ b/core/test/XiosFile_test.cpp @@ -1,7 +1,7 @@ /*! * @file XiosFile_test.cpp * @author Joe Wallwork - * @date 03 Dec 2024 + * @date 02 Jan 2025 * @brief Tests for XIOS axes * @details * This test is designed to test axis functionality of the C++ interface @@ -31,96 +31,96 @@ MPI_TEST_CASE("TestXiosFile", 2) { enableXios(); - // Initialize an Xios instance called xios_handler - Xios xios_handler("P0-0T01:30:00"); - REQUIRE(xios_handler.isInitialized()); - const size_t size = xios_handler.getClientMPISize(); + // Get the Xios singleton instance and check it's initialized + Xios* xiosHandler = Xios::getInstance("P0-0T01:30:00"); + REQUIRE(xiosHandler->isInitialized()); + const size_t size = xiosHandler->getClientMPISize(); REQUIRE(size == 2); - const size_t rank = xios_handler.getClientMPIRank(); + const size_t rank = xiosHandler->getClientMPIRank(); // Create a simple axis with two points - xios_handler.createAxis("axis_A"); - xios_handler.setAxisValues("axis_A", { 0.0, 1.0 }); + xiosHandler->createAxis("axis_A"); + xiosHandler->setAxisValues("axis_A", { 0.0, 1.0 }); // Create a 1D grid comprised of the single axis - xios_handler.createGrid("grid_1D"); - xios_handler.gridAddAxis("grid_1D", "axis_A"); + xiosHandler->createGrid("grid_1D"); + xiosHandler->gridAddAxis("grid_1D", "axis_A"); // Create a field on the 1D grid - xios_handler.createField("field_A"); - xios_handler.setFieldOperation("field_A", "instant"); - xios_handler.setFieldGridRef("field_A", "grid_1D"); + xiosHandler->createField("field_A"); + xiosHandler->setFieldOperation("field_A", "instant"); + xiosHandler->setFieldGridRef("field_A", "grid_1D"); // --- Tests for file API const std::string fileId = "output"; - REQUIRE_THROWS_WITH(xios_handler.getFileName(fileId), "Xios: Undefined file 'output'"); - xios_handler.createFile(fileId); - REQUIRE_THROWS_WITH(xios_handler.createFile(fileId), "Xios: File 'output' already exists"); + REQUIRE_THROWS_WITH(xiosHandler->getFileName(fileId), "Xios: Undefined file 'output'"); + xiosHandler->createFile(fileId); + REQUIRE_THROWS_WITH(xiosHandler->createFile(fileId), "Xios: File 'output' already exists"); // File name - REQUIRE_THROWS_WITH(xios_handler.getFileName(fileId), "Xios: Undefined name for file 'output'"); + REQUIRE_THROWS_WITH(xiosHandler->getFileName(fileId), "Xios: Undefined name for file 'output'"); const std::string fileName = "diagnostic"; - xios_handler.setFileName(fileId, fileName); - REQUIRE(xios_handler.getFileName(fileId) == fileName); + xiosHandler->setFileName(fileId, fileName); + REQUIRE(xiosHandler->getFileName(fileId) == fileName); // File type - REQUIRE_THROWS_WITH(xios_handler.getFileType(fileId), "Xios: Undefined type for file 'output'"); + REQUIRE_THROWS_WITH(xiosHandler->getFileType(fileId), "Xios: Undefined type for file 'output'"); const std::string fileType = "one_file"; - xios_handler.setFileType(fileId, fileType); - REQUIRE(xios_handler.getFileType(fileId) == fileType); + xiosHandler->setFileType(fileId, fileType); + REQUIRE(xiosHandler->getFileType(fileId) == fileType); // Output frequency - REQUIRE_THROWS_WITH(xios_handler.getFileOutputFreq(fileId), + REQUIRE_THROWS_WITH(xiosHandler->getFileOutputFreq(fileId), "Xios: Undefined output frequency for file 'output'"); - Duration timestep = xios_handler.getCalendarTimestep(); - xios_handler.setFileOutputFreq(fileId, timestep); - REQUIRE(xios_handler.getFileOutputFreq(fileId).seconds() == 1.5 * 60 * 60); + Duration timestep = xiosHandler->getCalendarTimestep(); + xiosHandler->setFileOutputFreq(fileId, timestep); + REQUIRE(xiosHandler->getFileOutputFreq(fileId).seconds() == 1.5 * 60 * 60); // Split frequency REQUIRE_THROWS_WITH( - xios_handler.getFileSplitFreq(fileId), "Xios: Undefined split frequency for file 'output'"); - xios_handler.setFileSplitFreq(fileId, timestep); - REQUIRE(xios_handler.getFileSplitFreq(fileId).seconds() == 1.5 * 60 * 60); + xiosHandler->getFileSplitFreq(fileId), "Xios: Undefined split frequency for file 'output'"); + xiosHandler->setFileSplitFreq(fileId, timestep); + REQUIRE(xiosHandler->getFileSplitFreq(fileId).seconds() == 1.5 * 60 * 60); // File mode const std::string mode = "write"; - xios_handler.setFileMode(fileId, mode); - REQUIRE(xios_handler.getFileMode(fileId) == mode); + xiosHandler->setFileMode(fileId, mode); + REQUIRE(xiosHandler->getFileMode(fileId) == mode); // File parallel access mode const std::string parAccess = "collective"; - xios_handler.setFileParAccess(fileId, parAccess); - REQUIRE(xios_handler.getFileParAccess(fileId) == parAccess); + xiosHandler->setFileParAccess(fileId, parAccess); + REQUIRE(xiosHandler->getFileParAccess(fileId) == parAccess); // Add field - xios_handler.fileAddField(fileId, "field_A"); - std::vector fieldIds = xios_handler.fileGetFieldIds(fileId); + xiosHandler->fileAddField(fileId, "field_A"); + std::vector fieldIds = xiosHandler->fileGetFieldIds(fileId); REQUIRE(fieldIds.size() == 1); REQUIRE(fieldIds[0] == "field_A"); // Create a new file for each time unit to check more thoroughly that XIOS interprets output // frequency and split frequency correctly. // (If we reused the same file then the XIOS interface would raise warnings.) - xios_handler.createFile("year"); - xios_handler.setFileOutputFreq("year", Duration("P1-0T00:00:00")); - xios_handler.setFileSplitFreq("year", Duration("P2-0T00:00:00")); - REQUIRE(xios_handler.getFileOutputFreq("year").seconds() == 365 * 24 * 60 * 60); - REQUIRE(xios_handler.getFileSplitFreq("year").seconds() == 2 * 365 * 24 * 60 * 60); - xios_handler.createFile("day"); - xios_handler.setFileOutputFreq("day", Duration("P0-1T00:00:00")); - xios_handler.setFileSplitFreq("day", Duration("P0-2T00:00:00")); - REQUIRE(xios_handler.getFileOutputFreq("day").seconds() == 24 * 60 * 60); - REQUIRE(xios_handler.getFileSplitFreq("day").seconds() == 2 * 24 * 60 * 60); - xios_handler.createFile("hour"); - xios_handler.setFileOutputFreq("hour", Duration("P0-0T01:00:00")); - xios_handler.setFileSplitFreq("hour", Duration("P0-0T02:00:00")); - REQUIRE(xios_handler.getFileOutputFreq("hour").seconds() == 60 * 60); - REQUIRE(xios_handler.getFileSplitFreq("hour").seconds() == 2 * 60 * 60); - xios_handler.createFile("minute"); - xios_handler.setFileOutputFreq("minute", Duration("P0-0T00:01:00")); - xios_handler.setFileSplitFreq("minute", Duration("P0-0T00:02:00")); - REQUIRE(xios_handler.getFileOutputFreq("minute").seconds() == 60); - REQUIRE(xios_handler.getFileSplitFreq("minute").seconds() == 2 * 60); - xios_handler.createFile("second"); - xios_handler.setFileOutputFreq("second", Duration("P0-0T00:00:01")); - xios_handler.setFileSplitFreq("second", Duration("P0-0T00:00:02")); - REQUIRE(xios_handler.getFileOutputFreq("second").seconds() == 1); - REQUIRE(xios_handler.getFileSplitFreq("second").seconds() == 2); + xiosHandler->createFile("year"); + xiosHandler->setFileOutputFreq("year", Duration("P1-0T00:00:00")); + xiosHandler->setFileSplitFreq("year", Duration("P2-0T00:00:00")); + REQUIRE(xiosHandler->getFileOutputFreq("year").seconds() == 365 * 24 * 60 * 60); + REQUIRE(xiosHandler->getFileSplitFreq("year").seconds() == 2 * 365 * 24 * 60 * 60); + xiosHandler->createFile("day"); + xiosHandler->setFileOutputFreq("day", Duration("P0-1T00:00:00")); + xiosHandler->setFileSplitFreq("day", Duration("P0-2T00:00:00")); + REQUIRE(xiosHandler->getFileOutputFreq("day").seconds() == 24 * 60 * 60); + REQUIRE(xiosHandler->getFileSplitFreq("day").seconds() == 2 * 24 * 60 * 60); + xiosHandler->createFile("hour"); + xiosHandler->setFileOutputFreq("hour", Duration("P0-0T01:00:00")); + xiosHandler->setFileSplitFreq("hour", Duration("P0-0T02:00:00")); + REQUIRE(xiosHandler->getFileOutputFreq("hour").seconds() == 60 * 60); + REQUIRE(xiosHandler->getFileSplitFreq("hour").seconds() == 2 * 60 * 60); + xiosHandler->createFile("minute"); + xiosHandler->setFileOutputFreq("minute", Duration("P0-0T00:01:00")); + xiosHandler->setFileSplitFreq("minute", Duration("P0-0T00:02:00")); + REQUIRE(xiosHandler->getFileOutputFreq("minute").seconds() == 60); + REQUIRE(xiosHandler->getFileSplitFreq("minute").seconds() == 2 * 60); + xiosHandler->createFile("second"); + xiosHandler->setFileOutputFreq("second", Duration("P0-0T00:00:01")); + xiosHandler->setFileSplitFreq("second", Duration("P0-0T00:00:02")); + REQUIRE(xiosHandler->getFileOutputFreq("second").seconds() == 1); + REQUIRE(xiosHandler->getFileSplitFreq("second").seconds() == 2); - xios_handler.close_context_definition(); - xios_handler.context_finalize(); + xiosHandler->close_context_definition(); + xiosHandler->context_finalize(); } } diff --git a/core/test/XiosGrid_test.cpp b/core/test/XiosGrid_test.cpp index 1c4e24d85..685b920d7 100644 --- a/core/test/XiosGrid_test.cpp +++ b/core/test/XiosGrid_test.cpp @@ -1,7 +1,7 @@ /*! * @file XiosGrid_test.cpp * @author Joe Wallwork - * @date 03 Dec 2024 + * @date 02 Jan 2025 * @brief Tests for XIOS axes * @details * This test is designed to test axis functionality of the C++ interface @@ -28,50 +28,50 @@ MPI_TEST_CASE("TestXiosGrid", 2) { enableXios(); - // Initialize an Xios instance called xios_handler - Xios xios_handler; - REQUIRE(xios_handler.isInitialized()); - const size_t size = xios_handler.getClientMPISize(); + // Get the Xios singleton instance and check it's initialized + Xios* xiosHandler = Xios::getInstance(); + REQUIRE(xiosHandler->isInitialized()); + const size_t size = xiosHandler->getClientMPISize(); REQUIRE(size == 2); - const size_t rank = xios_handler.getClientMPIRank(); + const size_t rank = xiosHandler->getClientMPIRank(); // Create a 4x2 horizontal domain with a partition halving the x-extent - xios_handler.createDomain("domain_XY"); - xios_handler.setDomainType("domain_XY", "rectilinear"); - xios_handler.setDomainGlobalXSize("domain_XY", 4); - xios_handler.setDomainGlobalYSize("domain_XY", 2); - xios_handler.setDomainLocalXStart("domain_XY", 2 * rank); - xios_handler.setDomainLocalYStart("domain_XY", 0); - xios_handler.setDomainLocalXValues("domain_XY", { -1.0 + rank, -0.5 + rank }); - xios_handler.setDomainLocalYValues("domain_XY", { -1.0, 1.0 }); + xiosHandler->createDomain("domain_XY"); + xiosHandler->setDomainType("domain_XY", "rectilinear"); + xiosHandler->setDomainGlobalXSize("domain_XY", 4); + xiosHandler->setDomainGlobalYSize("domain_XY", 2); + xiosHandler->setDomainLocalXStart("domain_XY", 2 * rank); + xiosHandler->setDomainLocalYStart("domain_XY", 0); + xiosHandler->setDomainLocalXValues("domain_XY", { -1.0 + rank, -0.5 + rank }); + xiosHandler->setDomainLocalYValues("domain_XY", { -1.0, 1.0 }); // Create a vertical axis with 2 points - xios_handler.createAxis("axis_Z"); - xios_handler.setAxisValues("axis_Z", { 0.0, 1.0 }); + xiosHandler->createAxis("axis_Z"); + xiosHandler->setAxisValues("axis_Z", { 0.0, 1.0 }); // --- Tests for grid API const std::string gridId = { "grid_2D" }; - REQUIRE_THROWS_WITH(xios_handler.getGridName(gridId), "Xios: Undefined grid 'grid_2D'"); - xios_handler.createGrid(gridId); - REQUIRE_THROWS_WITH(xios_handler.createGrid(gridId), "Xios: Grid 'grid_2D' already exists"); + REQUIRE_THROWS_WITH(xiosHandler->getGridName(gridId), "Xios: Undefined grid 'grid_2D'"); + xiosHandler->createGrid(gridId); + REQUIRE_THROWS_WITH(xiosHandler->createGrid(gridId), "Xios: Grid 'grid_2D' already exists"); // Grid name const std::string gridName = { "test_grid" }; REQUIRE_THROWS_WITH( - xios_handler.getGridName(gridId), "Xios: Undefined name for grid 'grid_2D'"); - xios_handler.setGridName(gridId, gridName); - REQUIRE(xios_handler.getGridName(gridId) == gridName); + xiosHandler->getGridName(gridId), "Xios: Undefined name for grid 'grid_2D'"); + xiosHandler->setGridName(gridId, gridName); + REQUIRE(xiosHandler->getGridName(gridId) == gridName); // Add axis - xios_handler.gridAddAxis("grid_2D", "axis_Z"); - std::vector axisIds = xios_handler.gridGetAxisIds(gridId); + xiosHandler->gridAddAxis("grid_2D", "axis_Z"); + std::vector axisIds = xiosHandler->gridGetAxisIds(gridId); REQUIRE(axisIds.size() == 1); REQUIRE(axisIds[0] == "axis_Z"); // Add domain - xios_handler.gridAddDomain("grid_2D", "domain_XY"); - std::vector domainIds = xios_handler.gridGetDomainIds(gridId); + xiosHandler->gridAddDomain("grid_2D", "domain_XY"); + std::vector domainIds = xiosHandler->gridGetDomainIds(gridId); REQUIRE(domainIds.size() == 1); REQUIRE(domainIds[0] == "domain_XY"); - xios_handler.close_context_definition(); - xios_handler.context_finalize(); + xiosHandler->close_context_definition(); + xiosHandler->context_finalize(); } } diff --git a/core/test/XiosReadWrite_test.cpp b/core/test/XiosReadWrite_test.cpp deleted file mode 100644 index e9bfe5d70..000000000 --- a/core/test/XiosReadWrite_test.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/*! - * @file XiosReadWrite_test.cpp - * @author Joe Wallwork - * @date 11 Dec 2024 - * @brief Tests for XIOS write method - * @details - * This test is designed to test the read and write methods of the C++ - * interface for XIOS. - * - */ -#include -#undef INFO - -#include "StructureModule/include/ParametricGrid.hpp" -#include "include/ModelMetadata.hpp" -#include "include/NextsimModule.hpp" -#include "include/ParaGridIO.hpp" - -#include - -namespace Nextsim { - -/*! - * Create a formatted attribute ID. - * - * @param label Label to use for the attribute format - * @param dim Spatial dimensions - * @return "