Component Architecture

This section links the core concepts, presented in the Mesh Representation and Mesh Types sections, to the underlying implementation of the Mint mesh data model. The Component Architecture of Mint’s mesh data model consists of a class hierarchy that follows directly the taxonomy of Mesh Types discussed earlier. The constituent classes of the mesh data model are combined using a mix of class inheritance and composition, as illustrated in the class diagram depicted in Fig. 18.

Mint Class Hierarchy Diagram

Fig. 18 Component Architecture of the Mint mesh data model, depicting the core mesh classes and the inter-relationship between them. The solid arrows indicate an inheritance relationship, while the dashed arrows indicate an ownership relationship between two classes.

At the top level, The Mesh Base Class, implemented in mint::Mesh, stores common mesh attributes and fields. Moreover, it defines a unified Application Programming Interface (API) for the various Mesh Types. See the Mint Doxygen API Documentation for a complete specification of the API. The Concrete Mesh Classes extend The Mesh Base Class and implement the Mesh Representation for each of the Mesh Types respectively. The mint::ConnectivityArray and mint::MeshCoordinates classes, are the two main internal support classes that underpin the implementation of the Concrete Mesh Classes and facilitate the representation of the constituent Geometry and Topology of the mesh.

Note

All Mint classes and functions are encapsulated in the axom::mint namespace.

The Mesh Base Class

The Mesh Base Class stores common attributes associated with a mesh. Irrespective of the mesh type, a Mint mesh has two identifiers. The mesh BlockID and mesh DomainID, which are assigned by domain decomposition. Notably, the computational domain can consist of one or more blocks, which are usually defined by the user or application. Each block is then subsequently partitioned to multiple domains that are distributed across processing units for parallel computation. For example, a sample block and domain decomposition is depicted in Fig. 19. Each of the constituent domains is represented by a corresponding mint::Mesh instance, which in aggregate define the entire problem domain.

Block and Domain Decomposition.

Fig. 19 Sample block & domain decomposition of the computational domain. The computational domain is defined using \(3\) blocks (left). Each block is further partitioned into two or more domains(right). A mint::Mesh instance represents one of the constituent domains used to define the overall problem domain.

Note

A mint::Mesh instance provides the means to store the mesh BlockID and DomainID respectively. However, Mint does not impose a numbering or partitioning scheme. Assignment of the BlockID and DomainID is handled at the application level and by the underlying mesh partitioner that is being employed.

Moreover, each mint::Mesh instance has associated Mesh Field Data, represented by the mint::FieldData class. Each of the constituent topological mesh entities, i.e. the Cells, Faces and Nodes comprising the mesh, has a handle to a corresponding mint::FieldData instance. The mint::FieldData object essentialy provides a container to store and manage a collection of fields, defined over the corresponding mesh entity.

Warning

Since a Particle Mesh is defined by a set of Nodes, it can only store Field Data at its constituent Nodes. All other supported Mesh Types can have Field Data associated with their constituent Cells, Faces and Nodes.

Mesh Field Data

A mint::FieldData instance typically stores multiple fields. Each field is represented by an instance of a mint::Field object and defines a named numerical quantity, such as mass, velocity, temperature, etc., defined on a given mesh. Moreover, a field can be either single-component, i.e. a scalar quantity, or, multi-component, e.g. a vector or tensor quantity. Typically, a field represents some physical quantity that is being modeled, or, an auxiliary quantity that is needed to perform a particular calculation.

In addition, each mint::Field instance can be of different data type. The mint::FieldData object can store different types of fields. For example, floating point quantities i.e., float or double, as well as, integral quantities, i.e. int32_t, int64_t, etc. This is accomplished using a combination of C++ templates and inheritance. The mint::Field object is an abstract base class that defines a type-agnostic interface to encapsulate a field. Since mint::Field is an abstract base class, it is not instantiated directly. Instead, all fields are created by instantiating a mint::FieldVariable object, a class templated on data type, that derives from the mint::Field base class. For example, the code snippet below illustrates how fields of different type can be instantiated.

...

// create a scalar field to store mass as a single precision quantity
mint::Field* mass = new mint::FieldVariable< float >( "mass", size );

// create a velocity vector field as a double precision floating point quantity
constexpr int NUM_COMPONENTS = 3;
mint::Field* vel = new mint::FieldVariable< double >( "vel", size, NUM_COMPONENTS );

...

Generally, in application code, it is not necessary to create fields using the mint::FieldVariable class directly. The mint::Mesh object provides convenience methods for adding, removing and accessing fields on a mesh. Consult the Tutorial for more details on Working with Fields on a Mesh.

Concrete Mesh Classes

The Concrete Mesh Classes, extend The Mesh Base Class and implement the underlying Mesh Representation of the various Mesh Types, depicted in Fig. 20.

Supported Mesh Types.

Fig. 20 Depiction of the supported Mesh Types with labels of the corresponding Mint class used for the underlying Mesh Representation.

Structured Mesh

All Structured Mesh types in Mint can be represented by an instance of the mint::StructuredMesh class, which derives directly from The Mesh Base Class, mint::Mesh. The mint::StructuredMesh class is also an abstract base class that encapsulates the implementation of the implicit, ordered and regular Topology that is common to all Structured Mesh types. The distinguishing characteristic of the different Structured Mesh types is the representation of the constituent Geometry. Mint implements each of the different Structured Mesh types by a corresponding class, which derives directly from mint::StructuredMesh and thereby inherit its implicit Topology representation.

Consequently, support for the Uniform Mesh is implemented in mint::UniformMesh. The Geometry of a Uniform Mesh is implicit, given by two attributes, the mesh origin and spacing. Consequently, the mint::UniformMesh consists of two data members to store the origin and spacing of the Uniform Mesh and provides functionality for evaluating the spatial coordinates of a node given its corresponding IJK lattice coordinates.

Similarly, support for the Rectilinear Mesh is implemented in mint::RectilinearMesh. The constituent Geometry representation of the Rectilinear Mesh is semi-implicit. The spatial coordinates of the Nodes along each axis are specified explicitly while the coordinates of the interior Nodes are evaluated by taking the Cartesian product of the corresponding coordinate along each coordinate axis. The mint::RectilinearMesh consists of seperate arrays to store the coordinates along each axis for the semi-implicit Geometry representation of the Rectilinear Mesh.

Support for the Curvilinear Mesh is implemented by the mint::CurvilinearMesh class. The Curvilinear Mesh requires explicit representation of its constituent Geometry. The mint::CurvilinearMesh makes use of the mint::MeshCoordinates class to explicitly represent the spatial coordinates associated with the constituent Nodes of the mesh.

Unstructured Mesh

Mint’s Unstructured Mesh representation is provided by the mint::UnstructuredMesh class, which derives directly from the The Mesh Base Class, mint::Mesh. An Unstructured Mesh has both explicit Geometry and Topology. As with the mint::CurvilinearMesh class, the explicit Geometry representation of the Unstructured Mesh employs the mint::MeshCoordinates. The constituent Topology is handled by the mint::ConnectivityArray, which is employed for the representation of all the topological Connectivity information, i.e. cell-to-node, face-to-node, face-to-cell, etc.

Note

Upon construction, a mint::UnstructuredMesh instance consists of the minimum sufficient representation for an Unstructured Mesh comprised of the cell-to-node Connectivity information. Applications that require face Connectivity information must explicitly call the initializeFaceConnectivity() method on the corresponding Unstructured Mesh object.

Depending on the cell Topology being employed, an Unstructured Mesh can be classified as either a Single Cell Type Topology Unstructured Mesh or a Mixed Cell Type Topology Unstructured Mesh. To accomodate these two different representations, the mint::UnstructuredMesh class, is templated on CELL_TOPOLOGY. Internally, the template argument is used to indicate the type of mint::ConnectivityArray to use, i.e. whether, stride access addressing or indirect addressing is used, for Single Cell Type Topology and Mixed Cell Type Topology respectively.

Particle Mesh

Support for the Particle Mesh representation is implemented in mint::ParticleMesh, which derives directly from The Mesh Base Class, mint::Mesh. A Particle Mesh discretizes the domain by a set of particles, which correspond to the constituent Nodes of the mesh. The Nodes of a Particle Mesh can also be thought of as Cells, however, since this information is trivially obtrained, there is not need to be stored explicitly, e.g. using a Single Cell Type Topology Unstructured Mesh representation. Consequently, the Particle Mesh representation consists of explicit Geometry and implicit Topology. As with the mint::CurvilinearMesh and mint::UnstructuredMesh, the explicit Geometry of the Particle Mesh is represented by employing the mint::MeshCoordinates as an internal class member.

The following code snippet provides a simple examples illustrating how to construct and operate on a Particle Mesh.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and
// other Axom Project Developers. See the top-level LICENSE file for details.
//
// SPDX-License-Identifier: (BSD-3-Clause)

/*!
 * \file
 *
 * \brief Illustrates how to construct and use a ParticleMesh to perform
 *  operations on a set of particles.
 */

// Axom utilities
#include "axom/core.hpp"
#include "axom/mint.hpp"

// namespace aliases
namespace mint = axom::mint;
namespace utilities = axom::utilities;

//------------------------------------------------------------------------------
int main(int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv))
{
  using int64 = axom::IndexType;
  const axom::IndexType NUM_PARTICLES = 100;
  const int DIMENSION = 3;

  const double HI = 10.0;
  const double LO = -10.0;
  const double VLO = 0.0;
  const double VHI = 1.0;

  // STEP 0: create the ParticleMesh
  mint::ParticleMesh particles(DIMENSION, NUM_PARTICLES);

  // STEP 1: Add fields to the Particles
  double* vx = particles.createField<double>("vx", mint::NODE_CENTERED);
  double* vy = particles.createField<double>("vy", mint::NODE_CENTERED);
  double* vz = particles.createField<double>("vz", mint::NODE_CENTERED);
  int64* id = particles.createField<int64>("id", mint::NODE_CENTERED);

  // STEP 2: grab handle to the particle position arrays
  double* px = particles.getCoordinateArray(mint::X_COORDINATE);
  double* py = particles.getCoordinateArray(mint::Y_COORDINATE);
  double* pz = particles.getCoordinateArray(mint::Z_COORDINATE);

  // STEP 3: loop over the particle data
  const int64 numParticles = particles.getNumberOfNodes();
  for(int64 i = 0; i < numParticles; ++i)
  {
    px[i] = utilities::random_real(LO, HI);
    py[i] = utilities::random_real(LO, HI);
    pz[i] = utilities::random_real(LO, HI);

    vx[i] = utilities::random_real(VLO, VHI);
    vy[i] = utilities::random_real(VLO, VHI);
    vz[i] = utilities::random_real(VLO, VHI);

    id[i] = i;

  }  // END

  // STEP 4: write the particle mesh in VTK format for visualization
  mint::write_vtk(&particles, "particles.vtk");

  return 0;
}

Mesh Storage Management

Mint provides a flexible Mesh Storage Management system that can optionally interoperate with Sidre as the underlying, in-memory, hierarchichal datastore. This enables Mint to natively conform to Conduit’s Blueprint protocol for representing a computational mesh in memory and thereby, facilitate with the integration across different physics packages.

Mint’s Mesh Storage Management substrate supports three storage options. The applicable operations and ownership state of each storage option are summarized in the table below, followed by a brief description of each option.

  Modify Reallocate Ownership
Native Storage Mint
External Storage   Application
Sidre Storage Sidre

Native Storage

A Mint object using Native Storage owns all memory and associated data. The data can be modified and the associated memory space can be reallocated to grow and shrink as needed. However, once the Mint object goes out-of-scope, all data is deleted and the memory is returned to the system.

See the Tutorial for more information and a set of concrete examples on how to create a mesh using Native Storage.

External Storage

A Mint object using External Storage has a pointer to a supplied application buffer. In this case, the data can be modified, but the application maintains ownership of the underlying memory. Consequently, the memory space cannot be reallocated and once the Mint object goes out-of-scope, the data is not deleted. The data remains persistent in the application buffers until it is deleted by the application.

See the Tutorial for more information on Using External Storage.

Sidre Storage

A Mint object using Sidre Storage is associated with a Sidre Group object which has owneship of the mesh data. In this case the data can be modified and the associated memory can be reallocated to grow and shrink as needed. However, when the Mint object goes out-of-scope, the data remains persistent in Sidre.

See the Tutorial for more information and a set of concrete examples on Using Sidre.