diff --git a/core/src/Xios.cpp b/core/src/Xios.cpp index 914f60274..6a5f4074a 100644 --- a/core/src/Xios.cpp +++ b/core/src/Xios.cpp @@ -59,14 +59,16 @@ namespace Nextsim { static const std::string xOutputPfx = "XiosOutput"; static const std::string xInputPfx = "XiosInput"; -static const std::string xDiagonsticPfx = "XiosDiagnostic"; +static const std::string xDiagnosticPfx = "XiosDiagnostic"; 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::DIAGNOSTIC_PERIOD_KEY, xDiagonsticPfx + ".period" }, - { Xios::DIAGNOSTIC_FILE_KEY, xDiagonsticPfx + ".filename" }, - { Xios::DIAGNOSTIC_FIELD_NAMES_KEY, xDiagonsticPfx + ".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" } }; @@ -1371,28 +1373,33 @@ void Xios::createFile(const std::string fileId, const int fieldType) setFileType(fileId, "one_file"); setFileParAccess(fileId, "collective"); - // Set the input or output period based on the model configuration + // 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 + std::string periodStr; ModelMetadata& metadata = ModelMetadata::getInstance(); if (fieldType == INPUT_RESTART || fieldType == OUTPUT_RESTART) { setFileOutputFreq(fileId, metadata.restartPeriod); - if (fieldType == INPUT_RESTART) { - fieldIds = configGetInputRestartFieldNames(); - } else { - fieldIds = configGetOutputRestartFieldNames(); - } } else { std::string periodStr; if (fieldType == FORCING) { istringstream( Configured::getConfiguration(keyMap.at(FORCING_PERIOD_KEY), std::string())) >> periodStr; - fieldIds = configGetForcingFieldNames(); } else { istringstream( Configured::getConfiguration(keyMap.at(DIAGNOSTIC_PERIOD_KEY), std::string())) >> periodStr; - fieldIds = configGetDiagnosticFieldNames(); } if (periodStr.empty() || periodStr == "0") { setFileOutputFreq(fileId, metadata.runLength()); @@ -1401,6 +1408,31 @@ void Xios::createFile(const std::string fileId, const int fieldType) } } + // Set the output file splitting frequency + if (fieldType == OUTPUT_RESTART || fieldType == DIAGNOSTIC) { + std::string splitStr; + if (fieldType == OUTPUT_RESTART) { + istringstream( + Configured::getConfiguration(keyMap.at(OUTPUT_SPLITFREQ_KEY), std::string())) + >> splitStr; + } else if (fieldType == DIAGNOSTIC) { + istringstream( + Configured::getConfiguration(keyMap.at(DIAGNOSTIC_SPLITFREQ_KEY), std::string())) + >> splitStr; + } + if (!splitStr.empty()) { + xios::CFile* file = getFile(fileId); + if (cxios_is_defined_file_split_freq(file)) { + Logged::warning("Xios: Split frequency already set for file '" + fileId + "'"); + } + cxios_set_file_split_freq(file, convertDurationToXios(Duration(splitStr))); + if (!cxios_is_defined_file_split_freq(file)) { + throw std::runtime_error( + "Xios: Failed to set split frequency for file '" + fileId + "'"); + } + } + } + // XiosOutput.field_names, XiosInput.field_names, XiosDiagnostic.field_names, or // XiosForcing.field_names entries in the config. for (std::string fieldId : fieldIds) { @@ -1453,24 +1485,6 @@ void Xios::setFileOutputFreq(const std::string fileId, const Duration freq) } } -/*! - * Set the split frequency of a file with a given ID - * - * @param the file ID - * @param split frequency to set - */ -void Xios::setFileSplitFreq(const std::string fileId, const Duration freq) -{ - 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(freq)); - if (!cxios_is_defined_file_split_freq(file)) { - throw std::runtime_error("Xios: Failed to set split frequency for file '" + fileId + "'"); - } -} - /*! * Set the mode of a file with a given ID * @@ -1541,23 +1555,6 @@ Duration Xios::getFileOutputFreq(const std::string fileId) return convertDurationFromXios(duration); } -/*! - * Get the split frequency of a file with a given ID - * - * @param the file ID - * @return split frequency of the corresponding file - */ -Duration Xios::getFileSplitFreq(const std::string fileId) -{ - xios::CFile* file = getFile(fileId); - if (!cxios_is_defined_file_split_freq(file)) { - throw std::runtime_error("Xios: Undefined split frequency for file '" + fileId + "'"); - } - cxios_duration duration; - cxios_get_file_split_freq(file, &duration); - return convertDurationFromXios(duration); -} - /*! * Get the mode of a file with a given ID * diff --git a/core/src/include/Xios.hpp b/core/src/include/Xios.hpp index 584b6b525..01f34bed1 100644 --- a/core/src/include/Xios.hpp +++ b/core/src/include/Xios.hpp @@ -105,11 +105,9 @@ class Xios : public Configured { 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 setFileSplitFreq(const std::string fileId, const Duration splitFreq); void setFileParAccess(const std::string fileId, const std::string parAccess); std::string getFileType(const std::string fileId); Duration getFileOutputFreq(const std::string fileId); - Duration getFileSplitFreq(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); @@ -118,10 +116,12 @@ 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/src/include/xios_c_interface.hpp b/core/src/include/xios_c_interface.hpp index 59c008cbb..eafecde23 100644 --- a/core/src/include/xios_c_interface.hpp +++ b/core/src/include/xios_c_interface.hpp @@ -164,7 +164,6 @@ void cxios_set_file_mode(xios::CFile* file_hdl, const char* mode, int mode_size) void cxios_set_file_par_access(xios::CFile* file_hdl, const char* par_access, int par_access_size); void cxios_get_file_type(xios::CFile* file_hdl, char* type, int type_size); void cxios_get_file_output_freq(xios::CFile* file_hdl, cxios_duration* output_freq_c); -void cxios_get_file_split_freq(xios::CFile* file_hdl, cxios_duration* split_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); diff --git a/core/test/XiosFile_test.cpp b/core/test/XiosFile_test.cpp index 805a5af08..bae28fd67 100644 --- a/core/test/XiosFile_test.cpp +++ b/core/test/XiosFile_test.cpp @@ -92,12 +92,6 @@ MPI_TEST_CASE("TestXiosFile", 2) // 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); - // Split frequency - REQUIRE_THROWS_WITH(xiosHandler.getFileSplitFreq(outFileId), - "Xios: Undefined split frequency for file 'xios_test_output'"); - 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 // when a file is created @@ -117,7 +111,7 @@ MPI_TEST_CASE("TestXiosFile", 2) REQUIRE(outputIds[0] == "hice"); // Create a new file for each time unit to check more thoroughly that XIOS interprets output - // frequency and split frequency correctly. + // 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"; @@ -127,29 +121,19 @@ MPI_TEST_CASE("TestXiosFile", 2) const std::string secondId = prefix + "_second"; xiosHandler.createFile(yearId, xiosHandler.OUTPUT_RESTART); xiosHandler.setFileOutputFreq(yearId, Duration("P1-0T00:00:00")); - xiosHandler.setFileSplitFreq(yearId, Duration("P2-0T00:00:00")); REQUIRE(xiosHandler.getFileOutputFreq(yearId).seconds() == 365 * 24 * 60 * 60); - REQUIRE(xiosHandler.getFileSplitFreq(yearId).seconds() == 2 * 365 * 24 * 60 * 60); xiosHandler.createFile(dayId, xiosHandler.OUTPUT_RESTART); xiosHandler.setFileOutputFreq(dayId, Duration("P0-1T00:00:00")); - xiosHandler.setFileSplitFreq(dayId, Duration("P0-2T00:00:00")); REQUIRE(xiosHandler.getFileOutputFreq(dayId).seconds() == 24 * 60 * 60); - REQUIRE(xiosHandler.getFileSplitFreq(dayId).seconds() == 2 * 24 * 60 * 60); xiosHandler.createFile(hourId, xiosHandler.OUTPUT_RESTART); xiosHandler.setFileOutputFreq(hourId, Duration("P0-0T01:00:00")); - xiosHandler.setFileSplitFreq(hourId, Duration("P0-0T02:00:00")); REQUIRE(xiosHandler.getFileOutputFreq(hourId).seconds() == 60 * 60); - REQUIRE(xiosHandler.getFileSplitFreq(hourId).seconds() == 2 * 60 * 60); xiosHandler.createFile(minuteId, xiosHandler.OUTPUT_RESTART); xiosHandler.setFileOutputFreq(minuteId, Duration("P0-0T00:01:00")); - xiosHandler.setFileSplitFreq(minuteId, Duration("P0-0T00:02:00")); REQUIRE(xiosHandler.getFileOutputFreq(minuteId).seconds() == 60); - REQUIRE(xiosHandler.getFileSplitFreq(minuteId).seconds() == 2 * 60); xiosHandler.createFile(secondId, xiosHandler.OUTPUT_RESTART); xiosHandler.setFileOutputFreq(secondId, Duration("P0-0T00:00:01")); - xiosHandler.setFileSplitFreq(secondId, Duration("P0-0T00:00:02")); REQUIRE(xiosHandler.getFileOutputFreq(secondId).seconds() == 1); - REQUIRE(xiosHandler.getFileSplitFreq(secondId).seconds() == 2); xiosHandler.close_context_definition(); xiosHandler.context_finalize(); diff --git a/core/test/XiosWrite_test.cpp b/core/test/XiosWrite_test.cpp index 8d6346dea..effc4d844 100644 --- a/core/test/XiosWrite_test.cpp +++ b/core/test/XiosWrite_test.cpp @@ -49,6 +49,7 @@ 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; config << "[XiosDiagnostic]" << std::endl; config << "filename = " << diagnosticFilename << std::endl; config << "field_names = " << hsnowName << std::endl; @@ -110,11 +111,6 @@ MPI_TEST_CASE("TestXiosWrite", 2) xiosHandler.setFieldType(uName, ModelArray::Type::CG); xiosHandler.setFieldType(hsnowName, ModelArray::Type::H); - // Set file split frequency for restarts (but not diagnostics) - // NOTE: Files are created when the XIOS handler is constructed - const std::string fileId = "xios_test_output"; - xiosHandler.setFileSplitFreq(fileId, Duration("P0-0T03:00:00")); - xiosHandler.close_context_definition(); // Create some fake data to test writing methods diff --git a/docs/xios.rst b/docs/xios.rst index c0d3a275f..0cd6f1563 100644 --- a/docs/xios.rst +++ b/docs/xios.rst @@ -6,12 +6,19 @@ Using XIOS for reading and writing files Introduction ------------ -Section under construction. +`XIOS`_ stands for *XML Input/Output Server*. It is an asynchronous I/O tool +widely used in the climate modelling community. While it is traditionally +configured using user-provided XML files, nextSIM-DG configures it directly with +API calls to reduce user requirements. -XIOS concepts -------------- +.. _XIOS: https://forge.ipsl.fr/ioserver -Section under construction. +The integration of XIOS into nextSIM-DG's is built around a static ``Xios`` +handler class, which provides a C++ API for the various XIOS functions. When the +handler is instantiated, the configuration sections (see section below) are +parsed. Based on the values that are parsed, the handler object will +automatically create data structures for XIOS and associate these as +appropriate. Further details on how this works can be found in the following. Configuration ------------- @@ -77,33 +84,111 @@ The ``field_names`` entry may contain a single field name or a comma-separated list. Note that all of the ``XiosOutput``, ``XiosInput``, ``XiosDiagnostic``, and ``XiosForcing`` sections are optional. -As elsewhere in the model, these configuration values are parsed by calling the -``Model.configure`` member function. Since building with XIOS implies also -building with MPI, you will need to pass the MPI communicator to this member -function when calling it. +When writing out files, the ``split_period`` option can be included in either of +the ``XiosOutput`` or ``XiosDiagnostic`` sections, which instructs XIOS to +create a separate output file for each such period. For a given period, a file +will be written with a name of the form +``_-.nc``, where ```` is the +user-provided file name and ```` and ```` are the start +and end of the associated period. + +As elsewhere in the model, the configuration values above are all parsed by +calling the ``Model.configure`` member function. Since building with XIOS +implies also building with MPI, you will need to pass the MPI communicator to +this member function when calling it. + +Order of operations for XIOS setup +---------------------------------- + +The required order of operations to set everything up correctly for reading and +writing files using XIOS is demonstrated in the ``core/test/XiosRead_test.cpp`` +and ``core/test/XiosWrite_test.cpp`` worked examples and is elaborated in the +following: + +1. Set configuration options as for other parts of the model. (See section above + for options.) +2. Get the ``ModelMetadata`` singleton instance by calling the static + ``ModelMetadata::getInstance`` member function, passing the MPI partition + metadata file as an argument. +3. Configure the ``Model`` by constructing it and calling its ``configure`` + member function. +4. Construct a ``ParametricGrid`` and a new ``ParaGridIO`` pointer. +5. Associate the ``ParaGridIO`` pointer with the ``ParametricGrid`` instance + using the latter's ``setIO`` member function. +6. Get the ``Xios`` handler singleton using ``Xios::getInstance``. +7. For each field to be written to file with XIOS, call the ``setFieldType`` + member function of ``Xios``, providing the field name as the first argument + and the ``ModelArray::Type::`` enum as the second argument (replacing + ``>`` as appropriate). +8. Call the ``close_context_definition`` member function of ``Xios``. -The XIOS I/O implementation supports fields of ``HField``, ``VertexField``, -``DGField``, ``DGSField``, and ``CGField``. When reading from file, the field -type will be deduced from its dimension. When writing to file, the field type -should be set using the ``setFieldType`` member function of the ``Xios`` handler -class. +XIOS concepts +------------- -Developer notes -^^^^^^^^^^^^^^^ +XIOS has several key concepts, five of which are used in nextSIM-DG. Whilst it's +not important to understand all of them in detail, it's useful to have an idea +of how they are used. -The integration of XIOS into nextSIM-DG's is built around a static ``Xios`` -handler class, which provides a C++ API for the various XIOS functions. When the -handler is instantiated, the config sections above will be parsed. Based on the -values that are parsed, the handler object will automatically create ``Field`` -and ``File`` data structures for XIOS and associate these as appropriate. If the -``XiosInput`` section is used, an attempt will be made to open the file provided -by the ``filename`` entry. Note that the nextSIM-DG XIOS integration is set up -such that the filename and field names coincide with the fileId and fieldId of -the corresponding ``File`` and ``Field`` objects. - -XIOS requires information on the domain decomposition for it to be able to read -and write data in parallel. This information is held by the ``ModelMetadata`` -class, which may be constructed based off a partition metadata file. Upon -construction of this object, if XIOS is enabled then the XIOS handler will -automatically create a ``Domain`` data structure with the appropriate local and -global sizes and offsets. +XIOS Domain concept +^^^^^^^^^^^^^^^^^^^ + +The Domain type defines the horizontal domain and its MPI parallel +decomposition. The Domain definition requires the global size of the :math:`x`- +and :math:`y`-dimensions of the domain, as well as the local sizes and start +indices (corner) in each dimension for each MPI rank. This information is +provided to XIOS automatically upon configuring the ``Model``. + +Different domains are used for different field types, depending on the number of +degrees of freedom (DoFs) they have in the horizontal. The ``HDomain`` has one +DoF per cell and is used for the ``HField``, ``DGField``, and ``DGSField`` +types. The ``VertexDomain`` has one DoF per vertex and is used for the +``VertexField`` type. Finally, the ``CGDomain`` has the same (degree-dependant) +number of DoFs as the continuous Galerkin discretisation and is used by +``CGField``. + +XIOS Axis concept +^^^^^^^^^^^^^^^^^ + +The Axis type is used to define an additional dimension for some field types. +For ``VertexField``, it gives rise to a vector field with as many components as +the spatial dimension, i.e., two. For ``DGField`` and ``DGSField``, the Axis +concept is used to give rise to vector fields with as many components as +``dg_comp`` and ``dgstress_comp``, respectively. These Axes are set up +automatically at the same point at which Domains are created. + +XIOS Grid concept +^^^^^^^^^^^^^^^^^ + +The Grid type is used to define the discretisation for each field. That is, it +associates a Domain and (in some cases) an Axis with a Field. + +XIOS Field concept +^^^^^^^^^^^^^^^^^^ + +An instance of the Field type is based on a Grid and is associated with a +``ModelArray::Type``. Fields are created automatically upon configuring the +``Model``. For fields that are read from file, the field type is determined +automatically during the file read. For fields that are written, it's required +to call the ``setFieldType`` member function of the ``Xios`` singleton, +providing the field name and ``ModelArray::Type``. + +XIOS File concept +^^^^^^^^^^^^^^^^^ + +The File concept holds metadata related to input and output files, including +which Fields are associated with it and whether it is to be used for reading or +writing. Files are created automatically upon configuring the ``Model``. + +Developer notes +--------------- + +* While XIOS and nextSIM-DG are both written in C++, the majority of XIOS' users + work in Fortran and interact with it via its Fortran bindings. These are based + off its C interface using the ``iso_c_binding`` Fortran module. Instead of + ``include``-ing the XIOS source directly in nextSIM-DG, we decided to wrap the + C interface. The header file ``core/src/include/xios_c_header.hpp`` provides + an interface for the functions that are used. + +* The nextSIM-DG XIOS integration is set up such that the filename and field + names coincide with the fileId and fieldId of the corresponding ``File`` and + ``Field`` objects.