diff --git a/CMakeLists.txt b/CMakeLists.txt index 66a1dbeed..fec764c5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,26 @@ if(enable-julia-bindings) set(CMAKE_CXX_STANDARD "${_CMAKE_CXX_STANDARD}") endif(enable-julia-bindings) +# HDF5 support (optional) + +option(enable-hdf5-support "enable HDF5 support" OFF) +option(enable-hdf5-automatic-support "if set, try to support HDF5, even if not explicitly requested by enable-hdf5-support" ON) +if(enable-hdf5-support) + find_package(HDF5 REQUIRED COMPONENTS CXX) +else(enable-hdf5-support) + if(enable-hdf5-automatic-support) + find_package(HDF5 COMPONENTS CXX) + endif(enable-hdf5-automatic-support) +endif(enable-hdf5-support) +if(HDF5_FOUND) + set(MGIS_HDF5_SUPPORT ON) + MESSAGE(STATUS "hdf5 headers : " ${HDF5_INCLUDE_DIRS}) + MESSAGE(STATUS "hdf5 librairies : " ${HDF5_LIBRARIES}) + MESSAGE(STATUS "hdf5 definitions : " ${HDF5_DEFINITIONS}) +else(HDF5_FOUND) + set(MGIS_HDF5_SUPPORT OFF) +endif(HDF5_FOUND) + # summary if(enable-c-bindings) @@ -129,6 +149,10 @@ if(enable-fenics-bindings) message(STATUS "FEniCS bindings support enabled") endif(enable-fenics-bindings) +if(MGIS_HDF5_SUPPORT) + message(STATUS "HDF5 support enabled") +endif(MGIS_HDF5_SUPPORT) + find_package(Threads) # if(MINGW) # file(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/thread-mingw.cxx" diff --git a/INSTALL-cmake.md b/INSTALL-cmake.md index 5f70a5583..189b1e04d 100644 --- a/INSTALL-cmake.md +++ b/INSTALL-cmake.md @@ -63,6 +63,13 @@ Options - `enable-exceptions`: use exceptions to report contract violation and error reporting. By default, contract violation leads to abort the program. - `enable-parallel-stl-algorithms`: by default, STL algorithms are used if available +- `enable-hdf5-support`: enable HDF5 support for save/restore + operations. Note that if this support is not explicitely requested, + `MGIS` still tries by default to support `HDF5` but configuration will + not fail if the `HDF5` library is not found. See + `enable-hdf5-automatic-support` for details. +- `enable-hdf5-automatic-support`: if set, `MGIS` tries by default to + support `HDF5` even if enable-hdf5-support` is not set. `cmake` usefull variables ======================= diff --git a/bindings/python/src/Variable.cxx b/bindings/python/src/Variable.cxx index 1517ed655..4c45915eb 100644 --- a/bindings/python/src/Variable.cxx +++ b/bindings/python/src/Variable.cxx @@ -104,7 +104,10 @@ void declareVariable(pybind11::module_& m) { // free functions m.def("getVariable", getVariableByString, pybind11::return_value_policy::reference); - m.def("getVariableSize", &mgis::behaviour::getVariableSize); + m.def("getVariableSize", + pybind11::overload_cast( + &mgis::behaviour::getVariableSize)); m.def("getVariableSize", getVariableSizeByString); m.def("getArraySize", &mgis::behaviour::getArraySize); m.def("getVariableOffset", getVariableOffsetByString); diff --git a/docs/web/release-notes-3.2.md b/docs/web/release-notes-3.2.md index ff3b38cdc..d8bd953a7 100644 --- a/docs/web/release-notes-3.2.md +++ b/docs/web/release-notes-3.2.md @@ -22,6 +22,20 @@ This version is meant to be used with `TFEL` Version 5.2. # Documentation +## New `cmake` options + +### HDF5 support + +The following options control the support of the `HDF5` library: + +- `enable-hdf5-support`: enable HDF5 support for save/restore + operations. Note that if this support is not explicitely requested, + `MGIS` still tries by default to support `HDF5` but configuration will + not fail if the `HDF5` library is not found. See + `enable-hdf5-automatic-support` for details. +- `enable-hdf5-automatic-support`: if set, `MGIS` tries by default to + support `HDF5` even if enable-hdf5-support` is not set. + ## `Doxygen` documentation The doxygen documentation is now online: @@ -29,7 +43,7 @@ The doxygen documentation is now online: # New features -## Scripts to define environment variables for `mGIS` to work properly +## Scripts to define environment variables for `MGIS` to work properly Depending on the system and compilation options, some of following variables shall be set for `MGIS` to work properly: `MGISHOME`, `PATH`, @@ -72,6 +86,25 @@ directory (refered to `` in the following): # New features of the `MGIS/Behaviour` library +## Save/restore operations in `MaterialStateManager` and `MaterialDataManager` + +If `HDF5` support is enabled, two functions `save` and `restore` are +available: + +- The `save` function allows saving the values stored in a + `MaterialStateManager` or in a `MaterialDataManager` to an `HDF5` + file. +- The `restore` function allows retrieving the values stored in a + `MaterialStateManager` or in a `MaterialDataManager` from an `HDF5` + file. + +Those functions have options allowing to precisely select what is saved +or restored. + +By default, one expects to restore the maximum amount of information. +The `getGreedyMaterialStateManagerRestoreOptions` function creates +option that can restore everything that has been saved. + ## Control on variables updated/reverted in `MaterialStateManager` By default, material properties, mass density and external state @@ -152,6 +185,10 @@ const auto e2 = f.get<0, tfel::math::stensor<2, real>>(1); For more details, see +## Issue 209: Add basic support for `save`/`restore` operations in `MaterialDataManager` + +For more details, see + ## Issue 201: Add the ability to update only the state variables of a `MaterialStateManager` For more details, see diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index a946b6b78..539424fa0 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -46,9 +46,14 @@ mgis_header(MGIS/Behaviour FiniteStrainSupport.hxx) mgis_header(MGIS/Model Model.hxx) if(MGIS_HAVE_TFEL) -mgis_header(MGIS Database.hxx) + mgis_header(MGIS Database.hxx) endif(MGIS_HAVE_TFEL) +if(MGIS_HDF5_SUPPORT) + mgis_header(MGIS/Utilities HDF5Forward.hxx) + mgis_header(MGIS/Utilities HDF5Support.hxx) +endif(MGIS_HDF5_SUPPORT) + if(enable-mgis-function) mgis_header(MGIS/Function CompileTimeSize.hxx) mgis_header(MGIS/Function SpaceConcept.hxx) diff --git a/include/MGIS/Behaviour/MaterialDataManager.hxx b/include/MGIS/Behaviour/MaterialDataManager.hxx index 06245ca15..3f798a553 100644 --- a/include/MGIS/Behaviour/MaterialDataManager.hxx +++ b/include/MGIS/Behaviour/MaterialDataManager.hxx @@ -19,6 +19,10 @@ #include #include #include +#ifdef MGIS_HAVE_HDF5 +#include "MGIS/Utilities/HDF5Forward.hxx" +#endif /* MGIS_HAVE_HDF5 */ + #include "MGIS/Config.hxx" #include "MGIS/Behaviour/MaterialStateManager.hxx" @@ -280,6 +284,46 @@ namespace mgis::behaviour { MGIS_EXPORT std::vector allocatePostProcessingVariables( const MaterialDataManager&, const std::string_view); +#ifdef MGIS_HAVE_HDF5 + + /*! + * \brief structure used to customize the saving of a `MaterialDataManager` + */ + struct MaterialDataManagerSavingOptions + : public MaterialStateManagerSavingOptions {}; + + /*! + * \brief save a `MaterialDataManager` to an HDF5 group + * \param[in] ctx: execution context + * \param[in] g: group + * \param[in] m: material data manager + * \param[in] opts: options + */ + MGIS_EXPORT [[nodiscard]] bool save( + Context&, + H5::Group&, + const MaterialDataManager&, + const MaterialDataManagerSavingOptions& = {}) noexcept; + /*! + * \brief structure used to customize the saving of a `MaterialDataManager` + */ + struct MaterialDataManagerRestoreOptions + : public MaterialStateManagerRestoreOptions {}; + /*! + * \brief restore a `MaterialDataManager` from an HDF5 group + * \param[in] ctx: execution context + * \param[in] g: group + * \param[in] m: material data manager + * \param[in] opts: options + */ + MGIS_EXPORT [[nodiscard]] bool restore( + Context&, + MaterialDataManager&, + const H5::Group&, + const MaterialDataManagerRestoreOptions& = {}) noexcept; + +#endif /* MGIS_HAVE_HDF5 */ + } // end of namespace mgis::behaviour #endif /* LIB_MGIS_BEHAVIOUR_MATERIALDATAMANAGER_HXX */ diff --git a/include/MGIS/Behaviour/MaterialStateManager.hxx b/include/MGIS/Behaviour/MaterialStateManager.hxx index b37cf5010..ef21984e5 100644 --- a/include/MGIS/Behaviour/MaterialStateManager.hxx +++ b/include/MGIS/Behaviour/MaterialStateManager.hxx @@ -22,6 +22,10 @@ #include #include #include +#ifdef MGIS_HAVE_HDF5 +#include "MGIS/Utilities/HDF5Forward.hxx" +#endif /* MGIS_HAVE_HDF5 */ + #include "MGIS/Config.hxx" #include "MGIS/StorageMode.hxx" @@ -240,6 +244,37 @@ namespace mgis::behaviour { const MaterialStateManager::StorageMode = MaterialStateManager::LOCAL_STORAGE, const MaterialStateManager::UpdatePolicy = MaterialStateManager::UPDATE); + /*! + * \brief set the given material property + * \param[out] m: material data manager + * \param[in] n: name + * \param[in] v: value + * \param[in] p: update policy + */ + MGIS_EXPORT [[nodiscard]] bool setMaterialProperty( + Context&, + MaterialStateManager&, + const std::string_view&, + const real, + const MaterialStateManager::UpdatePolicy = + MaterialStateManager::UPDATE) noexcept; + /*! + * \brief set the given material property + * \param[out] m: material data manager + * \param[in] n: name + * \param[in] v: values + * \param[in] s: storage mode + * \param[in] p: update policy + */ + MGIS_EXPORT [[nodiscard]] bool setMaterialProperty( + Context&, + MaterialStateManager&, + const std::string_view&, + const std::span&, + const MaterialStateManager::StorageMode = + MaterialStateManager::LOCAL_STORAGE, + const MaterialStateManager::UpdatePolicy = + MaterialStateManager::UPDATE) noexcept; /*! * \return true if the given external state variable is defined. * \param[out] m: material data manager @@ -354,6 +389,93 @@ namespace mgis::behaviour { const mgis::behaviour::MaterialStateManager&, const std::string_view); +#ifdef MGIS_HAVE_HDF5 + + /*! + * \brief structure used to customize the saving of a `MaterialStateManager` + */ + struct MaterialStateManagerSavingOptions { + const bool allow_overwrite = true; + const bool save_gradients = true; + const bool save_thermodynamic_forces = true; + const bool save_stored_energies = true; + const bool save_dissipated_energies = true; + const bool save_mass_densities = true; + const bool save_material_properties = true; + const bool save_external_state_variables = true; + }; + + /*! + * \brief save a `MaterialStateManager` to an HDF5 group + * \param[in] ctx: execution context + * \param[in] g: group + * \param[in] s: material state manager + * \param[in] opts: options + */ + MGIS_EXPORT [[nodiscard]] bool save( + Context&, + H5::Group&, + const MaterialStateManager&, + const MaterialStateManagerSavingOptions& = {}) noexcept; + + /*! + * \brief structure used to customize how to restore a `MaterialStateManager` + */ + struct MaterialStateManagerRestoreOptions { + const bool restore_gradients = true; + const bool restore_thermodynamic_forces = true; + /*! + * \brief flag stating if the stored energies shall be read + * + * \note this flag is ignored if the behaviour does not compute the + * stored energy + */ + const bool restore_stored_energies = true; + /*! + * \brief flag stating if the dissipated energies shall be read + * + * \note this flag is ignored if the behaviour does not compute the + * dissipated energy + */ + const bool restore_dissipated_energies = true; + const bool restore_internal_state_variables = true; + const bool restore_mass_densities = true; + const bool restore_material_properties = true; + //! \brief list of material properties that shall not be restored + const std::vector ignored_material_properties = {}; + const bool restore_external_state_variables = true; + //! \brief list of external state variables that shall not be restored + const std::vector ignored_external_state_variables = {}; + }; // end of MaterialStateManagerRestoreOptions + /*! + * \brief return restore options selecting all that can be read in the given + * group. + * + * \param[in] ctx: execution context + * \param[in] g: group + */ + MGIS_EXPORT [[nodiscard]] std::optional + getGreedyMaterialStateManagerRestoreOptions(Context&, + const H5::Group&) noexcept; + /*! + * \brief restore a `MaterialStateManager` from a HDF5 group + * + * \param[in] ctx: execution context + * \param[in] g: group + * \param[in] s: material state manager + * \param[in] opts: options + * + * \note update policies are set to their values for material properties and + * external state variables created during the restoration + */ + MGIS_EXPORT [[nodiscard]] bool restore( + Context&, + MaterialStateManager&, + const H5::Group&, + const MaterialStateManagerRestoreOptions&) noexcept; + +#endif /* MGIS_HAVE_HDF5 */ + } // end of namespace mgis::behaviour #endif /* LIB_MGIS_BEHAVIOUR_MATERIALSTATEMANAGER_HXX */ diff --git a/include/MGIS/Behaviour/Variable.hxx b/include/MGIS/Behaviour/Variable.hxx index fe167d8d5..485aaf7d7 100644 --- a/include/MGIS/Behaviour/Variable.hxx +++ b/include/MGIS/Behaviour/Variable.hxx @@ -19,6 +19,7 @@ #include #include #include "MGIS/Config.hxx" +#include "MGIS/Context.hxx" #include "MGIS/Behaviour/Hypothesis.hxx" namespace mgis::behaviour { @@ -57,53 +58,72 @@ namespace mgis::behaviour { * \param[in] vs: variables * \param[in] n: name */ - MGIS_EXPORT bool contains(const std::vector &, - const std::string_view); + MGIS_EXPORT [[nodiscard]] bool contains(const std::vector &, + const std::string_view) noexcept; /*! * \return the variable with the given name * \param[in] vs: variables * \param[in] n: name */ - MGIS_EXPORT const Variable &getVariable(const std::vector &, - const std::string_view); + MGIS_EXPORT [[nodiscard]] const Variable &getVariable( + const std::vector &, const std::string_view); + /*! + * \return the variable with the given name + * \param[in] vs: variables + * \param[in] n: name + */ + MGIS_EXPORT [[nodiscard]] std::optional getVariable( + Context &, + const std::vector &, + const std::string_view) noexcept; /*! * \return the type of a variable from an identifier * \param[in] id: type identifier */ - MGIS_EXPORT Variable::Type getVariableType(const int); + MGIS_EXPORT [[nodiscard]] Variable::Type getVariableType(const int); /*! * \return a symbolic representation from a type identifier * \param[in] id: type identifier */ - MGIS_EXPORT std::string getVariableTypeSymbolicRepresentation(const int); + MGIS_EXPORT [[nodiscard]] std::string getVariableTypeSymbolicRepresentation( + const int); + /*! + * \return the size of a variable + * \param[in] v: variable + * \param[in] h: modelling hypothesis + */ + MGIS_EXPORT [[nodiscard]] size_type getVariableSize(const Variable &, + const Hypothesis); /*! * \return the size of a variable + * \param[in, out] ctx: execution context * \param[in] v: variable * \param[in] h: modelling hypothesis */ - MGIS_EXPORT size_type getVariableSize(const Variable &, const Hypothesis); + MGIS_EXPORT [[nodiscard]] std::optional getVariableSize( + Context &, const Variable &, const Hypothesis) noexcept; /*! * \return the size of an array that may contain the values described by the * given array of variables * \param[in] vs: variables * \param[in] h: modelling hypothesis */ - MGIS_EXPORT size_type getArraySize(const std::vector &, - const Hypothesis); + MGIS_EXPORT [[nodiscard]] size_type getArraySize( + const std::vector &, const Hypothesis); /*! * \return the offset of the given variable for the given hypothesis * \param[in] vs: variables * \param[in] n: variable name * \param[in] h: modelling hypothesis */ - MGIS_EXPORT size_type getVariableOffset(const std::vector &, - const std::string_view, - const Hypothesis); + MGIS_EXPORT [[nodiscard]] size_type getVariableOffset( + const std::vector &, const std::string_view, const Hypothesis); /*! * \return the type of the given variable as a string * \param[in] v: variable */ - MGIS_EXPORT std::string getVariableTypeAsString(const Variable &); + MGIS_EXPORT [[nodiscard]] std::string getVariableTypeAsString( + const Variable &); } // end of namespace mgis::behaviour diff --git a/include/MGIS/ErrorBacktrace.hxx b/include/MGIS/ErrorBacktrace.hxx index ad47d03c8..fd39fe13e 100644 --- a/include/MGIS/ErrorBacktrace.hxx +++ b/include/MGIS/ErrorBacktrace.hxx @@ -48,7 +48,13 @@ namespace mgis { //! \brief specify if error reporting shall be fatal static void unsetErrorReportingAsFatal() noexcept; #ifdef MGIS_USE_SOURCE_LOCATION_INFORMATION - InvalidResult registerErrorMessage( + /*! + * \brief register a new error message + * \param[in] e: error message + * \param[in] l: description of the call site + * \note for convenience, this method always return an invalid object + */ + [[nodiscard]] InvalidResult registerErrorMessage( const char *const, const std::source_location & = std::source_location::current()) override final; @@ -58,25 +64,27 @@ namespace mgis { * \param[in] l: description of the call site * \note for convenience, this method always return an invalid object */ - InvalidResult registerErrorMessage( + [[nodiscard]] InvalidResult registerErrorMessage( const ErrorReport, const std::source_location & = std::source_location::current()) noexcept; #else - InvalidResult registerErrorMessage(const char *const) override final; + [[nodiscard]] InvalidResult registerErrorMessage( + const char *const) override final; /*! * \brief register a new error message * \param[in] e: error code * \note for convenience, this method always return an invalid results */ - InvalidResult registerErrorMessage(const ErrorReport) noexcept; + [[nodiscard]] InvalidResult registerErrorMessage( + const ErrorReport) noexcept; #endif /*! * \brief register a new error message * \param[in] e: error code * \note for convenience, this method always return `InvalidResult` */ - InvalidResult registerErrorMessageWithoutSourceLocation( + [[nodiscard]] InvalidResult registerErrorMessageWithoutSourceLocation( const ErrorReport) noexcept; //! \brief remove the error messages void clearErrorMessages() noexcept; @@ -84,15 +92,15 @@ namespace mgis { * \return the registered error messages * \param[in] b: boolean. If true, the error message are cleared */ - std::string getErrorMessage(const bool = true) noexcept; + [[nodiscard]] std::string getErrorMessage(const bool = true) noexcept; /*! * \return the error message without the decoration (file name, function * name, line number) \param[in] b: boolean. If true, the error message are * cleared */ - std::string getRawErrorMessage(const bool = true) noexcept; + [[nodiscard]] std::string getRawErrorMessage(const bool = true) noexcept; //! \return if the list registered error messages is empty - bool empty() const noexcept; + [[nodiscard]] bool empty() const noexcept; //! \brief destructor ~ErrorBacktrace() noexcept override; @@ -129,25 +137,31 @@ namespace mgis { #ifdef MGIS_USE_SOURCE_LOCATION_INFORMATION /*! * \brief a custom Lippincott-like function that extract error messages from - * an exception an \param[out] e: error back trace handler \param[in] l: - * description of the call site + * an exception + * + * \param[out] e: error back trace handler + * \param[in] l: description of the call site */ - MGIS_EXPORT InvalidResult registerExceptionInErrorBacktrace( + MGIS_EXPORT [[nodiscard]] InvalidResult registerExceptionInErrorBacktrace( ErrorBacktrace &, const std::source_location & = std::source_location::current()) noexcept; #else /*! * \brief a custom Lippincott-like function that extract error messages from - * an exception an \param[out] e: error back trace handler + * an exception + * + * \param[out] e: error back trace handler */ - MGIS_EXPORT InvalidResult - registerExceptionInErrorBacktrace(ErrorBacktrace &) noexcept; + MGIS_EXPORT [[nodiscard]] InvalidResult registerExceptionInErrorBacktrace( + ErrorBacktrace &) noexcept; #endif /*! * \brief a custom Lippincott-like function that extract error messages from - * an exception an \param[out] e: error back trace handler + * an exception + * + *\param[out] e: error back trace handler */ - MGIS_EXPORT InvalidResult + MGIS_EXPORT [[nodiscard]] InvalidResult registerExceptionInErrorBacktraceWithoutSourceLocation( ErrorBacktrace &) noexcept; diff --git a/include/MGIS/Utilities/HDF5Forward.hxx b/include/MGIS/Utilities/HDF5Forward.hxx new file mode 100644 index 000000000..b7cdc21e0 --- /dev/null +++ b/include/MGIS/Utilities/HDF5Forward.hxx @@ -0,0 +1,25 @@ +/*! + * \file MGIS/Utilities/HDF5Forward.hxx + * \brief + * \author Thomas Helfer + * \date 02/02/2026 + * \copyright (C) Copyright Thomas Helfer 2018. + * Use, modification and distribution are subject + * to one of the following licences: + * - GNU Lesser General Public License (LGPL), Version 3.0. (See accompanying + * file LGPL-3.0.txt) + * - CECILL-C, Version 1.0 (See accompanying files + * CeCILL-C_V1-en.txt and CeCILL-C_V1-fr.txt). + */ + +#ifndef LIB_MGIS_UTILITIES_HDF5FORWARD_HXX +#define LIB_MGIS_UTILITIES_HDF5FORWARD_HXX + +namespace H5 { + + class Group; + class DataSet; + +} // end of namespace H5 + +#endif /* LIB_MGIS_UTILITIES_HDF5FORWARD_HXX */ diff --git a/include/MGIS/Utilities/HDF5Support.hxx b/include/MGIS/Utilities/HDF5Support.hxx new file mode 100644 index 000000000..2025b931e --- /dev/null +++ b/include/MGIS/Utilities/HDF5Support.hxx @@ -0,0 +1,216 @@ +/*! + * \file MGIS/Utilities/HDF5Support.hxx + * \brief + * \author Thomas Helfer + * \date 29/01/2026 + * \copyright (C) Copyright Thomas Helfer 2018. + * Use, modification and distribution are subject + * to one of the following licences: + * - GNU Lesser General Public License (LGPL), Version 3.0. (See accompanying + * file LGPL-3.0.txt) + * - CECILL-C, Version 1.0 (See accompanying files + * CeCILL-C_V1-en.txt and CeCILL-C_V1-fr.txt). + */ + +#ifndef LIB_MGIS_UTILITIES_HDF5SUPPORT_HXX +#define LIB_MGIS_UTILITIES_HDF5SUPPORT_HXX + +#include +#include +#include +#include +#include "MGIS/Config.hxx" +#include "MGIS/Context.hxx" +#include "MGIS/Utilities/HDF5Forward.hxx" + +namespace mgis::utilities::hdf5 { + + template + H5::PredType getNativeType() noexcept; + // + template <> + MGIS_EXPORT [[nodiscard]] H5::PredType getNativeType() noexcept; + template <> + MGIS_EXPORT [[nodiscard]] H5::PredType getNativeType() noexcept; + template <> + MGIS_EXPORT [[nodiscard]] H5::PredType getNativeType() noexcept; + /*! + * \brief check if an object with the given path exists in the given group + * \param[in] g: group + * \param[in] p: path + */ + MGIS_EXPORT [[nodiscard]] bool exists(const H5::Group&, + const std::string&) noexcept; + /*! + * \brief check if a group with the given path exists in the given given + * \param[in] g: group + * \param[in] p: path + */ + MGIS_EXPORT [[nodiscard]] bool subGroupExists(const H5::Group&, + const std::string&) noexcept; + /*! + * \brief create a new group + * \param[out, in] ctx: execution context + * \param[in] g: parent group + * \param[in] n: group name + */ + MGIS_EXPORT [[nodiscard]] std::optional createGroup( + Context&, const H5::Group&, const std::string&) noexcept; + /*! + * \brief open a new group + * \param[out, in] ctx: execution context + * \param[in] g: parent group + * \param[in] n: group name + */ + MGIS_EXPORT [[nodiscard]] std::optional openGroup( + Context&, const H5::Group&, const std::string&) noexcept; + /*! + * \brief open a data set + * \param[out, in] ctx: execution context + * \param[in] g: parent group + * \param[in] n: data set name + */ + MGIS_EXPORT [[nodiscard]] std::optional openDataSet( + Context& ctx, const H5::Group&, const std::string&) noexcept; + /*! + * \brief remove a data set + * \param[out, in] ctx: execution context + * \param[in] g: parent group + * \param[in] n: data set name + */ + MGIS_EXPORT [[nodiscard]] bool removeDataSet(Context&, + const H5::Group&, + const std::string&) noexcept; + /*! + * \param[in] g: group + * \param[in] b: boolean allowing other objects than groups to be + * inside the given group + */ + MGIS_EXPORT [[nodiscard]] std::optional> + getSubGroupNames(Context&, const H5::Group&, const bool) noexcept; + /*! + * \param[out, in] ctx: execution context + * \param[out] n: names + * \param[in] g: group + * \param[in] b: boolean allowing other objects than groups to be + * inside the given group + */ + MGIS_EXPORT [[nodiscard]] bool getSubGroupNames(Context&, + std::vector&, + const H5::Group&, + const bool) noexcept; + /*! + * \return all the dataset names in a give group + * \param[out, in] ctx: execution context + * \param[in] g: group + */ + MGIS_EXPORT [[nodiscard]] std::optional> + getDataSetNames(Context&, const H5::Group&) noexcept; + /*! + * \return true if the given group contains an object with the + * given name + * \param[out, in] ctx: execution context + * \param[in] g: group + * \param[in] n: name + */ + MGIS_EXPORT [[nodiscard]] std::optional contains( + Context&, const H5::Group&, const std::string&) noexcept; + /*! + * \brief delete an existing group or dataset if it exists + * \param[out, in] ctx: execution context + * \param[in] g: parent group + * \param[in] n: group or dataset name + */ + MGIS_EXPORT [[nodiscard]] bool unlinkIfExists(Context&, + const H5::Group&, + const std::string&) noexcept; + /*! + * \param[out, in] ctx: execution context + * \param g : HDF5 group + * \param n : name of the dataset + * \param o : object to be written + * \param[in] b: allow overwrite + */ + MGIS_EXPORT [[nodiscard]] bool write(Context&, + H5::Group&, + const std::string&, + const real&, + const bool) noexcept; + /*! + * \param[out, in] ctx: execution context + * \param[out] o : object to be written + * \param g : HDF5 group + * \param n : name of the dataset + */ + MGIS_EXPORT [[nodiscard]] bool read(Context&, + real&, + const H5::Group&, + const std::string&) noexcept; + /*! + * \param[out, in] ctx: execution context + * \param g : HDF5 group + * \param n : name of the dataset + * \param o : object to be written + * \param[in] b: allow overwrite + */ + MGIS_EXPORT [[nodiscard]] bool write(Context&, + H5::Group&, + const std::string&, + std::span, + const bool) noexcept; + /*! + * \param[out, in] ctx: execution context + * \param[out] o : object to be read + * \param g : HDF5 group + * \param n : name of the dataset + */ + MGIS_EXPORT [[nodiscard]] bool read(Context&, + std::vector&, + const H5::Group&, + const std::string&) noexcept; + /*! + * \param[out, in] ctx: execution context + * \param[out] o : object to be read + * \param g : HDF5 group + * \param n : name of the dataset + */ + MGIS_EXPORT [[nodiscard]] bool read(Context&, + std::span, + const H5::Group&, + const std::string&) noexcept; + +#ifdef MGIS_USE_SOURCE_LOCATION_INFORMATION + /*! + * \brief a custom Lippincott-like function that extract error messages from + * a standard exception or an exception generated by the HD5 library + * + * \param[out] e: error back trace handler + * \param[in] l: description of the call site + */ + MGIS_EXPORT [[nodiscard]] InvalidResult registerH5ExceptionInErrorBacktrace( + ErrorBacktrace&, + const std::source_location& = std::source_location::current()) noexcept; +#else + /*! + * \brief a custom Lippincott-like function that extract error messages from + * a standard exception or an exception generated by the HD5 library + * + * \param[out] e: error back trace handler + * \param[in] l: description of the call site + */ + MGIS_EXPORT [[nodiscard]] InvalidResult registerH5ExceptionInErrorBacktrace( + ErrorBacktrace&) noexcept; +#endif + /*! + * \brief a custom Lippincott-like function that extract error messages from + *a standard exception or an exception generated by the HD5 library + * + *\param[out] e: error back trace handler + */ + MGIS_EXPORT [[nodiscard]] InvalidResult + registerH5ExceptionInErrorBacktraceWithoutSourceLocation( + ErrorBacktrace&) noexcept; + +} // end of namespace mgis::utilities::hdf5 + +#endif /* LIB_MGIS_UTILITIES_HDF5SUPPORT_HXX */ diff --git a/include/MGIS/Utilities/Markdown.hxx b/include/MGIS/Utilities/Markdown.hxx index 623f238bc..91c64771d 100644 --- a/include/MGIS/Utilities/Markdown.hxx +++ b/include/MGIS/Utilities/Markdown.hxx @@ -18,18 +18,14 @@ #include #include "MGIS/Config.hxx" -namespace mgis { +namespace mgis::utilities { - namespace utilities { + /*! + * \return the heading signs associated with the given heading level + * \param[in] l: heading level + */ + MGIS_EXPORT std::string get_heading_signs(const mgis::size_type); - /*! - * \return the heading signs associated with the given heading level - * \param[in] l: heading level - */ - MGIS_EXPORT std::string get_heading_signs(const mgis::size_type); - - } // end of namespace utilities - -} // end of namespace mgis +} // end of namespace mgis::utilities #endif /* LIB_MGIS_UTILITIES_MARKDOWN_HXX */ diff --git a/src/Behaviour.cxx b/src/Behaviour.cxx index ae86d0045..25e096ae2 100644 --- a/src/Behaviour.cxx +++ b/src/Behaviour.cxx @@ -12,8 +12,7 @@ * CeCILL-C_V1-en.txt and CeCILL-C_V1-fr.txt). */ -#include - +#include #include #include #include "MGIS/Raise.hxx" @@ -197,7 +196,7 @@ namespace mgis::behaviour { try { return load(l, f, h); } catch (...) { - registerExceptionInErrorBacktrace(ctx); + std::ignore = registerExceptionInErrorBacktrace(ctx); } return {}; } // end of load @@ -210,7 +209,7 @@ namespace mgis::behaviour { try { return load(o, l, f, h); } catch (...) { - registerExceptionInErrorBacktrace(ctx); + std::ignore = registerExceptionInErrorBacktrace(ctx); } return {}; } @@ -286,7 +285,7 @@ namespace mgis::behaviour { try { return loadFromDatabase(opts); } catch (...) { - registerExceptionInErrorBacktrace(ctx); + std::ignore = registerExceptionInErrorBacktrace(ctx); } return {}; } // end of loadFromDatabase @@ -298,7 +297,7 @@ namespace mgis::behaviour { try { return loadFromDatabase(o, opts); } catch (...) { - registerExceptionInErrorBacktrace(ctx); + std::ignore = registerExceptionInErrorBacktrace(ctx); } return {}; } // end of loadFromDatabase diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 211597a0e..76e4b4371 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,6 +49,11 @@ if(enable-mgis-function) endif(MGIS_HAVE_TFEL) endif(enable-mgis-function) +if(MGIS_HDF5_SUPPORT) + list(APPEND MFrontGenericInterface_SOURCES HDF5Support.cxx) +endif(MGIS_HDF5_SUPPORT) + + mgis_library(MFrontGenericInterface ${MFrontGenericInterface_SOURCES}) @@ -141,6 +146,21 @@ else(UNIX) endif(Threads_FOUND) endif(UNIX) +if(MGIS_HDF5_SUPPORT) + target_compile_definitions(MFrontGenericInterface PUBLIC MGIS_HAVE_HDF5) + target_compile_definitions(MFrontGenericInterface PUBLIC ${HDF5_DEFINITIONS}) + target_include_directories(MFrontGenericInterface SYSTEM PUBLIC ${HDF5_INCLUDE_DIRS}) + target_link_libraries(MFrontGenericInterface PUBLIC ${HDF5_LIBRARIES}) + # target_link_libraries(MFrontGenericInterface PUBLIC hdf5::hdf5_cpp) + if(enable-static) + target_compile_definitions(MFrontGenericInterface-static PUBLIC MGIS_HAVE_HDF5) + target_compile_definitions(MFrontGenericInterface-static PUBLIC ${HDF5_DEFINITIONS}) + target_include_directories(MFrontGenericInterface-static SYSTEM PUBLIC ${HDF5_INCLUDE_DIRS}) + target_link_libraries(MFrontGenericInterface-static PUBLIC ${HDF5_LIBRARIES}) + # target_link_libraries(MFrontGenericInterface-static PUBLIC hdf5::hdf5_cpp hdf5::hdf5) + endif(enable-static) +endif(MGIS_HDF5_SUPPORT) + if(MGIS_ADDITIONAL_LINK_FLAGS) target_link_options(MFrontGenericInterface PUBLIC "${MGIS_ADDITIONAL_LINK_FLAGS}") endif(MGIS_ADDITIONAL_LINK_FLAGS) diff --git a/src/ErrorBacktrace.cxx b/src/ErrorBacktrace.cxx index 269f61570..a0fe86153 100644 --- a/src/ErrorBacktrace.cxx +++ b/src/ErrorBacktrace.cxx @@ -4,6 +4,7 @@ * \date 04/11/2022 */ +#include #include #include #include "MGIS/Raise.hxx" @@ -223,10 +224,11 @@ namespace mgis { try { throw; } catch (std::exception &exception) { - e.registerErrorMessageWithoutSourceLocation( + std::ignore = e.registerErrorMessageWithoutSourceLocation( std::string{exception.what()}); } catch (...) { - e.registerErrorMessageWithoutSourceLocation("unknown exception thrown"); + std::ignore = e.registerErrorMessageWithoutSourceLocation( + "unknown exception thrown"); } return {}; } diff --git a/src/HDF5Support.cxx b/src/HDF5Support.cxx new file mode 100644 index 000000000..70167dea2 --- /dev/null +++ b/src/HDF5Support.cxx @@ -0,0 +1,396 @@ +/*! + * \file src/HDF5Support.cxx + * \brief + * \author Thomas Helfer + * \date 29/01/2026 + * \copyright (C) Copyright Thomas Helfer 2018. + * Use, modification and distribution are subject + * to one of the following licences: + * - GNU Lesser General Public License (LGPL), Version 3.0. (See accompanying + * file LGPL-3.0.txt) + * - CECILL-C, Version 1.0 (See accompanying files + * CeCILL-C_V1-en.txt and CeCILL-C_V1-fr.txt). + */ + +#include +#include "MGIS/Utilities/HDF5Support.hxx" + +namespace mgis::utilities::hdf5 { + + InvalidResult registerH5ExceptionInErrorBacktraceWithoutSourceLocation( + ErrorBacktrace& e) noexcept { + try { + throw; + } catch (H5::GroupIException& exception) { + std::ignore = + e.registerErrorMessageWithoutSourceLocation(exception.getDetailMsg()); + } catch (H5::Exception& exception) { + std::ignore = + e.registerErrorMessageWithoutSourceLocation(exception.getDetailMsg()); + } catch (std::exception& exception) { + std::ignore = e.registerErrorMessageWithoutSourceLocation( + std::string{exception.what()}); + } catch (...) { + std::ignore = e.registerErrorMessageWithoutSourceLocation( + "unknown exception thrown"); + } + return {}; + } + +#ifdef MGIS_USE_SOURCE_LOCATION_INFORMATION + InvalidResult registerH5ExceptionInErrorBacktrace( + ErrorBacktrace& e, const std::source_location& l) noexcept { + try { + throw; + } catch (H5::GroupIException& exception) { + std::ignore = e.registerErrorMessage(exception.getDetailMsg()); + } catch (H5::Exception& exception) { + std::ignore = e.registerErrorMessage(exception.getDetailMsg()); + } catch (std::exception& exception) { + std::ignore = e.registerErrorMessage(std::string{exception.what()}, l); + } catch (...) { + std::ignore = e.registerErrorMessage("unknown exception thrown"); + } + return {}; + } +#else + InvalidResult registerH5ExceptionInErrorBacktrace( + ErrorBacktrace& e) noexcept { + return registerH5ExceptionInErrorBacktraceWithoutSourceLocation(e); + } +#endif + + static std::vector tokenize(std::string_view s, + const char c, + const bool keep_empty_strings) { + std::vector res; + auto b = std::string::size_type{}; + auto e = s.find_first_of(c, b); + while (std::string::npos != e || std::string::npos != b) { + // Found a token, add it to the vector. + res.push_back(std::string{s.substr(b, e - b)}); + if (keep_empty_strings) { + b = e == std::string::npos ? e : e + 1; + } else { + b = s.find_first_not_of(c, e); + } + e = s.find_first_of(c, b); + } + return res; + } // end of tokenize + + bool exists(const H5::Group& g, const std::string& p) noexcept { + // break paths + const auto leafs = tokenize(p, '/', false); + if (leafs.empty()) { + return false; + } + auto cpath = std::string{}; + for (const auto& l : leafs) { + if (!cpath.empty()) { + cpath += '/'; + } + cpath += l; + const auto b = H5Lexists(g.getId(), cpath.c_str(), H5P_DEFAULT) > 0; + if (!b) { + return false; + } + } + return true; + } + + bool subGroupExists(const H5::Group& g, const std::string& p) noexcept { + if (!exists(g, p)) { + return false; + } + H5O_info_t infobuf; +#ifdef H5Oget_info_by_name_vers +#if H5Oget_info_by_name_vers >= 3 + auto status = H5Oget_info_by_name(g.getId(), p.c_str(), &infobuf, + H5O_INFO_ALL, H5P_DEFAULT); +#else + auto status = + H5Oget_info_by_name(g.getId(), p.c_str(), &infobuf, H5P_DEFAULT); +#endif +#else + auto status = + H5Oget_info_by_name(g.getId(), p.c_str(), &infobuf, H5P_DEFAULT); +#endif + return (status >= 0) && (infobuf.type == H5O_TYPE_GROUP); + } + + std::optional createGroup(Context& ctx, + const H5::Group& g, + const std::string& n) noexcept { + if (subGroupExists(g, n)) { + return openGroup(ctx, g, n); + } + try { + H5::Group gr = g.createGroup(n); + return gr; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return {}; + } // end of createGroup + + std::optional openGroup(Context& ctx, + const H5::Group& g, + const std::string& n) noexcept { + try { + H5::Group gr = g.openGroup(n); + return gr; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return {}; + } // end of openGroup + + bool removeDataSet(Context& ctx, + const H5::Group& g, + const std::string& n) noexcept { + try { + g.unlink(n); + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } // end of removeDataSet + + std::optional openDataSet(Context& ctx, + const H5::Group& g, + const std::string& n) noexcept { + try { + H5::DataSet d = g.openDataSet(n); + return d; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return {}; + } // end of openDataSet + + std::optional> getSubGroupNames( + Context& ctx, const H5::Group& g, const bool b) noexcept { + std::vector names; + if (!getSubGroupNames(ctx, names, g, b)) { + return {}; + } + return names; + } // end of getSubGroupNames + + bool getSubGroupNames(Context& ctx, + std::vector& n, + const H5::Group& g, + const bool b) noexcept { + n.clear(); + try { + const hsize_t s = g.getNumObjs(); + for (hsize_t i = 0; i != s; ++i) { + if (g.getObjTypeByIdx(i) == H5G_GROUP) { + n.push_back(g.getObjnameByIdx(i)); + } else if (!b) { + return ctx.registerErrorMessage("getSubGroupNames: object '" + + g.getObjnameByIdx(i) + + "' is not a group"); + } + } + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } // end of getSubGroupNames + + std::optional> getDataSetNames( + Context& ctx, const H5::Group& g) noexcept { + try { + std::vector names; + const hsize_t s = g.getNumObjs(); + for (hsize_t i = 0; i != s; ++i) { + if (g.getObjTypeByIdx(i) == H5G_DATASET) { + names.push_back(g.getObjnameByIdx(i)); + } + } + return names; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return {}; + } // end of getDataSetNames + + std::optional contains(Context& ctx, + const H5::Group& g, + const std::string& n) noexcept { + try { + bool found = false; + const hsize_t s = g.getNumObjs(); + for (hsize_t i = 0; (i != s) && (!found); ++i) { + if (g.getObjnameByIdx(i) == n) { + found = true; + } + } + return found; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return {}; + } // end of contains + + bool unlinkIfExists(Context& ctx, + const H5::Group& g, + const std::string& n) noexcept { + try { + if (exists(g, n)) { + g.unlink(n); + } + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } + + template <> + [[nodiscard]] H5::PredType getNativeType() noexcept { + return H5::PredType::NATIVE_FLOAT; + } // end of getNativeType + + template <> + [[nodiscard]] H5::PredType getNativeType() noexcept { + return H5::PredType::NATIVE_DOUBLE; + } // end of getNativeType + + template <> + [[nodiscard]] H5::PredType getNativeType() noexcept { + return H5::PredType::NATIVE_LDOUBLE; + } // end of getNativeType + + MGIS_EXPORT [[nodiscard]] bool write(Context& ctx, + H5::Group& g, + const std::string& d, + const real& o, + const bool b) noexcept { + if (b) { + if (!unlinkIfExists(ctx, g, d)) { + return false; + } + } + try { + hsize_t dimsf[1] = {1}; + H5::DataSpace dataspace(1, dimsf); + auto dataset = g.createDataSet(d, getNativeType(), dataspace); + dataset.write(&o, getNativeType()); + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } // end of write + + MGIS_EXPORT [[nodiscard]] bool read(Context& ctx, + real& o, + const H5::Group& g, + const std::string& d) noexcept { + auto odataset = openDataSet(ctx, g, d); + if (isInvalid(odataset)) { + return false; + } + try { + const auto s = odataset->getSpace(); + if (s.getSimpleExtentNdims() != 1) { + return ctx.registerErrorMessage("madnex::read: invalid type size"); + } + hsize_t dims[1]; + s.getSimpleExtentDims(dims); + if (dims[0] != 1) { + return ctx.registerErrorMessage("madnex::read: invalid type"); + } + odataset->read(&o, getNativeType()); + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } // end of read + + bool write(Context& ctx, + H5::Group& g, + const std::string& d, + std::span o, + const bool b) noexcept { + if (b) { + if (!unlinkIfExists(ctx, g, d)) { + return false; + } + } + try { + if (o.empty()) { + auto c = real{}; + hsize_t dimsf[1]; + dimsf[0] = 1u; + H5::DataSpace dataspace(1, dimsf); + H5::StrType datatype(getNativeType()); + auto dataset = g.createDataSet(d, datatype, dataspace); + dataset.write(&c, getNativeType()); + } else { + hsize_t dimsf[1]; + dimsf[0] = o.size(); + H5::DataSpace dataspace(1, dimsf); + auto dataset = g.createDataSet(d, getNativeType(), dataspace); + dataset.write(&o[0], getNativeType()); + } + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } + + bool read(Context& ctx, + std::vector& o, + const H5::Group& g, + const std::string& d) noexcept { + auto odataset = openDataSet(ctx, g, d); + if (isInvalid(odataset)) { + return false; + } + try { + H5::DataSpace filespace = odataset->getSpace(); + hsize_t dims[1]; + filespace.getSimpleExtentDims(dims); + o.resize(dims[0]); + odataset->read(o.data(), getNativeType()); + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } + + bool read(Context& ctx, + std::span values, + const H5::Group& g, + const std::string& n) noexcept { + using namespace mgis::utilities::hdf5; + const auto odataset = openDataSet(ctx, g, n); + if (isInvalid(odataset)) { + return false; + } + try { + H5::DataSpace filespace = odataset->getSpace(); + hsize_t dims[1]; + filespace.getSimpleExtentDims(dims); + if (values.size() != dims[0]) { + return ctx.registerErrorMessage("invalid size while reading dataset '" + + n + "'"); + } + odataset->read(values.data(), getNativeType()); + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } // end of restore + +} // end of namespace mgis::utilities::hdf5 \ No newline at end of file diff --git a/src/MFrontGenericInterfaceConfig.cmake.in b/src/MFrontGenericInterfaceConfig.cmake.in index c16d647d2..e3993304e 100644 --- a/src/MFrontGenericInterfaceConfig.cmake.in +++ b/src/MFrontGenericInterfaceConfig.cmake.in @@ -37,6 +37,23 @@ if(MGIS_Threads_FOUND) find_package(Threads REQUIRED) endif(MGIS_Threads_FOUND) +set(MGIS_HDF5_SUPPORT @MGIS_HDF5_SUPPORT@) +if(MGIS_HDF5_SUPPORT) + set(MGIS_HDF5_DIR "@HDF5_DIR@") + if(MGIS_HDF5_DIR) + set(_HDF5_MODULE_PATH "@HDF5_DIR@") + if(HDF5_DIR) + set(_HDF5_MODULE_PATH "${HDF5_DIR}") + elseif(DEFINED ENV{${HDF5_DIR}}) + set(_HDF5_MODULE_PATH "$ENV{HDF5_DIR}") + endif(HDF5_DIR) + find_package(HDF5 REQUIRED COMPONENTS CXX + HINTS "${CMAKE_CURRENT_LIST_DIR}" "${_HDF5_MODULE_PATH}") + else() + find_package(HDF5 REQUIRED COMPONENTS CXX) + endif() +endif(MGIS_HDF5_SUPPORT) + set(MGIS_REQUIRED_ADDITIONAL_PACKAGES "@MGIS_REQUIRED_ADDITIONAL_PACKAGES@") foreach(_package ${MGIS_REQUIRED_ADDITIONAL_PACKAGES}) find_package(${_package} REQUIRED) diff --git a/src/MaterialDataManager.cxx b/src/MaterialDataManager.cxx index 21bcf949d..60841221c 100644 --- a/src/MaterialDataManager.cxx +++ b/src/MaterialDataManager.cxx @@ -15,6 +15,9 @@ #include #include #include "MGIS/Raise.hxx" +#ifdef MGIS_HAVE_HDF5 +#include "MGIS/Utilities/HDF5Support.hxx" +#endif /* MGIS_HAVE_HDF5 */ #include "MGIS/Behaviour/Behaviour.hxx" #include "MGIS/Behaviour/MaterialDataManager.hxx" @@ -182,4 +185,63 @@ namespace mgis::behaviour { return outputs; } // end of allocatePostProcessingVariables +#ifdef MGIS_HAVE_HDF5 + + bool save(Context& ctx, + H5::Group& g, + const MaterialDataManager& m, + const MaterialDataManagerSavingOptions& opts) noexcept { + using namespace mgis::utilities::hdf5; + // group for the state at the beginning of the time step + if (!unlinkIfExists(ctx, g, "s0")) { + return false; + } + auto og_s0 = openGroup(ctx, g, "s0"); + if (isInvalid(og_s0)) { + return false; + } + // group for the state at the end of the time step + if (!unlinkIfExists(ctx, g, "s1")) { + return false; + } + auto og_s1 = openGroup(ctx, g, "s1"); + if (isInvalid(og_s1)) { + return false; + } + // + if (!save(ctx, *og_s0, m.s0, opts)) { + return false; + } + if (!save(ctx, *og_s1, m.s1, opts)) { + return false; + } + return true; + } // end of save + + bool restore(Context& ctx, + MaterialDataManager& m, + const H5::Group& g, + const MaterialDataManagerRestoreOptions& opts) noexcept { + using namespace mgis::utilities::hdf5; + // group for the state at the beginning of the time step + auto og_s0 = openGroup(ctx, g, "s0"); + if (isInvalid(og_s0)) { + return false; + } + auto og_s1 = openGroup(ctx, g, "s1"); + if (isInvalid(og_s1)) { + return false; + } + // + if (!restore(ctx, m.s0, *og_s0, opts)) { + return false; + } + if (!restore(ctx, m.s1, *og_s1, opts)) { + return false; + } + return true; + } // end of restore + +#endif /* MGIS_HAVE_HDF5 */ + } // end of namespace mgis::behaviour diff --git a/src/MaterialStateManager.cxx b/src/MaterialStateManager.cxx index 3dea5d93b..c4738d82d 100644 --- a/src/MaterialStateManager.cxx +++ b/src/MaterialStateManager.cxx @@ -11,10 +11,13 @@ * - CECILL-C, Version 1.0 (See accompanying files * CeCILL-C_V1-en.txt and CeCILL-C_V1-fr.txt). */ -#include +#include #include #include "MGIS/Raise.hxx" +#ifdef MGIS_HAVE_HDF5 +#include "MGIS/Utilities/HDF5Support.hxx" +#endif /* MGIS_HAVE_HDF5 */ #include "MGIS/Behaviour/Behaviour.hxx" #include "MGIS/Behaviour/MaterialStateManager.hxx" @@ -136,9 +139,9 @@ namespace mgis::behaviour { MaterialStateManager::~MaterialStateManager() = default; - static MaterialStateManager::FieldHolder& getFieldHolder( + [[nodiscard]] static MaterialStateManager::FieldHolder& getFieldHolder( std::map& m, - const std::string_view& n) { + const std::string_view& n) noexcept { // #if __cplusplus > 201103L // return m[n]; // #else /* __cplusplus > 201103L */ @@ -146,17 +149,6 @@ namespace mgis::behaviour { // #endif /* __cplusplus > 201103L */ } // end of getFieldHolder - // static std::map::iterator - // getFieldHolderIterator( - // std::map& m, - // const std::string_view& n) { - // // #if __cplusplus > 201103L - // // return m.find(n); - // // #else /* __cplusplus > 201103L */ - // return m.find(n.to_string()); - // // #endif /* __cplusplus > 201103L */ - // } // end of getFieldHolder - static std::map::const_iterator getFieldHolderIterator( @@ -183,12 +175,34 @@ namespace mgis::behaviour { .value = v, .shall_be_updated = (p == MaterialStateManager::UPDATE)}; } // end of setMaterialProperty - MGIS_EXPORT void setMaterialProperty( + bool setMaterialProperty( + Context& ctx, MaterialStateManager& m, const std::string_view& n, - const std::span& v, - const MaterialStateManager::StorageMode s, - const MaterialStateManager::UpdatePolicy p) { + const real v, + const MaterialStateManager::UpdatePolicy p) noexcept { + const auto omp = getVariable(ctx, m.b.mps, n); + if (isInvalid(omp)) { + return false; + } + const auto& mp = *(*omp); + if (mp.type != Variable::SCALAR) { + return ctx.registerErrorMessage( + "setMaterialProperty: " + "invalid material property " + "(only scalar material property is supported)"); + } + getFieldHolder(m.material_properties, + n) = MaterialStateManager::FieldHolder{ + .value = v, .shall_be_updated = (p == MaterialStateManager::UPDATE)}; + return true; + } // end of setMaterialProperty + + void setMaterialProperty(MaterialStateManager& m, + const std::string_view& n, + const std::span& v, + const MaterialStateManager::StorageMode s, + const MaterialStateManager::UpdatePolicy p) { const auto mp = getVariable(m.b.mps, n); mgis::raise_if(mp.type != Variable::SCALAR, "setMaterialProperty: " @@ -209,6 +223,42 @@ namespace mgis::behaviour { } } // end of setMaterialProperty + bool setMaterialProperty( + Context& ctx, + MaterialStateManager& m, + const std::string_view& n, + const std::span& v, + const MaterialStateManager::StorageMode s, + const MaterialStateManager::UpdatePolicy p) noexcept { + const auto omp = getVariable(ctx, m.b.mps, n); + if (isInvalid(omp)) { + return false; + } + const auto& mp = *(*omp); + if (mp.type != Variable::SCALAR) { + return ctx.registerErrorMessage( + "setMaterialProperty: " + "invalid material property " + "(only scalar material property is supported)"); + } + if (static_cast(v.size()) != m.n) { + return ctx.registerErrorMessage( + "setMaterialProperty: invalid number of values " + "(does not match the number of integration points)"); + } + if (s == MaterialStateManager::LOCAL_STORAGE) { + getFieldHolder(m.material_properties, n) = + MaterialStateManager ::FieldHolder{ + .value = std::vector{v.begin(), v.end()}, + .shall_be_updated = (p == MaterialStateManager::UPDATE)}; + } else { + getFieldHolder(m.material_properties, + n) = MaterialStateManager ::FieldHolder{ + .value = v, .shall_be_updated = (p == MaterialStateManager::UPDATE)}; + } + return true; + } // end of setMaterialProperty + bool isMaterialPropertyDefined(const MaterialStateManager& m, const std::string_view& n) { const auto p = getFieldHolderIterator(m.material_properties, n); @@ -506,4 +556,390 @@ namespace mgis::behaviour { } } // end of extractInternalStateVariable +#ifdef MGIS_HAVE_HDF5 + + [[nodiscard]] static bool save( + Context& ctx, + H5::Group& g, + const std::string& n, + const MaterialStateManager::FieldHolder& f, + const MaterialStateManagerSavingOptions& opts) noexcept { + using namespace mgis::utilities::hdf5; + if (std::holds_alternative(f.value)) { + return write(ctx, g, n, std::get(f.value), opts.allow_overwrite); + } else if (std::holds_alternative>(f.value)) { + return write(ctx, g, n, std::get>(f.value), + opts.allow_overwrite); + } + return write(ctx, g, n, std::get>(f.value), + opts.allow_overwrite); + } // end of save + + bool save(Context& ctx, + H5::Group& g, + const MaterialStateManager& s, + const MaterialStateManagerSavingOptions& opts) noexcept { + using namespace mgis::utilities::hdf5; + if (opts.save_gradients) { + if (!write(ctx, g, "gradients", s.gradients, opts.allow_overwrite)) { + return false; + } + } + if (opts.save_thermodynamic_forces) { + if (!write(ctx, g, "thermodynamic_forces", s.thermodynamic_forces, + opts.allow_overwrite)) { + return false; + } + } + if (s.mass_density.has_value()) { + if (!save(ctx, g, "mass_density", *(s.mass_density), opts)) { + return false; + } + } + // + if (opts.save_material_properties) { + auto og_mp = createGroup(ctx, g, "material_properties"); + if (isInvalid(og_mp)) { + return false; + } + for (const auto& [n, mp] : s.material_properties) { + if (!save(ctx, *og_mp, n, mp, opts)) { + return false; + } + } + } + // + if (!s.b.isvs.empty()) { + if (!write(ctx, g, "internal_state_variables", s.internal_state_variables, + opts.allow_overwrite)) { + return false; + } + } + if ((opts.save_stored_energies) && (!s.stored_energies.empty())) { + if (!write(ctx, g, "stored_energies", s.stored_energies, + opts.allow_overwrite)) { + return false; + } + } + if ((opts.save_dissipated_energies) && (!s.dissipated_energies.empty())) { + if (!write(ctx, g, "dissipated_energies", s.dissipated_energies, + opts.allow_overwrite)) { + return false; + } + } + // + if (opts.save_external_state_variables) { + auto og_esv = createGroup(ctx, g, "external_state_variables"); + if (isInvalid(og_esv)) { + return false; + } + for (const auto& [n, esv] : s.external_state_variables) { + if (!save(ctx, *og_esv, n, esv, opts)) { + return false; + } + } + } + return true; + } // end of save + + std::optional + getGreedyMaterialStateManagerRestoreOptions(Context& ctx, + const Behaviour& b, + const H5::Group& g) noexcept { + using namespace mgis::utilities::hdf5; + const auto odatasets = getDataSetNames(ctx, g); + if (isInvalid(odatasets)) { + return {}; + } + auto contains = [odatasets](std::string_view n) noexcept { + return std::find(odatasets->begin(), odatasets->end(), n) != + odatasets->end(); + }; + auto select_ignored_variables = [&g, &ctx]( + const std::vector& variables, + const std::string& gn) { + auto og = openGroup(ctx, g, gn); + auto ignored_variables = std::vector{}; + if (isInvalid(og)) { + return ignored_variables; + } + const auto oldatasets = getDataSetNames(ctx, *og); + if (isInvalid(oldatasets)) { + return ignored_variables; + } + for (const auto& v : variables) { + const auto found = std::find(oldatasets->begin(), oldatasets->end(), + v.name) != oldatasets->end(); + if (!found) { + ignored_variables.push_back(v.name); + } + } + return ignored_variables; + }; + return MaterialStateManagerRestoreOptions{ + .restore_gradients = contains("gradients"), + .restore_thermodynamic_forces = contains("thermodynamic_forces"), + .restore_stored_energies = contains("stored_energies"), + .restore_dissipated_energies = contains("dissipated_energies"), + .restore_internal_state_variables = + contains("internal_state_variables"), + .restore_mass_densities = contains("mass_density"), + .restore_material_properties = subGroupExists(g, "material_properties"), + .ignored_material_properties = + select_ignored_variables(b.mps, "material_properties"), + .restore_external_state_variables = + subGroupExists(g, "external_state_variables"), + .ignored_external_state_variables = + select_ignored_variables(b.esvs, "external_state_variables")}; + } // end of getGreedyMaterialStateManagerRestoreOptions + + [[nodiscard]] static bool restoreScalarFieldHolder( + Context& ctx, + MaterialStateManager::FieldHolder& f, + const H5::Group& g, + const std::string& n, + const size_type ng) noexcept { + using namespace mgis::utilities::hdf5; + auto odataset = openDataSet(ctx, g, n); + if (isInvalid(odataset)) { + return false; + } + try { + const auto s = odataset->getSpace(); + if (s.getSimpleExtentNdims() != 1) { + return ctx.registerErrorMessage("unexpected multidimensional array"); + } + hsize_t dims[1]; + s.getSimpleExtentDims(dims); + if (dims[0] == 1) { + // uniform value + auto value = real{}; + odataset->read(&value, getNativeType()); + f = value; + return true; + } + if (dims[0] != ng) { + return ctx.registerErrorMessage( + "unexpected data size for '" + n + "' (expected '" + + std::to_string(ng) + "', got '" + std::to_string(dims[0]) + "')"); + } + if (std::holds_alternative>(f.value)) { + auto& values = std::get>(f.value); + const auto fsize = values.size(); + if (fsize != ng) { + return ctx.registerErrorMessage( + "invalid storage for '" + n + "' (expected '" + + std::to_string(ng) + "', got '" + std::to_string(fsize) + "')"); + } + odataset->read(values.data(), getNativeType()); + } else if (std::holds_alternative>(f.value)) { + auto& values = std::get>(f.value); + if (values.empty()) { + values.resize(ng); + } + const auto fsize = values.size(); + if (fsize != ng) { + return ctx.registerErrorMessage( + "invalid storage for '" + n + "' (expected '" + + std::to_string(ng) + "', got '" + std::to_string(fsize) + "')"); + } + odataset->read(values.data(), getNativeType()); + } + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } // end of restoreScalarFieldHolder + + [[nodiscard]] static bool restoreFieldHolder( + Context& ctx, + MaterialStateManager::FieldHolder& f, + const H5::Group& g, + const std::string& n, + const size_type ng, + const size_type vsize) noexcept { + using namespace mgis::utilities::hdf5; + if (vsize == 1) { + return restoreScalarFieldHolder(ctx, f, g, n, ng); + } + if (std::holds_alternative(f.value)) { + // by default, f can be initialized to a real value + f.value = std::vector{}; + } + auto odataset = openDataSet(ctx, g, n); + if (isInvalid(odataset)) { + return false; + } + try { + const auto s = odataset->getSpace(); + if (s.getSimpleExtentNdims() != 1) { + return ctx.registerErrorMessage("unexpected multidimensional array"); + } + hsize_t dims[1]; + s.getSimpleExtentDims(dims); + if (dims[0] == vsize) { + // uniform value + if (std::holds_alternative>(f.value)) { + auto& values = std::get>(f.value); + if (values.size() == vsize) { + odataset->read(values.data(), getNativeType()); + return true; + } + if (values.size() != ng * vsize) { + return ctx.registerErrorMessage( + "unexpected data size for '" + n + "' (expected '" + + std::to_string(ng * vsize) + "', got '" + + std::to_string(dims[0]) + "')"); + } + // duplicate the uniform values + auto tmp = std::vector(vsize); + odataset->read(tmp.data(), getNativeType()); + for (size_type idx = 0; idx != ng; ++idx) { + std::copy(tmp.begin(), tmp.end(), values.begin() + idx * vsize); + } + return true; + } + auto& values = std::get>(f.value); + if (values.empty()) { + values.resize(vsize); + } + odataset->read(values.data(), getNativeType()); + return true; + } + if (dims[0] != vsize * ng) { + return ctx.registerErrorMessage( + "unexpected data size for '" + n + "' (expected '" + + std::to_string(ng * vsize) + "', got '" + std::to_string(dims[0]) + + "')"); + } + if (std::holds_alternative>(f.value)) { + auto& values = std::get>(f.value); + const auto fsize = values.size(); + if (fsize != ng) { + return ctx.registerErrorMessage( + "invalid storage for '" + n + "' (expected '" + + std::to_string(ng) + "', got '" + std::to_string(fsize) + "')"); + } + odataset->read(values.data(), getNativeType()); + } else if (std::holds_alternative>(f.value)) { + auto& values = std::get>(f.value); + if (values.empty()) { + values.resize(ng); + } + const auto fsize = values.size(); + if (fsize != ng) { + return ctx.registerErrorMessage( + "invalid storage for '" + n + "' (expected '" + + std::to_string(ng) + "', got '" + std::to_string(fsize) + "')"); + } + odataset->read(values.data(), getNativeType()); + } + return true; + } catch (...) { + std::ignore = registerH5ExceptionInErrorBacktrace(ctx); + } + return false; + } // end of restoreFieldHolder + + bool restore(Context& ctx, + MaterialStateManager& s, + const H5::Group& g, + const MaterialStateManagerRestoreOptions& opts) noexcept { + using namespace mgis::utilities::hdf5; + if (opts.restore_gradients) { + if (!read(ctx, s.gradients, g, "gradients")) { + return ctx.registerErrorMessage("restoring gradients failed"); + } + } + if (opts.restore_thermodynamic_forces) { + if (!read(ctx, s.thermodynamic_forces, g, "thermodynamic_forces")) { + return ctx.registerErrorMessage( + "restoring thermodynamic forces failed"); + } + } + if (opts.restore_mass_densities) { + if (!s.mass_density.has_value()) { + s.mass_density = real{}; + } + if (!restoreScalarFieldHolder(ctx, *(s.mass_density), g, "mass_density", + s.n)) { + return ctx.registerErrorMessage("restoring mass density failed"); + } + } + if (opts.restore_internal_state_variables) { + if (!read(ctx, s.internal_state_variables, g, + "internal_state_variables")) { + return ctx.registerErrorMessage( + "restoring internal state variables failed"); + } + } + if ((opts.restore_stored_energies) && (s.b.computesStoredEnergy)) { + if (!read(ctx, s.stored_energies, g, "stored_energies")) { + return ctx.registerErrorMessage("restoring stored energies failed"); + return false; + } + } + if ((opts.restore_dissipated_energies) && (s.b.computesDissipatedEnergy)) { + if (!read(ctx, s.dissipated_energies, g, "dissipated_energies")) { + return ctx.registerErrorMessage("restoring dissipated energies failed"); + } + } + if ((opts.restore_material_properties) && (!s.b.mps.empty())) { + if (!subGroupExists(g, "material_properties")) { + return ctx.registerErrorMessage("no material properties saved"); + } + auto ogmp = openGroup(ctx, g, "material_properties"); + if (isInvalid(ogmp)) { + return false; + } + for (const auto& mp : s.b.mps) { + if (std::find(opts.ignored_material_properties.begin(), + opts.ignored_material_properties.end(), + mpxs.name) != opts.ignored_material_properties.end()) { + continue; + } + const auto ovsize = getVariableSize(ctx, mp, s.b.hypothesis); + if (isInvalid(ovsize)) { + return ctx.registerErrorMessage("restoring material property '" + + mp.name + "' failed"); + } + auto& f = s.material_properties[mp.name]; + if (!restoreFieldHolder(ctx, f, *ogmp, mp.name, s.n, *ovsize)) { + return ctx.registerErrorMessage("restoring material property '" + + mp.name + "' failed"); + } + } + } + if ((opts.restore_external_state_variables) && (!s.b.esvs.empty())) { + if (!subGroupExists(g, "external_state_variables")) { + return ctx.registerErrorMessage("no external state variables saved"); + } + auto ogesv = openGroup(ctx, g, "external_state_variables"); + if (isInvalid(ogesv)) { + return false; + } + for (const auto& esv : s.b.esvs) { + if (std::find(opts.ignored_external_state_variables.begin(), + opts.ignored_external_state_variables.end(), esv.name) != + opts.ignored_external_state_variables.end()) { + continue; + } + const auto ovsize = getVariableSize(ctx, esv, s.b.hypothesis); + if (isInvalid(ovsize)) { + return ctx.registerErrorMessage( + "restoring external state variable '" + esv.name + "' failed"); + } + auto& f = s.external_state_variables[esv.name]; + if (!restoreFieldHolder(ctx, f, *ogesv, esv.name, s.n, *ovsize)) { + return ctx.registerErrorMessage( + "restoring external state variable '" + esv.name + "' failed"); + } + } + } + return true; + } // end of restore + +#endif /* MGIS_HAVE_HDF5 */ + } // end of namespace mgis::behaviour diff --git a/src/Model.cxx b/src/Model.cxx index a29f93231..2461d454e 100644 --- a/src/Model.cxx +++ b/src/Model.cxx @@ -12,6 +12,7 @@ * CeCILL-C_V1-en.txt and CeCILL-C_V1-fr.txt). */ +#include #include "MGIS/Raise.hxx" #include "MGIS/Context.hxx" #include "MGIS/Model/Model.hxx" @@ -50,7 +51,7 @@ namespace mgis::model { try { return ::mgis::model::load(l, m, h); } catch (...) { - registerExceptionInErrorBacktrace(ctx); + std::ignore = registerExceptionInErrorBacktrace(ctx); } return {}; } // end of load @@ -67,7 +68,7 @@ namespace mgis::model { try { return ::mgis::model::loadFromDatabase(opts); } catch (...) { - registerExceptionInErrorBacktrace(ctx); + std::ignore = registerExceptionInErrorBacktrace(ctx); } return {}; } // end of load diff --git a/src/Variable.cxx b/src/Variable.cxx index f9afaa4c9..91d21ae38 100644 --- a/src/Variable.cxx +++ b/src/Variable.cxx @@ -112,7 +112,7 @@ namespace mgis::behaviour::internals { mgis::raise("invalid space dimension"); } return static_cast(N); - } // end of getVariableSize + } // end of getTinyVectorVariableSize static size_t getSymmetricTensorVariableSize( int &v, const mgis::behaviour::Hypothesis h) { @@ -302,7 +302,19 @@ namespace mgis::behaviour { return s; } // end of getVariableSize - bool contains(const std::vector &vs, const std::string_view n) { + std::optional getVariableSize(Context &ctx, + const Variable &v, + const Hypothesis h) noexcept { + try { + return getVariableSize(v, h); + } catch (...) { + std::ignore = registerExceptionInErrorBacktrace(ctx); + } + return {}; + } // end of getVariableSize + + bool contains(const std::vector &vs, + const std::string_view n) noexcept { return std::find_if(vs.begin(), vs.end(), [&n](const Variable &v) { return v.name == n; }) != vs.end(); @@ -318,6 +330,19 @@ namespace mgis::behaviour { return *p; } // end of getVariable + std::optional getVariable( + Context &ctx, + const std::vector &vs, + const std::string_view n) noexcept { + const auto p = std::find_if( + vs.begin(), vs.end(), [&n](const Variable &v) { return v.name == n; }); + if (p == vs.end()) { + return ctx.registerErrorMessage("getVariable: no variable named '" + + std::string(n) + "'"); + } + return &(*p); + } // end of getVariable + size_type getArraySize(const std::vector &vs, const Hypothesis h) { auto s = size_type{}; for (const auto &v : vs) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 236f07c1b..e74ce6210 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -665,4 +665,22 @@ endif(MGIS_HAVE_MFRONT_SUPPORT) endif(MGIS_HAVE_TFEL) +if(MGIS_HDF5_SUPPORT) + + add_executable(HDF5Test EXCLUDE_FROM_ALL HDF5Test.cxx) + target_link_libraries(HDF5Test PRIVATE MFrontGenericInterface) + add_test(NAME HDF5Test + COMMAND HDF5Test "$") + add_dependencies(check HDF5Test) + if((CMAKE_HOST_WIN32) AND (NOT MSYS)) + set_property(TEST HDF5Test + PROPERTY DEPENDS BehaviourTest + PROPERTY ENVIRONMENT "PATH=$\;${MGIS_PATH_STRING}") + else((CMAKE_HOST_WIN32) AND (NOT MSYS)) + set_property(TEST HDF5Test + PROPERTY DEPENDS BehaviourTest) + endif((CMAKE_HOST_WIN32) AND (NOT MSYS)) + +endif(MGIS_HDF5_SUPPORT) + endif(enable-mgis-function) diff --git a/tests/HDF5Test.cxx b/tests/HDF5Test.cxx new file mode 100644 index 000000000..0a71101f6 --- /dev/null +++ b/tests/HDF5Test.cxx @@ -0,0 +1,114 @@ +/*! + * \file HDF5Test.cxx + * \brief + * \author Thomas Helfer + * \date 29/01/2026 + */ + +#include +#include +#include +#include +#include "MGIS/Behaviour/State.hxx" +#include "MGIS/Behaviour/Behaviour.hxx" +#include "MGIS/Behaviour/MaterialDataManager.hxx" +#include "MGIS/Behaviour/Integrate.hxx" +#include "MGIS/Utilities/HDF5Support.hxx" + +int main(const int argc, const char* const* argv) { + using namespace mgis; + using namespace mgis::behaviour; + using namespace mgis::utilities::hdf5; + if (argc != 2) { + std::cerr << "HDF5Test: invalid number of arguments\n"; + std::exit(-1); + } + auto ctx = Context{}; + auto file = H5::H5File("HDF5Test.h5", H5F_ACC_TRUNC); + auto root = file.openGroup("/"); + const auto b = load(argv[1], "Norton", Hypothesis::TRIDIMENSIONAL); + MaterialDataManager m{b, 100}; + const auto de = 5.e-5; + // initialize the external state variable + m.s1.external_state_variables["Temperature"] = 293.15; + // copy d.s1 in d.s0 + update(m); + const auto dt = real(180); + for (size_type idx = 0; idx != m.n; ++idx) { + m.s1.gradients[idx * m.s1.gradients_stride] = de; + } + for (size_type i = 0; i != 20; ++i) { + auto og = createGroup(ctx, root, "TimeStep_" + std::to_string(i)); + if (isInvalid(og)) { + std::cerr << ctx.getErrorMessage() << '\n'; + return EXIT_FAILURE; + } + integrate(m, IntegrationType::INTEGRATION_NO_TANGENT_OPERATOR, dt, 0, m.n); + if (!save(ctx, *og, m.s1)) { + std::cerr << ctx.getErrorMessage() << '\n'; + return EXIT_FAILURE; + } + update(m); + for (size_type idx = 0; idx != m.n; ++idx) { + m.s1.gradients[idx * m.s1.gradients_stride] += de; + } + } + // restore and checks + const auto o = + getVariableOffset(b.isvs, "EquivalentViscoplasticStrain", b.hypothesis); + auto pi = std::array{}; // values of the equivalent plastic strain + auto pe = std::array{}; // values of the equivalent plastic strain + const auto ni = size_type{o}; + const auto ne = + size_type{(m.n - 1) * m.s0.internal_state_variables_stride + o}; + for (size_type i = 0; i != 20; ++i) { + auto og = openGroup(ctx, root, "TimeStep_" + std::to_string(i)); + if (isInvalid(og)) { + std::cerr << ctx.getErrorMessage() << '\n'; + return EXIT_FAILURE; + } + setExternalStateVariable(m.s1, "Temperature", 500); + if (!restore(ctx, m.s1, *og, {.restore_mass_densities = false})) { + std::cerr << ctx.getErrorMessage() << '\n'; + return EXIT_FAILURE; + } + const auto& T = m.s1.external_state_variables.at("Temperature"); + if (!std::holds_alternative(T.value)) { + std::cerr << "invalid type for the temperature\n"; + return EXIT_FAILURE; + } + if (std::abs(std::get(T.value) - 293.15) > 1e-10) { + std::cerr << "invalid value for the temperature\n"; + return EXIT_FAILURE; + } + pi[i] = m.s1.internal_state_variables[ni]; + pe[i] = m.s1.internal_state_variables[ne]; + } + // + const auto p_ref = std::array{ + 1.3523277308229e-11, 1.0955374667213e-07, 5.5890770166084e-06, + 3.2392193670428e-05, 6.645865307584e-05, 9.9676622883138e-05, + 0.00013302758358953, 0.00016635821069889, 0.00019969195920296, + 0.00023302522883648, 0.00026635857194317, 0.000299691903777, + 0.0003330252373404, 0.00036635857063843, 0.00039969190397718, + 0.00043302523730968, 0.00046635857064314, 0.00049969190397646, + 0.00053302523730979, 0.00056635857064313}; + std::cerr.precision(14); + for (size_type i = 0; i != 20; ++i) { + if (std::abs(pi[i] - p_ref[i]) > 1.e-12) { + std::cerr << "invalid value for the equivalent " + "viscoplastic strain at the first integration point" + << "(expected '" << p_ref[i] << "', computed '" << pi[i] + << "')\n"; + return EXIT_FAILURE; + } + if (std::abs(pe[i] - p_ref[i]) > 1.e-12) { + std::cerr << "invalid value for the equivalent " + "viscoplastic strain at the last integration point" + << "(expected '" << p_ref[i] << "', computed '" << pe[i] + << "')\n"; + return EXIT_FAILURE; + } + } + return EXIT_SUCCESS; +} // end of main diff --git a/tests/StridedCoalescedMemoryAccessFunctionViewTest.cxx b/tests/StridedCoalescedMemoryAccessFunctionViewTest.cxx index 8c5a3a197..a3eb9e367 100644 --- a/tests/StridedCoalescedMemoryAccessFunctionViewTest.cxx +++ b/tests/StridedCoalescedMemoryAccessFunctionViewTest.cxx @@ -44,8 +44,8 @@ struct StridedCoalescedMemoryAccessFunctionViewBaseTest final auto space = BasicLinearSpace{2}; auto values = std::vector{5, 12, -2, 3}; auto coalesced_view = - StridedCoalescedMemoryAccessFunctionViewBase( - space, values); + StridedCoalescedMemoryAccessFunctionViewBase(space, values); auto ptr0 = coalesced_view.getValues(0); auto ptr1 = coalesced_view.getValues(1); return {std::array{ptr0[0], ptr0[1]}, //