Skip to content

Latest commit

 

History

History
1255 lines (932 loc) · 48.5 KB

tutorial.rst

File metadata and controls

1255 lines (932 loc) · 48.5 KB

Tutorial

The sections/tutorial section consists of simple examples and code snippets that illustrate how to use Mint's core classes and functions to construct and operate on the various supported MeshTypes. The examples presented herein aim to illustrate specific Mint concepts and capabilities in a structured and simple format. To quickly learn basic Mint concepts and capabilities through an illustrative walk-through of a complete working code example, see the sections/mint/getting_started section. Additional code examples, based on Mint mini-apps, are provided in the sections/examples section. For thorough documentation of the interfaces of the various classes and functions in Mint, developers are advised to consult the Mint Doxygen API Documentation, in conjunction with this sections/tutorial.

Create a Uniform Mesh

A UniformMesh is relatively the simplest StructuredMesh type, but also, the most restrictive mesh out of all MeshTypes. The constituent Nodes of the UniformMesh are uniformly spaced along each axis on a regular lattice. Consequently, a UniformMesh can be easily constructed by simply specifying the spatial extents of the domain and desired dimensions, e.g. the number of Nodes along each dimension.

For example, a 50 × 50 UniformMesh, defined on a bounded domain given by the interval ℐ : [ − 5.0, 5.0] × [ − 5.0, 5.0], can be easily constructed as follows:

../../../examples/user_guide/mint_tutorial.cpp

The resulting mesh is depicted in figs/uniformMesh50x50.

Resulting Uniform Mesh.

Create a Rectilinear Mesh

A RectilinearMesh, also called a product mesh, is similar to a UniformMesh. However, the constituent Nodes of a RectilinearMesh are not uniformly spaced. The spacing between adjacent Nodes can vary arbitrarily along each axis, but the Topology of the mesh remains a regular StructuredMesh Topology. To allow for this flexibility, the coordinates of the Nodes along each axis are explicitly stored in separate arrays, i.e. x, y and z, for each coordinate axis respectively.

The following code snippet illustrates how to construct a 25 × 25 RectilinearMesh where the spacing of the Nodes grows according to an exponential stretching function along the x and y axis respectively. The resulting mesh is depicted in figs/rectilinearMesh25x25.

../../../examples/user_guide/mint_tutorial.cpp

Resulting RectilinearMesh.

Create a Curvilinear Mesh

A CurvilinearMesh, also called a body-fitted mesh, is the most general of the StructuredMesh types. Similar to the UniformMesh and RectilinearMesh types, a CurvilinearMesh also has regular StructuredMesh Topology. However, the coordinates of the Nodes comprising a CurvilinearMesh are defined explicitly, enabling the use of a StructuredMesh discretization with more general geometric domains, i.e., the domain may not be necessarily Cartesian. Consequently, the coordinates of the Nodes are specified explicitly in separate arrays, x, y, and z.

The following code snippet illustrates how to construct a 25 × 25 CurvilinearMesh. The coordinates of the Nodes follow from the equation of a cylinder with a radius of 2.5. The resulting mesh is depicted in figs/curvilinearMesh25x25.

../../../examples/user_guide/mint_tutorial.cpp

Resulting CurvilinearMesh.

Create an Unstructured Mesh

An UnstructuredMesh with SingleCellTopology has both explicit Topology and Geometry. However, the cell type that the mesh stores is known a priori, allowing for an optimized underlying MeshRepresentation, compared to the more general MixedCellTopology MeshRepresentation.

Since both Geometry and Topology are explicit, an UnstructuredMesh is created by specifying:

  1. the coordinates of the constituent Nodes, and
  2. the Cells comprising the mesh, defined by the cell-to-node Connectivity

    Resulting SingleCellTopology UnstructuredMesh

The following code snippet illustrates how to create the simple UnstructuredMesh depicted in figs/triangularMesh.

../../../examples/user_guide/mint_tutorial.cpp

An UnstructuredMesh is represented by the mint::UnstructuredMesh template class. The template argument of the class, mint::SINGLE_SHAPE indicates the mesh has SingleCellTopology. The two arguments to the class constructor correspond to the problem dimension and cell type, which in this case, is 2 and mint::TRIANGLE respectively. Once the mesh is constructed, the Nodes and Cells are appended to the mesh by calls to the appendNode() and appendCell() methods respectively. The resulting mesh is shown in figs/triangularMesh.

Tip

The storage for the mint::UstructuredMesh will grow dynamically as new Nodes and Cells are appended on the mesh. However, reallocations tend to be costly operations. For best performance, it is advised the node capacity and cell capacity for the mesh are specified in the constructor if known a priori. Consult the Mint Doxygen API Documentation for more details.

Create a Mixed Unstructured Mesh

Compared to the SingleCellTopology UnstructuredMesh, a MixedCellTopology UnstructuredMesh has also explicit Topology and Geometry. However, the cell type is not fixed. Notably, the mesh can store different CellTypes, e.g. triangles and quads, as shown in the simple 2D mesh depicted in figs/mixedMesh.

Sample MixedCellTopology UnstructuredMesh

As with the SingleCellTopology UnstructuredMesh, a MixedCellTopology UnstructuredMesh is created by specifying:

  1. the coordinates of the constituent Nodes, and
  2. the Cells comprising the mesh, defined by the cell-to-node Connectivity

The following code snippet illustrates how to create the simple MixedCellTopology UnstructuredMesh depicted in figs/mixedMesh, consisting of 2 triangles and 1 quadrilateral Cells.

../../../examples/user_guide/mint_tutorial.cpp

Similarly, a MixedCellTopology UnstructuredMesh is represented by the mint::UnstructuredMesh template class. However, the template argument to the class is mint::MIXED_SHAPE, which indicates that the mesh has MixedCellTopology. In this case, the class constructor takes only a single argument that corresponds to the problem dimension, i.e. 2. Once the mesh is constructed, the constituent Nodes of the mesh are specified by calling the appendNode() method on the mesh. Similarly, the Cells are specified by calling the appendCell() method. However, in this case, appendCell() takes one additional argument that specifies the cell type, since that can vary.

Tip

The storage for the mint::UstructuredMesh will grow dynamically as new Nodes and Cells are appended on the mesh. However, reallocations tend to be costly operations. For best performance, it is advised the node capacity and cell capacity for the mesh are specified in the constructor if known a priori. Consult the Mint Doxygen API Documentation for more details.

Working with Fields

A mesh typically has associated FieldData that store various numerical quantities on the constituent Nodes, Cells and Faces of the mesh.

Warning

Since a ParticleMesh is defined by a set of Nodes, it can only store FieldData at its constituent Nodes. All other supported MeshTypes can have FieldData associated with their constituent Cells, Faces and Nodes.

Add Fields

Given a mint::Mesh instance, a field is created by specifying:

  1. The name of the field,
  2. The field association, i.e. centering, and
  3. Optionally, the number of components of the field, required if the field is not a scalar quantity.

For example, the following code snippet creates the scalar density field, den, stored at the cell centers, and the vector velocity field, vel, stored at the Nodes:

../../../examples/user_guide/mint_tutorial.cpp

Note

If Sidre is used as the backend MeshStorageManagement substrate, createField() will populate the Sidre tree hierarchy accordingly. See usingSidre for more information.

  • Note, the template argument to the createField() method indicates the underlying field type, e.g. double, int , etc. In this case, both fields are of double field type.
  • The name of the field is specified by the first required argument to the createField() call.
  • The field association, is specified by the second argument to the createField() call.
  • A third, optional, argument may be specified to indicate the number of components of the corresponding field. In this case, since vel is a vector quantity the number of components must be explicitly specified.
  • The createField() method returns a raw pointer to the data corresponding to the new field, which can be used by the application.

Note

Absence of the third argument when calling createField() indicates that the number of components of the field defaults to 1 and thereby the field is assumed to be a scalar quantity.

Request Fields by Name

Specific, existing fields can be requested by calling getFieldPtr() on the target mesh as follows:

../../../examples/user_guide/mint_tutorial.cpp

  • As with the createField() method, the template argument indicates the underlying field type, e.g. double, int , etc.
  • The first argument specifies the name of the requested field
  • The second argument specifies the corresponding association of the requested field.
  • The third argument is optional and it can be used to get back the number of components of the field, i.e. if the field is not a scalar quantity.

Note

Calls to getFieldPtr() assume that the caller knows a priori the:

  • Field name,
  • Field association, i.e. centering, and
  • The underlying field type, e.g. double, int, etc.

Check Fields

An application can also check if a field exists by calling hasField() on the mesh, which takes as arguments the field name and corresponding field association as follows:

../../../examples/user_guide/mint_tutorial.cpp

The hasField() method returns true or false indicating whether a given field is defined on the mesh.

Remove Fields

A field can be removed from a mesh by calling removeField() on the target mesh, which takes as arguments the field name and corresponding field association as follows:

../../../examples/user_guide/mint_tutorial.cpp

The removeField() method returns true or false indicating whether the corresponding field was removed successfully from the mesh.

Query Fields

In some cases, an application may not always know a priori the name or type of the field, or, we may want to write a function to process all fields, regardless of their type.

The following code snippet illustrates how to do that:

../../../examples/user_guide/mint_tutorial.cpp

  • The mint::FieldData instance obtained by calling getFieldData() on the target mesh holds all the fields with a field association given by FIELD_ASSOCIATION.
  • The total number of fields can be obtained by calling getNumFields() on the mint::FieldData instance, which allows looping over the fields with a simple for loop.
  • Within the loop, a pointer to a mint::Field instance, corresponding to a particular field, can be requested by calling getField() on the mint::FieldData instance, which takes the field index, ifield, as an argument.
  • Given the pointer to a mint::Field instance, an application can query the following field metadata:

    • The field name, by calling getName,
    • The number of tuples of the field, by calling getNumTuples()
    • The number of components of the field, by calling getNumComponents()
    • The underlying field type by calling getType()
  • Given the above metadata, the application can then obtain a pointer to the raw field data by calling getFieldPtr() on the target mesh, as shown in the code snippet above.

Using External Storage

A Mint mesh may also be constructed from ExternalStorage. In this case, the application holds buffers that describe the constituent Geometry, Topology and FieldData of the mesh, which are wrapped in Mint for further processing.

The following code snippet illustrates how to use ExternalStorage using the SingleCellTopology UnstructuredMesh used to demonstrate how to createAnUnstructuredMesh with NativeStorage:

../../../examples/user_guide/mint_tutorial.cpp

  • The application has the following buffers:
    • x and y buffers to hold the coordinates of the Nodes
    • cell_connectivity, which stores the cell-to-node connectivity
    • den which holds a scalar density field defined over the constituent Cells of the mesh.
  • The mesh is created by calling the mint::UnstructuredMesh class constructor, with the following arguments:
    • The cell type, i.e, mint::TRIANGLE,
    • The total number of cells, NUM_CELLS,
    • The cell_connectivity which specifies the Topology of the UnstructuredMesh,
    • The total number of nodes, NUM_NODES, and
    • The x, y coordinate buffers that specify the Geometry of the UnstructuredMesh.
  • The scalar density field is registered with Mint by calling the createField() method on the target mesh instance, as before, but also passing the raw pointer to the application buffer in a third argument.

Note

The other MeshTypes can be similarly constructed using ExternalStorage by calling the appropriate constructor. Consult the Mint Doxygen API Documentation for more details.

The resulting mesh instance points to the application's buffers. Mint may be used to process the data e.g., outputToVTK etc. The values of the data may also be modified, however the mesh cannot dynamically grow or shrink when using ExternalStorage.

Warning

A mesh using ExternalStorage may modify the values of the application data. However, the data is owned by the application that supplied the external buffers. Mint cannot reallocate external buffers to grow or shrink the the mesh. Once the mesh is deleted, the data remains persistent in the application buffers until it is deleted by the application.

Using Sidre

Mint can also use Sidre as the underlying MeshStorageManagement substrate, thereby, facilitate the integration of packages or codes within the overarching WSC software ecosystem. Sidre is another component of the Axom Toolkit that provides a centralized data management system that enables efficient coordination of data across the constituent packages of a multi-physics application.

There are two primary operations a package/code may want to perform:

  1. createANewMeshInSidre so that it can be shared with other packages.
  2. importAMeshFromSidre, presumably created by different package or code upstream, to operate on, e.g. evaluate a new field on the mesh, etc.

Code snippets illustrating these two operations are presented in the following sections using a simple UnstructuredMesh example. However, the basic concepts extend to all supported MeshTypes.

Note

To use Sidre with Mint, the Axom Toolkit must be compiled with Conduit support and Sidre must be enabled (default). Consult the Axom Quick Start Guide for the details on how to build the Axom Toolkit.

Create a new Mesh in Sidre

Creating a mesh using Sidre is very similar to creating a mesh that uses NativeStorage. The key difference is that when calling the mesh constructor, the target sidre::Group, that will consist of the mesh, must be specified.

Warning

The target sidre::Group supplied to the mesh constructor is expected to be empty.

The following code snippet illustrates this capability using the SingleCellTopology UnstructuredMesh used to demonstrate how to createAnUnstructuredMesh with NativeStorage. The key differences in the code are highlighted below:

../../../examples/user_guide/mint_tutorial.cpp

Note

A similar construction follows for all supported MeshTypes. To createANewMeshInSidre the target sidre::Group that will consist of the mesh is specified in the constructor in addition to any other arguments. Consult the Mint Doxygen API Documentation for more details.

When the constructor is called, the target sidre::Group is populated according to the Conduit Blueprint mesh description. Any subsequent changes to the mesh are reflected accordingly to the corresponding sidre::Group. The rawSidreData generated after the above code snippet executes are included for reference in the sections/mint/appendix.

However, once the mesh object goes out-of-scope the mesh description and any data remains persisted in Sidre. The mesh can be deleted from Sidre using the corresponding Sidre API calls.

Warning

A Mint mesh, bound to a Sidre Group, can only be deleted from Sidre when the Group consisting the mesh is deleted from Sidre, or, when the Sidre Datastore instance that holds the Group is deleted. When a mesh, bound to a Sidre Group is deleted, its mesh representation and any data remain persistent within the corresponding Sidre Group hierarchy.

Import a Mesh from Sidre

Support for importing an existing mesh from Sidre, that conforms to the Conduit Blueprint mesh description, is provided by the mint::getMesh() function. The mint::getMesh() function takes the sidre::Group instance consisting of the mesh as an argument and returns a corresponding mint::Mesh instance. Notably, the returned mint:Mesh instance can be any of the supported MeshTypes.

The following code snippet illustrates this capability:

../../../examples/user_guide/mint_tutorial.cpp

  • The mesh is imported from Sidre by calling mint::getMesh(), passing the sidre::Group consisting of the mesh as an argument.
  • The mesh type of the imported mesh can be queried by calling the getMeshType() on the imported mesh object.
  • Moreover, an application can check if the mesh is bound to a Sidre group by calling hasSidreGroup() on the mesh.
  • Once the mesh is imported, the application can operate on it, e.g. outputToVTK, etc., as illustrated in the above code snippet.
  • Any subsequent changes to the mesh are reflected accordingly to the corresponding sidre::Group

However, once the mesh object goes out-of-scope the mesh description and any data remains persisted in Sidre. The mesh can be deleted from Sidre using the corresponding Sidre API calls.

Warning

  • When a Mint mesh bound to a Sidre Group is deleted, its mesh representation and any data remain persistent within the corresponding Sidre Group hierarchy.
  • A Mint mesh, bound to a Sidre Group, is deleted from Sidre by deleting the corresponding Sidre Group, or, when the Sidre Datastore instance that holds the Group is deleted.

Node Traversal Functions

The NodeTraversalFunctions iterate over the constituent Nodes of the mesh and apply a user-supplied kernel operation, often specified with a Lambda Expression. The NodeTraversalFunctions are implemented by the mint::for_all_nodes() family of functions, which take an ExecutionPolicy as the first template argument, and optionally, a second template argument to indicate the ExecutionSignature of the supplied kernel.

Note

If a second template argument is not specified, the default ExecutionSignature is set to xargs::index, which indicates that the supplied kernel takes a single argument corresponding to the index of the iteration space, in this case the node index, nodeIdx.

Simple Loop Over Nodes

The following code snippet illustrates a simple loop over the Nodes of a 2D mesh that computes the velocity magnitude, vmag, given the corresponding velocity components, vx and vy.

../../../examples/user_guide/mint_tutorial.cpp

Loop with Coordinates

The coordinates of a node are sometimes also required in addition to its index. This additional information may be requested by supplying xargs::x (in 1D), xargs::xy (in 2D) or xargs::xyz (in 3D), as the second template argument to the for_all_nodes() method to specify the executionSignature for the kernel.

This capability is demonstrated by the following code snippet, consisting of a kernel that updates the nodal velocity components, based on old node positions, stored at the xold and yold node-centered fields, respectively.

../../../examples/user_guide/mint_tutorial.cpp

Note

  • The second template argument, mint::xargs::xy, indicates that the supplied kernel expects the x and y node coordinates as arguments in addition to its nodeIdx.

Loop with IJK Indices

When working with a StructuredMesh, it is sometimes required to expose the regular Topology of the StructuredMesh to obtain higher performance for a particular algorithm. This typically entails using the logical IJK ordering of the StructuredMesh to implement certain operations. The template argument, xargs::ij or xargs::ijk, for 2D or 3D respectively, may be used as the second template argument to the for_all_nodes() function to specify the executionSignature of the supplied kernel.

For example, the following code snippet illustrates how to obtain a node's i and j indices within a sample kernel that computes the linear index of each node and stores the result in a node-centered field, ID.

../../../examples/user_guide/mint_tutorial.cpp

Warning

In this case, the kernel makes use of the IJK indices and hence it is only applicable for a StructuredMesh.

Cell Traversal Functions

The CellTraversalFunctions iterate over the constituent Cells of the mesh and apply a user-supplied kernel operation, often specified with a Lambda Expression. The CellTraversalFunctions are implemented by the mint::for_all_cells() family of functions, which take an ExecutionPolicy as the first template argument, and optionally, a second template argument to indicate the ExecutionSignature of the supplied kernel.

Note

If a second template argument is not specified, the default ExecutionSignature is set to xargs::index, which indicates that the supplied kernel takes a single argument corresponding to the index of the iteration space, in this case the cell index, cellIdx.

Simple Loop Over Cells

The following code snippet illustrates a simple loop over the constituent Cells of the mesh that computes the cell density (den), given corresponding mass (mass) and volume (vol) quantities.

../../../examples/user_guide/mint_tutorial.cpp

Loop with Node IDs

Certain operations may require the IDs of the constituent cell Nodes for some calculation. The template argument, xargs::nodeids, may be used as the second template argument to the for_all_cells() function to specify the executionSignature for the kernel. The xargs::nodeids indicates that the supplied kernel also takes the the IDs of the constituent cell Nodes as an argument.

This feature is demonstrated with the following code snippet, which averages the node-centered velocity components to corresponding cell-centered fields:

../../../examples/user_guide/mint_tutorial.cpp

Note

  • xargs::nodeids indicates that the specified kernel takes three arguments:
    • cellIdx, the ID of the cell,
    • nodeIDs, an array of the constituent node IDs, and
    • N, the number of Nodes for the given cell.

Loop with Coordinates

The coordinates of the constituent cell Nodes are often required in some calculations. A cell's node coordinates may be supplied to the specified kernel as an argument using xargs::coords as the second template argument to the for_all_cells() function, to specify the executionSignature of the supplied kernel.

This feature is demonstrated with the following code snippet, which computes the cell centroid by averaging the coordinates of the constituent cell Nodes:

Note

Since this kernel does not use the node IDs, the argument to the kernel is annotated using the AXOM_UNUSED_PARAM macro to silence compiler warnings.

../../../examples/user_guide/mint_tutorial.cpp

Note

  • xargs::coords indicates that the specified kernel takes the following arguments:
    • cellIdx, the ID of the cell,
    • coords, a matrix that stores the cell coordinates, such that:

      • The number of rows corresponds to the problem dimension, and,
      • The number of columns corresponds to the number of nodes.
      • The ith column vector of the matrix stores the coordinates of the ith node.
    • nodeIdx array of corresponding node IDs.

Loop with Face IDs

The IDs of the constituent cell Faces are sometimes needed to access the corresponding face-centered quantities for certain operations. The face IDs can be obtained using xargs::faceids as the second template argument to the for_all_faces() function, to specify the executionSignature of the supplied kernel.

This feature is demonstrated with the following code snippet, which computes the perimeter of each cell by summing the pre-computed face areas:

../../../examples/user_guide/mint_tutorial.cpp

Note

  • xargs::faceids indicates that the specified kernel takes the following arguments:
    • cellIdx, the ID of the cell,
    • faceIDs, an array of the constituent face IDs, and,
    • N, the number of Faces for the given cell.

Loop with IJK Indices

As with the NodeTraversalFunctions, when working with a StructuredMesh, it is sometimes required to expose the regular Topology of the StructuredMesh to obtain higher performance for a particular algorithm. This typically entails using the logical IJK ordering of the StructuredMesh to implement certain operations. The template argument, xargs::ij (in 2D) or xargs::ijk (in 3D) may be used as the second template argument to the for_all_cells() function, to specify the executionSignature of the supplied kernel.

For example, the following code snippet illustrates to obtain a cell's i and j indices within a kernel that computes the linear index of each cell and stores the result in a cell-centered field, ID.

../../../examples/user_guide/mint_tutorial.cpp

Warning

In this case, the kernel makes use of the IJK indices and hence it is only applicable for a StructuredMesh.

Face Traversal Functions

The FaceTraversalFunctions functions iterate over the constituent Faces of the mesh and apply a user-supplied kernel operation, often specified with a Lambda Expression. The FaceTraversalFunctions are implemented by the mint::for_all_faces() family of functions. which take an ExecutionPolicy as the first template argument, and optionally, a second template argument to indicate the ExecutionSignature of the supplied kernel.

Note

If a second template argument is not specified, the default ExecutionSignature is set to xargs::index, which indicates that the supplied kernel takes a single argument corresponding to the index of the iteration space, in this case the face index, faceIdx.

Simple Loop Over Faces

The following code snippet illustrates a simple loop over the constituent Faces of a 2D mesh that computes an interpolated face-centered quantity (temp) based on pre-computed interpolation coefficients t1 , t2 and w.

../../../examples/user_guide/mint_tutorial.cpp

Loop with Node IDs

The IDs of the constituent face Nodes are sometimes needed to access associated node-centered data for certain calculations. The template argument, xargs::nodeids, may be used as the second template argument to the for_all_faces() function to specify the executionSignature of the supplied kernel. The xargs::nodeids template argument indicates that the supplied kernel also takes the IDs of the constituent face Nodes as an argument.

This feature is demonstrated with the following code snippet which averages the node-centered velocity components to corresponding face-centered quantities:

../../../examples/user_guide/mint_tutorial.cpp

Note

  • xargs::nodeids indicates that the specified kernel takes three arguments:
    • faceIdx, the ID of the cell,
    • nodeIDs, an array of the constituent node IDs, and
    • N, the number of Nodes for the corresponding face.

Loop with Coordinates

The coordinates of the constituent face Nodes are often required in some calculations. The constituent face node coordinates may be supplied to the specified kernel as an argument using xargs::coords as the second template argument to the for_all_faces() function, to specify the executionSignature of the supplied kernel.

This feature is demonstrated with the following code snippet, which computes the face centroid by averaging the coordinates of the constituent face Nodes:

Note

Since this kernel does not use the node IDs, the argument to the kernel is annotated using the AXOM_UNUSED_PARAM macro to silence compiler warnings.

../../../examples/user_guide/mint_tutorial.cpp

Note

  • xargs::coords indicates that the specified kernel takes the following arguments:
  • faceIdx, the ID of the cell,
  • coords, a matrix that stores the cell coordinates, such that:

    • The number of rows corresponds to the problem dimension, and,
    • The number of columns corresponds to the number of nodes.
    • The ith column vector of the matrix stores the coordinates of the ith node.
  • nodeIdx array of corresponding node IDs.

Loop with Cell IDs

The constituent Faces of a mesh can be bound to either one or two Cells. The IDs of the Cells abutting a face are required in order to obtain the corresponding cell-centered quantities, needed by some calculations. The template argument, xargs::cellids, may be used as the second template argument to the for_all_faces() function to specify the executionSignature of the supplied kernel. Thereby, indicate that the supplied kernel also takes the IDs of the two abutting cells as an argument.

Note

External boundary faces are only bound to one cell. By convention, the ID of the second cell for external boundary faces is set to  − 1.

This functionality is demonstrated with the following example that loops over the constituent Faces of a mesh and marks external boundary faces:

../../../examples/user_guide/mint_tutorial.cpp

Note

  • xargs::coords indicates that the specified kernel takes the following arguments:
  • faceIdx, the ID of the cell,
  • c1, the ID of the first cell,
  • c2, the ID of the second cell, set to a  − 1 if the face is an external boundary face.

Finite Elements

Mint provides basic support for sections/fem consisting of LagrangeBasis shape functions for commonly employed CellTypes and associated operations, such as functions to evaluate the Jacobian and compute the forward and inverse IsoparametricMapping.

Warning

Porting and refactoring of Mint's sections/fem for GPUs is under development. This feature will be available in future versions of Mint.

Create a Finite Element Object

All associated functionality with sections/fem is exposed to the application through the mint::FiniteElement class. The following code snippet illustrates how to createAFiniteElementObject using a Linear Lagrangian Quadrilateral Finite Element as an example:

../../../examples/user_guide/mint_tutorial.cpp

  • The mint::FiniteElement constructor takes two arguments:
    • An N × M Matrix consisting of the cell coordinates, where, N corresponds to physical dimension of the cell and M corresponds to the number of constituent cell nodes. The cell coordinates are organized in the matrix such that, each column vector stores the coordinates of a corresponding node.
    • The cell type, e.g. mint::QUAD
  • Then, mint::bind_basis() is called to bind the Finite Element object to the LagrangeBasis. Effectively, this step wires the pointers to the LagrangeBasis shape functions for the particular cell type.

A similar construction follows for different CellTypes and associated supported shape functions.

The Finite Element object, once constructed and bound to a basis, it may be used to perform the following operations:

  1. Given a point in reference space, ξ̂ ∈ Ω̄:
    • evaluateShapeFunctions, Ni(ξ), associated with each of the constituent cell nodes, which are often used as interpolation weights,
    • evaluateTheJacobian, J(ξ), and
    • Compute the forwardIsoparametricMap x⃗ : Ω̄ → Ωe
  2. Given a point in physical space,  ∈ Ω:
    • Compute the inverseIsoparametricMap, which attempts to evaluate the corresponding reference coordinates of the point, ξ̂ ∈ Ω̄, with respect to the finite element, Ωe. This operation is only defined for points that are inside the element (within some ϵ).

Evaluate Shape Functions

The shape functions can be readily computed from any mint::FiniteElement instance by calling the evaluateShapeFunctions() method on the finite element object. The following code snippet illustrates how to evaluateShapeFunctions at the isoparametric center of a quadrilateral element, given by ξ = (0.5, 0.5)T:

../../../examples/user_guide/mint_tutorial.cpp

  • The evaluateShapeFunctions() method takes two arguments:
    • xi, an input argument corresponding to the reference coordinates of the point, ξ̂, where the shape functions will be evaluated, and
    • N, an output argument which is an array of length equal to the number of constituent cell Nodes, storing the corresponding shape functions.

Evaluate the Jacobian

Similarly, for a reference point, ξ̂ ∈ Ω̄, the Jacobian matrix, consisting the sums of derivatives of shape functions and the corresponding determinant of the Jacobian, can be readily computed from the finite element object as follows:

../../../examples/user_guide/mint_tutorial.cpp

  • The Jacobian matrix is computed by calling the jacobian() method on the finite element object, which takes two arguments:
    • xi, an input argument corresponding to the reference coordinates of the point, ξ̂, where the Jacobian will be evaluated, and
    • A matrix, represented by the axom::numerics::Matrix class, to store the resulting Jacobian.

Note

The Jacobian matrix is not necessarily a square matrix. It can have N × M dimensions, where, N corresponds to the dimension in the reference xi-space and M is the physical dimension. For example, a quadrilateral element is defined in a 2D reference space, but it may be instantiated within a 3D ambient space. Consequently, the dimensions of the corresponding Jacobian would be 2 × 3 in this case.

  • The determinant of the Jacobian can then be computed by calling axom::numerics::determinant(), with the Jacobian as the input argument.

Forward Isoparametric Map

Given a point in reference space, ξ̂ ∈ Ω̄, the corresponding physical point,  ∈ Ωe is computed by calling the computePhysicalCoords() method on the finite element object as illustrated below:

../../../examples/user_guide/mint_tutorial.cpp

The computePhysicalCoords() method takes two arguments:

  • xi, an input argument corresponding to the reference coordinates of the point, ξ̂, whose physical coordinates are computed, and
  • xc, an output array argument that stores the computed physical coordinates,  ∈ Ωe

Inverse Isoparametric Map

Similarly, given a point in physical space,  ∈ Ω, a corresponding point in the reference space of the element, ξ̂ ∈ Ω̄, can be obtained by calling the computeReferenceCoords() method on the finite element object as illustrated by the following:

../../../examples/user_guide/mint_tutorial.cpp

The computeReferenceCoords() method takes two arguments:

  • xc an input argument consisting of the physical point coordinates, whose reference coordinates are computed, and
  • xi an output array to store the computed reference coordinates, if successful.

The inverseIsoparametricMap typically requires an iterative, non-linear solve, which is typically implemented with a Newton-Raphson. Moreover, the inverseIsoparametricMap is only defined for points within the element, Ωe. Consequently, the computeReferenceCoords() method returns a status that indicates whether the operation was successful. Specifically, computeReferenceCoords() can return the following statuses:

  • INVERSE_MAP_FAILED

    This typically indicates that the Newton-Raphson iteration did not converge, e.g., negative Jacobian, etc.

  • OUTSIDE_ELEMENT

    This indicates that the Newton-Raphson converged, but the point is outside the element. Consequently, valid reference coordinates do not exist for the given point with respect to the element.

  • INSIDE_ELEMENT

    This indicates the the Newton-Raphson converged and the point is inside the element

Output to VTK

Mint provides native support for writing meshes in the ASCII Legacy VTK File Format. Legacy VTK files are popular due to their simplicity and can be read by a variety of visualization tools, such as VisIt and ParaView. Thereby, enable quick visualization of the various MeshTypes and constituent FieldData, which can significantly aid in debugging.

Warning

The Legacy VTK File Format does not provide support for face-centered fields. Consequently, the output consists of only the node-centered and cell-centered fields of the mesh.

The functionality for outputting a mesh to VTK is provided by the mint::write_vtk() function. This is a free function in the axom::mint namespace, which takes two arguments: (1) a pointer to a mint::Mesh object, and, (2) the filename of the target VTK file, as illustrated in the code snippet below:

../../../examples/user_guide/mint_tutorial.cpp

This function can be invoked on a mint::Mesh object, which can correspond to any of the supported MeshTypes. The concrete mesh type will be reflected in the resulting VTK output file according to the VTK File Format specification.

Note

Support for VTK output is primarily intended for debugging and quick visualization of meshes. This functionality is not intended for routine output or restart dumps from a simulation. Production I/O capabilities in the Axom Toolkit are supported through Sidre. Consult the Sidre documentation for the details.