diff --git a/CMakeLists.txt b/CMakeLists.txt index bf531fbc4..b148a92f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,7 @@ if(NOT disable-tfel) if(TFEL_FOUND) include(tfel) include(material-properties) + include(generic-parallel) include(behaviours) endif(TFEL_FOUND) endif(NOT disable-tfel) diff --git a/cmake/modules/generic-parallel.cmake b/cmake/modules/generic-parallel.cmake new file mode 100644 index 000000000..72abe7f0a --- /dev/null +++ b/cmake/modules/generic-parallel.cmake @@ -0,0 +1,53 @@ +# Libraries of material properties generated by the `generic-parallel` +# interface of `MFront` (`TFEL` 5.2 or later), compiled by the C++ compiler +# of `MGIS`. Only the backends whose sources are standard C++ sources are +# supported (`stlpar`). The sources are generated in a directory of their +# own. +# +# usage: mfront_generic_parallel_material_properties_check_library(name +# backend properties...) +function(mfront_generic_parallel_material_properties_check_library name + backend) + set(_directory + "${CMAKE_CURRENT_BINARY_DIR}/generic-parallel-material-properties-${backend}") + file(MAKE_DIRECTORY "${_directory}") + set(_configuration "${_directory}/configuration.json") + file(WRITE "${_configuration}" + "interfaces_options: {\n" + " generic-parallel: {\n" + " backend: \"${backend}\"\n" + " }\n" + "}\n") + set(_sources) + foreach(source ${ARGN}) + set(_file "${CMAKE_CURRENT_SOURCE_DIR}/${source}.mfront") + set(_output "${_directory}/src/${source}-generic-parallel-${backend}.cxx") + add_custom_command( + OUTPUT "${_output}" + COMMAND "${MFRONT}" + ARGS "--interface=generic-parallel" + ARGS "--configuration-file=${_configuration}" + ARGS "${_file}" + DEPENDS "${_file}" "${MFRONT}" "${_configuration}" + WORKING_DIRECTORY "${_directory}" + COMMENT "mfront source ${_file} (generic-parallel interface)") + list(APPEND _sources "${_output}") + endforeach(source ${ARGN}) + add_library(${name} SHARED EXCLUDE_FROM_ALL ${_sources}) + add_dependencies(build-tests ${name}) + target_include_directories(${name} + PRIVATE "${_directory}/include" + PRIVATE "${TFEL_INCLUDE_PATH}") + target_compile_definitions(${name} PRIVATE MFRONT_COMPILING) + if(backend STREQUAL "stlpar") + # libraries required by the parallel algorithms of the standard library + # (see the `enable-parallel-stl-algorithms` option) + target_link_libraries(${name} PRIVATE ${MGIS_ADDITIONAL_LIBRARIES}) + if(UNIX AND NOT APPLE) + # the library stays loaded until the end of the process: the threads + # of TBB may still run when the libraries are unloaded at exit (as in + # the configuration files of the stlpar backend of TFEL) + target_link_libraries(${name} PRIVATE "-Wl,-z,nodelete") + endif(UNIX AND NOT APPLE) + endif(backend STREQUAL "stlpar") +endfunction(mfront_generic_parallel_material_properties_check_library) diff --git a/docs/web/release-notes-3.2.md b/docs/web/release-notes-3.2.md index af0158fc6..518b34900 100644 --- a/docs/web/release-notes-3.2.md +++ b/docs/web/release-notes-3.2.md @@ -307,6 +307,63 @@ post-processings: `behaviour_get_number_of_post_processings`, functions of this module, indices, offsets and ranges of integration points start at 1. +# New features of the `MGIS/MaterialProperty` library + +## Material properties generated by the `generic-parallel` interface + +The `generic-parallel` interface of `MFront` (`TFEL` 5.2 or later) +generates functions evaluating a material property on a batch of points, +on CPUs or on GPUs. The `load` function recognizes those libraries by the +version of their functions (`_generic_parallel_api_version` +symbol) and fills the new members of the `MaterialProperty` structure: + +- `mfront_interface`: interface of `MFront` used to generate the + material property; +- `batch_api_version`: version of the functions of batches of points; +- `backend`: backend of the library (`cuda`, `hip`, `stlpar` or `sycl`); +- `memory_space`: memory space of the computations (`host`, `device` or + `any`); +- `execution_policy`: identifier of the execution policy expected by the + library; +- `batch_fct` and `batch_report_fct`: functions evaluating the material + property on a batch of points and analysing the statistics of an + evaluation. + +A library exporting another version of those functions, an invalid memory +space, or generated by a version of the `generic-parallel` interface which +does not export the version of its functions, is refused. The +`isGenericParallelMaterialProperty` function tells if a material property +provides the functions of batches of points. + +The types of those functions (`mgis_mp_BatchData`, +`mgis_mp_BatchStatistics`, `mgis_mp_BatchOutputStatus`, etc.), declared in +`MGIS/MaterialProperty/BatchMaterialPropertyFctPtr.hxx`, are copies of the +ones of `TFEL`: `MGIS` does not depend on `TFEL`. The execution policies of +the backends are defined by `TFEL`: `MGIS` only declares their type +(`MGIS/BackendExecutionPolicy.h`), and a code creating a policy includes the +headers of `TFEL` and links the `MFrontGenericParallelRuntime` library. + +### Example of usage + +~~~~{.cxx} +using namespace mgis::material_property; +const auto mp = load("libGenericParallelMaterial-stlpar.so", "YoungModulus"); +const auto T = std::array{300, 400, 500, 600}; +auto E = std::array{}; +const auto args = std::array{ + BatchConstField{.values = T.data(), .point_stride = 1}}; +const auto d = BatchData{.number_of_points = 4, + .output = BatchField{.values = E.data(), .point_stride = 1}, + .arguments = args.data(), + .number_of_arguments = 1, + .out_of_bounds_policy = MGIS_MATERIALPROPERTY_NONE_POLICY, + .statuses = nullptr, + .statistics = nullptr, + .execution_policy = nullptr}; +auto s = BatchOutputStatus{}; +const auto r = mp.batch_fct(&s, &d); +~~~~ + # New features of the `MGIS/Function` library ## Support for quantities @@ -491,6 +548,10 @@ For more details, see +## Issue 320: generic-parallel material properties: the libraries generated by TFEL 5.2 can't be loaded nor checked + +For more details, see + ## Issue 308: Wrong statements and typos in the documentation and error messages For more details, see diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 6b948bb06..619549394 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -1,4 +1,5 @@ mgis_header(MGIS Config-c.h) +mgis_header(MGIS BackendExecutionPolicy.h) mgis_header(MGIS Config.hxx) mgis_header(MGIS LogStream.hxx) mgis_header(MGIS LogStream.ixx) @@ -32,6 +33,7 @@ mgis_header(MGIS LibrariesManager.hxx) mgis_header(MGIS/MaterialProperty OutputStatus.hxx) mgis_header(MGIS/MaterialProperty OutOfBoundsPolicy.hxx) mgis_header(MGIS/MaterialProperty MaterialPropertyFctPtr.hxx) +mgis_header(MGIS/MaterialProperty BatchMaterialPropertyFctPtr.hxx) mgis_header(MGIS/MaterialProperty MaterialProperty.hxx) mgis_header(MGIS/Behaviour Hypothesis.hxx) mgis_header(MGIS/Behaviour TimeStepStage.hxx) diff --git a/include/MGIS/BackendExecutionPolicy.h b/include/MGIS/BackendExecutionPolicy.h new file mode 100644 index 000000000..cdecd0427 --- /dev/null +++ b/include/MGIS/BackendExecutionPolicy.h @@ -0,0 +1,46 @@ +/*! + * \file include/MGIS/BackendExecutionPolicy.h + * \brief This file declares the type of the execution policies of the + * backends of the `generic-parallel` interfaces of `MFront`. Those policies + * are defined by `TFEL` (`MFront/GenericParallel/` headers, base class + * `mfront::generic_parallel::AbstractBackendExecutionPolicy`): `MGIS` only + * gives them to the generated functions, so that a declaration is enough and + * `MGIS` does not depend on `TFEL` at compile time. In `C`, the policies are + * opaque and can only be given as null pointers (default policy of the + * backend). + * \date 22/10/2026 + * \copyright 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_BACKENDEXECUTIONPOLICY_H +#define LIB_MGIS_BACKENDEXECUTIONPOLICY_H + +// NOLINTBEGIN(modernize-use-using) +#ifdef __cplusplus + +namespace mfront::generic_parallel { + + //! \brief base class of the execution policies of the backends + struct AbstractBackendExecutionPolicy; + +} // end of namespace mfront::generic_parallel + +//! \brief a simple alias +typedef mfront::generic_parallel::AbstractBackendExecutionPolicy + mgis_gp_AbstractBackendExecutionPolicy; + +#else /* __cplusplus */ + +//! \brief opaque structure +typedef struct mgis_gp_AbstractBackendExecutionPolicy + mgis_gp_AbstractBackendExecutionPolicy; + +#endif /* __cplusplus */ +// NOLINTEND(modernize-use-using) + +#endif /* LIB_MGIS_BACKENDEXECUTIONPOLICY_H */ diff --git a/include/MGIS/LibrariesManager.hxx b/include/MGIS/LibrariesManager.hxx index 51585acc0..b842e42c0 100644 --- a/include/MGIS/LibrariesManager.hxx +++ b/include/MGIS/LibrariesManager.hxx @@ -27,7 +27,7 @@ #endif /* (defined _WIN32 || defined _WIN64) && (!defined __CYGWIN__) */ #include "MGIS/Config.hxx" -#include "MGIS/MaterialProperty/MaterialPropertyFctPtr.hxx" +#include "MGIS/MaterialProperty/BatchMaterialPropertyFctPtr.hxx" #include "MGIS/Behaviour/FiniteStrainBehaviourOptions.hxx" #include "MGIS/Behaviour/BehaviourFctPtr.hxx" #include "MGIS/Behaviour/Hypothesis.hxx" @@ -91,6 +91,49 @@ namespace mgis { * \param[in] n: entry point name */ std::string getInterface(const std::string &, const std::string &); + /*! + * \return true if the given entry point provides the functions of + * batches of points of the `generic-parallel` interface, i.e. if the + * library exports the version of those functions + * (`_generic_parallel_api_version` symbol) + * \param[in] l: library name + * \param[in] n: entry point name + */ + bool hasGenericParallelFunctions(const std::string &, const std::string &); + /*! + * \return the version of the functions of batches of points + * (`_generic_parallel_api_version` symbol) + * \param[in] l: library name + * \param[in] n: entry point name + */ + unsigned short getGenericParallelAPIVersion(const std::string &, + const std::string &); + /*! + * \return the backend of a library generated by the `generic-parallel` + * interface (`_generic_parallel_backend` symbol) + * \param[in] l: library name + * \param[in] n: entry point name + */ + std::string getGenericParallelBackend(const std::string &, + const std::string &); + /*! + * \return the memory space of the computations of a library generated by + * the `generic-parallel` interface: `host`, `device` or `any` + * (`_generic_parallel_memory_space` symbol) + * \param[in] l: library name + * \param[in] n: entry point name + */ + std::string getGenericParallelMemorySpace(const std::string &, + const std::string &); + /*! + * \return the identifier of the execution policy expected by a library + * generated by the `generic-parallel` interface + * (`_generic_parallel_execution_policy` symbol) + * \param[in] l: library name + * \param[in] n: entry point name + */ + std::string getGenericParallelExecutionPolicy(const std::string &, + const std::string &); /*! * \return the author of the `MFront` file * \param[in] l: library name @@ -129,6 +172,26 @@ namespace mgis { */ mgis::material_property::MaterialPropertyFctPtr getMaterialProperty( const std::string &, const std::string &); + /*! + * \return the function evaluating a material property on a batch of + * points (`_generic_parallel` symbol of a library generated by the + * `generic-parallel` interface) + * \param[in] l: library name + * \param[in] mp: material property name + */ + mgis::material_property::BatchMaterialPropertyFctPtr + getBatchMaterialProperty(const std::string &, const std::string &); + /*! + * \return the function analysing the statistics of an evaluation of a + * material property on a batch of points + * (`_generic_parallel_report` symbol of a library generated by the + * `generic-parallel` interface) + * \param[in] l: library name + * \param[in] mp: material property name + */ + mgis::material_property::BatchReportFctPtr + getBatchMaterialPropertyReportFunction(const std::string &, + const std::string &); /*! * \return the name of the output of the given material property * \param[in] l: library name @@ -833,6 +896,16 @@ namespace mgis { * \param[in] n: symbol name */ bool contains(const std::string &, const std::string &); + /*! + * \brief throw an exception if the given entry point does not provide + * the functions of batches of points of the `generic-parallel` interface + * \param[in] m: calling method + * \param[in] l: library name + * \param[in] n: entry point name + */ + void checkGenericParallelFunctions(const char *const, + const std::string &, + const std::string &); /*! * \return the adress of the given symbol. If no symbol is found, a * null pointer is returned. diff --git a/include/MGIS/MaterialProperty/BatchMaterialPropertyFctPtr.hxx b/include/MGIS/MaterialProperty/BatchMaterialPropertyFctPtr.hxx new file mode 100644 index 000000000..5e851a6c3 --- /dev/null +++ b/include/MGIS/MaterialProperty/BatchMaterialPropertyFctPtr.hxx @@ -0,0 +1,238 @@ +/*! + * \file include/MGIS/MaterialProperty/BatchMaterialPropertyFctPtr.hxx + * \brief This file declares the type of the functions generated by the + * `generic-parallel` interface of `MFront` for material properties. + * \date 08/10/2026 + * \copyright 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_MATERIALPROPERTY_BATCHMATERIALPROPERTYFCTPTR_HXX +#define LIB_MGIS_MATERIALPROPERTY_BATCHMATERIALPROPERTYFCTPTR_HXX + +#include "MGIS/MaterialProperty/MaterialPropertyFctPtr.hxx" +#include "MGIS/BackendExecutionPolicy.h" + +/*! + * \brief version of the functions generated by the `generic-parallel` + * interface for material properties supported by MGIS. It is compared to + * the version exported by the libraries + * (`_generic_parallel_api_version` symbol) when a material property + * is loaded: any other version is refused. This version and the structures + * declared in this file are copies of the ones declared in the + * `MFront/GenericParallelMaterialProperty/MaterialProperty.h` header of + * `TFEL`, with the same members in the same order, so that `MGIS` does not + * depend on `TFEL` at compile time. + */ +#define MGIS_MP_GENERIC_PARALLEL_API_VERSION 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*! + * \brief statistics of the evaluation of a material property on a batch of + * points (copy of the `mfront_gpmp_Statistics` structure of `TFEL`). The + * device only updates them when an event occurs at a point (the `stlpar` + * backend merges the statuses of the points by a reduction on the host). They + * are initialized by `mgis_mp_initializeBatchStatistics` and analysed by the + * report function of the material property, on the host: they must be + * accessible by the host and by the device (see `mgis_mp_BatchData`). + */ +typedef struct mgis_mp_BatchStatistics { + /*! + * \brief number of points where the evaluation failed (physical bound + * violated, bound violated with the strict policy, invalid result) + */ + unsigned long long number_of_failures; + /*! + * \brief opaque key of the first point where the evaluation failed, or + * `~0` if no point failed. Only the report function of the material + * property decodes it. + */ + unsigned long long first_failure; + //! \brief number of points where a bound is violated with the warning + //! policy + unsigned long long number_of_unreliable_results; + /*! + * \brief opaque key of the first point where the result is unreliable, or + * `~0` if no result is unreliable + */ + unsigned long long first_unreliable_result; +} mgis_mp_BatchStatistics; + +/*! + * \brief initialize the statistics of an evaluation (copy of the + * `mfront_gpmp_initializeStatistics` function of `TFEL`) + * \param[out] s: statistics + */ +static inline void mgis_mp_initializeBatchStatistics( + mgis_mp_BatchStatistics* const s) { + s->number_of_failures = 0; + s->first_failure = ~0ull; + s->number_of_unreliable_results = 0; + s->first_unreliable_result = ~0ull; +} /* end of mgis_mp_initializeBatchStatistics */ + +/*! + * \brief values of an argument on a batch of points (copy of the + * `mfront_gpmp_ConstField` structure of `TFEL`): the value at the `i`-th + * point is `values[i * point_stride]` + */ +typedef struct mgis_mp_BatchConstField { + //! \brief pointer to the first value + const mgis_real* values; + //! \brief stride between two points: a null stride denotes a uniform value + mgis_size_type point_stride; +} mgis_mp_BatchConstField; + +/*! + * \brief values of the output on a batch of points (copy of the + * `mfront_gpmp_Field` structure of `TFEL`) + */ +typedef struct mgis_mp_BatchField { + //! \brief pointer to the first value + mgis_real* values; + //! \brief stride between two points (null for a batch of one point only) + mgis_size_type point_stride; +} mgis_mp_BatchField; + +/*! + * \brief data of the evaluation of a material property on a batch of + * points (copy of the `mfront_gpmp_BatchData` structure of `TFEL`). The + * array of the arguments is read on the host. + */ +typedef struct mgis_mp_BatchData { + //! \brief number of points + mgis_size_type number_of_points; + //! \brief output values + mgis_mp_BatchField output; + //! \brief arguments, read on the host (null if there is no argument) + const mgis_mp_BatchConstField* arguments; + //! \brief number of arguments + mgis_size_type number_of_arguments; + //! \brief out of bounds policy + mgis_mp_OutOfBoundsPolicy out_of_bounds_policy; + //! \brief status of each point, or null + int* statuses; + /*! + * \brief statistics of the evaluation, or null. If given, the output + * status only reports the errors of the batch, and the report function + * gives the result once the computations are completed. Asynchronous + * evaluations require statistics. The statistics must be accessible by + * the host and by the device where the computations are performed + * (managed or shared memory if the memory space of the library is + * `device`). + */ + mgis_mp_BatchStatistics* statistics; + /*! + * \brief execution policy of the backend of the library, or null for the + * default policy of the backend (see `MGIS/BackendExecutionPolicy.h`) + */ + const mgis_gp_AbstractBackendExecutionPolicy* execution_policy; +} mgis_mp_BatchData; + +/*! + * \brief summary of the evaluation of a material property on a batch of + * points (copy of the `mfront_gpmp_OutputStatus` structure of `TFEL`). The + * `status` member is also the value returned by the functions: + * + * - `0`: the result has been evaluated at all points; + * - `1`: a bound is violated with the warning policy at some points; + * - `-1`: a physical bound, or a bound with the strict policy, is violated + * at some points (the output is `NaN` at those points); + * - `-4`: the result is not finite at some points; + * - `-5`: invalid arguments, nothing was computed; + * - `-6`: invalid parameters, nothing was computed; + * - `-7`: runtime error of the backend. + * + * The codes `0`, `1`, `-1` and `-4` are the codes of the `generic` + * interface at the reported point: the first point where the evaluation + * failed, otherwise the first point where the result is unreliable. + */ +typedef struct mgis_mp_BatchOutputStatus { + //! \brief status + int status; + //! \brief number of points where the evaluation failed + mgis_size_type number_of_failures; + /*! + * \brief index of the first point where the evaluation failed, or the + * maximal value of the type if no point failed + */ + mgis_size_type first_failure_index; + //! \brief number of points where a bound is violated with the warning + //! policy + mgis_size_type number_of_unreliable_results; + /*! + * \brief index of the first point where the result is unreliable, or the + * maximal value of the type if no result is unreliable + */ + mgis_size_type first_unreliable_result_index; + /*! + * \brief rank of the variable of the check retained at the reported point + * for the statuses `-1` and `1` (see `mgis_mp_OutputStatus`), zero + * otherwise + */ + int bounds_status; + //! \brief error message + char msg[512]; +} mgis_mp_BatchOutputStatus; + +/*! + * \brief type of a function evaluating a material property on a batch of + * points (`_generic_parallel` function) + */ +typedef int(MGIS_MATERIALPROPERTY_ADDCALL_PTR + mgis_mp_BatchMaterialPropertyFctPtr)( + mgis_mp_BatchOutputStatus* const, const mgis_mp_BatchData* const); + +/*! + * \brief type of the function analysing the statistics of an evaluation + * of a material property, once the evaluation is completed + * (`_generic_parallel_report` function), and reporting the result in + * an output status, as a synchronous evaluation does + */ +typedef int(MGIS_MATERIALPROPERTY_ADDCALL_PTR mgis_mp_BatchReportFctPtr)( + mgis_mp_BatchOutputStatus* const, const mgis_mp_BatchStatistics* const); + +#ifdef __cplusplus +} // end of extern "C" +#endif /* __cplusplus */ + +#ifdef __cplusplus + +namespace mgis::material_property { + + //! \brief a simple alias + using BatchStatistics = ::mgis_mp_BatchStatistics; + //! \brief a simple alias + using BatchConstField = ::mgis_mp_BatchConstField; + //! \brief a simple alias + using BatchField = ::mgis_mp_BatchField; + //! \brief a simple alias + using BatchData = ::mgis_mp_BatchData; + //! \brief a simple alias + using BatchOutputStatus = ::mgis_mp_BatchOutputStatus; + //! \brief a simple alias + using BatchMaterialPropertyFctPtr = ::mgis_mp_BatchMaterialPropertyFctPtr; + //! \brief a simple alias + using BatchReportFctPtr = ::mgis_mp_BatchReportFctPtr; + + /*! + * \brief initialize the statistics of an evaluation (see + * `mgis_mp_initializeBatchStatistics`) + * \param[out] s: statistics + */ + inline void initializeBatchStatistics(BatchStatistics& s) noexcept { + mgis_mp_initializeBatchStatistics(&s); + } // end of initializeBatchStatistics + +} // end of namespace mgis::material_property + +#endif /* __cplusplus */ + +#endif /* LIB_MGIS_MATERIALPROPERTY_BATCHMATERIALPROPERTYFCTPTR_HXX */ diff --git a/include/MGIS/MaterialProperty/MaterialProperty.hxx b/include/MGIS/MaterialProperty/MaterialProperty.hxx index 71c4cbb7e..900a56440 100644 --- a/include/MGIS/MaterialProperty/MaterialProperty.hxx +++ b/include/MGIS/MaterialProperty/MaterialProperty.hxx @@ -19,6 +19,7 @@ #include #include "MGIS/Config.hxx" #include "MGIS/MaterialProperty/MaterialPropertyFctPtr.hxx" +#include "MGIS/MaterialProperty/BatchMaterialPropertyFctPtr.hxx" namespace mgis::material_property { @@ -51,14 +52,73 @@ namespace mgis::material_property { std::string tfel_version; //! \brief unit system used by the material property std::string unit_system; - //! \brief pointer to the function implementing the material property + /*! + * \brief pointer to the function implementing the material property at + * one point, null if the library has no such function (material property + * generated by the `generic-parallel` interface) + */ MaterialPropertyFctPtr fct = nullptr; //! \brief output of the material property std::string output; //! \brief inputs of the material property std::vector inputs; + /*! + * \brief interface of `MFront` used to generate the material property + * (`_mfront_interface` symbol): `Generic`, `GenericParallel` or + * empty for the libraries which do not export it. This member is not + * named `interface`, which is a macro of the headers of Windows. + */ + std::string mfront_interface; + /*! + * \brief version of the functions of batches of points exported by the + * library (`_generic_parallel_api_version` symbol), zero if the + * library has no such functions + */ + unsigned short batch_api_version = 0; + /*! + * \brief backend of a library generated by the `generic-parallel` + * interface (for instance `cuda`, `hip`, `stlpar` or `sycl`), for + * information only (see `memory_space`), empty for the other interfaces + */ + std::string backend; + /*! + * \brief memory space of the computations of a library generated by the + * `generic-parallel` interface: `host`, `device` or `any`. The values + * and the statuses of the points given to the functions of batches of + * points must be accessible in this memory space, and the statistics by + * the host and in this memory space (see `mgis_mp_BatchData`). Empty for + * the other interfaces, which compute on the host. + */ + std::string memory_space; + /*! + * \brief identifier of the execution policy expected by a library + * generated by the `generic-parallel` interface (for instance `cuda` or + * `sycl:oneapi`): a policy given to the functions of batches of points + * must have this identifier. Empty for the other interfaces. + */ + std::string execution_policy; + /*! + * \brief pointer to the function implementing the material property on + * a batch of points, null if the library has no such function + */ + BatchMaterialPropertyFctPtr batch_fct = nullptr; + /*! + * \brief pointer to the function analysing the statistics of an + * evaluation on a batch of points, null if the library has no such + * function + */ + BatchReportFctPtr batch_report_fct = nullptr; }; // end of struct MaterialProperty + /*! + * \return true if the library provides the functions evaluating the + * material property on batches of points (`generic-parallel` interface of + * `MFront`) + * \param[in] mp: material property + */ + MGIS_EXPORT bool isGenericParallelMaterialProperty( + const MaterialProperty &) noexcept; + /*! * \brief load the description of a material property from a library * diff --git a/src/LibrariesManager.cxx b/src/LibrariesManager.cxx index 2f9d61cd2..df0baaa07 100644 --- a/src/LibrariesManager.cxx +++ b/src/LibrariesManager.cxx @@ -199,6 +199,21 @@ namespace mgis { mgis::material_property::MaterialPropertyFctPtr LibrariesManager::getMaterialProperty(const std::string &l, const std::string &mp) { + if (this->getInterface(l, mp) == "GenericParallel") { + if (!this->hasGenericParallelFunctions(l, mp)) { + mgis::raise( + "LibrariesManager::getMaterialProperty: material property '" + mp + + "' in library '" + l + + "' has been generated by a version of the generic-parallel " + "interface which is not supported (no symbol '" + + mp + "_generic_parallel_api_version')"); + } + mgis::raise( + "LibrariesManager::getMaterialProperty: material property '" + mp + + "' in library '" + l + + "' has been generated by the generic-parallel interface, which " + "provides no function evaluating it at one point"); + } const auto p = this->getSymbolAddress(l, mp); if (p == nullptr) { mgis::raise( @@ -209,6 +224,37 @@ namespace mgis { return reinterpret_cast(p); } // end of getMaterialProperty + mgis::material_property::BatchMaterialPropertyFctPtr + LibrariesManager::getBatchMaterialProperty(const std::string &l, + const std::string &mp) { + this->checkGenericParallelFunctions( + "LibrariesManager::getBatchMaterialProperty", l, mp); + const auto p = this->getSymbolAddress(l, mp + "_generic_parallel"); + if (p == nullptr) { + mgis::raise( + "LibrariesManager::getBatchMaterialProperty: can't load material " + "property '" + + mp + "' in library '" + l + "'"); + } + return reinterpret_cast< + mgis::material_property::BatchMaterialPropertyFctPtr>(p); + } // end of getBatchMaterialProperty + + mgis::material_property::BatchReportFctPtr + LibrariesManager::getBatchMaterialPropertyReportFunction( + const std::string &l, const std::string &mp) { + this->checkGenericParallelFunctions( + "LibrariesManager::getBatchMaterialPropertyReportFunction", l, mp); + const auto p = this->getSymbolAddress(l, mp + "_generic_parallel_report"); + if (p == nullptr) { + mgis::raise( + "LibrariesManager::getBatchMaterialPropertyReportFunction: can't " + "load the report function of material property '" + + mp + "' in library '" + l + "'"); + } + return reinterpret_cast(p); + } // end of getBatchMaterialPropertyReportFunction + std::vector LibrariesManager::getMaterialPropertyInputsNames( const std::string &l, const std::string &mp) { return this->getNames(l, mp, "args"); @@ -661,6 +707,46 @@ namespace mgis { return *(static_cast(p)); } // end of getInterface + bool LibrariesManager::hasGenericParallelFunctions(const std::string &l, + const std::string &n) { + return this->contains(l, n + "_generic_parallel_api_version"); + } // end of hasGenericParallelFunctions + + void LibrariesManager::checkGenericParallelFunctions(const char *const m, + const std::string &l, + const std::string &n) { + if (!this->hasGenericParallelFunctions(l, n)) { + mgis::raise(std::string{m} + ": entry point '" + n + "' in library '" + + l + + "' has not been generated by the generic-parallel " + "interface (no symbol '" + + n + "_generic_parallel_api_version')"); + } + } // end of checkGenericParallelFunctions + + unsigned short LibrariesManager::getGenericParallelAPIVersion( + const std::string &l, const std::string &n) { + return *( + this->extract(l, n + "_generic_parallel_api_version")); + } // end of getGenericParallelAPIVersion + + std::string LibrariesManager::getGenericParallelBackend( + const std::string &l, const std::string &n) { + return *(this->extract(l, n + "_generic_parallel_backend")); + } // end of getGenericParallelBackend + + std::string LibrariesManager::getGenericParallelMemorySpace( + const std::string &l, const std::string &n) { + return *( + this->extract(l, n + "_generic_parallel_memory_space")); + } // end of getGenericParallelMemorySpace + + std::string LibrariesManager::getGenericParallelExecutionPolicy( + const std::string &l, const std::string &n) { + return *(this->extract( + l, n + "_generic_parallel_execution_policy")); + } // end of getGenericParallelExecutionPolicy + unsigned short LibrariesManager::getBehaviourType(const std::string &l, const std::string &b) { return *(this->extract(l, b + "_BehaviourType")); diff --git a/src/MaterialProperty.cxx b/src/MaterialProperty.cxx index 3740e1014..2a0c8a872 100644 --- a/src/MaterialProperty.cxx +++ b/src/MaterialProperty.cxx @@ -12,6 +12,8 @@ * CeCILL-C_V1-en.txt and CeCILL-C_V1-fr.txt). */ +#include +#include "MGIS/Raise.hxx" #include "MGIS/LibrariesManager.hxx" #include "MGIS/MaterialProperty/MaterialProperty.hxx" @@ -27,16 +29,47 @@ namespace mgis::material_property { MaterialProperty load(const std::string &l, const std::string &mp) { auto &lm = mgis::LibrariesManager::get(); + auto raise_if = [&l, &mp](const bool c, const std::string &msg) { + if (c) { + mgis::raise("load: " + msg + + ".\nError while trying to load material property '" + mp + + "' in library '" + l + "'\n"); + } + }; auto d = MaterialProperty{}; d.library = l; d.material_property = mp; d.tfel_version = lm.getTFELVersion(l, mp); d.unit_system = lm.getUnitSystem(l, mp); d.source = lm.getSource(l, mp); - d.fct = lm.getMaterialProperty(l, mp); + d.mfront_interface = lm.getInterface(l, mp); + if (lm.hasGenericParallelFunctions(l, mp)) { + // functions of batches of points (`generic-parallel` interface) + d.batch_api_version = lm.getGenericParallelAPIVersion(l, mp); + raise_if(d.batch_api_version != MGIS_MP_GENERIC_PARALLEL_API_VERSION, + "unmatched version of the functions of the generic-parallel " + "interface (the material property uses version " + + std::to_string(d.batch_api_version) + + ", mgis uses version " + + std::to_string(MGIS_MP_GENERIC_PARALLEL_API_VERSION) + ")"); + d.backend = lm.getGenericParallelBackend(l, mp); + d.memory_space = lm.getGenericParallelMemorySpace(l, mp); + raise_if((d.memory_space != "host") && (d.memory_space != "device") && + (d.memory_space != "any"), + "invalid memory space '" + d.memory_space + "'"); + d.execution_policy = lm.getGenericParallelExecutionPolicy(l, mp); + d.batch_fct = lm.getBatchMaterialProperty(l, mp); + d.batch_report_fct = lm.getBatchMaterialPropertyReportFunction(l, mp); + } else { + d.fct = lm.getMaterialProperty(l, mp); + } d.output = lm.getMaterialPropertyOutputName(l, mp); d.inputs = lm.getMaterialPropertyInputsNames(l, mp); return d; - } // end of load_behaviour + } // end of load + + bool isGenericParallelMaterialProperty(const MaterialProperty &mp) noexcept { + return mp.batch_fct != nullptr; + } // end of isGenericParallelMaterialProperty } // end of namespace mgis::material_property diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index afebc0c81..b0b012a3c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -113,6 +113,22 @@ if(MGIS_HAVE_MFRONT_SUPPORT) MaterialPropertyErrnoCheck2 Inconel600_YoungModulus) + # material properties generated by the `generic-parallel` interface + # (TFEL 5.2 or later) with the `stlpar` backend, which requires the + # parallel algorithms of the standard library + if(enable-parallel-stl-algorithms AND + EXISTS "${TFEL_INCLUDE_PATH}/MFront/GenericParallelMaterialProperty/MaterialProperty.h") + set(MGIS_HAVE_GENERIC_PARALLEL_MATERIAL_PROPERTIES ON) + mfront_generic_parallel_material_properties_check_library( + GenericParallelMaterialPropertyTest stlpar + MaterialPropertyBoundCheck) + add_library(GenericParallelInvalidMaterialProperties SHARED EXCLUDE_FROM_ALL + GenericParallelInvalidMaterialProperties.cxx) + target_link_libraries(GenericParallelInvalidMaterialProperties + PRIVATE MFrontGenericInterface) + add_dependencies(build-tests GenericParallelInvalidMaterialProperties) + endif() + mfront_behaviours_check_library( BehaviourTest Elasticity @@ -137,6 +153,22 @@ if(MGIS_HAVE_MFRONT_SUPPORT) # Tests that depend on behaviour libraries add_library_test_with_tfel_version(LoadMaterialPropertyTest LoadMaterialPropertyTest.cxx MaterialPropertyTest) + if(MGIS_HAVE_GENERIC_PARALLEL_MATERIAL_PROPERTIES) + add_executable(LoadGenericParallelMaterialPropertyTest EXCLUDE_FROM_ALL + LoadGenericParallelMaterialPropertyTest.cxx) + target_link_libraries(LoadGenericParallelMaterialPropertyTest + PRIVATE MFrontGenericInterface) + # the headers of TFEL are only used to check the copies of its types + target_include_directories(LoadGenericParallelMaterialPropertyTest + PRIVATE "${TFEL_INCLUDE_PATH}") + add_test(NAME LoadGenericParallelMaterialPropertyTest + COMMAND LoadGenericParallelMaterialPropertyTest + "$" + "$" + "$") + add_dependencies(build-tests LoadGenericParallelMaterialPropertyTest) + set_test_environment(LoadGenericParallelMaterialPropertyTest) + endif() # Tests with TFEL_VERSION add_tfel_version_test(MFrontGenericBehaviourInterfaceTest diff --git a/tests/GenericParallelInvalidMaterialProperties.cxx b/tests/GenericParallelInvalidMaterialProperties.cxx new file mode 100644 index 000000000..6a9311c02 --- /dev/null +++ b/tests/GenericParallelInvalidMaterialProperties.cxx @@ -0,0 +1,49 @@ +/*! + * \file tests/GenericParallelInvalidMaterialProperties.cxx + * \brief This file exports the symbols of material properties of the + * `generic-parallel` interface which `MGIS` must refuse: functions of a + * version which is not supported, invalid memory space, library generated + * by a version of the interface which does not export the version of its + * functions. No function is defined: those material properties are refused + * before any function is loaded. + * \date 24/10/2026 + * \copyright 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 "MGIS/Config-c.h" +#include "MGIS/MaterialProperty/BatchMaterialPropertyFctPtr.hxx" + +extern "C" { + +//! \brief version of the functions which is not supported +MGIS_VISIBILITY_EXPORT const char* UnsupportedVersion_mfront_interface = + "GenericParallel"; +MGIS_VISIBILITY_EXPORT unsigned short + UnsupportedVersion_generic_parallel_api_version = + MGIS_MP_GENERIC_PARALLEL_API_VERSION + 1; +MGIS_VISIBILITY_EXPORT const char* UnsupportedVersion_generic_parallel_backend = + "stlpar"; +MGIS_VISIBILITY_EXPORT const char* + UnsupportedVersion_generic_parallel_memory_space = "host"; + +//! \brief invalid memory space +MGIS_VISIBILITY_EXPORT const char* InvalidMemorySpace_mfront_interface = + "GenericParallel"; +MGIS_VISIBILITY_EXPORT unsigned short + InvalidMemorySpace_generic_parallel_api_version = + MGIS_MP_GENERIC_PARALLEL_API_VERSION; +MGIS_VISIBILITY_EXPORT const char* InvalidMemorySpace_generic_parallel_backend = + "stlpar"; +MGIS_VISIBILITY_EXPORT const char* + InvalidMemorySpace_generic_parallel_memory_space = "nowhere"; + +//! \brief no symbol giving the version of the functions +MGIS_VISIBILITY_EXPORT const char* NoVersion_mfront_interface = + "GenericParallel"; + +} // end of extern "C" diff --git a/tests/LoadGenericParallelMaterialPropertyTest.cxx b/tests/LoadGenericParallelMaterialPropertyTest.cxx new file mode 100644 index 000000000..8f0e0f14d --- /dev/null +++ b/tests/LoadGenericParallelMaterialPropertyTest.cxx @@ -0,0 +1,188 @@ +/*! + * \file tests/LoadGenericParallelMaterialPropertyTest.cxx + * \brief This file tests the loading of material properties generated by + * the `generic-parallel` interface of `MFront`, and checks that the types + * declared by `MGIS` are copies of the ones declared by `TFEL`. + * + * usage: LoadGenericParallelMaterialPropertyTest generic_parallel_library + * generic_library invalid_library + * \date 24/10/2026 + * \copyright 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 +#include +#include +#include +#include +#include +#include +#include +#include "MGIS/MaterialProperty/MaterialProperty.hxx" +#include "MFront/GenericParallelMaterialProperty/MaterialProperty.h" + +// copies of the types of `TFEL`: same version, same sizes, same members at +// the same offsets +static_assert(MGIS_MP_GENERIC_PARALLEL_API_VERSION == MFRONT_GPMP_API_VERSION); +static_assert(std::is_same_v); +static_assert(sizeof(mgis_size_type) == sizeof(mfront_gmp_size_type)); +static_assert(std::is_same_v); +static_assert(static_cast(MGIS_MATERIALPROPERTY_NONE_POLICY) == + static_cast(GENERIC_MATERIALPROPERTY_NONE_POLICY)); +static_assert(static_cast(MGIS_MATERIALPROPERTY_WARNING_POLICY) == + static_cast(GENERIC_MATERIALPROPERTY_WARNING_POLICY)); +static_assert(static_cast(MGIS_MATERIALPROPERTY_STRICT_POLICY) == + static_cast(GENERIC_MATERIALPROPERTY_STRICT_POLICY)); +#define MGIS_CHECK_SIZE(T1, T2) \ + static_assert(sizeof(mgis_mp_##T1) == sizeof(mfront_gpmp_##T2)) +#define MGIS_CHECK_MEMBER(T1, T2, m) \ + static_assert(offsetof(mgis_mp_##T1, m) == offsetof(mfront_gpmp_##T2, m)) +MGIS_CHECK_SIZE(BatchStatistics, Statistics); +MGIS_CHECK_MEMBER(BatchStatistics, Statistics, number_of_failures); +MGIS_CHECK_MEMBER(BatchStatistics, Statistics, first_failure); +MGIS_CHECK_MEMBER(BatchStatistics, Statistics, number_of_unreliable_results); +MGIS_CHECK_MEMBER(BatchStatistics, Statistics, first_unreliable_result); +MGIS_CHECK_SIZE(BatchConstField, ConstField); +MGIS_CHECK_MEMBER(BatchConstField, ConstField, values); +MGIS_CHECK_MEMBER(BatchConstField, ConstField, point_stride); +MGIS_CHECK_SIZE(BatchField, Field); +MGIS_CHECK_MEMBER(BatchField, Field, values); +MGIS_CHECK_MEMBER(BatchField, Field, point_stride); +MGIS_CHECK_SIZE(BatchData, BatchData); +MGIS_CHECK_MEMBER(BatchData, BatchData, number_of_points); +MGIS_CHECK_MEMBER(BatchData, BatchData, output); +MGIS_CHECK_MEMBER(BatchData, BatchData, arguments); +MGIS_CHECK_MEMBER(BatchData, BatchData, number_of_arguments); +MGIS_CHECK_MEMBER(BatchData, BatchData, out_of_bounds_policy); +MGIS_CHECK_MEMBER(BatchData, BatchData, statuses); +MGIS_CHECK_MEMBER(BatchData, BatchData, statistics); +MGIS_CHECK_MEMBER(BatchData, BatchData, execution_policy); +MGIS_CHECK_SIZE(BatchOutputStatus, OutputStatus); +MGIS_CHECK_MEMBER(BatchOutputStatus, OutputStatus, status); +MGIS_CHECK_MEMBER(BatchOutputStatus, OutputStatus, number_of_failures); +MGIS_CHECK_MEMBER(BatchOutputStatus, OutputStatus, first_failure_index); +MGIS_CHECK_MEMBER(BatchOutputStatus, + OutputStatus, + number_of_unreliable_results); +MGIS_CHECK_MEMBER(BatchOutputStatus, + OutputStatus, + first_unreliable_result_index); +MGIS_CHECK_MEMBER(BatchOutputStatus, OutputStatus, bounds_status); +MGIS_CHECK_MEMBER(BatchOutputStatus, OutputStatus, msg); + +static bool success = true; + +static bool check(const bool b, const std::string_view msg) { + if (!b) { + success = false; + std::cerr << msg << '\n'; + } + return b; +} + +//! \brief description and evaluation of a material property on a batch +static void test1(const std::string& gp_library, + const std::string& generic_library) { + using namespace mgis; + using namespace mgis::material_property; + const auto mp = load(gp_library, "MaterialPropertyBoundCheck"); + check(isGenericParallelMaterialProperty(mp), "batch functions expected"); + check(mp.fct == nullptr, "no function of a point expected"); + check(mp.batch_report_fct != nullptr, "report function expected"); + check(mp.mfront_interface == "GenericParallel", "invalid interface"); + check(mp.batch_api_version == MGIS_MP_GENERIC_PARALLEL_API_VERSION, + "invalid version"); + check(mp.backend == "stlpar", "invalid backend"); + check(mp.memory_space == "host", "invalid memory space"); + check(mp.execution_policy == "stlpar", "invalid execution policy"); + check(mp.output == "YoungModulus", "invalid output"); + check((mp.inputs.size() == 1) && (mp.inputs[0] == "Temperature"), + "invalid inputs"); + // the third temperature is out of the bounds [200:300] + const auto T = std::array{250, 280, 100, 300}; + auto E = std::array{}; + auto statuses = std::array{12, 12, 12, 12}; + const auto args = std::array{ + BatchConstField{.values = T.data(), .point_stride = 1}}; + auto d = + BatchData{.number_of_points = 4, + .output = BatchField{.values = E.data(), .point_stride = 1}, + .arguments = args.data(), + .number_of_arguments = 1, + .out_of_bounds_policy = MGIS_MATERIALPROPERTY_NONE_POLICY, + .statuses = statuses.data(), + .statistics = nullptr, + .execution_policy = nullptr}; + // same values as the function of the `generic` interface + const auto g = load(generic_library, "MaterialPropertyBoundCheck"); + check(!isGenericParallelMaterialProperty(g), "no batch functions expected"); + check((g.fct != nullptr) && (g.batch_api_version == 0) && g.backend.empty(), + "invalid description of a material property of the generic interface"); + auto s = BatchOutputStatus{}; + check(mp.batch_fct(&s, &d) == 0, "evaluation failed"); + check(s.status == 0, "invalid output status"); + for (std::size_t i = 0; i != T.size(); ++i) { + auto gs = OutputStatus{}; + const auto Eg = g.fct(&gs, &T[i], 1, MGIS_MATERIALPROPERTY_NONE_POLICY); + check(std::abs(E[i] - Eg) <= 1e-14 * std::abs(Eg), + "invalid value at point " + std::to_string(i)); + check(statuses[i] == gs.status, + "invalid status at point " + std::to_string(i)); + } + // strict policy, statistics given by the calling code: the evaluation + // only reports the errors of the batch and the report function gives the + // result + auto statistics = BatchStatistics{}; + initializeBatchStatistics(statistics); + d.out_of_bounds_policy = MGIS_MATERIALPROPERTY_STRICT_POLICY; + d.statistics = &statistics; + check(mp.batch_fct(&s, &d) == 0, "invalid status of the batch"); + const auto r = mp.batch_report_fct(&s, &statistics); + check(r < 0, "the evaluation shall fail at the third point"); + check((s.number_of_failures == 1) && (s.first_failure_index == 2), + "invalid report"); + check((statuses[0] == 0) && (statuses[1] == 0) && (statuses[2] < 0) && + (statuses[3] == 0), + "invalid statuses of the points"); +} + +//! \brief material properties which must be refused +static void test2(const std::string& library) { + using namespace mgis::material_property; + auto check_refused = [&library](const std::string& n, + const std::string_view expected) { + try { + load(library, n); + check(false, "material property '" + n + "' shall be refused"); + } catch (std::exception& e) { + check(std::string_view{e.what()}.find(expected) != std::string_view::npos, + "unexpected message for '" + n + "': " + e.what()); + } + }; + check_refused("UnsupportedVersion", "unmatched version"); + check_refused("InvalidMemorySpace", "invalid memory space 'nowhere'"); + check_refused("NoVersion", "is not supported"); +} + +int main(const int argc, const char* const* argv) { + if (argc != 4) { + std::cerr << "usage: " << argv[0] + << " generic_parallel_library generic_library invalid_library\n"; + return EXIT_FAILURE; + } + try { + test1(argv[1], argv[2]); + test2(argv[3]); + } catch (std::exception& e) { + std::cerr << e.what() << '\n'; + return EXIT_FAILURE; + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} // end of main