|
MeshFields
GPU accelerated mesh-based fields
|
Shape functions for an element topology are defined within a shape function struct (e.g., MeshField::LinearTriangleShape). Mapping between the ordering used for those shape functions and those used in the mesh database (currently just Omega_h) is through a mapping struct (see Step 2 - Define the Omega_h Node Mapping Struct below). DOF values are stored in MeshField::Field which needs to be associated with a shape function and mapping via a ShapeField to provide the needed per entity/dof access operators.
Note, as of MeshFields version 1.1.0 only interpolating Lagrange linear and quadratic triangles and tetrahedra are supported. As such, there may be deficiencies in the interface to support other types of shape functions. Please use https://github.com/SCOREC/meshFields/issues to report any problems or ask questions.
This guide walks through every step required to add a new shape function for an Omega_h element topology (edge, triangle, quadrilateral, tetrahedron, etc.). The steps are:
src/MeshField_Shape.hppsrc/MeshField.hppsrc/MeshField.hppsrc/MeshField_ShapeField.hppCreateLagrangeField** in src/MeshField_ShapeField.hppRefer to the Nomenclature section on the main page.
MeshFields defines shape functions in a canonical parametric coordinate system that is independent of Omega_h. The node mapping struct (Step 2) bridges the two conventions.
The MeshFields canonical parametric coordinates and node ordering for linear and quadratic triangles and tetrahedrons follows, "The Finite Element Method: Its Basis and Fundamentals", 2013, Zienkiewicz, Taylor, and Zhu.
MeshFields uses
parametric coordinates to specify a location within an element of dimension
. The redundant coordinate
is omitted:
| Topology | Parametric coords | Range |
|---|---|---|
| Edge (1D) | | |
| Triangle (2D) | | , |
| Tet (3D) | | , |
Diagrams of the vertex and edge node ordering, with parametric coordinates, for the linear and quadratic triangle and tetrahedron are in docs/Ordering.tex.
Add a new struct to src/MeshField_Shape.hpp inside namespace MeshField.
| Member | Description |
|---|---|
numNodes | Total nodes per element |
meshEntDim | Parametric space dimension |
Order | Polynomial order |
DofHolders | Entity types that hold DOFs |
getNodeParametricCoords() | Returns a flat array of node coordinates (length numNodes * meshEntDim) |
getValues(xi) | Returns an array of shape function values at xi (length numNodes) |
getLocalGradients(xi) | Returns a flat array of gradients at xi (length meshEntDim * numNodes, row-major: ) |
Optional (needed by quadratic and higher-order shapes):
| Member | Description |
|---|---|
NumDofHolders | Count of entities of each type in DofHolders |
DofsPerHolder | DOFs per entity for each type in DofHolders |
Use the helper functions declared at the top of MeshField_Shape.hpp to assert that incoming parametric coordinates are in range. This is only a check for outliers as MeshField::ParametricCoordTol is a loose tolerance.
The mapping struct lives in src/MeshField.hpp inside namespace MeshField::Omegah. It translates element-local node indices (as numbered by the shape function) into the on-process mesh entity indices (as numbered by Omega_h) that hold the corresponding DOFs.
The Omega_h canonical orderings for simplices are depicted below:
Omega_h triangle ordering.
Omega_h tetrahedron ordering.
Meshes composed of hypercubes (quadrilaterals and hexahedrons), pyramids, and prisms are supported by Omega_h, but they cannot be adapted. Version 1.1.0 of MeshFields only supports Omega_h simplices.
Vertices. The MeshFields vertex node ordering for triangles and tetrahedra matches the Omega_h element-to-vertex ordering, so vertex node
maps directly to local vertex
with no rotation:
Edges. Edge nodes do not necessarily follow the Omega_h element-to-edge ordering, so the mapping struct uses a lookup table from node index to the Omega_h local edge index. For the quadratic triangle the MeshFields edges (E0: V0-V1, E1: V1-V2, E2: V0-V2) coincide with the Omega_h edges, so the table is the identity. For the quadratic tetrahedron the MeshFields edges (nodes 4-9: V0-V1, V0-V2, V0-V3, V1-V2, V2-V3, V1-V3) are reordered to the Omega_h edges (0: V0-V1, 1: V1-V2, 2: V2-V0, 3: V0-V3, 4: V1-V3, 5: V2-V3):
You must determine the correct vertex and edge mapping for your topology and DOF holders by comparing the Omega_h canonical vertex/edge ordering (see figures above based on Omega_h_simplex.hpp and the simplex_down_template function) with the node ordering established by getNodeParametricCoords() in your shape struct. It is a good idea to add a unit test that evaluates the shape functions at the parametric coordinates of each node and checks that the value for that node is 1.0 and all others are 0.0.
FieldElement returns signed Jacobian determinants for 2D and 3D elements. The integrator (src/MeshField_Integrate.hpp) uses the determinants as-is, and debug builds assert that each one is non-negative. The vertex ordering produced by your mapping must therefore follow the right-hand rule so that the Jacobian determinant of a valid (un-inverted) Omega_h element is positive. An incorrect vertex permutation results in a negative determinant and a failed assertion in debug builds.
| Member | Description |
|---|---|
Constructor (Omega_h::Mesh &) | Cache connectivity arrays from Omega_h; validate mesh family/dimension |
getTopology() | Return an array of mesh topologies (vertex, edge, triangle, etc...) this mapping applies to |
operator()(LO nodeIndex_in, LO componentIndex_in, LO elementIndex_in, Mesh_Topology elementTopo_in) | Return mapping tuple - see MeshField::ElementToDofHolderMap |
| Query | API call |
|---|---|
| Element->vertex connectivity | mesh.ask_elem_verts() -> flat LOs array, stride = simplex_degree(elemDim, 0) |
| Element->edge connectivity | mesh.ask_down(elemDim, 1).ab2b -> flat LOs array, stride = simplex_degree(elemDim, 1) |
| Element->face connectivity | mesh.ask_down(elemDim, 2).ab2b |
Add a new factory function (or extend an existing one) in src/MeshField.hpp inside namespace MeshField::Omegah. The existing getTriangleElement factory (from src/MeshField.hpp) shows the full pattern:
Callers obtain the shape and mapping via structured bindings:
An Accessor is a templated struct that provides operator()(entity, node, component, topology) for reading and writing DOF values from the underlying field storage. It is passed as a variadic template argument (Mixins...) to ShapeField.
Two built-in Accessors cover the common cases:
LinearAccessor – for shapes whose DOFs live only at vertices. All calls to operator() are forwarded to a single vertex field regardless of topology:
QuadraticAccessor – for shapes whose DOFs live at both vertices and edges. The operator() dispatches to the vertex field or edge field based on the topology argument:
If your shape introduces DOFs at a new entity type (e.g., triangle faces for a cubic shape), define a new Accessor following the same pattern:
QuadraticAccessor for EdgeAccessor edgeField;,) for each new entity type.operator() with a branch for the added topology.topo array.CreateLagrangeField<ExecutionSpace, Controller, DataType, order, dim, numComp>(meshInfo) (in src/MeshField_ShapeField.hpp) is the user-facing factory that allocates field storage and assembles a ShapeField. Add a new if constexpr branch for your shape's order and dim. The steps within that branch are:
meshInfo counts for every entity type that holds DOFs (e.g., meshInfo.numVtx, meshInfo.numEdge). Call fail() if a required count is zero.Controller sized for each DOF-holding entity type. Each field in the controller needs entries for numEntities * dofsPerHolder * numComp.MeshField::makeField<Ctrlr, N>(ctrl) for each field index N to obtain typed slice objects.QuadraticAccessor{vtxField, edgeField}).FieldWithController<Ctrlr, ShapeField<numComp, YourShape, Ctrlr, YourAccessor>>{ctrl, ShapeField(meshInfo, accessor)}.See the existing order == 2 && dim == 2 branch (quadratic triangle) as a concrete reference: