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: .. code:: cpp #include #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. .. code:: cpp // 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. .. code:: cpp // 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. .. code:: cpp // 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. .. code:: cpp #include #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. .. code:: cpp // 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. .. code:: cpp // 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-1: 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. .. code:: cpp // 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-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. .. code:: cpp // 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 ... } } } }