diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 059766fcb2..771b176366 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -28,6 +28,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Quest: Adds OMP support for fast GWN methods for STL/Triangulated STEP input and linearized NURBS Curve input. - Quest: Adds OMP supported, fast and accurate GWN method for NURBS curves and trimmed NURBS surfaces. - Klee: Adds an optional "center" parameter in scale operators that permits scaling relative to a custom center point. +- Bump: The `MergeMeshes` class was enhanced so it supports material-dependent/mixed Blueprint fields that are "element-associated". These fields contain per-material values for the materials in a zone. +- Bump: Added `axom::bump::views::dispatch_material_field()` function (and related functions) for creating a material view and a material-dependent or mixed field view. - Quest: `SamplingShaper` now supports selecting MFEM quadrature families for custom sample-point generation, including anisotropic per-direction sampling resolution on quadrilateral and hexahedral meshes. Quadrature type is selected via a new ``setQuadratureType`` method that accepts an enum value from ``mfem::Quadrature1D``. The number of samples in @@ -44,6 +46,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Inlet: Added the ability to have collections (array and dictionary) with variant user defined structures. ### Removed +- Bump: Removed `axom::bump::views::MultiBufferMaterialView`, which was a view type for an obsolete flavor of Blueprint matset. ### Deprecated - Core: Deprecates the pointer-based interface to linear-, quadratic- and cubic- polynomial solvers in favor of an ArrayView-based interface diff --git a/src/axom/bump/CMakeLists.txt b/src/axom/bump/CMakeLists.txt index d897086be7..03d0690291 100644 --- a/src/axom/bump/CMakeLists.txt +++ b/src/axom/bump/CMakeLists.txt @@ -45,6 +45,7 @@ set(bump_headers views/BasicIndexing.hpp views/dispatch_coordset.hpp views/dispatch_material.hpp + views/dispatch_material_field.hpp views/dispatch_rectilinear_topology.hpp views/dispatch_structured_topology.hpp views/dispatch_topology.hpp @@ -53,6 +54,7 @@ set(bump_headers views/dispatch_utilities.hpp views/ExplicitCoordsetView.hpp views/MaterialView.hpp + views/MixedFieldView.hpp views/NodeArrayView.hpp views/RectilinearCoordsetView.hpp views/Shapes.hpp diff --git a/src/axom/bump/ExtractZones.hpp b/src/axom/bump/ExtractZones.hpp index 91657ada3a..d68f282bef 100644 --- a/src/axom/bump/ExtractZones.hpp +++ b/src/axom/bump/ExtractZones.hpp @@ -41,7 +41,6 @@ class ExtractZones * * \param topoView The input topology view. * \param coordsetView The input coordset view. - * \param matsetView The input matset view. */ ExtractZones(const TopologyView &topoView, const CoordsetView &coordsetView) : m_topologyView(topoView) diff --git a/src/axom/bump/MergeMeshes.hpp b/src/axom/bump/MergeMeshes.hpp index ceea86a1bd..c24dec2aff 100644 --- a/src/axom/bump/MergeMeshes.hpp +++ b/src/axom/bump/MergeMeshes.hpp @@ -12,6 +12,7 @@ #include "axom/bump/views/Shapes.hpp" #include "axom/bump/views/dispatch_material.hpp" +#include "axom/bump/views/dispatch_material_field.hpp" #include "axom/bump/views/dispatch_unstructured_topology.hpp" #include "axom/bump/utilities/conduit_memory.hpp" #include "axom/bump/utilities/conduit_traits.hpp" @@ -112,7 +113,11 @@ class MergeMeshes { std::string m_topology; std::string m_association; + std::string m_matset; + int m_volume_dependent; int m_dtype; + int m_have_values; + int m_have_matset_values; std::vector m_components; }; @@ -223,7 +228,10 @@ class MergeMeshes * * \param inputs A vector of inputs to be merged. * \param n_options A node containing options. - * \param[out] output The node that will contain the merged mesh. + * \param[out] output The node that will contain the merged mesh. + * + * \note We merge matsets first (for derived classes) so we can use the merged matset + * when we merge mixed fields. */ void mergeInputs(const std::vector &inputs, const conduit::Node &n_options, @@ -231,8 +239,8 @@ class MergeMeshes { mergeCoordset(inputs, output); mergeTopology(inputs, n_options, output); - mergeFields(inputs, output); mergeMatset(inputs, output); + mergeFields(inputs, output); } /*! @@ -1168,6 +1176,7 @@ class MergeMeshes { // Make field information in case some inputs do not have the field. std::map fieldInfo; + const int InvalidVolumeDependent = -1; for(axom::IndexType i = 0; i < n; i++) { if(inputs[i].m_input->has_child("fields")) @@ -1176,21 +1185,57 @@ class MergeMeshes for(conduit::index_t c = 0; c < n_fields.number_of_children(); c++) { const conduit::Node &n_field = n_fields[c]; - const conduit::Node &n_values = n_field.fetch_existing("values"); FieldInformation fi; fi.m_topology = n_field.fetch_existing("topology").as_string(); fi.m_association = n_field.fetch_existing("association").as_string(); - if(n_values.number_of_children() > 0) + fi.m_matset = std::string(); + fi.m_volume_dependent = InvalidVolumeDependent; + fi.m_dtype = -1; + fi.m_have_values = 0; + fi.m_have_matset_values = 0; + + if(n_field.has_path("volume_dependent")) { - for(conduit::index_t comp = 0; comp < n_values.number_of_children(); comp++) + const auto &vd = n_field["volume_dependent"]; + if(vd.dtype().is_string()) + { + fi.m_volume_dependent = (vd.as_string() == "true") ? 1 : 0; + } + else if(vd.dtype().is_number()) { - fi.m_components.push_back(n_values[comp].name()); - fi.m_dtype = n_values[comp].dtype().id(); + fi.m_volume_dependent = (vd.to_int() != 0) ? 1 : 0; } } - else + + // If the field is a material field, save metadata. + if(n_field.has_path("matset") && n_field.has_path("matset_values")) { - fi.m_dtype = n_values.dtype().id(); + fi.m_matset = n_field["matset"].as_string(); + fi.m_have_matset_values = 1; + const conduit::Node &matset_values = n_field["matset_values"]; + fi.m_dtype = matset_values.dtype().is_object() ? matset_values[0].dtype().id() + : matset_values.dtype().id(); + + SLIC_ERROR_IF(fi.m_association == "vertex", + "Material fields with vertex centering are not supported."); + } + // If the field has values (all do except for some material fields), save metadata. + if(n_field.has_path("values")) + { + fi.m_have_values = 1; + const conduit::Node &n_values = n_field.fetch_existing("values"); + if(n_values.number_of_children() > 0) + { + for(conduit::index_t comp = 0; comp < n_values.number_of_children(); comp++) + { + fi.m_components.push_back(n_values[comp].name()); + fi.m_dtype = n_values[comp].dtype().id(); + } + } + else + { + fi.m_dtype = n_values.dtype().id(); + } } fieldInfo[n_field.name()] = fi; } @@ -1206,45 +1251,68 @@ class MergeMeshes conduit::Node &n_newField = n_newFields[it->first]; n_newField["association"] = it->second.m_association; n_newField["topology"] = it->second.m_topology; - conduit::Node &n_values = n_newField["values"]; + if(!it->second.m_matset.empty()) + { + n_newField["matset"] = it->second.m_matset; + } + if(it->second.m_volume_dependent != InvalidVolumeDependent) + { + n_newField["volume_dependent"] = ((it->second.m_volume_dependent == 1) ? "true" : "false"); + } + // Handle values if present if(it->second.m_components.empty()) { // Scalar - conduit::Node &n_values = n_newField["values"]; - n_values.set_allocator(conduitAllocatorId); - const std::string srcPath("fields/" + it->first + "/values"); - if(it->second.m_association == "element") - { - n_values.set(conduit::DataType(it->second.m_dtype, totalZones)); - copyZonal(inputs, n_values, srcPath); - } - else if(it->second.m_association == "vertex") + if(it->second.m_have_values) { - n_values.set(conduit::DataType(it->second.m_dtype, totalNodes)); - copyNodal(inputs, n_values, srcPath); + const std::string srcPath("fields/" + it->first + "/values"); + conduit::Node &n_values = n_newField["values"]; + n_values.set_allocator(conduitAllocatorId); + if(it->second.m_association == "element") + { + n_values.set(conduit::DataType(it->second.m_dtype, totalZones)); + copyZonal(inputs, n_values, srcPath); + } + else if(it->second.m_association == "vertex") + { + n_values.set(conduit::DataType(it->second.m_dtype, totalNodes)); + copyNodal(inputs, n_values, srcPath); + } } } else { // Vector - for(size_t ci = 0; ci < it->second.m_components.size(); ci++) + if(it->second.m_have_values) { - conduit::Node &n_comp = n_values[it->second.m_components[ci]]; - n_comp.set_allocator(conduitAllocatorId); - const std::string srcPath("fields/" + it->first + "/values/" + - it->second.m_components[ci]); - if(it->second.m_association == "element") - { - n_comp.set(conduit::DataType(it->second.m_dtype, totalZones)); - copyZonal(inputs, n_comp, srcPath); - } - else if(it->second.m_association == "vertex") + conduit::Node &n_values = n_newField["values"]; + for(size_t ci = 0; ci < it->second.m_components.size(); ci++) { - n_comp.set(conduit::DataType(it->second.m_dtype, totalNodes)); - copyNodal(inputs, n_comp, srcPath); + const std::string srcPath("fields/" + it->first + "/values/" + + it->second.m_components[ci]); + conduit::Node &n_comp = n_values[it->second.m_components[ci]]; + n_comp.set_allocator(conduitAllocatorId); + if(it->second.m_association == "element") + { + n_comp.set(conduit::DataType(it->second.m_dtype, totalZones)); + copyZonal(inputs, n_comp, srcPath); + } + else if(it->second.m_association == "vertex") + { + n_comp.set(conduit::DataType(it->second.m_dtype, totalNodes)); + copyNodal(inputs, n_comp, srcPath); + } } } } + // Handle matset_values if present. + if(it->second.m_have_matset_values) + { + // We make a path to the whole source field because mixed fields have + // different representations depending on the matset. + const std::string matSrcPath("fields/" + it->first); + copyMixedField(inputs, n_newField, matSrcPath); + } } } } @@ -1407,6 +1475,20 @@ class MergeMeshes // Do nothing. } + /*! + * \brief Merge a mixed field that exists on the various mesh inputs. + * + * \param inputs A vector of inputs to be merged. + * \param[out] n_field The new field that we're creating. + * \param srcPath The path to the source input's "matset_values" node. + */ + virtual void copyMixedField(const std::vector &AXOM_UNUSED_PARAM(inputs), + conduit::Node &AXOM_UNUSED_PARAM(n_field), + const std::string &AXOM_UNUSED_PARAM(srcPath)) const + { + // Do nothing. + } + int m_allocator_id; }; @@ -1467,6 +1549,25 @@ class DispatchAnyMatset { axom::bump::views::dispatch_material(n_matset, [&](auto matsetView) { func(matsetView); }); } + + /*! + * \brief Takes a matset node and a mixed field node and turns them into a matset view, + * using the mixed field as the volume fractions, and sends the view to the supplied + * function. + * + * \tparam FuncType A callable object/function/lambda. + * + * \param n_matset A node containing any kind of matset. + * \param func The function to invoke on the matset view. + */ + template + void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) + { + axom::bump::views::dispatch_material_field( + n_matset, + n_field, + [&](auto matsetView, auto mixedFieldView) { func(matsetView, mixedFieldView); }); + } }; /*! @@ -1523,6 +1624,25 @@ class DispatchTypedUnibufferMatset views::make_unibuffer_matset::view(n_matset); func(matsetView); } + + /*! + * \brief Takes a matset node and a mixed field node and turns them into a matset view, + * using the mixed field as the volume fractions, and sends the view to the supplied + * function. + * + * \tparam FuncType A callable object/function/lambda. + * + * \param n_matset A node containing any kind of matset. + * \param func The function to invoke on the matset view. + */ + template + void dispatchMixedField(conduit::Node &n_matset, conduit::Node &n_field, FuncType &&func) + { + axom::bump::views::dispatch_material_unibuffer_field( + n_matset, + n_field, + [&](auto matsetView, auto mixedFieldView) { func(matsetView, mixedFieldView); }); + } }; /*! @@ -1543,112 +1663,173 @@ class MergeMeshesAndMatsets : public MergeMeshes #if !defined(__CUDACC__) private: #endif + using FieldInformation = typename MergeMeshes::FieldInformation; + + struct MaterialInfo + { + bool hasMatsets; + bool defaultMaterial; + bool elementDominant; + bool multiBuffer; + int nmats; + std::map allMats; + std::string matsetName; + std::string topoName; + axom::IndexType totalZones; + axom::IndexType totalMatCount; + int itype; + int ftype; + }; + /*! - * \brief Merge matsets that exist on the various mesh inputs. + * \brief Get the material information we'll need to merge materials. * - * \param inputs A vector of inputs to be merged. - * \param[out] output The node that will contain the output mesh. + * \param inputs The inputs to be merged. + * \param[out] mi The material information. */ - virtual void mergeMatset(const std::vector &inputs, conduit::Node &output) const override + void getMaterialInfo(const std::vector &inputs, MaterialInfo &mi) const { - AXOM_ANNOTATE_SCOPE("mergeMatset"); - namespace utils = axom::bump::utilities; - const auto conduitAllocatorId = - axom::sidre::ConduitMemory::axomAllocIdToConduit(this->getAllocatorID()); - // Make a pass through the inputs and make a list of the material names. - bool hasMatsets = false, defaultMaterial = false; - int nmats = 0; - std::map allMats; - std::string matsetName, topoName; + mi.hasMatsets = false; + mi.defaultMaterial = false; + mi.nmats = 0; + mi.allMats.clear(); + mi.matsetName.clear(); + mi.topoName.clear(); + mi.totalZones = 0; + mi.totalMatCount = 0; + mi.itype = -1; + mi.ftype = -1; + mi.elementDominant = true; + mi.multiBuffer = false; + for(size_t i = 0; i < inputs.size(); i++) { if(inputs[i].m_input->has_path("matsets")) { conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); conduit::Node &n_matset = n_matsets[0]; - matsetName = n_matset.name(); - topoName = n_matset.fetch_existing("topology").as_string(); + mi.matsetName = n_matset.name(); + mi.topoName = n_matset.fetch_existing("topology").as_string(); + mi.elementDominant = conduit::blueprint::mesh::matset::is_element_dominant(n_matset); + mi.multiBuffer = conduit::blueprint::mesh::matset::is_multi_buffer(n_matset) && + !n_matset.has_child("material_ids"); auto matInfo = axom::bump::views::materials(n_matset); for(const auto &info : matInfo) { - if(allMats.find(info.m_name) == allMats.end()) + if(mi.allMats.find(info.m_name) == mi.allMats.end()) { - allMats[info.m_name] = nmats++; + mi.allMats[info.m_name] = mi.nmats++; } } - hasMatsets = true; + mi.hasMatsets = true; } else { - defaultMaterial = true; + mi.defaultMaterial = true; } } - if(hasMatsets) + // One or more inputs did not have a matset. + if(mi.hasMatsets && mi.defaultMaterial) { - MaterialDispatch disp; - auto *This = this; + mi.allMats["default"] = mi.nmats++; + } + } - // One or more inputs did not have a matset. - if(defaultMaterial) allMats["default"] = nmats++; + /*! + * \brief Counts the size of the materials and stores the results into \a mi. This lets us know + * how big the merged matset will be. + * + * \param inputs The inputs to be merged. + * \param[out] mi The material information. + */ + template + void countMaterialSizes(const std::vector &inputs, MaterialInfo &mi) const + { + AXOM_ANNOTATE_SCOPE("sizes"); + namespace utils = axom::bump::utilities; + // Make a pass through the matsets to determine the overall storage. + mi.totalZones = 0; + mi.totalMatCount = 0; + mi.itype = -1; + mi.ftype = -1; - // Make a pass through the matsets to determine the overall storage. - axom::IndexType totalZones = 0, totalMatCount = 0; - int itype, ftype; - { - AXOM_ANNOTATE_SCOPE("sizes"); - for(size_t i = 0; i < inputs.size(); i++) - { - const auto nzones = this->countZones(inputs, i); - totalZones += nzones; + MaterialDispatchType disp; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nzones = this->countZones(inputs, i); + mi.totalZones += nzones; - if(inputs[i].m_input->has_path("matsets")) - { - conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); - conduit::Node &n_matset = n_matsets[0]; - axom::IndexType matCount = 0; - disp.dispatchMatset(n_matset, [&](auto matsetView) { - // Figure out the types to use for storing the data. - using IType = typename decltype(matsetView)::IndexType; - using FType = typename decltype(matsetView)::FloatType; - itype = utils::cpp2conduit::id; - ftype = utils::cpp2conduit::id; - - matCount = This->mergeMatset_count(matsetView, nzones); - }); - totalMatCount += matCount; - } - else - { - totalMatCount += nzones; - } - } + if(inputs[i].m_input->has_path("matsets")) + { + conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets[0]; + axom::IndexType matCount = 0; + auto *This = this; + disp.dispatchMatset(n_matset, [&](auto matsetView) { + // Figure out the types to use for storing the data. + using IType = typename decltype(matsetView)::IndexType; + using FType = typename decltype(matsetView)::FloatType; + mi.itype = utils::cpp2conduit::id; + mi.ftype = utils::cpp2conduit::id; + + matCount = This->mergeMatset_count(matsetView, nzones); + }); + mi.totalMatCount += matCount; + } + else + { + mi.totalMatCount += nzones; } + } + } + + /*! + * \brief Merge matsets that exist on the various mesh inputs. + * + * \param inputs A vector of inputs to be merged. + * \param[out] output The node that will contain the output mesh. + * + * \note We only create a unibuffer matset for the merged matset. + */ + virtual void mergeMatset(const std::vector &inputs, conduit::Node &output) const override + { + AXOM_ANNOTATE_SCOPE("mergeMatset"); + namespace utils = axom::bump::utilities; + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(this->getAllocatorID()); + + MaterialInfo mi; + getMaterialInfo(inputs, mi); + + if(mi.hasMatsets) + { + countMaterialSizes(inputs, mi); // Allocate AXOM_ANNOTATE_BEGIN("allocate"); - conduit::Node &n_newMatset = output["matsets/" + matsetName]; - n_newMatset["topology"] = topoName; + conduit::Node &n_newMatset = output["matsets/" + mi.matsetName]; + n_newMatset["topology"] = mi.topoName; conduit::Node &n_volume_fractions = n_newMatset["volume_fractions"]; n_volume_fractions.set_allocator(conduitAllocatorId); - n_volume_fractions.set(conduit::DataType(ftype, totalMatCount)); + n_volume_fractions.set(conduit::DataType(mi.ftype, mi.totalMatCount)); conduit::Node &n_material_ids = n_newMatset["material_ids"]; n_material_ids.set_allocator(conduitAllocatorId); - n_material_ids.set(conduit::DataType(itype, totalMatCount)); + n_material_ids.set(conduit::DataType(mi.itype, mi.totalMatCount)); conduit::Node &n_sizes = n_newMatset["sizes"]; n_sizes.set_allocator(conduitAllocatorId); - n_sizes.set(conduit::DataType(itype, totalZones)); + n_sizes.set(conduit::DataType(mi.itype, mi.totalZones)); conduit::Node &n_offsets = n_newMatset["offsets"]; n_offsets.set_allocator(conduitAllocatorId); - n_offsets.set(conduit::DataType(itype, totalZones)); + n_offsets.set(conduit::DataType(mi.itype, mi.totalZones)); conduit::Node &n_indices = n_newMatset["indices"]; n_indices.set_allocator(conduitAllocatorId); - n_indices.set(conduit::DataType(itype, totalMatCount)); + n_indices.set(conduit::DataType(mi.itype, mi.totalMatCount)); AXOM_ANNOTATE_END("allocate"); { @@ -1656,10 +1837,12 @@ class MergeMeshesAndMatsets : public MergeMeshes // Make material_map. conduit::Node &n_material_map = n_newMatset["material_map"]; - for(auto it = allMats.begin(); it != allMats.end(); it++) + for(auto it = mi.allMats.begin(); it != mi.allMats.end(); it++) n_material_map[it->first] = it->second; // Populate + MaterialDispatch disp; + auto *This = this; disp.execute(n_material_ids, n_sizes, n_offsets, @@ -1695,7 +1878,7 @@ class MergeMeshesAndMatsets : public MergeMeshes axom::exclusive_scan(sizesView, offsetsView); // Make indices. - This->mergeMatset_indices(indicesView, totalMatCount); + This->mergeMatset_indices(indicesView, mi.totalMatCount); // Fill in material info. zOffset = 0; @@ -1710,7 +1893,7 @@ class MergeMeshesAndMatsets : public MergeMeshes disp.dispatchMatset(n_matset, [&](auto matsetView) { This->mergeMatset_copy(n_matset, - allMats, + mi.allMats, materialIdsView, offsetsView, volumeFractionsView, @@ -1721,7 +1904,7 @@ class MergeMeshesAndMatsets : public MergeMeshes } else { - const int dmat = allMats["default"]; + const int dmat = mi.allMats["default"]; This->mergeMatset_default(materialIdsView, offsetsView, volumeFractionsView, @@ -1935,6 +2118,237 @@ class MergeMeshesAndMatsets : public MergeMeshes materialIdsView[zoneStart] = matno; }); } + + /*! + * \brief Determine the field information (data type and component paths). + * + * \param inputs The mesh inputs being merged. + * \param srcFieldPath The path to the source field. + * \param[out] fi The field information. We only fill in the dtype and component names. + */ + void getMixedFieldInformation(const std::vector &inputs, + const std::string &srcFieldPath, + const MaterialInfo &mi, + FieldInformation &fi) const + { + fi.m_dtype = -1; + fi.m_components.clear(); + + std::string srcFieldMatsetValuesPath(srcFieldPath + "/matset_values"); + + for(size_t i = 0; i < inputs.size(); i++) + { + if(inputs[i].m_input->has_path(srcFieldMatsetValuesPath)) + { + const conduit::Node &n_matset_values = + inputs[i].m_input->fetch_existing(srcFieldMatsetValuesPath); + + // NOTE: we only try to populate components for fields that look like vectors + if(mi.multiBuffer) + { + for(conduit::index_t ci = 0; ci < n_matset_values.number_of_children(); ci++) + { + // n_component is a material name + const conduit::Node &n_component = n_matset_values[ci]; + + if(n_component.number_of_children() > 0) + { + // vector components under the material name + fi.m_dtype = n_component[0].dtype().id(); + for(conduit::index_t sci = 0; sci < n_matset_values.number_of_children(); sci++) + { + fi.m_components.push_back(n_component[sci].name()); + } + } + else + { + // scalar - the material name + fi.m_dtype = n_component.dtype().id(); + } + } + } + else + { + // unibuffer + if(n_matset_values.number_of_children() > 0) + { + // vector - the component name + for(conduit::index_t ci = 0; ci < n_matset_values.number_of_children(); ci++) + { + const conduit::Node &n_component = n_matset_values[ci]; + fi.m_dtype = n_component.dtype().id(); + fi.m_components.push_back(n_component.name()); + } + } + else + { + // scalar + fi.m_dtype = n_matset_values.dtype().id(); + } + } + break; + } + } + } + + /*! + * \brief Merge a mixed field that exists on the various mesh inputs. + * + * \param inputs A vector of inputs to be merged. + * \param[out] n_field The new field that we're creating. + * \param srcPath The path to the source input's mixed field node. + * + * \note This function relies on the materials having been merged first so we + * can use their pre-existing merged arrays. + * \note Mixed fields in Blueprint at this time are limited to scalars. + */ + virtual void copyMixedField(const std::vector &inputs, + conduit::Node &n_field, + const std::string &srcFieldPath) const override + { + AXOM_ANNOTATE_SCOPE("copyMixedField"); + MaterialInfo mi; + getMaterialInfo(inputs, mi); + + if(mi.hasMatsets) + { + const auto conduitAllocatorId = + axom::sidre::ConduitMemory::axomAllocIdToConduit(this->getAllocatorID()); + + countMaterialSizes(inputs, mi); + const std::string matsetName(mi.matsetName); + n_field["matset"] = matsetName; + + FieldInformation fi; + getMixedFieldInformation(inputs, srcFieldPath, mi, fi); + SLIC_ERROR_IF( + fi.m_dtype == -1, + axom::fmt::format("The new mixed field type for {} was not determined.", srcFieldPath)); + SLIC_ERROR_IF(fi.m_components.size() > 0, "Vector mixed vars not supported"); + + // Get the Conduit node for the matset we built previously + conduit::Node *n_matset = const_cast( + conduit::blueprint::mesh::utils::find_reference_node(n_field, "matset")); + SLIC_ERROR_IF(n_matset == nullptr, + axom::fmt::format("The new matset {} was not found.", matsetName)); + + // Get some parts of the new unibuffer matset. + conduit::Node &n_material_ids = n_matset->fetch_existing("material_ids"); + conduit::Node &n_sizes = n_matset->fetch_existing("sizes"); + conduit::Node &n_offsets = n_matset->fetch_existing("offsets"); + conduit::Node &n_indices = n_matset->fetch_existing("indices"); + + // Allocate the new mixed field. + conduit::Node &n_matset_values = n_field["matset_values"]; + n_matset_values.set_allocator(conduitAllocatorId); + n_matset_values.set(conduit::DataType(mi.ftype, mi.totalMatCount)); + + // Dispatch the mixed material we built so we can access its arrays as views. + MaterialDispatch disp; + auto *This = this; + disp.execute( + n_material_ids, + n_sizes, + n_offsets, + n_indices, + n_matset_values, + [&](auto AXOM_UNUSED_PARAM(materialIdsView), + auto AXOM_UNUSED_PARAM(sizesView), + auto offsetsView, + auto AXOM_UNUSED_PARAM(indicesView), + auto matsetValuesView) { + // Iterate over the inputs and copy their mixed field + axom::IndexType zOffset = 0; + for(size_t i = 0; i < inputs.size(); i++) + { + const auto nzones = This->countZones(inputs, i); + + if(inputs[i].m_input->has_child("matsets") && inputs[i].m_input->has_path(srcFieldPath)) + { + // Get the source matset. + conduit::Node &n_matsets = inputs[i].m_input->fetch_existing("matsets"); + conduit::Node &n_matset = n_matsets.fetch_existing(matsetName); + // Get the source field. + conduit::Node &n_src_field = inputs[i].m_input->fetch_existing(srcFieldPath); + // Dispatch the source mixed field. + disp.dispatchMixedField(n_matset, + n_src_field, + [&](auto srcMatsetView, auto srcMixedFieldView) { + This->mergeMixedField_copy(srcMatsetView, + srcMixedFieldView, + matsetValuesView, + offsetsView, + nzones, + zOffset); + }); + } + else + { + This->mergeMixedField_default(matsetValuesView, offsetsView, nzones, zOffset); + } + zOffset += nzones; + } + }); + } + } + + /*! + * \brief Copy a mixed field from the \a srcMatsetView into the output \a mixedFieldView. + * + * \param srcMatsetView The input matset view that is used to traverse materials. + * \param srcMixedFieldView The input mixed field view that contains the field data for the materials. + * \param[out] outputView The output view that contains the merged field. + * \param offsetsView The offsets view that contains the offsets for the output zones. + * \param nzones The number of zones in the current input. + * \param zOffset The starting offset in the output mixed field view. + */ + template + void mergeMixedField_copy(SourceMatsetView srcMatsetView, + SourceMixedFieldView srcMixedFieldView, + OutputFieldView outputView, + OffsetsView offsetsView, + axom::IndexType nzones, + axom::IndexType zOffset) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + // Get this zone's materials. + auto zoneMat = srcMatsetView.beginZone(zoneIndex); + const auto nmats = zoneMat.size(); + + // Store the materials in the new material. + const auto zoneStart = offsetsView[zOffset + zoneIndex]; + for(axom::IndexType mi = 0; mi < nmats; mi++, zoneMat++) + { + const auto destIndex = zoneStart + mi; + // Copy the value for the zoneMat from the mixed field view into the output. + outputView[destIndex] = srcMixedFieldView.value(zoneMat); + } + }); + } + + /*! + * \brief Fill a mixed field with a default value. + * + * \param[out] mixedFieldView The output view that contains the merged field. + * \param offsetsView The offsets view that contains the offsets for the output zones. + * \param nzones The number of zones in the current input. + * \param zOffset The starting offset in the output mixed field view. + */ + template + void mergeMixedField_default(MixedFieldView mixedFieldView, + OffsetsView offsetsView, + axom::IndexType nzones, + axom::IndexType zOffset) const + { + axom::for_all( + nzones, + AXOM_LAMBDA(axom::IndexType zoneIndex) { + const auto zoneStart = offsetsView[zOffset + zoneIndex]; + mixedFieldView[zoneStart] = 0; + }); + } }; } // end namespace bump diff --git a/src/axom/bump/docs/sphinx/bump_views.rst b/src/axom/bump/docs/sphinx/bump_views.rst index 9bd68f515d..7affff0113 100644 --- a/src/axom/bump/docs/sphinx/bump_views.rst +++ b/src/axom/bump/docs/sphinx/bump_views.rst @@ -165,16 +165,25 @@ Matsets The BUMP component provides material views to wrap Blueprint matsets behind an interface that supports queries of the matset data without having to care much about its internal representation. -Blueprint provides 4 flavors of matset, each with a different representation. The -``axom::bump::views::UnibufferMaterialView`` class wraps unibuffer matsets, which consist of -several arrays that define materials for each zone in the associated topology. The view's -methods allow algorithms to query the list of materials for each zone. +Blueprint describes 4 flavors of matset, each with a different representation. In practice, +Blueprint supports 4 flavors of matset, unibuffer element-dominant, multibuffer element-dominant, +and multibuffer material-dominant. The ``axom::bump::views::UnibufferMaterialView`` class wraps +unibuffer element-dominant matsets, which consist of several arrays that define materials for +each zone in the associated topology. The view's methods allow algorithms to query the list of +materials for each zone. .. literalinclude:: ../../tests/bump_views.cpp :start-after: _bump_views_matsetview_begin :end-before: _bump_views_matsetview_end :language: C++ +Mixed (or material-dependent) fields are supported using `axom::bump::views::MixedFieldView`. +Mixed field data are arranged in the same manner as their related matset, leading to +multiple representations. This class is used in conjunction with a material view with the material +view providing the iterators that are used for traversal and the mixed field view for accessing +the field values. The ``axom::bump::views::dispatch_material_field()`` function can be +used to simplify creating mixed field views. + ---------- Dispatch ---------- diff --git a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp index fc5bbba91e..f77d1dd9f3 100644 --- a/src/axom/bump/tests/blueprint_testing_data_helpers.hpp +++ b/src/axom/bump/tests/blueprint_testing_data_helpers.hpp @@ -75,6 +75,15 @@ void braid(const std::string &type, const Dimensions &dims, conduit::Node &mesh) add_distance(mesh); } +// Return the max value for element i in vfA, vfB, vfC. +float make_field_value(const std::vector &vfA, + const std::vector &vfB, + const std::vector &vfC, + size_t i) +{ + return vfA[i] + vfB[i] + vfC[i]; +} + /*! * \brief Make a new "unibuffer" matset from the input vectors. The unibuffer * matset is a style of matset in Blueprint that combines material ids @@ -85,15 +94,17 @@ void braid(const std::string &type, const Dimensions &dims, conduit::Node &mesh) * \param vfC The volume fractions for material C over all zones in the mesh. * \param matnos The material numbers to use for materials A, B, C. * \param[out] matset The node that will contain the matset. + * \param[out] mfield The node that will contain the mixed field. */ void make_unibuffer(const std::vector &vfA, const std::vector &vfB, const std::vector &vfC, const std::vector &matnos, - conduit::Node &matset) + conduit::Node &matset, + conduit::Node &mfield) { std::vector material_ids; - std::vector volume_fractions; + std::vector volume_fractions, field_values; std::vector sizes(vfA.size(), 0); std::vector offsets(vfA.size(), 0); std::vector indices; @@ -123,6 +134,8 @@ void make_unibuffer(const std::vector &vfA, sizes[i]++; } + field_values.push_back(make_field_value(vfA, vfB, vfC, i)); + offsets[i] = offset; offset += sizes[i]; } @@ -132,6 +145,13 @@ void make_unibuffer(const std::vector &vfA, matset["indices"].set(indices); matset["sizes"].set(sizes); matset["offsets"].set(offsets); + + // Add per-material field values into a mixed field. + if(mfield.has_path("topology")) + { + mfield["values"].set(field_values); + mfield["matset_values"].set(volume_fractions); + } } /*! @@ -142,12 +162,14 @@ void make_unibuffer(const std::vector &vfA, * \param vfC The volume fractions for material C over all zones in the mesh. * \param matnos The material numbers to use for materials A, B, C. * \param[out] matset The node that will contain the matset. + * \param[out] mfield The node that will contain the mixed field. */ void make_multibuffer(const std::vector &vfA, const std::vector &vfB, const std::vector &vfC, const std::vector &AXOM_UNUSED_PARAM(matnos), - conduit::Node &matset) + conduit::Node &matset, + conduit::Node &mfield) { std::vector indices(vfA.size()); std::iota(indices.begin(), indices.end(), 0); @@ -157,6 +179,14 @@ void make_multibuffer(const std::vector &vfA, matset["volume_fractions/B/indices"].set(indices); matset["volume_fractions/C/values"].set(vfC); matset["volume_fractions/C/indices"].set(indices); + + // Add per-material field values into a mixed field. + if(mfield.has_path("topology")) + { + mfield["matset_values/A/values"].set(vfA); + mfield["matset_values/B/values"].set(vfB); + mfield["matset_values/C/values"].set(vfC); + } } /*! @@ -167,16 +197,26 @@ void make_multibuffer(const std::vector &vfA, * \param vfC The volume fractions for material C over all zones in the mesh. * \param matnos The material numbers to use for materials A, B, C. * \param[out] matset The node that will contain the matset. + * \param[out] mfield The node that will contain the mixed field. */ void make_element_dominant(const std::vector &vfA, const std::vector &vfB, const std::vector &vfC, const std::vector &AXOM_UNUSED_PARAM(matnos), - conduit::Node &matset) + conduit::Node &matset, + conduit::Node &mfield) { + // NOTE: These are not sparse. matset["volume_fractions/A"].set(vfA); matset["volume_fractions/B"].set(vfB); matset["volume_fractions/C"].set(vfC); + + if(mfield.has_path("topology")) + { + mfield["matset_values/A"].set(vfA); + mfield["matset_values/B"].set(vfB); + mfield["matset_values/C"].set(vfC); + } } /*! @@ -187,14 +227,16 @@ void make_element_dominant(const std::vector &vfA, * \param vfC The volume fractions for material C over all zones in the mesh. * \param matnos The material numbers to use for materials A, B, C. * \param[out] matset The node that will contain the matset. + * \param[out] mfield The node that will contain the mixed field. */ void make_material_dominant(const std::vector &vfA, const std::vector &vfB, const std::vector &vfC, const std::vector &AXOM_UNUSED_PARAM(matnos), - conduit::Node &matset) + conduit::Node &matset, + conduit::Node &mfield) { - std::vector svfA, svfB, svfC; + std::vector svfA, svfB, svfC; // sparse arrays std::vector ziA, ziB, ziC; const size_t n = vfA.size(); for(size_t zi = 0; zi < n; zi++) @@ -221,6 +263,13 @@ void make_material_dominant(const std::vector &vfA, matset["element_ids/A"].set(ziA); matset["element_ids/B"].set(ziB); matset["element_ids/C"].set(ziC); + + if(mfield.has_path("topology")) + { + mfield["matset_values/A"].set(svfA); + mfield["matset_values/B"].set(svfB); + mfield["matset_values/C"].set(svfC); + } } /*! @@ -230,6 +279,7 @@ void make_material_dominant(const std::vector &vfA, * \param topoName The name of mesh topology. * \param dims The dimensions of the mesh. * \param cleanMats Whether to make a matset of only clean zones (1 mat/zone). + * \param makeMixedField Whether to make a mixed field if !cleanMats. * \param[out] mesh The mesh node to which a matset will be added. * * *--------------* @@ -247,8 +297,11 @@ void make_matset(const std::string &type, const std::string &topoName, const Dimensions &dims, bool cleanMats, + bool makeMixedField, conduit::Node &mesh) { + SLIC_ERROR_IF(cleanMats && makeMixedField, + "We cannot make a mixed field when making clean materials."); constexpr int sampling = 10; int midx = sampling * dims[0] / 2; int midy = sampling * dims[1] / 2; @@ -342,6 +395,15 @@ void make_matset(const std::string &type, mesh["fields/vfC/association"] = "element"; mesh["fields/vfC/values"].set(vfC); + conduit::Node mfield; + if(makeMixedField) + { + mfield["topology"] = topoName; + mfield["association"] = "element"; + mfield["matset"] = "mat"; + mfield["volume_dependent"] = "false"; + } + const std::vector matnos {{22, 66, 33}}; conduit::Node &matset = mesh["matsets/mat"]; matset["topology"] = topoName; @@ -352,19 +414,24 @@ void make_matset(const std::string &type, // produce different material types. if(type == "unibuffer") { - make_unibuffer(vfA, vfB, vfC, matnos, matset); + make_unibuffer(vfA, vfB, vfC, matnos, matset, mfield); } else if(type == "multibuffer") { - make_multibuffer(vfA, vfB, vfC, matnos, matset); + make_multibuffer(vfA, vfB, vfC, matnos, matset, mfield); } else if(type == "element_dominant") { - make_element_dominant(vfA, vfB, vfC, matnos, matset); + make_element_dominant(vfA, vfB, vfC, matnos, matset, mfield); } else if(type == "material_dominant") { - make_material_dominant(vfA, vfB, vfC, matnos, matset); + make_material_dominant(vfA, vfB, vfC, matnos, matset, mfield); + } + + if(makeMixedField) + { + mesh["fields/mixed"].move(mfield); } } diff --git a/src/axom/bump/tests/bump_mergemeshes.cpp b/src/axom/bump/tests/bump_mergemeshes.cpp index 799d4e592d..61dd6a27db 100644 --- a/src/axom/bump/tests/bump_mergemeshes.cpp +++ b/src/axom/bump/tests/bump_mergemeshes.cpp @@ -13,20 +13,61 @@ #include "axom/bump/tests/blueprint_testing_helpers.hpp" #include "axom/bump/tests/blueprint_testing_data_helpers.hpp" +#include + #include #include +// NOTE: Conduit 0.9.6 and later has macros but Axom is not quite on that version yet. +#include "conduit/conduit_config.h" +#define AXOM_CONDUIT_MAKE_VERSION_VALUE(MAJOR, MINOR, PATCH) \ + (((MAJOR) * 10000) + ((MINOR) * 100) + (PATCH)) +#define AXOM_CONDUIT_VERSION_VALUE \ + AXOM_CONDUIT_MAKE_VERSION_VALUE(CONDUIT_VERSION_MAJOR, CONDUIT_VERSION_MINOR, CONDUIT_VERSION_PATCH) + namespace bump = axom::bump; namespace utils = axom::bump::utilities; +//#define AXOM_DEBUG_MERGE_MESHES_TEST +#ifdef AXOM_DEBUG_MERGE_MESHES_TEST +void saveMesh(const conduit::Node &n_mesh, const std::string &fileRoot) +{ + #ifdef CONDUIT_RELAY_IO_HDF5_ENABLED + const std::string protocol("hdf5"); + conduit::relay::io::save(n_mesh, fileRoot + ".yaml", "yaml"); + #else + const std::string protocol("yaml"); + #endif + // These save with a ".root" extension + conduit::relay::io::blueprint::save_mesh(n_mesh, fileRoot, protocol); +} +#endif + //------------------------------------------------------------------------------ template struct test_mergemeshes { static void test() + { + std::vector matsetTypes {"unibuffer", "element_dominant", "material_dominant"}; + for(const auto &matsetType : matsetTypes) + { + for(int matflags = 3; matflags >= 1; matflags--) + { + SLIC_INFO(axom::fmt::format("test({}, {})", matsetType, matflags)); + test(matsetType, matflags); + } + } + } + + static void test(const std::string &matsetType, int matflags) { conduit::Node hostMesh; - create(hostMesh); + create(hostMesh, matsetType, matflags); +#ifdef AXOM_DEBUG_MERGE_MESHES_TEST + const auto preMergeFilename = axom::fmt::format("preMerge_{}_{}", matflags, matsetType); + saveMesh(hostMesh, preMergeFilename); +#endif // host->device conduit::Node deviceMesh; @@ -55,25 +96,37 @@ struct test_mergemeshes // Execute conduit::Node opts, deviceResult; opts["topology"] = "mesh"; - bump::MergeMeshes mm; + bump::MergeMeshesAndMatsets mm; mm.execute(inputs, opts, deviceResult); // device->host conduit::Node hostResult; utils::copy(hostResult, deviceResult); - +#ifdef AXOM_DEBUG_MERGE_MESHES_TEST + const auto postMergeFilename = axom::fmt::format("postMerge_{}_{}", matflags, matsetType); + saveMesh(hostResult, postMergeFilename); +#endif constexpr double tolerance = 1.e-7; conduit::Node expectedResult, info; - result(expectedResult); - bool success = compareConduit(expectedResult, hostResult, tolerance, info); + result(expectedResult, matflags); + bool success = false; + try + { + success = compareConduit(expectedResult, hostResult, tolerance, info); + } + catch(const conduit::Error &e) + { + e.print(); + } if(!success) { info.print(); + printNode(hostResult); } EXPECT_TRUE(success); } - static void create(conduit::Node &mesh) + static void create(conduit::Node &mesh, const std::string &matsetType, int matflags) { const char *yaml = R"xx( domain0000: @@ -101,6 +154,25 @@ struct test_mergemeshes topology: mesh association: element values: [0,1,2, 3,4,5] + zonal_mixed: + topology: mesh + association: element + matset: mat + values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25] + # matset_values encodes 100+zone.mat + matset_values: [100.1, 101.1, 102.1, 103.2,103.3, 104.2,104.3, 105.2,105.3] + matsets: + mat: + material_map: + A: 1 + B: 2 + C: 3 + topology: mesh + material_ids: [1, 1, 1, 2,3, 2,3, 2,3] + volume_fractions: [1., 1., 1., 0.5,0.5, 0.5,0.5, 0.5,0.5] + indices: [0, 1, 2, 3,4, 5,6, 7,8] + sizes: [1, 1, 1, 2, 2, 2] + offsets: [0, 1, 2, 3, 5, 7] domain0001: coordsets: coords: @@ -130,13 +202,110 @@ struct test_mergemeshes topology: mesh association: element values: [0,1,2,3, 4,5,6,7, 8] + zonal_mixed: + topology: mesh + association: element + matset: mat + values: [-200.1, -201.15, -202.3, -203.15, -204.1, -205.15, -206.2, -207.15, -208.15] + # matset_values encodes -200+zone.mat + matset_values: [-200.1, -201.1,-201.2, -202.3, -203.1,-203.2, -204.1, -205.1,-205.2, -206.2, -207.1,-207.2, -208.1,-208.2] + matsets: + mat: + material_map: + A: 1 + B: 2 + #C: 3 + topology: mesh + material_ids: [1, 1,2, 2, 1,2, 1, 1,2, 2, 1,2, 1,2] + volume_fractions: [1., 0.5,0.5, 1., 0.5,0.5, 1., 0.5,0.5, 1., 0.5,0.5, 0.5,0.5] + indices: [0, 1,2, 3, 4,5, 6, 7,8, 9, 10,11, 12,13] + sizes: [1, 2, 1, 2, 1, 2, 1, 2, 2] + offsets: [0, 1, 3, 4, 6, 7, 9, 10, 12] )xx"; mesh.parse(yaml); + + for(int dom = 0; dom < 2; dom++) + { + changeMatsetType(mesh[dom], matsetType); + } + applyMatFlags(mesh, matflags); } - static void result(conduit::Node &mesh) + /// Remove mixed field and matset on domains according to matflags. This tests merging domains that are missing materials/fields. + static void applyMatFlags(conduit::Node &mesh, int matflags) { - const char *yaml = R"xx( + for(int dom = 0; dom < 2; dom++) + { + if(!axom::utilities::bitIsSet(matflags, dom)) + { + conduit::Node &domain = mesh[dom]; + domain["fields"].remove("zonal_mixed"); + domain.remove("matsets"); + } + } + } + + static void changeMatsetType(conduit::Node &domain, const std::string &matsetType) + { + // Change the material and field representations + if(matsetType == "element_dominant") + { + conduit::Node domainCopy(domain); + conduit::Node &srcMatset = domainCopy["matsets/mat"]; + conduit::Node &srcField = domainCopy["fields/zonal_mixed"]; + + domain.remove("matsets/mat"); + domain.remove("fields/zonal_mixed"); + + // These functions changed in Conduit 0.9.6 +#if AXOM_CONDUIT_VERSION_VALUE < AXOM_CONDUIT_MAKE_VERSION_VALUE(0, 9, 6) + conduit::blueprint::mesh::matset::to_multi_buffer_full(srcMatset, domain["matsets/mat"]); + conduit::blueprint::mesh::field::to_multi_buffer_full(srcMatset, + srcField, + "mat", + domain["fields/zonal_mixed"]); +#else + conduit::blueprint::mesh::matset::to_multi_buffer_by_element(srcMatset, domain["matsets/mat"]); + conduit::blueprint::mesh::field::to_multi_buffer_by_element(srcMatset, + srcField, + "mat", + domain["fields/zonal_mixed"]); +#endif + // Make sure we preserve the material_map. + if(srcMatset.has_child("material_map")) + { + domain["matsets/mat/material_map"].set(srcMatset["material_map"]); + } + } + else if(matsetType == "material_dominant") + { + conduit::Node domainCopy(domain); + conduit::Node &srcMatset = domainCopy["matsets/mat"]; + conduit::Node &srcField = domainCopy["fields/zonal_mixed"]; + + domain.remove("matsets/mat"); + domain.remove("fields/zonal_mixed"); + + conduit::blueprint::mesh::matset::to_multi_buffer_by_material(srcMatset, domain["matsets/mat"]); + conduit::blueprint::mesh::field::to_multi_buffer_by_material(srcMatset, + srcField, + "mat", + domain["fields/zonal_mixed"]); + // Make sure we preserve the material_map. + if(srcMatset.has_child("material_map")) + { + domain["matsets/mat/material_map"].set(srcMatset["material_map"]); + } + } + } + + static void result(conduit::Node &mesh, int matflags) + { + // NOTE: We pass back different baselines for different matflags. The fields and matset change. + // It is simpler to just have a totally separate baseline to parse. + + // Result for matflags=3 - both input domains had the material and the mixed field. + const char *yaml3 = R"xx( coordsets: coords: type: "explicit" @@ -156,8 +325,151 @@ struct test_mergemeshes quad: 3 tri: 2 shapes: [3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3] +matsets: + mat: + topology: "mesh" + volume_fractions: [1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5] + material_ids: [0, 0, 0, 1, 2, 1, 2, 1, 2, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1] + sizes: [1, 1, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2] + offsets: [0, 1, 2, 3, 5, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21] + indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] + material_map: + A: 0 + B: 1 + C: 2 +fields: + nodal: + association: "vertex" + topology: "mesh" + values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2] + zonal: + association: "element" + topology: "mesh" + values: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7, 8] + zonal_mixed: + association: "element" + topology: "mesh" + matset: "mat" + values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25, -200.1, -201.15, -202.3, -203.15, -204.1, -205.15, -206.2, -207.15, -208.15] + matset_values: [100.1, 101.1, 102.1, 103.2, 103.3, 104.2, 104.3, 105.2, 105.3, -200.1, -201.1, -201.2, -202.3, -203.1, -203.2, -204.1, -205.1, -205.2, -206.2, -207.1, -207.2, -208.1, -208.2] )xx"; - mesh.parse(yaml); + + // Result for matflags=2 - domain 0 lacked the material and domain so we get default values where domain 0's data would be. + const char *yaml2 = R"xx( +coordsets: + coords: + type: "explicit" + values: + x: [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 1.5, 1.5] + y: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 0.5, 1.5] +topologies: + mesh: + type: "unstructured" + coordset: "coords" + elements: + connectivity: [0, 1, 5, 4, 4, 5, 9, 8, 8, 9, 13, 12, 2, 3, 7, 6, 6, 7, 11, 10, 10, 11, 15, 14, 1, 16, 5, 1, 2, 16, 2, 6, 16, 16, 6, 5, 5, 17, 9, 5, 6, 17, 6, 10, 17, 10, 9, 17, 9, 10, 14, 13] + sizes: [4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4] + offsets: [0, 4, 8, 12, 16, 20, 24, 27, 30, 33, 36, 39, 42, 45, 48] + shape: "mixed" + shape_map: + quad: 3 + tri: 2 + shapes: [3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3] +matsets: + mat: + topology: "mesh" + volume_fractions: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5] + material_ids: [2, 2, 2, 2, 2, 2, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1] + sizes: [1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 2] + offsets: [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 12, 13, 15, 16, 18] + indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] + material_map: + A: 0 + B: 1 + default: 2 +fields: + nodal: + association: "vertex" + topology: "mesh" + values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2] + zonal: + association: "element" + topology: "mesh" + values: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7, 8] + zonal_mixed: + association: "element" + topology: "mesh" + matset: "mat" + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -200.1, -201.15, -202.3, -203.15, -204.1, -205.15, -206.2, -207.15, -208.15] + matset_values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -200.1, -201.1, -201.2, -202.3, -203.1, -203.2, -204.1, -205.1, -205.2, -206.2, -207.1, -207.2, -208.1, -208.2] +)xx"; + + // Result for matflags=1 - domain 1 lacked the material and domain so we get default values where domain 1's data would be. + const char *yaml1 = R"xx( +coordsets: + coords: + type: "explicit" + values: + x: [0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 0.0, 1.0, 2.0, 3.0, 1.5, 1.5] + y: [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 0.5, 1.5] +topologies: + mesh: + type: "unstructured" + coordset: "coords" + elements: + connectivity: [0, 1, 5, 4, 4, 5, 9, 8, 8, 9, 13, 12, 2, 3, 7, 6, 6, 7, 11, 10, 10, 11, 15, 14, 1, 16, 5, 1, 2, 16, 2, 6, 16, 16, 6, 5, 5, 17, 9, 5, 6, 17, 6, 10, 17, 10, 9, 17, 9, 10, 14, 13] + sizes: [4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4] + offsets: [0, 4, 8, 12, 16, 20, 24, 27, 30, 33, 36, 39, 42, 45, 48] + shape: "mixed" + shape_map: + quad: 3 + tri: 2 + shapes: [3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3] +matsets: + mat: + topology: "mesh" + volume_fractions: [1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + material_ids: [0, 0, 0, 1, 2, 1, 2, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3] + sizes: [1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1] + offsets: [0, 1, 2, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17] + indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] + material_map: + A: 0 + B: 1 + C: 2 + default: 3 +fields: + nodal: + association: "vertex" + topology: "mesh" + values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2] + zonal: + association: "element" + topology: "mesh" + values: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7, 8] + zonal_mixed: + association: "element" + topology: "mesh" + matset: "mat" + values: [100.1, 101.1, 102.1, 103.25, 104.25, 105.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + matset_values: [100.1, 101.1, 102.1, 103.2, 103.3, 104.2, 104.3, 105.2, 105.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] +)xx"; + + switch(matflags) + { + case 3: + mesh.parse(yaml3); + break; + case 2: + mesh.parse(yaml2); + break; + case 1: + mesh.parse(yaml1); + break; + default: + SLIC_ERROR("Unsupported matflags value."); + break; + } } }; diff --git a/src/axom/bump/tests/bump_views.cpp b/src/axom/bump/tests/bump_views.cpp index 21f5b43978..491691e866 100644 --- a/src/axom/bump/tests/bump_views.cpp +++ b/src/axom/bump/tests/bump_views.cpp @@ -586,6 +586,9 @@ TEST(bump_views, strided_structured_seq) { test_strided_structured::test(); } template struct test_braid2d_mat { + struct NoMixedFields + { }; + static void test(const std::string &type, const std::string &mattype, const std::string &name) { namespace utils = axom::bump::utilities; @@ -597,9 +600,15 @@ struct test_braid2d_mat // Create the data const bool cleanMats = false; + const bool makeMixedField = true; conduit::Node hostMesh, deviceMesh; axom::blueprint::testing::data::braid(type, dims, hostMesh); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, hostMesh); + axom::blueprint::testing::data::make_matset(mattype, + "mesh", + zoneDims, + cleanMats, + makeMixedField, + hostMesh); utils::copy(deviceMesh, hostMesh); TestApp.saveVisualization(name + "_orig", hostMesh); @@ -616,25 +625,56 @@ struct test_braid2d_mat utils::make_array_view(deviceMesh["matsets/mat/indices"])); // _bump_views_matsetview_end // clang-format on + SLIC_INFO("unibuffer: matsetView"); test_matsetview(nzones, matsetView, allocatorID); - } - else if(mattype == "multibuffer") - { - axom::bump::views::dispatch_material_multibuffer( + test_matsetview_iterators(nzones, matsetView, NoMixedFields {}, allocatorID); + + // Test mixed field. + axom::bump::views::dispatch_material_unibuffer_field( deviceMesh["matsets/mat"], - [&](auto matsetView) { test_matsetview(nzones, matsetView, allocatorID); }); + deviceMesh["fields/mixed"], + [&](auto matsetView, auto mixedFieldView) { + SLIC_INFO("element_dominant: mixedFieldView"); + test_matsetview_iterators(nzones, matsetView, mixedFieldView, allocatorID); + }); } else if(mattype == "element_dominant") { axom::bump::views::dispatch_material_element_dominant( deviceMesh["matsets/mat"], - [&](auto matsetView) { test_matsetview(nzones, matsetView, allocatorID); }); + [&](auto matsetView) { + SLIC_INFO("element_dominant: matsetView"); + test_matsetview(nzones, matsetView, allocatorID); + test_matsetview_iterators(nzones, matsetView, NoMixedFields {}, allocatorID); + }); + + // Test mixed field. + axom::bump::views::dispatch_material_element_dominant_field( + deviceMesh["matsets/mat"], + deviceMesh["fields/mixed"], + [&](auto matsetView, auto mixedFieldView) { + SLIC_INFO("element_dominant: mixedFieldView"); + test_matsetview_iterators(nzones, matsetView, mixedFieldView, allocatorID); + }); } else if(mattype == "material_dominant") { axom::bump::views::dispatch_material_material_dominant( deviceMesh["matsets/mat"], - [&](auto matsetView) { test_matsetview(nzones, matsetView, allocatorID); }); + [&](auto matsetView) { + SLIC_INFO("material_dominant: matsetView"); + test_matsetview(nzones, matsetView, allocatorID); + test_matsetview_iterators(nzones, matsetView, NoMixedFields {}, allocatorID); + }); + + // Test mixed field. + axom::bump::views::dispatch_material_material_dominant_field( + deviceMesh["matsets/mat"], + deviceMesh["fields/mixed"], + [&](auto matsetView, auto mixedFieldView) { + SLIC_INFO("material_dominant: mixedFieldView"); + test_matsetview_iterators(nzones, matsetView, mixedFieldView, allocatorID); + }); } } @@ -703,13 +743,20 @@ struct test_braid2d_mat { EXPECT_EQ(results[i], resultsHost[i]); } - - // Test iterators. - test_matsetview_iterators(nzones, matsetView, allocatorID); } - template - static void test_matsetview_iterators(axom::IndexType nzones, MatsetView matsetView, int allocatorID) + template + struct ViewPackage + { + MatsetView matsetView; + MatsetFieldView fieldView; + }; + + template + static void test_matsetview_iterators(axom::IndexType nzones, + MatsetView matsetView, + MatsetFieldView fieldView, + int allocatorID) { using ZoneIndex = typename MatsetView::ZoneIndex; // Allocate results array on device. @@ -717,15 +764,18 @@ struct test_braid2d_mat axom::Array resultsArrayDevice(nResults, nResults, allocatorID); auto resultsView = resultsArrayDevice.view(); + // Bundle the views together for device access. + ViewPackage deviceViews {matsetView, fieldView}; + axom::for_all( nzones, AXOM_LAMBDA(axom::IndexType index) { typename MatsetView::IDList ids {}; typename MatsetView::VFList vfs {}; - matsetView.zoneMaterials(index, ids, vfs); + deviceViews.matsetView.zoneMaterials(index, ids, vfs); // Get the end iterator for the zone. - const auto end = matsetView.endZone(index); + const auto end = deviceViews.matsetView.endZone(index); int eq_count = 0; int count = 0; @@ -742,12 +792,26 @@ struct test_braid2d_mat // Make sure the iterator order is the same as for the values we got from zoneMaterials(). int i = 0; - for(auto it = matsetView.beginZone(index); it != end; it++, i++) + for(auto it = deviceViews.matsetView.beginZone(index); it != end; it++, i++) { eq_count += (vfs[i] == it.volume_fraction() && ids[i] == it.material_id()) ? 1 : 0; count++; } + // If we passed in a mixed field view, make sure its field contains the same + // values as the volume fractions. That is how the dataset's fields are + // constructed. + if constexpr(!std::is_same_v) + { + int i = 0; + for(auto it = deviceViews.matsetView.beginZone(index); it != end; it++, i++) + { + const auto value = deviceViews.fieldView.value(it); + eq_count += (value == it.volume_fraction()) ? 1 : 0; + count++; + } + } + // Test ArrayView version of zoneMaterials(). using IndexType = typename MatsetView::IndexType; using FloatType = typename MatsetView::FloatType; @@ -756,7 +820,7 @@ struct test_braid2d_mat FloatType vfStorage[ARRAY_SIZE]; axom::ArrayView idView(idStorage, ARRAY_SIZE); axom::ArrayView vfView(vfStorage, ARRAY_SIZE); - const auto nmats = matsetView.zoneMaterials(index, idView, vfView); + const auto nmats = deviceViews.matsetView.zoneMaterials(index, idView, vfView); eq_count += (nmats == ids.size()) ? 1 : 0; count++; for(axom::IndexType j = 0; j < nmats; j++) @@ -802,30 +866,6 @@ TEST(bump_views, matset_unibuffer_hip) } #endif -// Multibuffer -TEST(bump_views, matset_multibuffer_seq) -{ - test_braid2d_mat::test("uniform", "multibuffer", "uniform2d_multibuffer"); -} -#if defined(AXOM_USE_OPENMP) -TEST(bump_views, matset_multibuffer_omp) -{ - test_braid2d_mat::test("uniform", "multibuffer", "uniform2d_multibuffer"); -} -#endif -#if defined(AXOM_USE_CUDA) -TEST(bump_views, matset_multibuffer_cuda) -{ - test_braid2d_mat::test("uniform", "multibuffer", "uniform2d_multibuffer"); -} -#endif -#if defined(AXOM_USE_HIP) -TEST(bump_views, matset_multibuffer_hip) -{ - test_braid2d_mat::test("uniform", "multibuffer", "uniform2d_multibuffer"); -} -#endif - // Element-dominant TEST(bump_views, matset_element_dominant_seq) { @@ -874,77 +914,6 @@ TEST(bump_views, matset_material_dominant_hip) } #endif -TEST(bump_views, matset_multibuffer) -{ - const char *yaml = R"( -matsets: - matset: - topology: topology - volume_fractions: - a: - values: [0, 0, 0, 0.33, 0, 0.3] # [0, 0, 0, a1, 0, a0] - indices: [5, 3] - b: - values: [0, 0.7, 1., 0.67, 0] # [0, b0, b2, b1, 0] - indices: [1, 3, 2] - material_map: # (optional) - a: 0 - b: 1 -)"; - conduit::Node matsets; - matsets.parse(yaml); - const conduit::Node &n_matset = matsets["matsets/matset"]; - axom::bump::views::dispatch_material_multibuffer(n_matset, [&](auto matsetView) { - using IDList = typename decltype(matsetView)::IDList; - using VFList = typename decltype(matsetView)::VFList; - using VFType = typename VFList::value_type; - - EXPECT_EQ(matsetView.numberOfZones(), 3); - EXPECT_EQ(matsetView.numberOfMaterials(0), 2); - EXPECT_EQ(matsetView.numberOfMaterials(1), 2); - EXPECT_EQ(matsetView.numberOfMaterials(2), 1); - - IDList m0, m1, m2; - VFList vf0, vf1, vf2; - matsetView.zoneMaterials(0, m0, vf0); - EXPECT_EQ(m0.size(), 2); - EXPECT_EQ(vf0.size(), 2); - EXPECT_EQ(m0[0], 0); - EXPECT_EQ(m0[1], 1); - EXPECT_EQ(vf0[0], 0.3); - EXPECT_EQ(vf0[1], 0.7); - - VFType vf; - EXPECT_TRUE(matsetView.zoneContainsMaterial(0, 0, vf)); - EXPECT_EQ(vf, 0.3); - EXPECT_TRUE(matsetView.zoneContainsMaterial(0, 1, vf)); - EXPECT_EQ(vf, 0.7); - - matsetView.zoneMaterials(1, m1, vf1); - EXPECT_EQ(m1.size(), 2); - EXPECT_EQ(vf1.size(), 2); - EXPECT_EQ(m1[0], 0); - EXPECT_EQ(m1[1], 1); - EXPECT_EQ(vf1[0], 0.33); - EXPECT_EQ(vf1[1], 0.67); - - EXPECT_TRUE(matsetView.zoneContainsMaterial(1, 0, vf)); - EXPECT_EQ(vf, 0.33); - EXPECT_TRUE(matsetView.zoneContainsMaterial(1, 1, vf)); - EXPECT_EQ(vf, 0.67); - - matsetView.zoneMaterials(2, m2, vf2); - EXPECT_EQ(m2.size(), 1); - EXPECT_EQ(m2[0], 1); - EXPECT_EQ(vf2[0], 1); - - EXPECT_FALSE(matsetView.zoneContainsMaterial(2, 0, vf)); - EXPECT_EQ(vf, 0.); - EXPECT_TRUE(matsetView.zoneContainsMaterial(2, 1, vf)); - EXPECT_EQ(vf, 1.); - }); -} - //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { diff --git a/src/axom/bump/views/MaterialView.hpp b/src/axom/bump/views/MaterialView.hpp index 00f72ba2da..dd3c74ee1d 100644 --- a/src/axom/bump/views/MaterialView.hpp +++ b/src/axom/bump/views/MaterialView.hpp @@ -45,6 +45,17 @@ using MaterialInformation = std::vector; */ MaterialInformation materials(const conduit::Node &matset); +/*! + * \brief This struct can encode some positional information about the material + * view const_iterators. + */ +struct IteratorIndex +{ + axom::IndexType m_zoneIndex {0}; //!< Which zone we're working on + axom::IndexType m_bufferIndex {0}; //!< Which buffer we're working on + axom::IndexType m_localIndex {0}; //!< The element in the buffer we're working in +}; + //--------------------------------------------------------------------------- // Material views - These objects are meant to wrap Blueprint Matsets behind // an interface that lets us query materials for a single @@ -53,7 +64,7 @@ MaterialInformation materials(const conduit::Node &matset); //--------------------------------------------------------------------------- /*! - \brief Material view for unibuffer matsets. + \brief Material view for unibuffer element-dominant matsets. \tparam IndexT The integer type used for material data. \tparam FloatT The floating point type used for material data (volume fractions). @@ -222,6 +233,10 @@ class UnibufferMaterialView return m_currentIndex != rhs.m_currentIndex || m_zoneIndex != rhs.m_zoneIndex || m_view != rhs.m_view; } + IteratorIndex AXOM_HOST_DEVICE index() const + { + return IteratorIndex {static_cast(m_zoneIndex), 0, m_index}; + } private: DISABLE_DEFAULT_CTOR(const_iterator); @@ -239,7 +254,7 @@ class UnibufferMaterialView void AXOM_HOST_DEVICE advance(bool doIncrement) { m_currentIndex += (doIncrement && m_currentIndex < size()) ? 1 : 0; - const auto idx = m_view->m_offsets[m_zoneIndex] + m_currentIndex; + const axom::IndexType idx = m_view->m_offsets[m_zoneIndex] + m_currentIndex; if(idx < m_view->m_indices.size()) { m_index = m_view->m_indices[idx]; @@ -293,304 +308,7 @@ class UnibufferMaterialView }; /*! - \brief View for multi-buffer matsets. - - \tparam IndexT The integer type used for material data. - \tparam FloatT The floating point type used for material data (volume fractions). - \tparam MAXMATERIALS The maximum number of materials to support. - - \verbatim - -matsets: - matset: - topology: topology - volume_fractions: - a: - values: [0, 0, 0, a1, 0, a0] - indices: [5, 3] - b: - values: [0, b0, b2, b1, 0] - indices: [1, 3, 2] - material_map: # (optional) - a: 0 - b: 1 - - \endverbatim - */ -template -class MultiBufferMaterialView -{ -public: - using MaterialID = IndexT; - using ZoneIndex = IndexT; - using IndexType = IndexT; - using FloatType = FloatT; - using IDList = StaticArray; - using VFList = StaticArray; - - constexpr static axom::IndexType MaxMaterials = MAXMATERIALS; - constexpr static axom::IndexType InvalidIndex = -1; - - void add(MaterialID matno, - const axom::ArrayView &indices, - const axom::ArrayView &vfs) - { - SLIC_ASSERT(m_size + 1 < MaxMaterials); -#if !defined(AXOM_DEVICE_CODE) - const auto begin = m_matnos.data(); - const auto end = begin + m_size; - SLIC_ERROR_IF(std::find(begin, end, matno) != end, - "Adding a duplicate material number is not allowed."); -#endif - m_indices[m_size] = indices; - m_values[m_size] = vfs; - m_matnos[m_size] = matno; - m_size++; - } - - AXOM_HOST_DEVICE - axom::IndexType numberOfZones() const - { - axom::IndexType nzones = 0; - for(int i = 0; i < m_size; i++) nzones = axom::utilities::max(nzones, m_indices[i].size()); - return nzones; - } - - AXOM_HOST_DEVICE - axom::IndexType numberOfMaterials(ZoneIndex zi) const - { - axom::IndexType nmats = 0; - for(axom::IndexType i = 0; i < m_size; i++) - { - const auto &curIndices = m_indices[i]; - const auto &curValues = m_values[i]; - - if(zi < static_cast(curIndices.size())) - { - const auto idx = curIndices[zi]; - nmats += (curValues[idx] > 0) ? 1 : 0; - } - } - - return nmats; - } - - AXOM_HOST_DEVICE - void zoneMaterials(ZoneIndex zi, IDList &ids, VFList &vfs) const - { - ids.clear(); - vfs.clear(); - - for(axom::IndexType i = 0; i < m_size; i++) - { - const auto &curIndices = m_indices[i]; - const auto &curValues = m_values[i]; - - if(zi < static_cast(curIndices.size())) - { - const auto idx = curIndices[zi]; - if(curValues[idx] > 0) - { - ids.push_back(m_matnos[i]); - vfs.push_back(curValues[idx]); - } - } - } - } - - AXOM_HOST_DEVICE - axom::IndexType zoneMaterials(ZoneIndex zi, - axom::ArrayView &ids, - axom::ArrayView &vfs) const - { - axom::IndexType n = 0; - for(axom::IndexType i = 0; i < m_size; i++) - { - const auto &curIndices = m_indices[i]; - const auto &curValues = m_values[i]; - - if(zi < static_cast(curIndices.size())) - { - const auto idx = curIndices[zi]; - if(curValues[idx] > 0) - { - ids[n] = m_matnos[i]; - vfs[n] = curValues[idx]; - n++; - } - } - } - return n; - } - - AXOM_HOST_DEVICE - bool zoneContainsMaterial(ZoneIndex zi, MaterialID mat) const - { - FloatType tmp {}; - return zoneContainsMaterial(zi, mat, tmp); - } - - AXOM_HOST_DEVICE - bool zoneContainsMaterial(ZoneIndex zi, MaterialID mat, FloatType &vf) const - { - bool found = false; - vf = FloatType {}; - axom::IndexType mi = indexOfMaterialID(mat); - if(mi != InvalidIndex) - { - const auto &curIndices = m_indices[mi]; - const auto &curValues = m_values[mi]; - if(zi < static_cast(curIndices.size())) - { - const auto idx = curIndices[zi]; - vf = curValues[idx]; - found = curValues[idx] > 0; - } - } - return found; - } - - /*! - * \brief An iterator class for iterating over read-only data in a zone. - * The iterator can access material ids and volume fractions for one - * material at a time in the associated zone. - */ - class const_iterator - { - // Let the material view call the const_iterator constructor. - friend class MultiBufferMaterialView; - - public: - /// Get the current material id for the iterator. - MaterialID AXOM_HOST_DEVICE material_id() const - { - SLIC_ASSERT(m_currentIndex < m_view->m_size); - return m_view->m_matnos[m_currentIndex]; - } - /// Get the current volume fraction for the iterator. - FloatType AXOM_HOST_DEVICE volume_fraction() const - { - SLIC_ASSERT(m_currentIndex < m_view->m_size); - const auto &curIndices = m_view->m_indices[m_currentIndex]; - const auto &curValues = m_view->m_values[m_currentIndex]; - const auto idx = curIndices[m_zoneIndex]; - return curValues[idx]; - } - axom::IndexType AXOM_HOST_DEVICE size() const { return m_view->numberOfMaterials(m_zoneIndex); } - ZoneIndex AXOM_HOST_DEVICE zoneIndex() const { return m_zoneIndex; } - void AXOM_HOST_DEVICE operator++() - { - m_currentIndex += (m_currentIndex < m_view->m_size) ? 1 : 0; - advance(); - } - void AXOM_HOST_DEVICE operator++(int) - { - m_currentIndex += (m_currentIndex < m_view->m_size) ? 1 : 0; - advance(); - } - bool AXOM_HOST_DEVICE operator==(const const_iterator &rhs) const - { - return m_currentIndex == rhs.m_currentIndex && m_zoneIndex == rhs.m_zoneIndex && - m_view == rhs.m_view; - } - bool AXOM_HOST_DEVICE operator!=(const const_iterator &rhs) const - { - return m_currentIndex != rhs.m_currentIndex || m_zoneIndex != rhs.m_zoneIndex || - m_view != rhs.m_view; - } - - private: - DISABLE_DEFAULT_CTOR(const_iterator); - - /// Constructor - AXOM_HOST_DEVICE const_iterator(const MultiBufferMaterialView *view, - ZoneIndex zoneIndex, - axom::IndexType currentIndex = 0) - : m_view(view) - , m_zoneIndex(zoneIndex) - , m_currentIndex(currentIndex) - { } - - /// Advance to the next valid material slot for the zone. - void AXOM_HOST_DEVICE advance() - { - while(m_currentIndex < m_view->m_size) - { - const auto &curIndices = m_view->m_indices[m_currentIndex]; - const auto &curValues = m_view->m_values[m_currentIndex]; - - if(m_zoneIndex < static_cast(curIndices.size())) - { - const auto idx = curIndices[m_zoneIndex]; - if(curValues[idx] > 0) - { - break; - } - } - m_currentIndex++; - } - } - - const MultiBufferMaterialView *m_view; - ZoneIndex m_zoneIndex; - axom::IndexType m_currentIndex; - }; - // Let the const_iterator access members. - friend class const_iterator; - - /*! - * \brief Return the iterator for the beginning of a zone's material data. - * - * \param zi The zone index being queried. - * - * \return The iterator for the beginning of a zone's material data. - */ - const_iterator AXOM_HOST_DEVICE beginZone(ZoneIndex zi) const - { - SLIC_ASSERT(zi < static_cast(numberOfZones())); - - auto it = const_iterator(this, zi, 0); - it.advance(); - return it; - } - - /*! - * \brief Return the iterator for the end of a zone's material data. - * - * \param zi The zone index being queried. - * - * \return The iterator for the end of a zone's material data. - */ - const_iterator AXOM_HOST_DEVICE endZone(ZoneIndex zi) const - { - SLIC_ASSERT(zi < static_cast(numberOfZones())); - return const_iterator(this, zi, m_size); - } - -private: - AXOM_HOST_DEVICE - axom::IndexType indexOfMaterialID(MaterialID mat) const - { - axom::IndexType index = InvalidIndex; - for(axom::IndexType mi = 0; mi < m_size; mi++) - { - if(mat == m_matnos[mi]) - { - index = mi; - break; - } - } - return index; - } - - axom::StackArray, MAXMATERIALS> m_values {}; - axom::StackArray, MAXMATERIALS> m_indices {}; - axom::StackArray m_matnos {}; - axom::IndexType m_size {0}; -}; - -/*! - \brief View for element-dominant matsets. + \brief View for multibuffer element-dominant matsets. \tparam IndexT The integer type used for material data. \tparam FloatT The floating point type used for material data (volume fractions). @@ -773,6 +491,10 @@ class ElementDominantMaterialView return m_currentIndex != rhs.m_currentIndex || m_zoneIndex != rhs.m_zoneIndex || m_view != rhs.m_view; } + IteratorIndex AXOM_HOST_DEVICE index() const + { + return IteratorIndex {static_cast(m_zoneIndex), m_currentIndex, m_zoneIndex}; + } private: DISABLE_DEFAULT_CTOR(const_iterator); @@ -869,7 +591,7 @@ class ElementDominantMaterialView }; /*! - \brief View for material-dominant matsets. + \brief View for multibuffer material-dominant matsets. \tparam IndexT The integer type used for material data. \tparam FloatT The floating point type used for material data (volume fractions). @@ -1085,6 +807,10 @@ class MaterialDominantMaterialView return m_miIndex != rhs.m_miIndex || m_index != rhs.m_index || m_zoneIndex != rhs.m_zoneIndex || m_view != rhs.m_view; } + IteratorIndex AXOM_HOST_DEVICE index() const + { + return IteratorIndex {static_cast(m_zoneIndex), m_miIndex, m_index}; + } private: DISABLE_DEFAULT_CTOR(const_iterator); diff --git a/src/axom/bump/views/MixedFieldView.hpp b/src/axom/bump/views/MixedFieldView.hpp new file mode 100644 index 0000000000..7e5f499b4a --- /dev/null +++ b/src/axom/bump/views/MixedFieldView.hpp @@ -0,0 +1,156 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_BUMP_VIEWS_MIXED_FIELD_VIEW_HPP_ +#define AXOM_BUMP_VIEWS_MIXED_FIELD_VIEW_HPP_ + +#include "axom/core/ArrayView.hpp" +#include "axom/core/StaticArray.hpp" +#include "axom/bump/views/MaterialView.hpp" + +namespace axom +{ +namespace bump +{ +namespace views +{ + +// Base template for MixedFieldTraits. These traits are used in conjunction with the +// MixedFieldView to access MatsetView-specific field data. +template +struct MixedFieldTraits +{ }; + +/*! + * \brief Specialization for UnibufferMaterialView that can store field data organized + * in a compatible manner. + */ +template +struct MixedFieldTraits, FieldT> +{ + using MatsetView = UnibufferMaterialView; + using ValueView = axom::ArrayView; + + /*! + * \brief Set the field values view. + * \param values The field values. + */ + void set(ValueView values) { m_values = values; } + + /*! + * \brief Return the field value for the iterator index. + * + * \param index The IteratorIndex that provides the indices to use for looking up the value. + * + * \return The field value at the provided index. + */ + AXOM_HOST_DEVICE FieldT value(const IteratorIndex &index) const + { + SLIC_ASSERT(axom::utilities::inBounds_0_N(index.m_localIndex, m_values.size())); + return m_values[index.m_localIndex]; + } + + ValueView m_values; +}; + +/*! + * \brief Specialization for ElementDominantMaterialView that can store field data organized + * in a compatible manner. + */ +template +struct MixedFieldTraits, FieldT> +{ + using MatsetView = ElementDominantMaterialView; + using ValueView = axom::ArrayView; + + /*! + * \brief Add the values to the list of buffers. + * \param values The values to be added. + */ + void add(ValueView values) { m_values.push_back(values); } + + /*! + * \brief Return the field value for the iterator index. + * + * \param index The IteratorIndex that provides the indices to use for looking up the value. + * + * \return The field value at the provided index. + */ + AXOM_HOST_DEVICE FieldT value(const IteratorIndex &index) const + { + SLIC_ASSERT(axom::utilities::inBounds_0_N(index.m_bufferIndex, m_values.size())); + SLIC_ASSERT( + axom::utilities::inBounds_0_N(index.m_localIndex, m_values[index.m_bufferIndex].size())); + return m_values[index.m_bufferIndex][index.m_localIndex]; + } + + axom::StaticArray m_values; +}; + +/*! + * \brief Specialization for MaterialDominantMaterialView that can store field data organized + * in a compatible manner. + */ +template +struct MixedFieldTraits, FieldT> +{ + using MatsetView = MaterialDominantMaterialView; + using ValueView = axom::ArrayView; + + /*! + * \brief Add the values to the list of buffers. + * \param values The values to be added. + */ + void add(ValueView values) { m_values.push_back(values); } + + /*! + * \brief Return the field value for the iterator index. + * + * \param index The IteratorIndex that provides the indices to use for looking up the value. + * + * \return The field value at the provided index. + */ + AXOM_HOST_DEVICE FieldT value(const IteratorIndex &index) const + { + SLIC_ASSERT(axom::utilities::inBounds_0_N(index.m_bufferIndex, m_values.size())); + SLIC_ASSERT( + axom::utilities::inBounds_0_N(index.m_localIndex, m_values[index.m_bufferIndex].size())); + return m_values[index.m_bufferIndex][index.m_localIndex]; + } + + axom::StaticArray m_values; +}; + +/*! + * \param This view enables the user to traverse Blueprint mixed field data using + * data from a MatsetView const_iterator. + */ +template +class MixedFieldView +{ +public: + using Traits = MixedFieldTraits; + + Traits &traits() { return m_traits; } + const Traits &traits() const { return m_traits; } + + /*! + * \brief Given a MatsetView's const_iterator, use it to look up the typed field + * data in the field. + */ + AXOM_HOST_DEVICE FieldT value(const typename MatsetView::const_iterator &it) const + { + return m_traits.value(it.index()); + } + +private: + Traits m_traits; +}; + +} // end namespace views +} // end namespace bump +} // end namespace axom +#endif diff --git a/src/axom/bump/views/dispatch_material.hpp b/src/axom/bump/views/dispatch_material.hpp index 8db64e49f9..5a16ea64c4 100644 --- a/src/axom/bump/views/dispatch_material.hpp +++ b/src/axom/bump/views/dispatch_material.hpp @@ -19,66 +19,50 @@ namespace bump { namespace views { - -/*! - * \brief Make a unibuffer matset view from a Conduit node. - */ -template -struct make_unibuffer_matset +namespace detail { - using MatsetView = UnibufferMaterialView; - /*! - * \brief Wrap the Conduit node as a unibuffer matset view. - * - * \param n_matset The Conduit node that contains the matset. - * - * \return A UnibufferMaterialView. - */ - static MatsetView view(const conduit::Node &n_matset) - { - namespace utils = axom::bump::utilities; - verify(n_matset, "matset"); - MatsetView m; - m.set(utils::make_array_view(n_matset["material_ids"]), - utils::make_array_view(n_matset["volume_fractions"]), - utils::make_array_view(n_matset["sizes"]), - utils::make_array_view(n_matset["offsets"]), - utils::make_array_view(n_matset["indices"])); - return m; - } -}; +inline void verifyMixedField(const conduit::Node &n_field) +{ + SLIC_ERROR_IF(!n_field.has_path("matset_values"), + "The mixed field does not contain matset_values"); +} /*! - * \brief Dispatch a Conduit node containing a unibuffer matset to a function as the appropriate type of matset view. + * \brief Dispatch Conduit nodes containing a unibuffer element-dominant matset and a values array + * to a function as the appropriate type of matset view. * * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. + * \param values The node that contains the values to be used as volume fractions. * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) +bool dispatch_material_unibuffer_with_values(const conduit::Node &matset, + const conduit::Node &values, + FuncType &&func) { bool retval = false; verify(matset, "matset"); if(conduit::blueprint::mesh::matset::is_uni_buffer(matset)) { - indexNodeToArrayViewSame( - matset["material_ids"], - matset["sizes"], - matset["offsets"], - matset["indices"], - [&](auto material_ids, auto sizes, auto offsets, auto indices) { - floatNodeToArrayView(matset["volume_fractions"], [&](auto volume_fractions) { - using IndexType = typename decltype(material_ids)::value_type; - using FloatType = typename decltype(volume_fractions)::value_type; - - UnibufferMaterialView matsetView; - matsetView.set(material_ids, volume_fractions, sizes, offsets, indices); - func(matsetView); - }); - }); + indexNodeToArrayViewSame(matset["material_ids"], + matset["sizes"], + matset["offsets"], + matset["indices"], + [&](auto material_ids, auto sizes, auto offsets, auto indices) { + floatNodeToArrayView(values, [&](auto typedValues) { + using IndexType = typename decltype(material_ids)::value_type; + using FloatType = typename decltype(typedValues)::value_type; + + UnibufferMaterialView matsetView; + matsetView.set(material_ids, typedValues, sizes, offsets, indices); + func(matsetView); + }); + }); retval = true; } return retval; @@ -111,85 +95,30 @@ IntElement getMaterialID(const conduit::Node &matset, } /*! - * \brief Dispatch a Conduit node containing a multibuffer matset to a function as the appropriate type of matset view. + * \brief Dispatch a Conduit node containing a multibuffer element-dominant matset + * to a function as the appropriate type of matset view. * * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. + * \param values_object The node that contains the values object to be used as volume fractions. * \param func The function/lambda that will operate on the matset view. - */ -template -bool dispatch_material_multibuffer(const conduit::Node &matset, FuncType &&func) -{ - bool retval = false; - verify(matset, "matset"); - if(conduit::blueprint::mesh::matset::is_multi_buffer(matset)) - { - const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); - if(volume_fractions.number_of_children() > 0) - { - const conduit::Node &n_firstValues = volume_fractions[0].fetch_existing("values"); - const conduit::Node &n_firstIndices = volume_fractions[0].fetch_existing("indices"); - indexNodeToArrayView(n_firstIndices, [&](auto firstIndices) { - floatNodeToArrayView(n_firstValues, [&](auto firstValues) { - using IntElement = - typename std::remove_const::type; - using FloatElement = - typename std::remove_const::type; - using IntView = axom::ArrayView; - using FloatView = axom::ArrayView; - - MultiBufferMaterialView matsetView; - - for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) - { - const conduit::Node &values = volume_fractions[i].fetch_existing("values"); - const conduit::Node &indices = volume_fractions[i].fetch_existing("indices"); - - const IntElement *indices_ptr = indices.value(); - const FloatElement *values_ptr = values.value(); - - IntView indices_view(const_cast(indices_ptr), - indices.dtype().number_of_elements()); - FloatView values_view(const_cast(values_ptr), - values.dtype().number_of_elements()); - - // Get the material number if we can. - IntElement matno = getMaterialID(matset, - volume_fractions[i].name(), - static_cast(i)); - - matsetView.add(matno, indices_view, values_view); - } - - func(matsetView); - }); - }); - retval = true; - } - } - return retval; -} - -/*! - * \brief Dispatch a Conduit node containing a element-dominant matset to a function as the appropriate type of matset view. - * - * \tparam FuncType The function/lambda type that will take the matset. * - * \param matset The node that contains the matset. - * \param func The function/lambda that will operate on the matset view. + * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType &&func) +bool dispatch_material_element_dominant_with_values(const conduit::Node &matset, + const conduit::Node &values_object, + FuncType &&func) { bool retval = false; verify(matset, "matset"); - if(conduit::blueprint::mesh::matset::is_element_dominant(matset)) + if(conduit::blueprint::mesh::matset::is_multi_buffer(matset) && + conduit::blueprint::mesh::matset::is_element_dominant(matset)) { - const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); - if(volume_fractions.number_of_children() > 0) + if(values_object.number_of_children() > 0) { - const conduit::Node &n_firstValues = volume_fractions[0]; + const conduit::Node &n_firstValues = values_object[0]; floatNodeToArrayView(n_firstValues, [&](auto firstValues) { using FloatElement = typename std::remove_const::type; @@ -198,16 +127,16 @@ bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType && ElementDominantMaterialView matsetView; - for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) + for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) { - const conduit::Node &values = volume_fractions[i]; + const conduit::Node &values = values_object[i]; const FloatElement *values_ptr = values.value(); FloatView values_view(const_cast(values_ptr), values.dtype().number_of_elements()); // Get the material number if we can. IntElement matno = - getMaterialID(matset, volume_fractions[i].name(), static_cast(i)); + getMaterialID(matset, values_object[i].name(), static_cast(i)); matsetView.add(matno, values_view); } @@ -226,21 +155,26 @@ bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType && * \tparam FuncType The function/lambda type that will take the matset. * * \param matset The node that contains the matset. + * \param values_object The node that contains the values to use as volume fractions and indices. * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. */ template -bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType &&func) +bool dispatch_material_material_dominant_with_values(const conduit::Node &matset, + const conduit::Node &values_object, + FuncType &&func) { bool retval = false; verify(matset, "matset"); - if(conduit::blueprint::mesh::matset::is_material_dominant(matset)) + if(conduit::blueprint::mesh::matset::is_multi_buffer(matset) && + conduit::blueprint::mesh::matset::is_material_dominant(matset)) { - const conduit::Node &volume_fractions = matset.fetch_existing("volume_fractions"); const conduit::Node &element_ids = matset.fetch_existing("element_ids"); - if(volume_fractions.number_of_children() > 0 && - volume_fractions.number_of_children() == element_ids.number_of_children()) + if(values_object.number_of_children() > 0 && + values_object.number_of_children() == element_ids.number_of_children()) { - const conduit::Node &n_firstValues = volume_fractions[0]; + const conduit::Node &n_firstValues = values_object[0]; const conduit::Node &n_firstIndices = element_ids[0]; indexNodeToArrayView(n_firstIndices, [&](auto firstIndices) { @@ -254,10 +188,10 @@ bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType & MaterialDominantMaterialView matsetView; - for(conduit::index_t i = 0; i < volume_fractions.number_of_children(); i++) + for(conduit::index_t i = 0; i < values_object.number_of_children(); i++) { const conduit::Node &indices = element_ids[i]; - const conduit::Node &values = volume_fractions[i]; + const conduit::Node &values = values_object[i]; const IntElement *indices_ptr = indices.value(); const FloatElement *values_ptr = values.value(); @@ -283,6 +217,98 @@ bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType & return retval; } +} // end namespace detail + +/*! + * \brief Make a unibuffer matset view from a Conduit node. + */ +template +struct make_unibuffer_matset +{ + using MatsetView = UnibufferMaterialView; + + /*! + * \brief Wrap the Conduit node as a unibuffer matset view. + * + * \param n_matset The Conduit node that contains the matset. + * + * \return A UnibufferMaterialView. + */ + static MatsetView view(const conduit::Node &n_matset) + { + namespace utils = axom::bump::utilities; + verify(n_matset, "matset"); + MatsetView m; + m.set(utils::make_array_view(n_matset["material_ids"]), + utils::make_array_view(n_matset["volume_fractions"]), + utils::make_array_view(n_matset["sizes"]), + utils::make_array_view(n_matset["offsets"]), + utils::make_array_view(n_matset["indices"])); + return m; + } +}; + +/*! + * \brief Dispatch a Conduit node containing a unibuffer matset to a function as + * the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. + */ +template +bool dispatch_material_unibuffer(const conduit::Node &matset, FuncType &&func) +{ + verify(matset, "matset"); + return detail::dispatch_material_unibuffer_with_values( + matset, + matset["volume_fractions"], + std::forward(func)); +} + +/*! + * \brief Dispatch a Conduit node containing a element-dominant matset to a function as + * the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. + */ +template +bool dispatch_material_element_dominant(const conduit::Node &matset, FuncType &&func) +{ + verify(matset, "matset"); + return detail::dispatch_material_element_dominant_with_values(matset, + matset["volume_fractions"], + std::forward(func)); +} + +/*! + * \brief Dispatch a Conduit node containing a material-dominant matset to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset. + * + * \param matset The node that contains the matset. + * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. + */ +template +bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType &&func) +{ + verify(matset, "matset"); + return detail::dispatch_material_material_dominant_with_values( + matset, + matset["volume_fractions"], + std::forward(func)); +} + /*! * \brief Dispatch a Conduit node containing a matset to a function as the appropriate type of matset view. * @@ -290,24 +316,32 @@ bool dispatch_material_material_dominant(const conduit::Node &matset, FuncType & * * \param matset The node that contains the matset. * \param func The function/lambda that will operate on the matset view. + * + * \return true if the dispatch worked, false otherwise. */ template bool dispatch_material(const conduit::Node &matset, FuncType &&func) { bool retval = dispatch_material_unibuffer(matset, std::forward(func)); + // Multibuffer if(!retval) { - retval = - dispatch_material_multibuffer(matset, std::forward(func)); + retval = dispatch_material_element_dominant(matset, + std::forward(func)); } if(!retval) { - retval = dispatch_material_element_dominant(matset, std::forward(func)); + retval = + dispatch_material_material_dominant(matset, + std::forward(func)); } - if(!retval) + // NOTE: Blueprint describes a unibuffer material-dominant matset type but does not technically implement it. + // https://llnl-conduit.readthedocs.io/en/latest/blueprint_mesh.html#material-sets + if(!retval && conduit::blueprint::mesh::matset::is_uni_buffer(matset) && + conduit::blueprint::mesh::matset::is_material_dominant(matset)) { - retval = dispatch_material_material_dominant(matset, std::forward(func)); + SLIC_ERROR("Unibuffer material dominant matsets are unsupported."); } return retval; } diff --git a/src/axom/bump/views/dispatch_material_field.hpp b/src/axom/bump/views/dispatch_material_field.hpp new file mode 100644 index 0000000000..f2d806c7e1 --- /dev/null +++ b/src/axom/bump/views/dispatch_material_field.hpp @@ -0,0 +1,211 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other +// Axom Project Contributors. See top-level LICENSE and COPYRIGHT +// files for dates and other details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_BUMP_DISPATCH_MATERIAL_FIELD_HPP_ +#define AXOM_BUMP_DISPATCH_MATERIAL_FIELD_HPP_ +#include "axom/bump/views/dispatch_material.hpp" +#include "axom/bump/views/MixedFieldView.hpp" + +namespace axom +{ +namespace bump +{ +namespace views +{ +namespace detail +{ +/*! + * \brief Dispatch a unibuffer matset_values field. + * + * \tparam MatsetView The type of matset view associated with the field. This has + * implications for the field storage. + * \tparam FuncType The function/lambda type to call on the MixedFieldView. + */ +template +bool dispatch_unibuffer_field(const conduit::Node &n_field, FuncType &&func) +{ + bool rv = false; + detail::verifyMixedField(n_field); + const conduit::Node &matset_values = n_field["matset_values"]; + SLIC_ERROR_IF(!matset_values.dtype().is_number(), "The matset_values must be a number."); + // NOTE: For now support float, double types. + axom::bump::views::floatNodeToArrayView(matset_values, [&](auto valuesView) { + using FieldT = typename decltype(valuesView)::value_type; + MixedFieldView mixedFieldView; + mixedFieldView.traits().set(valuesView); + func(mixedFieldView); + rv = true; + }); + return rv; +} + +/*! + * \brief Dispatch a multibuffer matset_values field. + * + * \tparam MatsetView The type of matset view associated with the field. This has + * implications for the field storage. + * \tparam FuncType The function/lambda type to call on the MixedFieldView. + */ +template +bool dispatch_multibuffer_field(const conduit::Node &n_field, FuncType &&func) +{ + bool rv = false; + detail::verifyMixedField(n_field); + const conduit::Node &matset_values = n_field["matset_values"]; + SLIC_ERROR_IF(matset_values.number_of_children() <= 1, "Missing fields in matset_values."); + // NOTE: For now support float, double types. + axom::bump::views::floatNodeToArrayView(matset_values[0], [&](auto firstValuesView) { + using FieldT = typename decltype(firstValuesView)::value_type; + MixedFieldView mixedFieldView; + mixedFieldView.traits().add(firstValuesView); + for(conduit::index_t i = 1; i < matset_values.number_of_children(); i++) + { + mixedFieldView.traits().add(axom::bump::utilities::make_array_view(matset_values[i])); + } + func(mixedFieldView); + rv = true; + }); + return rv; +} + +} // end namespace detail + +/*! + * \brief Dispatch Conduit nodes containing a unibuffer matset and a values array + * to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset and mixed field view. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset and mixed field views. + */ +template +bool dispatch_material_unibuffer_field(const conduit::Node &matset, + const conduit::Node &n_field, + FuncType &&func) +{ + verify(matset, "matset"); + detail::verifyMixedField(n_field); + + auto handleMatset = [&](auto matsetView) { + using MatsetView = decltype(matsetView); + detail::dispatch_unibuffer_field(n_field, [&](auto mixedFieldView) { + func(matsetView, mixedFieldView); + }); + }; + + return dispatch_material_unibuffer( + matset, + std::forward(handleMatset)); +} + +/*! + * \brief Dispatch Conduit nodes containing a element-dominant matset and related field + * to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset and mixed field view. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset and mixed field views. + */ +template +bool dispatch_material_element_dominant_field(const conduit::Node &matset, + const conduit::Node &n_field, + FuncType &&func) +{ + verify(matset, "matset"); + detail::verifyMixedField(n_field); + auto handleMatset = [&](auto matsetView) { + using MatsetView = decltype(matsetView); + detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) { + func(matsetView, mixedFieldView); + }); + }; + + return dispatch_material_element_dominant( + matset, + std::forward(handleMatset)); +} + +/*! + * \brief Dispatch Conduit nodes containing a material-dominant matset and related field + * to a function as the appropriate type of matset view. + * + * \tparam FuncType The function/lambda type that will take the matset and mixed field view. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset and mixed field views. + */ +template +bool dispatch_material_material_dominant_field(const conduit::Node &matset, + const conduit::Node &n_field, + FuncType &&func) +{ + verify(matset, "matset"); + detail::verifyMixedField(n_field); + auto handleMatset = [&](auto matsetView) { + using MatsetView = decltype(matsetView); + detail::dispatch_multibuffer_field(n_field, [&](auto mixedFieldView) { + func(matsetView, mixedFieldView); + }); + }; + + return dispatch_material_material_dominant( + matset, + std::forward(handleMatset)); +} + +/*! + * \brief Dispatch Conduit nodes containing a matset and related field + * to a function as the appropriate type of matset view. The matset will be used + * to access the per-material field data. + * + * \tparam FuncType The function/lambda type that will take the matset and mixed field view. + * + * \param matset The node that contains the matset. + * \param n_field The node that contains the values to be used as volume fractions / field. + * \param func The function/lambda that will operate on the matset and mixed field views. + */ +template +bool dispatch_material_field(const conduit::Node &matset, const conduit::Node &n_field, FuncType &&func) +{ + bool retval = + dispatch_material_unibuffer_field(matset, + n_field, + std::forward(func)); + // Multibuffer + if(!retval) + { + retval = + dispatch_material_element_dominant_field(matset, + n_field, + std::forward(func)); + } + if(!retval) + { + retval = dispatch_material_material_dominant_field( + matset, + n_field, + std::forward(func)); + } + // NOTE: Blueprint describes a unibuffer material-dominant matset type but does not technically implement it. + // https://llnl-conduit.readthedocs.io/en/latest/blueprint_mesh.html#material-sets + if(!retval && conduit::blueprint::mesh::matset::is_uni_buffer(matset) && + conduit::blueprint::mesh::matset::is_material_dominant(matset)) + { + SLIC_ERROR("Unibuffer material dominant matsets are unsupported."); + } + return retval; +} + +} // end namespace views +} // end namespace bump +} // end namespace axom + +#endif diff --git a/src/axom/mir/tests/mir_elvira2d.cpp b/src/axom/mir/tests/mir_elvira2d.cpp index 7441711f66..dc020d4779 100644 --- a/src/axom/mir/tests/mir_elvira2d.cpp +++ b/src/axom/mir/tests/mir_elvira2d.cpp @@ -52,7 +52,13 @@ struct braid2d_mat_test axom::StackArray dims {10, 10}; axom::StackArray zoneDims {dims[0] - 1, dims[1] - 1}; axom::blueprint::testing::data::braid(type, dims, n_mesh); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, n_mesh); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, + "mesh", + zoneDims, + cleanMats, + makeMixedField, + n_mesh); } // Select a chunk of clean and mixed zones. diff --git a/src/axom/mir/tests/mir_equiz2d.cpp b/src/axom/mir/tests/mir_equiz2d.cpp index 1e759da3aa..83fffa2c45 100644 --- a/src/axom/mir/tests/mir_equiz2d.cpp +++ b/src/axom/mir/tests/mir_equiz2d.cpp @@ -69,7 +69,13 @@ void braid2d_mat_test(const std::string &type, const std::string domainName = axom::fmt::format("domain_{:07}", dom); conduit::Node &hostDomain = (nDomains > 1) ? hostMesh[domainName] : hostMesh; axom::blueprint::testing::data::braid(type, dims, hostDomain); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, hostDomain); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, + "mesh", + zoneDims, + cleanMats, + makeMixedField, + hostDomain); TestApp.saveVisualization(name + "_orig", hostDomain); } diff --git a/src/axom/mir/tests/mir_equiz3d.cpp b/src/axom/mir/tests/mir_equiz3d.cpp index aea22da66e..1dff31f326 100644 --- a/src/axom/mir/tests/mir_equiz3d.cpp +++ b/src/axom/mir/tests/mir_equiz3d.cpp @@ -33,7 +33,13 @@ void braid3d_mat_test(const std::string &type, const std::string &mattype, const const bool cleanMats = false; conduit::Node hostMesh, deviceMesh; axom::blueprint::testing::data::braid(type, dims, hostMesh); - axom::blueprint::testing::data::make_matset(mattype, "mesh", zoneDims, cleanMats, hostMesh); + const bool makeMixedField = false; // for now + axom::blueprint::testing::data::make_matset(mattype, + "mesh", + zoneDims, + cleanMats, + makeMixedField, + hostMesh); utils::copy(deviceMesh, hostMesh); TestApp.saveVisualization(name + "_orig", hostMesh);