General concepts » Meshes » Mesh Connectivity

Understanding and computing connectivity relations.

Connectivity describes the topological relationships between polytopes of different dimensions in a mesh. Understanding connectivity is essential for many finite element operations.

Introduction

In finite element analysis, we often need to know how mesh entities relate to each other:

  • Which cells share a given edge?
  • What are the faces of a cell?
  • Which vertices belong to a face?

These relationships are called incidence relations or connectivity.

Mathematical Notation

An incidence relation $ d \rightarrow d' $ maps polytopes of dimension $ d $ to polytopes of dimension $ d' $ that are incident to them.

For example, in a 2D mesh:

  • $ 1 \rightarrow 2 $ : Maps edges to incident cells
  • $ 2 \rightarrow 1 $ : Maps cells to their edges
  • $ 0 \rightarrow 2 $ : Maps vertices to incident cells

Computing Connectivity

Connectivity is not computed by default and must be explicitly requested:

#include <Rodin/Geometry.h>

using namespace Rodin;
using namespace Rodin::Geometry;

int main() {
  Mesh mesh;
  mesh = mesh.UniformGrid(Polytope::Type::Triangle, {16, 16});

  // Compute face-to-cell connectivity
  mesh.getConnectivity().compute(1, 2);

  return 0;
}

Common Connectivity Relations

2D Meshes

For 2D meshes (dimension = 2):

RelationDescriptionWhen Needed
0 → 1Vertices to edgesEdge operations
0 → 2Vertices to cellsVertex-based operations
1 → 0Edges to verticesAlways available
1 → 1Edges to adjacent edgesEdge neighbor queries
1 → 2Edges to cellsBoundary conditions
2 → 0Cells to verticesAlways available
2 → 1Cells to edgesCell neighbor queries
2 → 2Cells to cellsCell adjacency

3D Meshes

For 3D meshes (dimension = 3):

RelationDescriptionWhen Needed
0 → 3Vertices to cellsVertex-based operations
1 → 3Edges to cellsEdge operations
2 → 3Faces to cellsBoundary conditions
3 → 2Cells to facesCell neighbor queries
3 → 1Cells to edgesEdge-based operations
3 → 0Cells to verticesAlways available

Boundary Operations

Most important**: For boundary conditions and boundary iterations, you need:

  • 2D meshes: mesh.getConnectivity().compute(1, 2) (edge-to-cell)
  • 3D meshes: mesh.getConnectivity().compute(2, 3) (face-to-cell)
Mesh mesh;
mesh = mesh.UniformGrid(Polytope::Type::Triangle, {16, 16});

// Required for boundary operations in 2D
mesh.getConnectivity().compute(1, 2);

// Now we can iterate over boundary
for (auto it = mesh.getBoundary(); it; ++it) {
  std::cout << "Boundary edge " << it->getIndex() << std::endl;
}

Accessing Connectivity

Once computed, use connectivity to query relationships:

mesh.getConnectivity().compute(1, 2);
mesh.getConnectivity().compute(2, 1);

// Get all edges
for (auto edge = mesh.getFace(); edge; ++edge) {
  // Get cells incident to this edge
  const auto& incidentCells = 
    mesh.getConnectivity().getIncidence({1, edge->getIndex()}, 2);

  std::cout << "Edge " << edge->getIndex() 
            << " has " << incidentCells.size() 
            << " incident cells" << std::endl;
}

Complete Example

#include <Rodin/Geometry.h>

using namespace Rodin;
using namespace Rodin::Geometry;

int main() {
  // Create mesh
  Mesh mesh;
  mesh = mesh.UniformGrid(Polytope::Type::Triangle, {8, 8});

  std::cout << "Mesh info:" << std::endl;
  std::cout << "  Vertices: " << mesh.getVertexCount() << std::endl;
  std::cout << "  Edges: " << mesh.getFaceCount() << std::endl;
  std::cout << "  Cells: " << mesh.getCellCount() << std::endl;

  // Compute connectivity
  mesh.getConnectivity().compute(1, 2);  // Edge to cell
  mesh.getConnectivity().compute(2, 1);  // Cell to edge
  mesh.getConnectivity().compute(2, 2);  // Cell to cell

  // Count boundary edges
  size_t boundaryCount = 0;
  for (auto edge = mesh.getFace(); edge; ++edge) {
    if (mesh.isBoundary(edge->getIndex())) {
      boundaryCount++;
    }
  }
  std::cout << "Boundary edges: " << boundaryCount << std::endl;

  // Find cell neighbors
  auto cell = mesh.getCell(0);  // Get first cell
  for (auto neighbor = cell->getAdjacent(); neighbor; ++neighbor) {
    std::cout << "Cell 0 is adjacent to cell " 
              << neighbor->getIndex() << std::endl;
  }

  return 0;
}

Performance Considerations

Computational Cost

Computing connectivity can be expensive for large meshes:

  • Compute only what you need
  • Compute once, use many times
  • Some relations are computed together (e.g., $ d \rightarrow d' $ and $ d' \rightarrow d $ )

Timing Example

#include <chrono>

Mesh mesh;
mesh = mesh.UniformGrid(Polytope::Type::Triangle, {1024, 1024});

auto t0 = std::chrono::high_resolution_clock::now();
mesh.getConnectivity().compute(1, 2);
mesh.getConnectivity().compute(2, 1);
mesh.getConnectivity().compute(1, 1);
auto t1 = std::chrono::high_resolution_clock::now();

auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0);
std::cout << "Connectivity computation took " 
          << duration.count() << " ms" << std::endl;

When Is Connectivity Required?

Many operations require specific connectivity to be computed:

OperationRequired Connectivity
Boundary iteration $ (d-1) \rightarrow d $
Dirichlet BC $ (d-1) \rightarrow d $
Cell adjacency $ d \rightarrow d $
SubMesh creationVarious
Partitioning $ d \rightarrow d $

If required connectivity is missing, Rodin will raise an exception.

See Also