diff --git a/core/src/Model.cpp b/core/src/Model.cpp index 90b166000..ca5354203 100644 --- a/core/src/Model.cpp +++ b/core/src/Model.cpp @@ -77,6 +77,13 @@ void Model::configureTime() { ModelMetadata& metadata = ModelMetadata::getInstance(); +#ifdef USE_XIOS + // Enable XIOS in the config + std::stringstream config; + config << "[xios]" << std::endl << "enable = true" << std::endl; + Configurator::addStream(std::unique_ptr(new std::stringstream(config.str()))); +#endif + // Start/stop times. Run length will override stop time, if present. std::string startTimeStr = Configured::getConfiguration(keyMap.at(STARTTIME_KEY), std::string()); diff --git a/core/src/ModelMetadata.cpp b/core/src/ModelMetadata.cpp index 8438b8caa..0f5910e5c 100644 --- a/core/src/ModelMetadata.cpp +++ b/core/src/ModelMetadata.cpp @@ -214,9 +214,6 @@ 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.setCalendarStart(time); #endif } @@ -226,9 +223,6 @@ 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"); - } xiosHandler.incrementCalendar(); #endif } diff --git a/core/src/ParaGridIO_Xios.cpp b/core/src/ParaGridIO_Xios.cpp index 3f9b47b74..1409f9e28 100644 --- a/core/src/ParaGridIO_Xios.cpp +++ b/core/src/ParaGridIO_Xios.cpp @@ -34,12 +34,9 @@ ParaGridIO::ParaGridIO(ParametricGrid& grid) : IParaGridIO(grid) { Xios& xiosHandler = Xios::getInstance(); - // NOTE: getInstance will call the constructor for the Xios handler class the first time it is - // called. This will automatically: - // * Create XIOS input and output files if the XiosInput.filename and XiosOutput.filename - // entries are set in the config. - // * Create all fields found in the config based off the field names found in the - // XiosInput.field_names and XiosOutput.field_names entries in the config. + xiosHandler.setupDomains(); + xiosHandler.setupAxes(); + xiosHandler.setupGrids(); } ParaGridIO::~ParaGridIO() = default; diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 82e344fe8..0b213cb87 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -40,6 +40,21 @@ #include #include +#ifndef DGCOMP +#define DGCOMP 6 // Define to prevent errors from static analysis tools +#error "Number of DG components (DGCOMP) not defined" // But throw an error anyway +#endif + +#ifndef DGSTRESSCOMP +#define DGSTRESSCOMP 8 // Define to prevent errors from static analysis tools +#error "Number of DG stress components (DGSTRESSCOMP) not defined" // But throw an error anyway +#endif + +#ifndef CGDEGREE +#define CGDEGREE 2 // Define to prevent errors from static analysis tools +#error "CG degree (CGDEGREE) not defined" // But throw an error anyway +#endif + namespace Nextsim { static const std::string xOutputPfx = "XiosOutput"; @@ -106,6 +121,15 @@ Xios::Xios(const std::string contextid, const std::string calendartype) } } } + + // Verify the XIOS context has been initialized properly + bool init; + cxios_context_is_initialized(contextId.c_str(), contextId.length(), &init); + if (!init) { + throw std::runtime_error("Xios: context '" + contextId + "' not initialized"); + } + + parseInputFiles(); } firstTime = false; } @@ -237,6 +261,9 @@ void Xios::configureServer() clientCalendar, convertDurationToXios(metadata.stepLength())); cxios_create_calendar(clientCalendar); cxios_update_calendar_timestep(clientCalendar); + if (!cxios_is_defined_calendar_wrapper_timestep(clientCalendar)) { + throw std::runtime_error("Xios: Calendar timestep has not been set"); + } // Set default calendar origin setCalendarOrigin(TimePoint("1970-01-01T00:00:00Z")); // Unix epoch @@ -245,28 +272,6 @@ void Xios::configureServer() setCalendarStart(metadata.startTime()); } -/*! - * @return size of the client MPI communicator - */ -int Xios::getClientMPISize() { return mpi_size; } - -/*! - * @return rank of the client MPI communicator - */ -int Xios::getClientMPIRank() { return mpi_rank; } - -/*! - * Verify XIOS server is initialized - * - * @return true when XIOS server is initialized - */ -bool Xios::isInitialized() -{ - bool init = false; - cxios_context_is_initialized(contextId.c_str(), contextId.length(), &init); - return init; -} - /*! * Return datetime as std::string using ISO 8601 format (default). * @@ -384,18 +389,6 @@ void Xios::setCalendarStep(const int stepNumber) { cxios_update_calendar(stepNum */ void Xios::incrementCalendar() { setCalendarStep(getCalendarStep() + 1); } -/*! - * Get calendar type - * - * @return calendar type - */ -std::string Xios::getCalendarType() -{ - char cStr[cStrLen]; - cxios_get_calendar_wrapper_type(clientCalendar, cStr, cStrLen); - return convertCStrToCppStr(cStr, cStrLen); -} - /*! * Get calendar origin * @@ -426,21 +419,6 @@ TimePoint Xios::getCalendarStart() return TimePoint(convertXiosDatetimeToString(calendar_start, true)); } -/*! - * Get calendar timestep - * - * @return calendar timestep - */ -Duration Xios::getCalendarTimestep() -{ - if (!cxios_is_defined_calendar_wrapper_timestep(clientCalendar)) { - throw std::runtime_error("Xios: Calendar timestep has not been set"); - } - cxios_duration calendar_timestep; - cxios_get_calendar_wrapper_timestep(clientCalendar, &calendar_timestep); - return convertDurationFromXios(calendar_timestep); -} - /*! * Get calendar step * @@ -538,31 +516,6 @@ void Xios::setAxisSize(const std::string axisId, const size_t size) } } -/*! - * Set the values associated with a given axis - * - * @param the axis ID - * @param the values to set - */ -void Xios::setAxisValues(const std::string axisId, std::vector values) -{ - xios::CAxis* axis = getAxis(axisId); - if (cxios_is_defined_axis_value(axis)) { - Logged::warning("Xios: Values already set for axis '" + axisId + "'"); - } - if (!cxios_is_defined_axis_n_glo(axis)) { - setAxisSize(axisId, values.size()); - } - int size = getAxisSize(axisId); - if (size != values.size()) { - throw std::runtime_error("Xios: Size incompatible with values for axis '" + axisId + "'"); - } - cxios_set_axis_value(axis, values.data(), &size); - if (!cxios_is_defined_axis_value(axis)) { - throw std::runtime_error("Xios: Failed to set values for axis '" + axisId + "'"); - } -} - /*! * Get the size of a given axis (the number of global points) * @@ -580,26 +533,6 @@ size_t Xios::getAxisSize(const std::string axisId) return (size_t)size; } -/*! - * Get the values associated with a given axis - * - * @param the axis ID - * @return the corresponding values - */ -std::vector Xios::getAxisValues(const std::string axisId) -{ - xios::CAxis* axis = getAxis(axisId); - if (!cxios_is_defined_axis_value(axis)) { - throw std::runtime_error("Xios: Undefined values for axis '" + axisId + "'"); - } - int size = getAxisSize(axisId); - double* values = new double[size]; - cxios_get_axis_value(axis, values, &size); - std::vector vec(values, values + size); - delete[] values; - return vec; -} - /*! * Get the domain_definition group * @@ -637,11 +570,16 @@ xios::CDomain* Xios::getDomain(const std::string domainId) } /*! - * Create axes, domains, and grids based off the provided metadata. + * @brief Do an initial read of input files to deduce field dimensions. + * + * @details This function will read the dimension information from any NetCDF input files (restarts + * and/or forcings) and set dimensions appropriately. It will then set the field type of + * each input field. */ -void Xios::affixModelMetadata() +void Xios::parseInputFiles() { auto& metadata = ModelMetadata::getInstance(); + // Initial read of the NetCDF file to deduce the dimensions for (std::string filename : { inputFilename, forcingFilename }) { if (filename.length() == 0) { @@ -752,8 +690,21 @@ void Xios::affixModelMetadata() throw std::runtime_error(ncWhat); } } +} - // Create XIOS domains associated with each ModelArray type +/*! + * @brief Create XIOS domains associated with each ModelArray type + * + * @details This function sets up the XIOS domains for each field type based on the configuration + * in the domainIds map and in the ModelMetadata class. + */ +void Xios::setupDomains() +{ + auto& metadata = ModelMetadata::getInstance(); + + ModelArray::setNComponents(ModelArray::Type::VERTEX, ModelArray::nCoords); + ModelArray::setNComponents(ModelArray::Type::DG, DGCOMP); + ModelArray::setNComponents(ModelArray::Type::DGSTRESS, DGSTRESSCOMP); for (auto entry : domainIds) { ModelArray::Type type = entry.first; const std::string domainId = entry.second; @@ -875,15 +826,20 @@ void Xios::affixModelMetadata() counter++; } } +} - // Create XIOS axes for each ModelArray type - for (auto entry : ModelArray::componentMap) { +/*! + * @brief Create XIOS axes for each ModelArray type + * + * @details This function sets up the XIOS axes for each field type based on the configuration + * in the axisIds map and in the ModelArray class. + */ +void Xios::setupAxes() +{ + for (auto entry : axisIds) { ModelArray::Type type = entry.first; - ModelArray::Dimension dim = entry.second; - if (axisIds.count(type) == 0) { - continue; - } - const std::string axisId = axisIds[type]; + const std::string axisId = entry.second; + ModelArray::Dimension dim = ModelArray::componentMap.at(type); createAxis(axisId); setAxisSize(axisId, ModelArray::size(dim)); xios::CAxis* axis = getAxis(axisId); @@ -893,7 +849,16 @@ void Xios::affixModelMetadata() throw std::runtime_error("Xios: Failed to set name for axis '" + axisId + "'"); } } +} +/*! + * @brief Create XIOS grids for each ModelArray type + * + * @details This function sets up the XIOS grids for each field type based on the configuration + * in the gridIds, axisIds, and domainIds maps. + */ +void Xios::setupGrids() +{ // Create XIOS grid associated with domain and possibly axis for (auto entry : gridIds) { ModelArray::Type type = entry.first; diff --git a/core/src/discontinuousgalerkin/ModelArrayDetails.cpp b/core/src/discontinuousgalerkin/ModelArrayDetails.cpp index bd2ff7ccb..8e6132983 100644 --- a/core/src/discontinuousgalerkin/ModelArrayDetails.cpp +++ b/core/src/discontinuousgalerkin/ModelArrayDetails.cpp @@ -6,7 +6,7 @@ #include "include/ModelArray.hpp" #ifndef DGCOMP -#define DGCOMP 3 +#define DGCOMP 6 #endif #ifndef DGSTRESSCOMP diff --git a/core/src/include/Xios.hpp b/core/src/include/Xios.hpp index 04218813d..584b6b525 100644 --- a/core/src/include/Xios.hpp +++ b/core/src/include/Xios.hpp @@ -27,8 +27,6 @@ namespace Nextsim { // Forward declarations to avoid circular dependencies class ParaGridIO; -void enableXios(); - class Xios : public Configured { private: //! Private constructor @@ -69,32 +67,21 @@ class Xios : public Configured { void configure() override; void configureServer(); - /* MPI decomposition */ - int getClientMPISize(); - int getClientMPIRank(); - /* Calendar, date and duration */ void setCalendarType(const std::string type); void setCalendarOrigin(const TimePoint origin); void setCalendarStart(const TimePoint start); void setCalendarStep(const int stepNumber); void incrementCalendar(); - std::string getCalendarType(); TimePoint getCalendarOrigin(); TimePoint getCalendarStart(); - Duration getCalendarTimestep(); int getCalendarStep(); TimePoint getCurrentDate(); /* Axis */ void createAxis(const std::string axisId); void setAxisSize(const std::string axisId, const size_t size); - void setAxisValues(const std::string axisId, std::vector values); size_t getAxisSize(const std::string axisId); - std::vector getAxisValues(const std::string axisId); - - /* Domain */ - void affixModelMetadata(); /* Grid */ void createGrid(const std::string gridId); @@ -128,9 +115,6 @@ class Xios : public Configured { void fileAddField(const std::string fileId, const std::string fieldId); std::vector fileGetFieldIds(const std::string fileId); - /* I/O */ - void read(const std::string fieldId, ModelArray& modelarray); - enum { ENABLED_KEY, OUTPUT_FIELD_NAMES_KEY, @@ -163,9 +147,10 @@ class Xios : public Configured { MPI_Fint nullComm_F; int mpi_rank { 0 }; int mpi_size { 0 }; + int cStrLen { 20 }; // Length of C-strings passed to XIOS - /* Length of C-strings passed to XIOS */ - int cStrLen { 20 }; + /* Configuration */ + void parseInputFiles(); /* Calendar, date and duration */ std::string calendarType; @@ -189,6 +174,7 @@ class Xios : public Configured { }; xios::CAxisGroup* getAxisGroup(); xios::CAxis* getAxis(const std::string axisId); + void setupAxes(); /* Domain */ std::map domainIds = { @@ -200,6 +186,19 @@ class Xios : public Configured { }; xios::CDomainGroup* getDomainGroup(); xios::CDomain* getDomain(std::string domainId); + void setupDomains(); + + /* Grid */ + xios::CGridGroup* getGridGroup(); + xios::CGrid* getGrid(const std::string gridId); + std::map gridIds = { + { ModelArray::Type::H, "HGrid" }, + { ModelArray::Type::VERTEX, "VertexGrid" }, + { ModelArray::Type::DG, "DGGrid" }, + { ModelArray::Type::DGSTRESS, "DGSGrid" }, + { ModelArray::Type::CG, "CGGrid" }, + }; + void setupGrids(); /* Field */ xios::CFieldGroup* getFieldGroup(); @@ -214,17 +213,6 @@ class Xios : public Configured { bool configCheckField(const std::string fieldId, const bool readAccess); std::map fieldTypes; - /* Grid */ - xios::CGridGroup* getGridGroup(); - xios::CGrid* getGrid(const std::string gridId); - std::map gridIds = { - { ModelArray::Type::H, "HGrid" }, - { ModelArray::Type::VERTEX, "VertexGrid" }, - { ModelArray::Type::DG, "DGGrid" }, - { ModelArray::Type::DGSTRESS, "DGSGrid" }, - { ModelArray::Type::CG, "CGGrid" }, - }; - /* File */ xios::CFileGroup* getFileGroup(); xios::CFile* getFile(const std::string fileId); @@ -245,6 +233,7 @@ class Xios : public Configured { }; /* I/O */ + void read(const std::string fieldId, ModelArray& modelarray); void write(const std::string fieldId, ModelArray& modelarray); /* Declare any classes that need to access private members */ diff --git a/core/src/include/xios_c_interface.hpp b/core/src/include/xios_c_interface.hpp index 73a715957..59c008cbb 100644 --- a/core/src/include/xios_c_interface.hpp +++ b/core/src/include/xios_c_interface.hpp @@ -62,8 +62,6 @@ void cxios_update_calendar(int step); // timestep methods void cxios_set_calendar_wrapper_timestep( xios::CCalendarWrapper* calendar_wrapper_hdl, cxios_duration timestep_c); -void cxios_get_calendar_wrapper_timestep( - xios::CCalendarWrapper* calendar_wrapper_hdl, cxios_duration* timestep_c); bool cxios_is_defined_calendar_wrapper_timestep(xios::CCalendarWrapper* calendar_wrapper_hdl); void cxios_update_calendar_timestep(xios::CCalendarWrapper* calendarWrapper_hdl); @@ -76,13 +74,10 @@ void cxios_xml_tree_add_axis( void cxios_axis_handle_create(xios::CAxis** _ret, const char* _id, int _id_len); void cxios_axis_valid_id(bool* _ret, const char* _id, int _id_len); void cxios_set_axis_n_glo(xios::CAxis* axis_hdl, int n_glo); -void cxios_set_axis_value(xios::CAxis* axis_hdl, double* value, int* extent); void cxios_set_axis_dim_name(xios::CAxis* axis_hdl, const char* dim_name, int dim_name_size); void cxios_set_axis_dim_name(xios::CAxis* axis_hdl, const char* dim_name, int dim_name_size); void cxios_get_axis_n_glo(xios::CAxis* axis_hdl, int* n_glo); -void cxios_get_axis_value(xios::CAxis* axis_hdl, double* value, int* extent); bool cxios_is_defined_axis_n_glo(xios::CAxis* axis_hdl); -bool cxios_is_defined_axis_value(xios::CAxis* axis_hdl); bool cxios_is_defined_axis_dim_name(xios::CAxis* axis_hdl); // domain group methods diff --git a/core/test/XiosAxis_test.cpp b/core/test/XiosAxis_test.cpp index 30db04f16..51c9128e5 100644 --- a/core/test/XiosAxis_test.cpp +++ b/core/test/XiosAxis_test.cpp @@ -28,8 +28,6 @@ namespace Nextsim { */ MPI_TEST_CASE("TestXiosAxis", 3) { - // Enable XIOS in the 'config' and provide parameters to configure it - enableXios(); std::stringstream config; config << "[model]" << std::endl; config << "start = 2023-03-17T17:11:00Z" << std::endl; @@ -47,14 +45,14 @@ MPI_TEST_CASE("TestXiosAxis", 3) model.configureTime(); // Get the Xios singleton instance and check it's initialized + // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this + // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to + // Model::configureTime() above. Xios& xiosHandler = Xios::getInstance(); - REQUIRE(xiosHandler.isInitialized()); - REQUIRE(xiosHandler.getClientMPISize() == 3); // --- Tests for axis API const std::string axisId = { "axis_A" }; 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 @@ -62,16 +60,6 @@ MPI_TEST_CASE("TestXiosAxis", 3) const size_t axisSize { 2 }; xiosHandler.setAxisSize(axisId, axisSize); REQUIRE(xiosHandler.getAxisSize(axisId) == axisSize); - // Axis values - REQUIRE_THROWS_WITH( - 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 }; - 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)); xiosHandler.close_context_definition(); xiosHandler.context_finalize(); diff --git a/core/test/XiosCalendar_test.cpp b/core/test/XiosCalendar_test.cpp index 3c3b02919..0365f417f 100644 --- a/core/test/XiosCalendar_test.cpp +++ b/core/test/XiosCalendar_test.cpp @@ -29,8 +29,6 @@ namespace Nextsim { */ MPI_TEST_CASE("TestXiosCalendar", 1) { - // Enable XIOS in the 'config' and provide parameters to configure it - enableXios(); std::stringstream config; config << "[model]" << std::endl; config << "start = 2023-03-17T17:11:00Z" << std::endl; @@ -48,13 +46,12 @@ MPI_TEST_CASE("TestXiosCalendar", 1) model.configureTime(); // Get the Xios singleton instance and check it's initialized + // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this + // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to + // Model::configureTime() above. Xios& xiosHandler = Xios::getInstance(); - REQUIRE(xiosHandler.isInitialized()); - REQUIRE(xiosHandler.getClientMPISize() == 1); // --- Tests for calendar API - // Calendar type - REQUIRE(xiosHandler.getCalendarType() == "Gregorian"); // Calendar origin REQUIRE(xiosHandler.getCalendarOrigin().format() == "1970-01-01T00:00:00Z"); // Default TimePoint origin("2020-01-23T00:08:15Z"); @@ -68,7 +65,8 @@ MPI_TEST_CASE("TestXiosCalendar", 1) REQUIRE(start == xiosHandler.getCalendarStart()); REQUIRE(start.format() == "2023-03-17T17:11:00Z"); // Timestep - REQUIRE(xiosHandler.getCalendarTimestep().seconds() == 3600.0); // Read from config + ModelMetadata& metadata = ModelMetadata::getInstance(); + REQUIRE(metadata.stepLength().seconds() == 3600.0); // Read from config xiosHandler.close_context_definition(); diff --git a/core/test/XiosField_test.cpp b/core/test/XiosField_test.cpp index d5403189c..348db4a56 100644 --- a/core/test/XiosField_test.cpp +++ b/core/test/XiosField_test.cpp @@ -28,8 +28,6 @@ namespace Nextsim { */ MPI_TEST_CASE("TestXiosField", 3) { - // Enable XIOS in the 'config' and provide parameters to configure it - enableXios(); std::stringstream config; config << "[model]" << std::endl; config << "start = 2023-03-17T17:11:00Z" << std::endl; @@ -52,13 +50,14 @@ MPI_TEST_CASE("TestXiosField", 3) model.configureTime(); // Get the Xios singleton instance and check it's initialized + // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this + // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to + // Model::configureTime() above. Xios& xiosHandler = Xios::getInstance(); - REQUIRE(xiosHandler.isInitialized()); - REQUIRE(xiosHandler.getClientMPISize() == 3); // Create an axis with two points xiosHandler.createAxis("axis_A"); - xiosHandler.setAxisValues("axis_A", { 0.0, 1.0 }); + xiosHandler.setAxisSize("axis_A", 2); // Create a 1D grid comprised of the single axis xiosHandler.createGrid("grid_1D"); @@ -85,7 +84,8 @@ MPI_TEST_CASE("TestXiosField", 3) // setFieldReadAccess (see above note) REQUIRE(!xiosHandler.getFieldReadAccess(fieldId)); // Frequency offset - Duration freqOffset = xiosHandler.getCalendarTimestep(); + ModelMetadata& metadata = ModelMetadata::getInstance(); + Duration freqOffset = metadata.stepLength(); xiosHandler.setFieldFreqOffset(fieldId, freqOffset); // TODO: Overload == for Duration REQUIRE(xiosHandler.getFieldFreqOffset(fieldId).seconds() == freqOffset.seconds()); diff --git a/core/test/XiosFile_test.cpp b/core/test/XiosFile_test.cpp index 5159e13e0..805a5af08 100644 --- a/core/test/XiosFile_test.cpp +++ b/core/test/XiosFile_test.cpp @@ -14,6 +14,8 @@ #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; @@ -31,8 +33,6 @@ namespace Nextsim { */ MPI_TEST_CASE("TestXiosFile", 2) { - // Enable XIOS in the 'config' and provide parameters to configure it - enableXios(); std::stringstream config; config << "[model]" << std::endl; config << "start = 2023-03-17T17:11:00Z" << std::endl; @@ -58,19 +58,23 @@ MPI_TEST_CASE("TestXiosFile", 2) 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(); - REQUIRE(xiosHandler.isInitialized()); - REQUIRE(xiosHandler.getClientMPISize() == 2); + + // 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); - // Affix ModelMetadata to Xios handler - // TODO: Automate this - can't be inlined in Xios::getInstance because need set field types - xiosHandler.affixModelMetadata(); - // --- Tests for file API const std::string inFileId = "xios_test_input"; const std::string outFileId = "xios_test_output"; @@ -91,7 +95,8 @@ MPI_TEST_CASE("TestXiosFile", 2) // Split frequency REQUIRE_THROWS_WITH(xiosHandler.getFileSplitFreq(outFileId), "Xios: Undefined split frequency for file 'xios_test_output'"); - xiosHandler.setFileSplitFreq(outFileId, xiosHandler.getCalendarTimestep()); + ModelMetadata& metadata = ModelMetadata::getInstance(); + xiosHandler.setFileSplitFreq(outFileId, metadata.stepLength()); REQUIRE(xiosHandler.getFileSplitFreq(outFileId).seconds() == 1.5 * 60 * 60); // File mode // NOTE: setFileMode is set based off the XiosInput.filename and XiosOutput.filename entries diff --git a/core/test/XiosGrid_test.cpp b/core/test/XiosGrid_test.cpp index e518f3762..29a3c8428 100644 --- a/core/test/XiosGrid_test.cpp +++ b/core/test/XiosGrid_test.cpp @@ -9,9 +9,12 @@ #include #undef INFO +#include "StructureModule/include/ParametricGrid.hpp" #include "include/Finalizer.hpp" #include "include/Model.hpp" #include "include/ModelMPI.hpp" +#include "include/NextsimModule.hpp" +#include "include/ParaGridIO.hpp" #include "include/Xios.hpp" namespace Nextsim { @@ -27,8 +30,6 @@ namespace Nextsim { */ MPI_TEST_CASE("TestXiosGrid", 2) { - // Enable XIOS in the 'config' and provide parameters to configure it - enableXios(); std::stringstream config; config << "[model]" << std::endl; config << "start = 2023-03-17T17:11:00Z" << std::endl; @@ -46,13 +47,17 @@ MPI_TEST_CASE("TestXiosGrid", 2) model.configureTime(); // Get the Xios singleton instance and check it's initialized + // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this + // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to + // Model::configureTime() above. Xios& xiosHandler = Xios::getInstance(); - REQUIRE(xiosHandler.isInitialized()); - REQUIRE(xiosHandler.getClientMPISize() == 2); - // Affix ModelMetadata to Xios handler - // TODO: Automate this - can't be inlined in Xios::getInstance because need set field types - xiosHandler.affixModelMetadata(); + // 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); const std::string gridId = "HGrid"; REQUIRE_THROWS_WITH(xiosHandler.createGrid(gridId), "Xios: Grid 'HGrid' already exists"); @@ -60,7 +65,7 @@ MPI_TEST_CASE("TestXiosGrid", 2) // Add a vertical axis, too const std::string axisId = "z_axis"; xiosHandler.createAxis(axisId); - xiosHandler.setAxisValues(axisId, { 0.0, 1.0 }); + xiosHandler.setAxisSize(axisId, 2); xiosHandler.gridAddAxis("HGrid", axisId); std::vector axisIds = xiosHandler.getGridAxisIds(gridId); REQUIRE(axisIds.size() == 1); diff --git a/core/test/XiosRead_test.cpp b/core/test/XiosRead_test.cpp index 53077fab7..3dcbdab59 100644 --- a/core/test/XiosRead_test.cpp +++ b/core/test/XiosRead_test.cpp @@ -23,7 +23,7 @@ const std::string testFilesDir = TEST_FILES_DIR; const std::string restartFilename = testFilesDir + "/xios_test_input.nc"; const std::string forcingFilename = testFilesDir + "/xios_test_forcing.nc"; -static const int DG = 3; +static const int DGCOMP = 6; static const int DGSTRESSCOMP = 8; static const int CGDEGREE = 2; @@ -37,8 +37,6 @@ namespace Nextsim { */ MPI_TEST_CASE("TestXiosRead", 2) { - // Enable XIOS in the 'config' and provide parameters to configure it - enableXios(); std::stringstream config; config << "[model]" << std::endl; config << "start = 2023-03-17T17:11:00Z" << std::endl; @@ -66,52 +64,38 @@ MPI_TEST_CASE("TestXiosRead", 2) model.configureRestarts(); model.configureTime(); + // Get the Xios singleton instance and check it's initialized + // NOTE: The singleton is created when Xios::getInstance() is first called. In this test, this + // happens when the time sets set by ModelMetadata::setTime(). This occurs in the call to + // Model::configureTime() above. + Xios& xiosHandler = Xios::getInstance(); + // Create ParametricGrid and ParaGridIO instances + // NOTE: XIOS axes, domains, and grids are created by the ParaGridIO constructor Module::setImplementation("Nextsim::ParametricGrid"); ParametricGrid grid; ParaGridIO* pio = new ParaGridIO(grid); grid.setIO(pio); - // Get the Xios singleton instance and check it's initialized - Xios& xiosHandler = Xios::getInstance(); - REQUIRE(xiosHandler.isInitialized()); - REQUIRE(xiosHandler.getClientMPISize() == 2); - - // TODO: We could deduce this from the NetCDF file - ModelArray::setNComponents(ModelArray::Type::DG, DG); - ModelArray::setNComponents(ModelArray::Type::DGSTRESS, DGSTRESSCOMP); - ModelArray::setNComponents(ModelArray::Type::VERTEX, ModelArray::nCoords); - REQUIRE(ModelArray::nComponents(ModelArray::Type::DG) == DG); - REQUIRE(ModelArray::nComponents(ModelArray::Type::DGSTRESS) == DGSTRESSCOMP); - REQUIRE(ModelArray::nComponents(ModelArray::Type::VERTEX) == ModelArray::nCoords); - - // Affix ModelMetadata to Xios handler - // TODO: Automate this - can't be inlined in Xios::getInstance because need set field types - xiosHandler.affixModelMetadata(); - xiosHandler.close_context_definition(); - // Check calendar step is zero initially - REQUIRE(xiosHandler.getCalendarStep() == 0); - // Check the input files exists REQUIRE(std::filesystem::exists(restartFilename)); REQUIRE(std::filesystem::exists(forcingFilename)); // Check calendar step is zero initially - ModelMetadata& metadata = ModelMetadata::getInstance(); - metadata.setTime(xiosHandler.getCalendarStart()); REQUIRE(xiosHandler.getCalendarStep() == 0); // Deduce the local lengths of the two dimensions const size_t nx = ModelArray::size(ModelArray::Dimension::X); const size_t ny = ModelArray::size(ModelArray::Dimension::Y); + REQUIRE(ModelArray::nComponents(ModelArray::Type::DG) == DGCOMP); + REQUIRE(ModelArray::size(ModelArray::Dimension::DG) == DGCOMP); // Read restarts from file and check they take the expected values - metadata.setTime(xiosHandler.getCalendarStart()); - REQUIRE(xiosHandler.getCalendarStep() == 0); ModelState restarts = grid.getModelState(restartFilename); - const int rank = xiosHandler.getClientMPIRank(); + int rank; + MPI_Comm_rank(test_comm, &rank); for (auto& entry : restarts.data) { if (entry.first == maskName) { for (size_t j = 0; j < ny; ++j) { @@ -133,8 +117,8 @@ MPI_TEST_CASE("TestXiosRead", 2) } else if (entry.first == hiceName) { for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { - for (size_t d = 0; d < DG; ++d) { - float expected = 1.0 * (d + DG * (i + nx * j)); + for (size_t d = 0; d < DGCOMP; ++d) { + float expected = 1.0 * (d + DGCOMP * (i + nx * j)); REQUIRE(entry.second.components({ i, j })[d] == doctest::Approx(expected)); } } @@ -143,8 +127,8 @@ MPI_TEST_CASE("TestXiosRead", 2) for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { for (size_t d = 0; d < DGSTRESSCOMP; ++d) { - REQUIRE(entry.second.components({ i, j })[d] - == doctest::Approx(2.0 * (d + DGSTRESSCOMP * (i + nx * j)))); + float expected = 2.0 * (d + DGSTRESSCOMP * (i + nx * j)); + REQUIRE(entry.second.components({ i, j })[d] == doctest::Approx(expected)); } } } @@ -162,7 +146,8 @@ MPI_TEST_CASE("TestXiosRead", 2) } // Simulate 4 iterations (timesteps), reading forcing data at each - Duration timestep = xiosHandler.getCalendarTimestep(); + ModelMetadata& metadata = ModelMetadata::getInstance(); + Duration timestep = metadata.stepLength(); // TODO: Avoid making configGetForcingFieldNames public? auto forcingFieldNames = xiosHandler.configGetForcingFieldNames(); for (int ts = 0; ts <= 4; ts++) { diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index c7ab491eb..8d6346dea 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -23,7 +23,7 @@ const std::string testFilesDir = TEST_FILES_DIR; const std::string restartFilename = testFilesDir + "/xios_test_output.nc"; const std::string diagnosticFilename = testFilesDir + "/xios_test_diagnostic.nc"; -static const int DG = 3; +static const int DGCOMP = 6; static const int DGSTRESSCOMP = 8; static const int CGDEGREE = 2; @@ -37,8 +37,6 @@ namespace Nextsim { */ MPI_TEST_CASE("TestXiosWrite", 2) { - // Enable XIOS in the 'config' and provide parameters to configure it - enableXios(); std::stringstream config; config << "[model]" << std::endl; config << "start = 2023-03-17T17:11:00Z" << std::endl; @@ -67,16 +65,11 @@ MPI_TEST_CASE("TestXiosWrite", 2) model.configureRestarts(); model.configureTime(); - // Create ParametricGrid and ParaGridIO instances - Module::setImplementation("Nextsim::ParametricGrid"); - ParametricGrid grid; - ParaGridIO* pio = new ParaGridIO(grid); - grid.setIO(pio); - // 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(); - REQUIRE(xiosHandler.isInitialized()); - REQUIRE(xiosHandler.getClientMPISize() == 2); // Set ModelArray dimensions const size_t nx_glo = 4; @@ -85,7 +78,8 @@ MPI_TEST_CASE("TestXiosWrite", 2) const size_t ny = 2; size_t nx_start; const size_t ny_start = 0; - const int rank = xiosHandler.getClientMPIRank(); + int rank; + MPI_Comm_rank(test_comm, &rank); if (rank == 0) { nx_start = 0; } else { @@ -99,16 +93,14 @@ MPI_TEST_CASE("TestXiosWrite", 2) ModelArray::setDimension(ModelArray::Dimension::YVERTEX, ny_glo + 1, ny + 1, ny_start); ModelArray::setDimension( ModelArray::Dimension::YCG, CGDEGREE * ny_glo + 1, CGDEGREE * ny + 1, ny_start); - ModelArray::setNComponents(ModelArray::Type::DG, DG); - ModelArray::setNComponents(ModelArray::Type::DGSTRESS, DGSTRESSCOMP); - ModelArray::setNComponents(ModelArray::Type::VERTEX, ModelArray::nCoords); - REQUIRE(ModelArray::nComponents(ModelArray::Type::DG) == DG); - REQUIRE(ModelArray::nComponents(ModelArray::Type::DGSTRESS) == DGSTRESSCOMP); - REQUIRE(ModelArray::nComponents(ModelArray::Type::VERTEX) == ModelArray::nCoords); + REQUIRE(ModelArray::nComponents(ModelArray::Type::DG) == DGCOMP); - // Affix ModelMetadata to Xios handler - // TODO: Automate this - can't be inlined in Xios::getInstance because need set field types - xiosHandler.affixModelMetadata(); + // Create ParametricGrid and ParaGridIO instances + // NOTE: XIOS axes, domains, and grids are created by the ParaGridIO constructor + Module::setImplementation("Nextsim::ParametricGrid"); + ParametricGrid grid; + ParaGridIO* pio = new ParaGridIO(grid); + grid.setIO(pio); // Set field types xiosHandler.setFieldType(maskName, ModelArray::Type::H); @@ -150,8 +142,8 @@ MPI_TEST_CASE("TestXiosWrite", 2) hice.resize(); for (size_t j = 0; j < ny; ++j) { for (size_t i = 0; i < nx; ++i) { - for (size_t d = 0; d < DG; ++d) { - hice.components({ i, j })[d] = 1.0 * (d + DG * (i + nx * j)); + for (size_t d = 0; d < DGCOMP; ++d) { + hice.components({ i, j })[d] = 1.0 * (d + DGCOMP * (i + nx * j)); } } } @@ -182,11 +174,12 @@ MPI_TEST_CASE("TestXiosWrite", 2) REQUIRE_FALSE(std::filesystem::exists("xios_test_output*.nc")); REQUIRE_FALSE(std::filesystem::exists("xios_test_diagnostic*.nc")); + // Check calendar step is zero initially + REQUIRE(xiosHandler.getCalendarStep() == 0); + // Simulate 4 iterations (timesteps) - Duration timestep = xiosHandler.getCalendarTimestep(); ModelMetadata& metadata = ModelMetadata::getInstance(); - metadata.setTime(xiosHandler.getCalendarStart()); - REQUIRE(xiosHandler.getCalendarStep() == 0); + Duration timestep = metadata.stepLength(); for (int ts = 1; ts <= 4; ts++) { // Update the current timestep and verify it's updated in XIOS diff --git a/core/test/xios_test_input.cdl b/core/test/xios_test_input.cdl index f46c7f3ce..36673bdfc 100644 --- a/core/test/xios_test_input.cdl +++ b/core/test/xios_test_input.cdl @@ -7,7 +7,7 @@ dimensions: yvertex = 3 ; x_cg = 9 ; y_cg = 5 ; - dg_comp = 3 ; + dg_comp = 6 ; dgstress_comp = 8 ; ncoords = 2 ; variables: @@ -107,14 +107,14 @@ data: 4, 2 ; hice = - 0, 1, 2, - 3, 4, 5, - 0, 1, 2, - 3, 4, 5, - 6, 7, 8, - 9, 10, 11, - 6, 7, 8, - 9, 10, 11 ; + 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, + 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, + 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23 ; mask = 0, 0, 0, 0, diff --git a/docs/xios.rst b/docs/xios.rst index 26fd9720b..c0d3a275f 100644 --- a/docs/xios.rst +++ b/docs/xios.rst @@ -28,6 +28,10 @@ build nextSIM-DG with XIOS as the I/O driver. [xios] enable = true +However, there is no need to explicitly add this to the configuration because it +will be added automatically during the ``Model`` configuration step provided +nextSIM-DG has been built with XIOS support. + The ``model`` section, which is used elsewhere in nextSIM-DG, contains several entries relevant to XIOS. The ``start``, ``stop``, and ``time_step`` entries are used to configure the calendar used by XIOS. For example,