Usage

In this section, we will look at a few basic examples of how to read different mesh properties.

Reading structured grid information

In this section, we will see how we can read multi-block structured grid information from the library within the meshReaderLib folder. In all cases, we need to first create a structured grid instance and then read the mesh. This is achieved with the following function calls:

#include <filesystem>
#include "meshReaderLib/meshReader.hpp"

int main() {
  // provide a relative or absolute path to where the CGNS file is located that should be read
  auto structuredMeshFile = std::filesystem::path("path/to/structured/mesh.cgns");

  // create an instance of the structured mesh reading class
  ReadStructuredMesh structuredMesh(structuredMeshFile);

  // read all structured grid properties at once
  structuredMesh.readMesh();

  // perform any other function calls here to get grid properties, discussed below in detail
  // ...

  return 0;
}

After the structured mesh has been set up in this way, we can proceed to read specific mesh information.

Reading coordinate information

To read the coordinates of a structured grid, we call the dedicated getter function. An example of how to loop over the mesh is provided as well, as well as how to get the x and y coordinates.

// get the coordinates from the structured mesh
auto coordinates = structuredMesh.getCoordinates();

// get the number of grid blocks (could be a multi-block structured grid)
auto numberOfBlocks = coordinates.size();

// loop over all blocks
for (unsigned block = 0; block < numberOfBlocks; ++block) {
  // get the number of x-coordinate
  auto numberOfIndicesInX = coordinates[block].size();

  // loop over all x-coordinates
  for (unsigned indexX = 0; indexX < numberOfIndicesInX; ++indexX) {
    // repeat the same for the y-coordinate
    auto numberOfIndicesInY =  coordinates[block][indexX].size();

    // loop over all y-coordinates
    for (unsigned indexY = 0; indexY < numberOfIndicesInY; ++indexY) {
      const auto &x = coordinates[block][indexX][indexY][COORDINATE::X];
      const auto &y = coordinates[block][indexX][indexY][COORDINATE::Y];

      // process results and use x and y as required
      // ...

    }
  }
}

Reading interface connectivity information

If we have more than a single zone/block in our mesh, then we need to know at which interface these blocks are connected, so that we can request the correct neighboring vertices in our calculation later, where we update values close the the interface and where the computational stencil requires information from a neighboring block. The code below shows how to read this information.

// get the interface connectivity from the structured mesh
auto interfaceConnectivity = structuredMesh.getInterfaceConnectivity();

// get the number of interfaces in the mesh
auto numberOfInterfaces = interfaceConnectivity.size();

// loop over all interfaces
for (unsigned interface = 0; interface < numberOfInterfaces; ++interface) {
  auto ownerBlock = interfaceConnectivity[interface].zones[0];
  auto neighbourBlock = interfaceConnectivity[interface].zones[1];

  // get the number of vertices in interface
  auto numberOfVerticesInInterface = interfaceConnectivity[interface].ownerIndex.size();

  // loop over interface
  for (int vertex = 0; vertex < numberOfVerticesInInterface; ++vertex) {
    // get owner indices in x and y (i.e. indices i, j)
    const auto &owner_i = interfaceConnectivity[interface].ownerIndex[vertex][COORDINATE::X];
    const auto &owner_j = interfaceConnectivity[interface].ownerIndex[vertex][COORDINATE::Y];

    // get neighbour indices in x and y (i.e. indices i, j)
    const auto &neighbour_i = interfaceConnectivity[interface].neighbourIndex[vertex][COORDINATE::X];
    const auto &neighbour_j = interfaceConnectivity[interface].neighbourIndex[vertex][COORDINATE::Y];

    // use vertices here, for example, discretising a second order partial derivative d2u/dx2 as
    // u^new = (u[i           + 1] - 2 * u[i]       + u[i       - 1]) / (dx * dx), may be written as
    // u^new = (u[neighbour_i + 1] - 2 * u[owner_i] + u[owner_i - 1]) / (dx * dx)
    // ...
  }
}

Reading boundary condition information

Finally, we want to apply specific boundary conditions at domain boundaries. For this, we first need to know which boundary condition is prescribed at the boundary. To read boundary information, use the code below as a starting point.

// get the boundary condition information
auto boundaryConditions = structuredMesh.getBoundaryConditions();

// get the number of blocks in the mesh
auto numberOfBlocks = boundaryConditions.size();

// loop over all blocks
for (unsigned block = 0; block < numberOfBlocks; ++block) {
  // check how many boundaries are within each block
  auto numberOfBoundaries = boundaryConditions[block].size();

  // loop over all boundaries in current block
  for (unsigned boundary = 0; boundary < numberOfBoundaries; ++boundary) {
    // get the boundary type, can be of type BC::WALL, BC::SYMMETRY, BC::INLET, or BC::OUTLET
    auto bcType = boundaryConditions[block][boundary].boundaryType;

    // get the number of vertices in boundary
    auto numberOfVertices = boundaryConditions[block][boundary].indices.size();

    // loop over all vertices in boundary
    for (unsigned vertex = 0; vertex < numberOfVertices; ++vertex) {
      // get current vertex
      const auto bcVertex = boundaryConditions[block][boundary].indices[vertex];

      // apply boundary condition based on specified type
      if (bcType == BC::WALL) {
        // process wall ...
      } else if (bcType == BC::SYMMETRY) {
        // process symmetry ...
      } else if (bcType == BC::INLET) {
        // process inlet ...
      } else if (bcType == BC::OUTLET) {
        // process outlet ...
      }
    }
  }
}

Reading unstructured grid information

As we saw for the structured grid, we need to first set up an instance of the unstructured mesh reading class and read the mesh itself. Afterwards, we have access to the mesh information via getters just as with the structured grid. The information is now in an unstructured grid format and thus different from the structured grid information. Additional information on the vertex connectivity is available as well and the examples shown below indicate how to read an unstructured grid with all of its information. But first, we have to set up the unstructured grid as mentioned above.

#include <filesystem>
#include "meshReaderLib/meshReader.hpp"

int main() {
  // provide a relative or absolute path to where the CGNS file is located that should be read
  auto unstructuredMeshFile = std::filesystem::path("path/to/unstructured/mesh.cgns");

  // create an instance of the unstructured mesh reading class
  ReadUnstructuredMesh unstructuredMesh(unstructuredMeshFile);

  // read all unstructured grid properties at once
  unstructuredMesh.readMesh();

  // perform any other function calls here to get grid properties, discussed below in detail
  // ...

  return 0;
}

Reading coordinate (vertex) information

The unstructured grid exposes the getCoordinates() function which provides the x and y coordinates for each vertex in the unstructured grid. Since the unstructured grid is allowed to be split into several zones/blocks, we need to loop over the number of blocks first, just as we did with our structured grid as well. A complete example of accessing the x and y coordinates for an unstructured grid is shown below.

// get the coordinates from the unstructured mesh
auto coordinates = unstructuredMesh.getCoordinates();

// get the number of grid blocks (could be a multi-block unstructured grid)
auto numberOfBlocks = coordinates.size();

// loop over all blocks
for (unsigned block = 0; block < numberOfBlocks; ++block) {
  // get the number of vertices
  auto numberOfVertices = coordinates[block].size();

  // loop over all vertices
  for (unsigned vertex = 0; vertex < numberOfVertices; ++vertex) {
    const auto &x = coordinates[block][vertex][COORDINATE::X];
    const auto &y = coordinates[block][vertex][COORDINATE::Y];

    // process results and use x and y as required
    // ...

  }
}

Reading element connectivity table

As alluded to above, unstructured grids require additional element connectivity information, which specifies how vertices are connected into a cell/element. The unstructured grid class has the getInternalCells() function available for this, which exposes, for each cell/element the vertex ID that makes up the current cell. We can then get the location for each vertex in the cell by looking up the information in the coordinate array that we read in the previous code example.

// get the internal elements from the unstructured mesh
auto interalElements = unstructuredMesh.getInternalCells();

// get the number of grid blocks (could be a multi-block unstructured grid)
auto numberOfBlocks = interalElements.size();

// loop over all blocks
for (unsigned block = 0; block < numberOfBlocks; ++block) {
  // get the number of internal elements in current block
  auto numberOfVertices = interalElements[block].size();

  // loop over all elements
  for (unsigned element = 0; element < numberOfVertices; ++element) {
    // get number of vertices making up current element
    auto numberOfVertices = interalElements[block][element].size();

    if (numberOfVertices == 3) {
      auto v0 = interalElements[block][element][0];
      auto v1 = interalElements[block][element][1];
      auto v2 = interalElements[block][element][2];

      std::cout << "triangle with vertices: " << v0 << ", " << v1 << ", " << v2 << std::endl;

      std::cout << "triangle has vertices at: ";

      std::cout << "(" << coordinates[block][v0][COORDINATE::X] << ", ";
      std::cout << coordinates[block][v0][COORDINATE::Y] << "), ";

      std::cout << "(" << coordinates[block][v1][COORDINATE::X] << ", ";
      std::cout << coordinates[block][v1][COORDINATE::Y] << "), ";

      std::cout << "(" << coordinates[block][v2][COORDINATE::X] << ", ";
      std::cout << coordinates[block][v2][COORDINATE::Y] << "), ";
    }

    // process results and use x and y as required
    // ...

  }
}

Reading interface connectivity information

Similar to the structured grid, we may have several zones or blocks in our grid. At the interface between two zones/blocks, we need to know which faces of the unstructured cells are connected to each other so that we can request information from neighboring cells on different zones/blocks for the calculation of flow properties. An example to obtain the matching vertex pairs that make up a face is shown below.

// get the interface connectivity from the unstructured mesh
auto interfaceConnectivity = unstructuredMesh.getInterfaceConnectivity();

// get the number of blocks in the mesh
auto numberOfBlocks = interfaceConnectivity.size();

// loop over all interfaces
for (unsigned block = 0; block < numberOfBlocks; ++block) {
  // get the number of interfaces in current block
  auto numberOfInterfaces = interfaceConnectivity[block].size();

  // loop over all interfaces in current block
  for (unsigned interface = 0; interface < numberOfInterfaces; ++interface) {
    auto ownerBlock = interfaceConnectivity[block][interface].zones[0];
    auto neighbourBlock = interfaceConnectivity[block][interface].zones[1];

    // get the number of vertices in interface
    auto numberOfFacesInInterface = interfaceConnectivity[block][interface].ownerIndex.size();

    // loop over faces in interface
    for (int face = 0; face < numberOfFacesInInterface; ++face) {
      // get the starting vertex of the owning face
      const auto &ownerFaceStart = interfaceConnectivity[block][interface].ownerIndex[face][0];

      // get the ending vertex of the owning face
      const auto &ownerFaceEnd = interfaceConnectivity[block][interface].ownerIndex[face][1];

      // get the starting vertex of the neighbour face
      const auto &neighbourFaceStart = interfaceConnectivity[block][interface].neighbourIndex[face][0];

      // get the ending vertex of the neighbour face
      const auto &neighbourFaceEnd = interfaceConnectivity[block][interface].neighbourIndex[face][1];
    }
  }
}

Reading boundary condition information

Getting boundary conditions is also no different from reading boundary conditions on a structured grid. The only difference here is that instead of reading vertices that are on the boundary, we read vertex pairs that make up the face of the boundary. The example below shows how to extract the type of boundary, as well as the faces that are contained within that boundary patch.

// get the boundary condition information
auto boundaryConditions = unstructuredMesh.getBoundaryConditions();

// get the number of blocks in the mesh
auto numberOfBlocks = boundaryConditions.size();

// loop over all blocks
for (unsigned block = 0; block < numberOfBlocks; ++block) {
  // check how many boundaries are within each block
  auto numberOfBoundaries = boundaryConditions[block].size();

  // loop over all boundaries in current block
  for (unsigned boundary = 0; boundary < numberOfBoundaries; ++boundary) {
    // get the boundary type, can be of type BC::WALL, BC::SYMMETRY, BC::INLET, or BC::OUTLET
    auto bcType = boundaryConditions[block][boundary].boundaryType;

    // get the number of faces in boundary
    auto numberOfFaces = boundaryConditions[block][boundary].indices.size();

    // loop over all faces in boundary
    for (unsigned face = 0; face < numberOfFaces; ++face) {
      // get current starting location of boundary face
      const auto bcFaceStart = boundaryConditions[block][boundary].indices[face][0];

      // get current ending location of boundary face
      const auto bcFaceEnd = boundaryConditions[block][boundary].indices[face][1];

      // apply boundary condition based on specified type
      if (bcType == BC::WALL) {
        // process wall ...
      } else if (bcType == BC::SYMMETRY) {
        // process symmetry ...
      } else if (bcType == BC::INLET) {
        // process inlet ...
      } else if (bcType == BC::OUTLET) {
        // process outlet ...
      }
    }
  }
}