Skip to content

qcmesh

Core library for the aqcnes Quasi-Continuum Non-Equilibrium Solver

source: ETHZ GitLab license: GPLv3 DOI

Mesh - Quickstart

Example: Local mesh repair

First, we include the necessary headers and alias the qcmesh::mesh namespace:

examples/repair_mesh.cpp:17
#include "qcmesh/mesh/mesh_repair/repair_mesh.hpp"
#include "qcmesh/mesh/empty_data.hpp"
#include "qcmesh/mesh/mesh_repair/distopt/greedy_driver.hpp"
#include "qcmesh/mesh/serialization/empty_data.hpp"
#include "qcmesh/mesh/simplex_mesh.hpp"
#include <iostream>

namespace mesh = qcmesh::mesh;

In order to use the mesh repairing functionality, cells of our mesh must store a "cell quality". Cells below a given threshold will be repaired. qcmesh::mesh does not ship a ready to use cell data type, as this is assumed to be very different from project to project. Therefore we have to define our custom cell data containing a quality field.

examples/repair_mesh.cpp:36
struct CellData {
  double quality{};
};

To make qcmesh::mesh aware of this field, we also need to implement the trait qcmesh::mesh::traits::Quality:

examples/repair_mesh.cpp:44
template <> struct mesh::traits::Quality<CellData> {
  static double &quality(CellData &data) { return data.quality; }
  static double quality(const CellData &data) { return data.quality; }
};

Note

In qcmesh, we extend the concept of a "trait" a bit relative to what the C++ standard library understands under the term "trait". Where in the standard library a trait only carries information about a certain type, here we also attach functionality to types.

Even though, we dont need more than one mpi process in this example, we also need to tell boost::mpi on how to serialize / deserialize CellData so cells could be distributed over multiple mpi processes:

examples/repair_mesh.cpp:54
namespace boost::serialization {
template <class Archive>
void serialize(Archive &ar, CellData &data, const unsigned int) {
  ar &data.quality;
}
} // namespace boost::serialization

With some special configuration we can avoid unnecessary overhead during the serialization / deserialization:

examples/repair_mesh.cpp:66
BOOST_CLASS_IMPLEMENTATION(CellData, object_serializable)
BOOST_CLASS_TRACKING(CellData, track_never)
BOOST_IS_MPI_DATATYPE(CellData)

Now we are ready to create and repair a mesh. The mesh we are going to create in a top view looks like

    (2)
   / |
(3)  |
 | \ |
 | (0,1)
 | / |
(4)  |
   \ |
    (5)

Here the vertices are numbered from (0) to (5) and (0,1) means that vertex (0) is directly below vertex (1) (z axis). The main function below first defines vertices, then cells and finally the mesh. After the mesh is created, we repair it and output the new mesh topology:

examples/repair_mesh.cpp:84
int main() {
  // First initialize MPI.
  // Even though, we dont need more than one mpi process here and we also
  // only create a process local mesh, we still need to perform this step.
  const auto env = boost::mpi::environment{};
  using Cell = std::array<std::size_t, 4>;
  using Vertex = std::array<double, 3>;

  // Place vertices as depicted above:
  const auto d = double{0.025};
  const auto dy = double{0.1};
  const auto vertices = std::vector{Vertex{0., 0., -1.}, Vertex{0., 0., 1.},
                                    Vertex{0., dy, 0.},  Vertex{-1., d, 0.},
                                    Vertex{-1., -d, 0.}, Vertex{0., -1., 0.}};
  // Define cells by vertex indices:
  const auto cells =
      std::vector{Cell{0, 1, 2, 3}, Cell{0, 1, 3, 4}, Cell{0, 1, 4, 5}};
  // Create the mesh (The first template parameter mesh::EmptyData declares,
  // that vertices do not carry extra data):
  auto mesh = mesh::create_local_simplex_mesh<mesh::EmptyData, CellData>(
      vertices, cells);
  // Repair the mesh. The cell 0-1-3-4 has a quality below threshold and should
  // be replaced by edge removal:
  auto driver = mesh::GreedyDriver{};
  try {
    mesh::repair_mesh(mesh, driver);
  } catch (const std::exception &e) {
    std::cerr << e.what();
    boost::mpi::environment::abort(1);
  }
  // Output the new cells:
  std::cout << "Cells after repair:" << std::endl;
  for (const auto &[cell_id, cell] : mesh.cells)
    std::cout << static_cast<std::size_t>(cell_id) << ": ("
              << static_cast<std::size_t>(cell.nodes[0]) << ','
              << static_cast<std::size_t>(cell.nodes[1]) << ','
              << static_cast<std::size_t>(cell.nodes[2]) << ','
              << static_cast<std::size_t>(cell.nodes[3]) << ')' << std::endl;

  return 0;
}

Let's see the application in action.

$ examples/qcmesh-examples-repair-mesh
Cells after repair:
4: (0,2,4,5)
5: (1,5,4,2)
6: (0,2,3,4)