diff --git a/src/axom/multimat/docs/sphinx/figures/axom_multimat_diagrams.pptx b/src/axom/multimat/docs/sphinx/figures/axom_multimat_diagrams.pptx new file mode 100644 index 0000000000..26faa36b3e Binary files /dev/null and b/src/axom/multimat/docs/sphinx/figures/axom_multimat_diagrams.pptx differ diff --git a/src/axom/multimat/docs/sphinx/figures/mapping.png b/src/axom/multimat/docs/sphinx/figures/mapping.png new file mode 100644 index 0000000000..7c2e10cc67 Binary files /dev/null and b/src/axom/multimat/docs/sphinx/figures/mapping.png differ diff --git a/src/axom/multimat/docs/sphinx/figures/relation.png b/src/axom/multimat/docs/sphinx/figures/relation.png new file mode 100644 index 0000000000..a2ad0f09a9 Binary files /dev/null and b/src/axom/multimat/docs/sphinx/figures/relation.png differ diff --git a/src/axom/multimat/docs/sphinx/figures/sparsity.png b/src/axom/multimat/docs/sphinx/figures/sparsity.png new file mode 100644 index 0000000000..40fb5da078 Binary files /dev/null and b/src/axom/multimat/docs/sphinx/figures/sparsity.png differ diff --git a/src/axom/multimat/docs/sphinx/figures/volume_fractions.png b/src/axom/multimat/docs/sphinx/figures/volume_fractions.png new file mode 100644 index 0000000000..ee12c7bdfa Binary files /dev/null and b/src/axom/multimat/docs/sphinx/figures/volume_fractions.png differ diff --git a/src/axom/multimat/docs/sphinx/index.rst b/src/axom/multimat/docs/sphinx/index.rst index f063aa4d56..9276d66fd3 100644 --- a/src/axom/multimat/docs/sphinx/index.rst +++ b/src/axom/multimat/docs/sphinx/index.rst @@ -7,6 +7,33 @@ Multimat User Guide ==================== Axom's MultiMat component is a data management library for multimaterial field data -within multiphysics simulation codes. +within multiphysics simulation codes. Simulation codes use materials to overlay extra +parts and details onto a mesh without requiring those features to be modeled +conformally. Instead of using cells to model the part geometry, the geometry is +instead represented using materials and volume fractions. The method for adding +such details is often called *"shaping"* and it is covered in Axom's +:doc:`Klee <../../../../axom/klee/docs/sphinx/index>` and :doc:`Quest <../../../../axom/quest/docs/sphinx/index>` +components. -We are working on documentation for this component. +In addition to representing materials on a mesh, MultiMat is used to define +fields on the mesh, and over material subsets. This enables fields to contain multiple +values where needed for mixed-material cells. MultiMat supports flexible data mappings, +layouts, and dense vs sparse field storage, allowing field data to occupy less memory +than would otherwise be necessary. + + + +API Documentation +----------------- + +Doxygen generated API documentation can be found here: `API documentation <../../../../doxygen/html/multimattop.html>`_ + + +.. toctree:: + :caption: Contents + :maxdepth: 1 + + multimat_materials + multimat_field_concepts + multimat_using_fields + multimat_dynamic_mode diff --git a/src/axom/multimat/docs/sphinx/multimat_dynamic_mode.rst b/src/axom/multimat/docs/sphinx/multimat_dynamic_mode.rst new file mode 100644 index 0000000000..62d6616ae0 --- /dev/null +++ b/src/axom/multimat/docs/sphinx/multimat_dynamic_mode.rst @@ -0,0 +1,33 @@ +.. ## Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level LICENSE file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +****************************************************** +Dynamic Mode +****************************************************** + +The distribution of materials in MultiMat is controlled by the +Cell-Material Relation (CMR). For many MultiMat use cases, this is set +once (static mode) and then fields are defined on the MultiMat object. MultiMat +also supports a dynamic mode that permits materials to move around in the mesh. + +When creating a MultiMat object using the default constructor, it will default +to static mode using ``CELL_DOM`` data layout with sparse data. The data layout +argument later impacts the ``addEntry()`` and ``removeEntry()`` methods that modify +the MultiMat object's CMR in dynamic mode. For those methods, when MultiMat is +created with a ``CELL_DOM`` data layout, it means that the first argument to +``addEntry()`` will be a cell number and the second will be a material number. + +To convert the MultiMat object to dynamic mode, call the ``convertToDynamic()`` +method. This method changes some internal representations (including field +organization) to better support dynamic modifications of the CMR. For example, +when changing to dynamic mode, ``SPARSE`` fields are converted to ``DENSE`` +so further changes do not require field data to be reallocated/reorganized +again. The CMR is modified using calls to the ``addEntry()`` and ``removeEntry()`` +methods. + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_dynamic_mode_begin + :end-before: _multimat_dynamic_mode_end + :language: C++ diff --git a/src/axom/multimat/docs/sphinx/multimat_field_concepts.rst b/src/axom/multimat/docs/sphinx/multimat_field_concepts.rst new file mode 100644 index 0000000000..46e86151c9 --- /dev/null +++ b/src/axom/multimat/docs/sphinx/multimat_field_concepts.rst @@ -0,0 +1,118 @@ +.. ## Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level LICENSE file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +****************************************************** +Field Concepts +****************************************************** + +Fields are data that are defined over a mesh, typically with one or more values +per cell. Fields can be scalar, indicating 1-component per cell - or they can +contain multiple components as with vector data (2+ components per cell). This +section talks about important MultiMat field concepts that determine where fields +live on the mesh and how their data are organized in memory. + +####################### +Field Mapping +####################### + +MultiMat is associates materials with cells in a mesh, possibly subdividing cells. +MultiMat includes the concept of field *mapping*, which is where on the mesh the +field data live. Fields can be defined over the cells, which is how most simulations +think about cell-centered fields. With MultiMat, fields can also be defined over +the materials, allowing for compact storage of material-level data. Fields can +also be defined over the cells/material pairs from the Cell-Material Relation (CMR), +allowing fields to have data values for each material in a cell. + +.. figure:: figures/mapping.png + :figwidth: 700px + + Diagram showing field mapping concept. + ++--------------------+-----------------------------------------------------------+ +| FieldMapping | Meaning | ++====================+===========================================================+ +| PER_CELL | The field contains up to ncells * ncomponents values (for | +| | dense storage) and there are ncomponents values per cell. | +| | For scalars *ncomponents* is 1 so the field length is | +| | ncells. | ++--------------------+-----------------------------------------------------------+ +| PER_MAT | The field contains nmats * ncomponents values and there | +| | are ncomponents values per material. This mapping allows | +| | fields to be defined over the entire material region and | +| | any cell that uses the material inherits the per-material | +| | field value, allowing for very compact storage of | +| | material-level properties. | ++--------------------+-----------------------------------------------------------+ +| PER_CELL_MAT | The field contains up to ncells * nmats * ncomponents (for| +| | dense storage). This mapping allows materials within a | +| | cell to have their own unique values, which makes them | +| | useful for tracking data at a sub-cell level. | ++--------------------+-----------------------------------------------------------+ + +####################### +Data Layout +####################### + +Simulation codes contain a variety of algorithms that may have a preference for how +data are arranged in memory to ensure good performance. MultiMat supports +fields with a ``PER_CELL_MAT`` mapping and there are two ways to organize such data. +Fields are said to be **Cell-Dominant** (``CELL_DOM``) if they are stored such that +each cell stores all of its material data to memory before proceeding to data for +the next cell. Fields are **Material-Dominant** (``MAT_DOM``) if the data for all +cells that use the material is stored before proceeding to the next material. +The data layout for multi-material data can be thought of as 2 nested for-loops where +the outer loop is the dominant loop. For example, if iterating over materials and +then cells, the data are stored using ``MAT_DOM`` layout. + ++--------------------+----------------------------------------------------------+ +| DataLayout | Meaning | ++====================+==========================================================+ +| CELL_DOM | Data are stored for each cell and then for each material | +| | like this *(c=cell, m=material)*: | +| | | +| | ``{c0m0, c0m1, c0m2, ..., c1m0, c1m1, c1m2, ...}`` | ++--------------------+----------------------------------------------------------+ +| MAT_DOM | Data are stored for each material and then for each cell | +| | like this *(m=material, c=cell)*: | +| | | +| | ``{m0c0, m0c1, m0c2, m0c3, ... , m1c0, m1c1, m1c2, ...}``| ++--------------------+----------------------------------------------------------+ + +####################### +Sparsity Layout +####################### + +Sparsity primarily concerns fields with ``PER_CELL_MAT`` mapping. When initializing +the MultiMat object, the CMR indicates how materials are distributed +over the mesh. It is completely acceptable for materials to skip over certain cells, +which makes sense if we think about materials as a way to divide up the mesh into +various regions or parts. There are ncells * nmats pairs of data that could be entered +for MultiMat fields. For ``DENSE`` fields, the field must contain ncells * nmats values, +with values present for cell/material pairs whether materials are present or not. +This is an easy way to specify the data but it wastes memory by including extra +values whose only purpose is to keep the rectangular shape of the data array. + +For large meshes, compressing out unnecessary values can save a lot of memory. MultiMat +supports a ``SPARSE`` layout that does not include any unnecessary values. If we +regard the CMR as a matrix of true/false values, a user must only provide field values +for ``SPARSE`` data where the CMR contains true values. + + +.. figure:: figures/sparsity.png + :figwidth: 700px + + Mixed-material volume fraction field with both DENSE and SPARSE representations. + + + ++--------------------+----------------------------------------------------------+ +| SparsityLayout | Meaning | ++====================+==========================================================+ +| DENSE | Data are provided for all ncells * nmats pairs, even if | +| | there is no cell/material that is valid. | ++--------------------+----------------------------------------------------------+ +| SPARSE | Data are provided for only the cell/material pairs that | +| | are valid according to the CMR. | ++--------------------+----------------------------------------------------------+ diff --git a/src/axom/multimat/docs/sphinx/multimat_materials.rst b/src/axom/multimat/docs/sphinx/multimat_materials.rst new file mode 100644 index 0000000000..a300f8dd9c --- /dev/null +++ b/src/axom/multimat/docs/sphinx/multimat_materials.rst @@ -0,0 +1,81 @@ +.. ## Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level LICENSE file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +****************************************************** +Materials +****************************************************** + +MultiMat defines materials over the cells in a mesh. This section describes how to +use the MultiMat object to define materials. + +####################### +Cell Material Relation +####################### + +The distribution of materials over the mesh is determined using the **Cell-Material Relation** *(CMR)*. +If a mesh has *N* cells and *M* materials, each cell can have up to *M* possible values. +This means the mesh will hold N*M values if each cell contains all materials. +There are multiple ways to specify the CMR to MultiMat. A static material decomposition +is described here, though the CMR can also be built :doc:`dynamically `. +The easiest method for defining the CMR is to provide a bool vector containing +true/false values for whether a cell/material combination is valid. + +.. figure:: figures/relation.png + :figwidth: 700px + + Diagram showing mixed-material mesh with CELL_DOM and MAT_DOM ways of defining the Cell-Material Relation. + +The following code shows how to initialize a MultiMat object with 9 cells and 3 materials +and build the CMR by populating a bool vector that is given to the MultiMat object. +In the following example, the relation is expressed using a **Cell-Dominant** +data layout (``CELL_DOM``). This means that the data will be arranged such that all material +values for cell 0 are given, followed by all of the material values for cell 1, until all +cells have provided their flags. The bool vector contains *true* if a material is present +in a cell and *false* if the material is not present. This CMR vector is essentially +a mask for which cell/material combinations are valid. Data can also be transposed +into a **Material-Dominant** data layout (``MAT_DOM``) in which the materials are +iterated first, followed by cells that use the current material. + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_materials_cmr_begin + :end-before: _multimat_materials_cmr_end + :language: C++ + +####################### +Volume Fractions +####################### + +The CMR determines which materials are present in each cell; volume fractions determine +how much material is in each cell. If a cell contains materials A and B +at 20% and 80%, respectively, then the volume fractions for those materials in the +cell are: *0.2* and *0.8*. Note that the sum of volume fractions in a cell should equal 1 +to account for all of the cell, though this is not enforced unless the ``isValid()`` +method is called. Volume fractions must be provided for every valid cell/material pair +in the CMR and they must be specified using the same data layout as data in the CMR. + +.. figure:: figures/volume_fractions.png + :figwidth: 600px + + Diagram showing mixed-material mesh with volume fractions shown in CELL_DOM table. + +Volume fractions are stored in MultiMat as a field and fields have an added concept +of sparsity. Fields can provide data for every possible cell/material pair; this is +called a dense field. Dense fields are easy to understand: they have values for +every cell/material pair, even for materials that are not actually present. Fields +can also be sparse, saving memory by eliminating the zeroes where a material does not +exist. + +Volume fraction data are provided to MultiMat wrapped in an ``axom::ArrayView`` object, +which provides the default values for the volume fractions. The *ArrayView* is passed +to MultiMat using the ``setVolfracField()`` method. The following example shows how to +pass a dense volume fraction field to MultiMat. Note the zeroes where the material is +not present. After adding volume fractions, the MultiMat object is fully constructed +and it can be used to store field data. + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_materials_volfracs_begin + :end-before: _multimat_materials_volfracs_end + :language: C++ + diff --git a/src/axom/multimat/docs/sphinx/multimat_using_fields.rst b/src/axom/multimat/docs/sphinx/multimat_using_fields.rst new file mode 100644 index 0000000000..d60255ffa0 --- /dev/null +++ b/src/axom/multimat/docs/sphinx/multimat_using_fields.rst @@ -0,0 +1,250 @@ +.. ## Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level LICENSE file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +****************************************************** +Using Fields +****************************************************** + +Fields store useful simulation quantities. MultiMat supports defining fields +on the mesh and material subsets of the mesh. This section describes how to +create fields and access their data. + +####################### +Adding a Field +####################### + +The ``addField()`` method adds a field to a MultiMat object. The method +accepts arguments that indicate the mapping, layout, and sparsity for the supplied +data, which are given using an ``axom::ArrayView``. The data given in the view are +copied into new memory managed by MultiMat. + +The field mapping argument indicates the space where the data live: the mesh cells, +the materials, or the cells/material regions defined over the mesh. The +data layout argument indicates how the data are organized with respect to cells and +materials. For data that have 1 value per cell, pass ``PER_CELL``. For data that have +1 value per material *(ignoring how many cells use the material)*, pass ``PER_MAT``. For +data that have a unique value per material within a cell, pass ``PER_CELL_MAT``. +For ``PER_CELL_MAT`` data, it is important to know the data layout. Pass ``CELL_DOM`` +if all of the material values for a cell are sequential neighbors in memory; otherwise +pass ``MAT_DOM``. + +Sparsity layout indicates whether the data array contains the maximum number of +values (numMaterials * numCells for ``DENSE`` fields) or whether it instead contains +only the subset of elements where materials are defined. The length of ``SPARSE`` +fields is determined by the number of true values in the Cell-Mesh Relation (CMR). + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_using_fields_addfields_begin + :end-before: _multimat_using_fields_addfields_end + :language: C++ + +^^^^^^^^^^^^^^^^^^^^^^^ +Multi-Component Data +^^^^^^^^^^^^^^^^^^^^^^^ + +MultiMat can store fields with multiple components (vector data) by passing a non-unity +stride in the last argument when adding a field. Multi-component data are arranged +in memory as a contiguous block where the components of the first element (cell or material) +exist sequentially in memory, followed immediately by the components for the next element, +and so on. + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_using_fields_multicomponent_begin + :end-before: _multimat_using_fields_multicomponent_end + :language: C++ + +^^^^^^^^^^^ +Allocators +^^^^^^^^^^^ + +MultiMat supports allocating data through allocators. There are 2 separate allocators. +The "Slam" allocator allocates data for internal data structures. The field allocator +is used to allocate field bulk data, which is useful to override when writing GPU +algorithms. Both allocators can be set at once using the ``setAllocatorID()`` method. + +* setAllocatorID() +* setSlamAllocatorID() +* setFieldAllocatorID() + +####################### +External Field Data +####################### + +MultiMat normally allocates its own memory for fields, however fields that point to +externally-allocated memory can also be added using the ``addExternalField()`` method. +This method has the same arguments as the ``addField()`` method, except that the +the supplied view is used as the field's actual data instead of being used to initialize +additional memory. The ``addExternalField()`` method is useful when MultiMat is +managing fields allocated and initialized externally, such as through Sidre, Conduit, +MFEM, etc. + +####################### +Removing a Field +####################### + +Removing a field is done by calling the ``removeField()`` method on the MultiMat object +and passing the name of the field to be removed. MultiMat will remove the field from +its list of fields and deallocate memory, as needed. For external fields, deallocating the +field's bulk data is the responsibility of the caller. + +.. code-block:: cpp + + mm.removeField("myField"); + +############### +Introspection +############### + +The MultiMat object provides methods that permit host codes to determine the number +of fields, their names, and their properties. The ``getNumberOfFields()`` method returns +the number of fields. The ``getFieldName()`` method takes a field index and returns the +name of the field. The ``getFieldIdx()`` method returns the field index for a given field +name. + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_using_fields_introspection_begin + :end-before: _multimat_using_fields_introspection_end + :language: C++ + +####################### +Accessing Field Data +####################### + +Accessing fields and their data is best done when the field's properties are known. The +field mapping determines the mesh subset where the field is defined. Fields with either +``PER_CELL`` or ``PER_MAT`` mappings are defined along one dimension of the numMaterials * numCells +grid so they are *"1D"* fields. Fields with ``PER_CELL_MAT`` field mapping are defined using +both axes of the numMaterials * numCells grid so they are *"2D"* fields. + +MultiMat provides separate field access functions for 1D/2D fields. In addition, +there are specific 2D methods to access fields according to whether their data +are dense or sparse. Each of these templated methods returns a field object +specific to the field's stored data and layout. The field object is used to read/write +the field's data. + +* get1dField() +* get2dField() +* getDense2dField() +* getSparse2dField() + +^^^^^^^^^^^^^^ +Indexing Sets +^^^^^^^^^^^^^^ + +The data layout for a 2D field determines how it should be traversed. The +MultiMat object provides methods that access the Cell-Material Relation (CMR) and return +indexing sets that are useful for specific materials or cells. For example, if data +use a ``CELL_DOM`` layout then all of the material values for a cell are contiguous in +memory, even though a given cell might not use all possible materials. To write loops +over sparse data that focus on only the valid cell-material pairs from the +CMR, the ``getIndexingSetOfCell()`` and ``getIndexingSetOfMat()`` methods can be called. + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_using_fields_index_sets_begin + :end-before: _multimat_using_fields_index_sets_end + :language: C++ + +^^^^^^^^^^ +1D Fields +^^^^^^^^^^ + +1D Fields are those with a field mapping of ``PER_CELL`` or ``PER_MAT``. 1D fields can be +retrieved from MultiMat using the ``get1dField()`` method, which returns an object +that can access the field data. The ``get1dField()`` method takes a template argument +for the type of data stored in the field so if double-precision data are stored in +MultiMat then ``get1dField()`` should be called to access the field. + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_using_fields_1d_start + :end-before: _multimat_using_fields_1d_end + :language: C++ + +1D fields can store multi-component values as well, which adds a small amount of +complexity. The field provides a ``numComp()`` method that returns the number of +components. A component for a given cell is retrieved using the 2-argument +call ``operator()`` by passing the cell index and then the desired component index. + +.. literalinclude:: ../../examples/basic.cpp + :start-after: _multimat_using_fields_1dmc_start + :end-before: _multimat_using_fields_1dmc_end + :language: C++ + +^^^^^^^^^^ +2D Fields +^^^^^^^^^^ + +2D fields are those with a ``PER_CELL_MAT`` field mapping. Since the fields can vary over +materials and cells (in either order) and they can be dense or sparse, there are multiple +ways to iterate over the field data. + +Fields can be iterated using access patterns suitable for ``DENSE`` sparsity, even +when the data may be ``SPARSE``. The field's ``findValue()`` function is useful +in this case since it allows 2 indices to be passed in addition to a component index. +The first index is the cell number for ``CELL_DOM`` fields, making the second index the +material number. For ``MAT_DOM`` fields, the order is reversed. This approach to locating +the data is general and can be used to traverse the data in the opposite order of the +native data layout, if desired. However, each call to ``findValue()`` includes a +short search and the method can return ``nullptr`` if no valid cell/material pair +is located for the field. + +.. literalinclude:: ../../examples/traversal.cpp + :start-after: _multimat_using_fields_dense_start + :end-before: _multimat_using_fields_dense_end + :language: C++ + +For algorithms where sparse data traversal is desired, the MultiMat indexing sets +can be used directly as an alternative to dense traversal patterns. Cells are iterated +first in this example since the field has a ``CELL_DOM`` data layout. The materials for +the current cell are queried are used to to compute an index into the field data. + +.. literalinclude:: ../../examples/traversal.cpp + :start-after: _multimat_using_fields_indexset_start + :end-before: _multimat_using_fields_indexset_end + :language: C++ + +MultiMat 2D fields provide iterators as a means for writing simpler code. The iterators +can be used to write sparse data traversal algorithms without the added complexity +of using index sets. + +.. literalinclude:: ../../examples/traversal.cpp + :start-after: _multimat_using_fields_iterator_start + :end-before: _multimat_using_fields_iterator_end + :language: C++ + +Flat iterators can be used to traverse all data in a field. This is useful when +computing values over the field and the algorithm does not need to know the cell +or material associated with the data. + +.. literalinclude:: ../../examples/traversal.cpp + :start-after: _multimat_using_fields_flatiter_start + :end-before: _multimat_using_fields_flatiter_end + :language: C++ + +############# +Conversions +############# + +MultiMat can store fields with various data mappings, layouts, and sparsity values. +This allows for great flexibility in how fields are represented in memory. MultiMat +provides conversion routines that allow fields to be converted internally between +various representations. + +Field conversion can be done for a variety of reasons. Perhaps fields are converted from +sparse to dense to expose them to an external library that needs dense data. Or, perhaps +dense fields are retrieved from I/O routines and then made sparse in MultiMat during +simulation execution. Other times, depending on the needs of an algorithm, it can make +sense to transpose the data, changing ``CELL_DOM`` to ``MAT_DOM`` or vice-versa. The following +methods perform these data conversions and they take a field index as an argument. + +Convert field sparsity: + +* convertFieldToSparse() +* convertFieldToDense() + +Convert field data layout: + +* transposeField() +* convertFieldToMatDom() +* convertFieldToCellDom() diff --git a/src/axom/multimat/examples/CMakeLists.txt b/src/axom/multimat/examples/CMakeLists.txt index 0f3eef4f07..f7290909b2 100644 --- a/src/axom/multimat/examples/CMakeLists.txt +++ b/src/axom/multimat/examples/CMakeLists.txt @@ -13,20 +13,27 @@ set(multimat_example_headers helper.hpp ) set(multimat_example_sources + basic.cpp calculate.cpp calculate_gpu.cpp traversal.cpp ) +if(CONDUIT_FOUND) + set(multimat_basic_depends conduit::conduit) +endif() + foreach(example_source ${multimat_example_sources}) get_filename_component(example_name ${example_source} NAME_WE) + set(deps "multimat;${multimat_${example_name}_depends}") axom_add_executable( NAME multimat_${example_name}_ex HEADERS ${multimat_example_headers} SOURCES ${example_source} OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} - DEPENDS_ON multimat + DEPENDS_ON ${deps} FOLDER axom/multimat/examples ) + unset(deps) blt_add_test( NAME multimat_${example_name} diff --git a/src/axom/multimat/examples/basic.cpp b/src/axom/multimat/examples/basic.cpp new file mode 100644 index 0000000000..b7514c493e --- /dev/null +++ b/src/axom/multimat/examples/basic.cpp @@ -0,0 +1,508 @@ +// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/CLI11.hpp" +#include "axom/core.hpp" +#include "axom/fmt.hpp" +#include "axom/multimat.hpp" +#include "axom/slic.hpp" + +#ifdef AXOM_USE_CONDUIT + #include "conduit.hpp" + #include "conduit_relay.hpp" +#endif + +#include +#include +#include + +void addfields(axom::multimat::MultiMat &mm) +{ + // clang-format off + + //_multimat_using_fields_addfields_begin + constexpr int ncells = 9; + constexpr int nmats = 3; + constexpr int nComponents = 1; + // Add PER_CELL field. + double perCellData[] = {1., 2., 3., 4., 5., 6., 7., 8., 9.}; + axom::ArrayView perCellAV(perCellData, ncells); + mm.addField("perCell", + axom::multimat::FieldMapping::PER_CELL, + axom::multimat::DataLayout::CELL_DOM, + axom::multimat::SparsityLayout::DENSE, + perCellAV, + nComponents); + + // Add PER_MAT field. + double perMatData[] = {1., 2., 3.}; + axom::ArrayView perMatAV(perMatData, nmats); + mm.addField("perMat", + axom::multimat::FieldMapping::PER_MAT, + axom::multimat::DataLayout::MAT_DOM, + axom::multimat::SparsityLayout::DENSE, + perMatAV, + nComponents); + + // Add PER_CELL_MAT DENSE field. 0's where there is no material. + double perCellMatDense[ncells][nmats] = { + {0.55, 0.45, 0.}, // cell 0 + {0.425, 0.425, 0.15}, // cell 1 + {0.3, 0.4, 0.3}, // cell 2 + {0.425, 0.425, 0.15}, // cell 3 + {0., 0.2, 0.8}, // cell 4 + {0., 0., 1.}, // cell 5 + {0.3, 0.4, 0.3}, // cell 6 + {0., 0., 1.}, // cell 7 + {0., 0., 1.} // cell 8 + }; + axom::ArrayView perCellMatDenseAV(&perCellMatDense[0][0], ncells * nmats); + mm.addField("perCellMatDense", + axom::multimat::FieldMapping::PER_CELL_MAT, + axom::multimat::DataLayout::CELL_DOM, + axom::multimat::SparsityLayout::DENSE, + perCellMatDenseAV, + nComponents); + + // Add PER_CELL_MAT SPARSE field. 0's do not need to appear. + double perCellMatSparse[] = { + 0.55, 0.45, // cell 0 + 0.425, 0.425, 0.15, // cell 1 + 0.3, 0.4, 0.3, // cell 2 + 0.425, 0.425, 0.15, // cell 3 + 0.2, 0.8, // cell 4 + 1., // cell 5 + 0.3, 0.4, 0.3, // cell 6 + 1., // cell 7 + 1., // cell 8 + }; + axom::ArrayView perCellMatSparseAV(perCellMatSparse, + sizeof(perCellMatSparse) / sizeof(double)); + mm.addField("perCellMatSparse", + axom::multimat::FieldMapping::PER_CELL_MAT, + axom::multimat::DataLayout::CELL_DOM, + axom::multimat::SparsityLayout::SPARSE, + perCellMatSparseAV, + nComponents); + //_multimat_using_fields_addfields_end + + // clang-format on +} + +void multicomponent(axom::multimat::MultiMat &mm) +{ + // clang-format off + + //_multimat_using_fields_multicomponent_begin + constexpr int nComponents = 2; + double data[] = {0., 0., // cell 0 x,y components + 1., 1., // cell 1 x,y components + 2., 4., // cell 2 x,y components + 3., 9., // cell 3 x,y components + 4., 16., // cell 4 x,y components + 5., 25., // cell 5 x,y components + 6., 36., // cell 6 x,y components + 7., 49., // cell 7 x,y components + 8., 64., // cell 8 x,y components + }; + axom::ArrayView dataAV(data, sizeof(data) / sizeof(double)); + mm.addField("perCellMC", + axom::multimat::FieldMapping::PER_CELL, + axom::multimat::DataLayout::CELL_DOM, + axom::multimat::SparsityLayout::DENSE, + dataAV, + nComponents); + //_multimat_using_fields_multicomponent_end + + // clang-format on +} + +void introspection(axom::multimat::MultiMat &mm) +{ + //_multimat_using_fields_introspection_begin + + // Print the field names in the MultiMat object mm. + for(int i = 0; i < mm.getNumberOfFields(); i++) + { + // Get field properties + auto name = mm.getFieldName(i); + auto mapping = mm.getFieldMapping(i); + auto layout = mm.getFieldDataLayout(i); + auto sparsity = mm.getFieldSparsityLayout(i); + auto dataType = mm.getFieldDataType(i); + + std::cout << name << ":" + << "\n\tmapping: " << mapping << "\n\tlayout: " << layout + << "\n\tsparsity: " << sparsity << "\n\tdataType: " << dataType + << "\n"; + } + std::cout << "Volfrac index: " << mm.getFieldIdx("Volfrac") << std::endl; + + //_multimat_using_fields_introspection_end +} + +void using_fields_index_sets(axom::multimat::MultiMat &mm) +{ + //_multimat_using_fields_index_sets_begin + // CELL_DOM data (iterate over cells then materials) + const std::string fieldName("perCellMatSparse"); + auto f = mm.getSparse2dField(fieldName); + std::cout << "Field: " << fieldName << std::endl; + for(int i = 0; i < mm.getNumberOfCells(); i++) + { + std::cout << "\tcell " << i << " values: "; + for(const auto &idx : + mm.getIndexingSetOfCell(i, axom::multimat::SparsityLayout::SPARSE)) + { + std::cout << f[idx] << ", "; + } + std::cout << "\n"; + } + //_multimat_using_fields_index_sets_end +} + +void using_fields_1d(axom::multimat::MultiMat &mm) +{ + // _multimat_using_fields_1d_start + // Sum all values in the field. + double sum = 0.; + auto f = mm.get1dField("perCell"); + for(int i = 0; i < mm.getNumberOfCells(); i++) + { + sum += f[i]; + } + // _multimat_using_fields_1d_end + + SLIC_INFO(axom::fmt::format("sum={}", sum)); +} + +void using_fields_multi_component(axom::multimat::MultiMat &mm) +{ + // _multimat_using_fields_1dmc_start + double sum = 0.; + auto f = mm.get1dField("perCellMC"); + for(int i = 0; i < mm.getNumberOfCells(); i++) + { + for(int comp = 0; comp < f.numComp(); comp++) + { + sum += f(i, comp); + } + } + // _multimat_using_fields_1dmc_end + + SLIC_INFO(axom::fmt::format("sum={}", sum)); +} + +void dynamic_mode(axom::multimat::MultiMat &mm) +{ + //_multimat_dynamic_mode_begin + // mm is a MultiMat object. + + // Switch to dynamic mode + mm.convertToDynamic(); + + // Add material 1 in zone 7 that was not there before. + mm.addEntry(7, 1); + + // Remove material 1 in zone 0 + mm.removeEntry(0, 1); + + // Volume fraction updates omitted (iterate Volfrac field, set new values) + + //_multimat_dynamic_mode_end +} + +#ifdef AXOM_USE_CONDUIT +/** + * \brief Turn a MultiMat into a Blueprint matset and add its fields + * to the Blueprint mesh too. + * + * \param mm The MultiMat object that contains the materials and fields. + * \param mesh The node that contains the Blueprint mesh. + */ +void multimat_to_blueprint(axom::multimat::MultiMat &mm, conduit::Node &mesh) +{ + // Multimat to matset. + const auto VF = mm.get2dField("Volfrac"); + std::vector material_ids, indices, sizes, offsets; + std::vector volume_fractions; + int offset = 0, idx = 0; + for(int c = 0; c < mm.getNumberOfCells(); c++) + { + auto vf_for_cell = VF(c); + int size = 0; + for(int i = 0; i < vf_for_cell.size(); i++) + { + double value = vf_for_cell(i); + if(value > 0.) + { + material_ids.push_back(vf_for_cell.index(i)); + volume_fractions.push_back(vf_for_cell(i)); + indices.push_back(idx++); + size++; + } + } + sizes.push_back(size); + offsets.push_back(offset); + offset += size; + } + + mesh["matsets/matset/topology"] = "main"; + for(int m = 0; m < mm.getNumberOfMaterials(); m++) + { + mesh[axom::fmt::format("matsets/matset/material_map/mat{}", m)] = m; + } + mesh["matsets/matset/material_ids"].set(material_ids); + mesh["matsets/matset/volume_fractions"].set(volume_fractions); + mesh["matsets/matset/indices"].set(indices); + mesh["matsets/matset/sizes"].set(sizes); + mesh["matsets/matset/offsets"].set(offsets); + + // MultiMat to fields. + const std::string compNames[] = {"x", "y", "z"}; + for(int i = 0; i < mm.getNumberOfFields(); i++) + { + // Get field properties + auto name = mm.getFieldName(i); + auto mapping = mm.getFieldMapping(i); + auto dataType = mm.getFieldDataType(i); + SLIC_ASSERT(dataType == axom::multimat::DataTypeSupported::TypeDouble); + + conduit::Node &n_f = mesh["fields/" + name]; + n_f["association"] = "element"; + n_f["topology"] = "main"; + + if(mapping == axom::multimat::FieldMapping::PER_CELL) + { + auto f = mm.get1dField(name); + double *dptr = &f[0]; + + if(f.numComp() == 1) + { + n_f["values"].set_external(dptr, mm.getNumberOfCells()); + } + else + { + SLIC_ASSERT(f.numComp() <= 3); + for(int c = 0; c < f.numComp(); c++) + { + n_f["values/" + compNames[c]].set_external( + dptr, + mm.getNumberOfCells(), + 0, + f.numComp() * sizeof(double)); + dptr++; + } + } + } + else if(mapping == axom::multimat::FieldMapping::PER_MAT) + { + auto f = mm.get1dField(name); + + std::vector values; + for(int c = 0; c < mm.getNumberOfCells(); c++) + { + double value = 0.; + const auto matsInCell = mm.getMatInCell(c); + // Take the first material - could take the one with largest VF. + if(matsInCell.size() > 0) + { + int mat = matsInCell[0]; + value = f[mat]; + } + values.push_back(value); + } + + n_f["values"].set(values); + } + else if(mapping == axom::multimat::FieldMapping::PER_CELL_MAT) + { + auto f = mm.get2dField(name); + + if(f.numComp() == 1) + { + std::vector values, matset_values; + for(int c = 0; c < mm.getNumberOfCells(); c++) + { + const auto matsInCell = mm.getMatInCell(c); + double avg = 0.; + for(auto &m : matsInCell) + { + double *valptr = f.findValue(c, m); + if(valptr != nullptr) + { + matset_values.push_back(*valptr); + avg += *valptr; + } + } + values.push_back(avg / std::max(double(matsInCell.size()), 1.)); + } + + n_f["matset"] = "matset"; + n_f["values"].set(values); + n_f["matset_values"].set(matset_values); + } + else + { + std::vector values[3], matset_values[3]; + for(int c = 0; c < mm.getNumberOfCells(); c++) + { + const auto matsInCell = mm.getMatInCell(c); + double avg[3] = {0., 0., 0.}; + for(auto &m : matsInCell) + { + for(int comp = 0; comp < f.numComp(); comp++) + { + double *valptr = f.findValue(c, m, comp); + if(valptr != nullptr) + { + matset_values[comp].push_back(*valptr); + avg[comp] += *valptr; + } + } + } + for(int comp = 0; comp < f.numComp(); comp++) + { + values[comp].push_back(avg[comp] / + std::max(double(matsInCell.size()), 1.)); + } + } + + n_f["matset"] = "matset"; + for(int comp = 0; comp < f.numComp(); comp++) + { + n_f["values/" + compNames[comp]].set(values[comp]); + n_f["matset_values/" + compNames[comp]].set(matset_values[comp]); + } + } + } + } +} + +/** + * \brief Save the MultiMat to a Blueprint output file. + * + * \param mm The MultiMat object to save. It is compatible with the mesh in this routine. + */ +void save_blueprint(axom::multimat::MultiMat &mm) +{ + const char *yaml = R"( +coordsets: + coords: + type: explicit + values: + x: [0., 1., 2., 3., 0., 1., 2., 3., 0., 1., 2., 3., 0., 1., 2., 3.] + y: [0., 0., 0., 0., 1., 1., 1., 1., 2., 2., 2., 2., 3., 3., 3., 3.] +topologies: + main: + type: unstructured + coordset: coords + elements: + shape: quad + connectivity: [0,1,5,4, 1,2,6,5, 2,3,7,6, 4,5,9,8, 5,6,10,9, 6,7,11,10, 8,9,13,12, 9,10,14,13, 10,11,15,14] + sizes: [4, 4, 4, 4, 4, 4, 4, 4, 4] + offsets: [0, 4, 8, 12, 16, 20, 24, 28, 32] + )"; + + conduit::Node mesh; + mesh.parse(yaml); + + multimat_to_blueprint(mm, mesh); + + SLIC_INFO("Saving MultiMat to Blueprint."); + conduit::relay::io::blueprint::save_mesh(mesh, "basic", "hdf5"); + conduit::relay::io::save(mesh, "basic.yaml", "yaml"); +} +#endif + +int main(int argc, char *argv[]) +{ + axom::slic::SimpleLogger logger(axom::slic::message::Info); + axom::CLI::App app; +#ifdef AXOM_USE_CONDUIT + bool output = false; + app.add_flag("--output", output) + ->description("Whether to write a Blueprint mesh of the MultiMat data."); +#endif + // Parse command line options. + app.parse(argc, argv); + + // NOTE: Construct the MultiMat object here so we can see the + // declaration in the docs. + + // clang-format off + + //_multimat_materials_cmr_begin + constexpr int nmats = 3; + constexpr int ncells = 9; + + // Create the MultiMat object mm + axom::multimat::MultiMat mm; + mm.setNumberOfMaterials(nmats); + mm.setNumberOfCells(ncells); + + // Cell-Dominant data layout + int rel[ncells][nmats] = { + {1,1,0}, + {1,1,1}, + {1,1,1}, + {1,1,1}, + {0,1,1}, + {0,0,1}, + {1,1,1}, + {0,0,1}, + {0,0,1} + }; + std::vector relation(ncells * nmats, false); + for(int c = 0; c < ncells; c++) + { + for(int m = 0; m < nmats; m++) + { + relation[c * nmats + m] = rel[c][m] > 0; + } + } + mm.setCellMatRel(relation, axom::multimat::DataLayout::CELL_DOM); + //_multimat_materials_cmr_end + + //_multimat_materials_volfracs_begin + // Cell-Dominant, DENSE data layout + double volfracs[ncells][nmats] = { + {0.55, 0.45, 0.}, + {0.425, 0.425, 0.15}, + {0.3, 0.4, 0.3}, + {0.425, 0.425, 0.15}, + {0., 0.2, 0.8}, + {0., 0., 1.}, + {0.3, 0.4, 0.3}, + {0., 0., 1.}, + {0., 0., 1.} + }; + axom::ArrayView vfView(&volfracs[0][0], ncells * nmats); + mm.setVolfracField(vfView, + axom::multimat::DataLayout::CELL_DOM, + axom::multimat::SparsityLayout::DENSE); + //_multimat_materials_volfracs_end + + // clang-format on + + // Demonstrate other features. + addfields(mm); + multicomponent(mm); + introspection(mm); + using_fields_index_sets(mm); + using_fields_1d(mm); + using_fields_multi_component(mm); + +#ifdef AXOM_USE_CONDUIT + if(output) + { + save_blueprint(mm); + } +#endif + + dynamic_mode(mm); + introspection(mm); + return 0; +} diff --git a/src/axom/multimat/examples/traversal.cpp b/src/axom/multimat/examples/traversal.cpp index 078d1049ae..737c58782c 100644 --- a/src/axom/multimat/examples/traversal.cpp +++ b/src/axom/multimat/examples/traversal.cpp @@ -213,6 +213,7 @@ void various_traversal_methods(int nmats, timer.reset(); timer.start(); { + // _multimat_using_fields_dense_start auto map = mm.get2dField("CellMat Array"); for(int i = 0; i < mm.getNumberOfCells(); i++) @@ -230,6 +231,7 @@ void various_traversal_methods(int nmats, } } } + // _multimat_using_fields_dense_end } timer.stop(); SLIC_INFO(" Field2D: " << timer.elapsed() << " sec"); @@ -247,6 +249,7 @@ void various_traversal_methods(int nmats, // a Map pointer to point to a bivariateMap object //MultiMat::Field1D& map = mm.get1dField("CellMat Array"); + //_multimat_using_fields_indexset_start auto map = mm.get2dField("CellMat Array"); for(int i = 0; i < mm.getNumberOfCells(); i++) @@ -271,6 +274,7 @@ void various_traversal_methods(int nmats, } } } + //_multimat_using_fields_indexset_end } timer.stop(); SLIC_INFO(" Field2D: " << timer.elapsed() << " sec"); @@ -326,6 +330,7 @@ void various_traversal_methods(int nmats, timer.reset(); timer.start(); { + //_multimat_using_fields_iterator_start auto map2d = mm.get2dField("CellMat Array"); for(int i = 0; i < mm.getNumberOfCells() /*map2d.firstSetSize()*/; i++) { @@ -341,6 +346,7 @@ void various_traversal_methods(int nmats, SLIC_ASSERT(iter(0) == iter.value(0)); } } + //_multimat_using_fields_iterator_end } timer.stop(); SLIC_INFO(" Field2D: " << timer.elapsed() << " sec"); @@ -374,6 +380,7 @@ void various_traversal_methods(int nmats, timer.reset(); timer.start(); { + // _multimat_using_fields_flatiter_start auto map2d = mm.get2dField("CellMat Array"); for(auto iter = map2d.set_begin(); iter != map2d.set_end(); ++iter) { @@ -387,6 +394,7 @@ void various_traversal_methods(int nmats, sum += val; } } + // _multimat_using_fields_flatiter_end } timer.stop(); SLIC_INFO(" Field2D: " << timer.elapsed() << " sec"); diff --git a/src/axom/multimat/multimat.cpp b/src/axom/multimat/multimat.cpp index c51cdf9c50..d8801ded2e 100644 --- a/src/axom/multimat/multimat.cpp +++ b/src/axom/multimat/multimat.cpp @@ -1739,15 +1739,36 @@ void MultiMat::convertFieldToCellDom(int field_idx) transposeField(field_idx); } +std::string MultiMat::getFieldMappingAsString(int field_i) const +{ + if(m_fieldMappingVec[field_i] == FieldMapping::PER_CELL) + { + return "Per-Cell"; + } + else if(m_fieldMappingVec[field_i] == FieldMapping::PER_MAT) + { + return "Per-Material"; + } + else if(m_fieldMappingVec[field_i] == FieldMapping::PER_CELL_MAT) + { + return "Per-Cell-Material"; + } + else + { + SLIC_ASSERT(false); + } + return ""; +} + std::string MultiMat::getFieldDataLayoutAsString(int field_i) const { if(m_fieldDataLayoutVec[field_i] == DataLayout::CELL_DOM) { - return "Cell-Centric"; + return "Cell-Dominant"; } else if(m_fieldDataLayoutVec[field_i] == DataLayout::MAT_DOM) { - return "Material-Centric"; + return "Material-Dominant"; } else { @@ -1773,6 +1794,35 @@ std::string MultiMat::getFieldSparsityLayoutAsString(int field_i) const return ""; } +std::string MultiMat::getFieldDataTypeAsString(int field_i) const +{ + if(m_dataTypeVec[field_i] == DataTypeSupported::TypeUnknown) + { + return "Unknown"; + } + else if(m_dataTypeVec[field_i] == DataTypeSupported::TypeInt) + { + return "int"; + } + else if(m_dataTypeVec[field_i] == DataTypeSupported::TypeDouble) + { + return "double"; + } + else if(m_dataTypeVec[field_i] == DataTypeSupported::TypeFloat) + { + return "float"; + } + else if(m_dataTypeVec[field_i] == DataTypeSupported::TypeUnsignChar) + { + return "unsigned char"; + } + else + { + SLIC_ASSERT(false); + } + return ""; +} + void MultiMat::print() const { std::stringstream sstr; @@ -1945,3 +1995,89 @@ MultiMat::StaticVariableRelationType* MultiMat::getRel(int field_idx) return rel_ptr; } + +namespace axom +{ +namespace multimat +{ +std::ostream& operator<<(std::ostream& os, FieldMapping mapping) +{ + switch(mapping) + { + case FieldMapping::PER_CELL: + os << "PER_CELL"; + break; + case FieldMapping::PER_MAT: + os << "PER_MAT"; + break; + case FieldMapping::PER_CELL_MAT: + os << "PER_CELL_MAT"; + break; + default: + os << "Unknown"; + break; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, DataLayout layout) +{ + switch(layout) + { + case DataLayout::CELL_DOM: + os << "CELL_DOM"; + break; + case DataLayout::MAT_DOM: + os << "MAT_DOM"; + break; + default: + os << "Unknown"; + break; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, SparsityLayout sparsity) +{ + switch(sparsity) + { + case SparsityLayout::DENSE: + os << "DENSE"; + break; + case SparsityLayout::SPARSE: + os << "SPARSE"; + break; + default: + os << "Unknown"; + break; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, DataTypeSupported type) +{ + switch(type) + { + case DataTypeSupported::TypeUnknown: + os << "TypeUnknown"; + break; + case DataTypeSupported::TypeInt: + os << "TypeInt"; + break; + case DataTypeSupported::TypeDouble: + os << "TypeDouble"; + break; + case DataTypeSupported::TypeFloat: + os << "TypeFloat"; + break; + case DataTypeSupported::TypeUnsignChar: + os << "TypeUnsignChar"; + break; + default: + os << "Unknown"; + break; + } + return os; +} +} // end namespace multimat +} // end namespace axom diff --git a/src/axom/multimat/multimat.hpp b/src/axom/multimat/multimat.hpp index 1e2a1bdc07..2654c62144 100644 --- a/src/axom/multimat/multimat.hpp +++ b/src/axom/multimat/multimat.hpp @@ -549,8 +549,10 @@ class MultiMat void convertFieldToCellDom(int field_idx); DataLayout getFieldDataLayout(int field_idx) const; + std::string getFieldMappingAsString(int field_idx) const; std::string getFieldDataLayoutAsString(int field_idx) const; std::string getFieldSparsityLayoutAsString(int field_idx) const; + std::string getFieldDataTypeAsString(int field_idx) const; /** Convert the data to be stored in the specified layout. **/ void convertLayout(DataLayout, SparsityLayout); @@ -576,6 +578,19 @@ class MultiMat return m_fieldMappingVec[field_idx]; } + /** + * \brief Get the DataType for a field. + * + * A DataTypeSupported for a field indicates the underlying supported data + * type (e.g. double). + * + * \param field_idx the index of the field + */ + DataTypeSupported getFieldDataType(int field_idx) const + { + return m_dataTypeVec[field_idx]; + } + //Dynamic mode functions /** Convert the layout to use dynamic layout. In dynamic layout, users can @@ -1313,6 +1328,12 @@ BSet MultiMat::getCompatibleBivarSet(int fieldIdx) const return *ptr; } +// Output operators. +std::ostream& operator<<(std::ostream& os, FieldMapping mapping); +std::ostream& operator<<(std::ostream& os, DataLayout layout); +std::ostream& operator<<(std::ostream& os, SparsityLayout sparsity); +std::ostream& operator<<(std::ostream& os, DataTypeSupported type); + } //end namespace multimat } //end namespace axom