deal.II version GIT relicensing-6842-g793a97d2aa 2026-10-02 14:00:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
The 'An agglomeration-based solver for the Poisson problem' code gallery program

This program was contributed by Marco Feder <[email protected]>.
It comes without any warranty or support by its authors or the authors of deal.II.

This program is part of the deal.II code gallery and consists of the following files (click to inspect):

Pictures from this code gallery program

Annotated version of README.md

Polytopic Mesh DG Solver for Poisson

This program solves a Poisson problem on an agglomerated polytopal mesh using a symmetric interior penalty discontinuous Galerkin (SIPDG) method. Agglomerates are constructed by an R-tree based spatial indexing strategy, following the approach proposed in [1]. In addition, a graph-based METIS partitioner is also provided in this program for comparison.

Running the code:

As in the tutorial programs, type

cmake -DDEAL_II_DIR=/path/to/deal.II .

on the command line to configure the program. After that, you can compile with make and run with either make run or with

./agglomeration_poisson

on the command line.

Program output

Running the program produces two kinds of output:

  • terminal output (text summary),
  • visualization files (.vtu).

The program prints a short summary including:

  • the finite element degree (FE degree);
  • the triangulation size (Size of tria);
  • the agglomeration construction time (rtree/metis build time);
  • the number of agglomerated subdomains (N subdomains);
  • the number of DoFs per cell;
  • the assembly time;
  • a convergence table with #DoFs, L2 error, and H1 error.

The program writes the following .vtu files for each run:

  • grid_input_mesh.vtu, containing the input mesh from gmsh;
  • grid_fine_mesh_refined.vtu, containing the globally refined fine mesh;
  • grid_<partitioner>_<n_subdomains>.vtu, containing the agglomerated mesh partition information (cell-wise agglomeration labels);
  • interpolated_solution_<partitioner>_<n_subdomains>.vtu, containing the numerical solution interpolated to the fine grid together with agglomerate labels for visualization.

These files can be visualized in ParaView to inspect both the agglomeration structure and the computed solution.

Problem description:

We consider the Poisson problem in a bounded, simply connected domain \(\Omega \subset \mathbb{R}^d\), \(d = 2,3\). The strong formulation reads

\begin{align*} -\Delta u &= f && \text{in } \Omega, \\ u &= u_D && \text{on } \partial\Omega, \end{align*}

where the right-hand side satisfies \(f \in L^2(\Omega)\) and the prescribed Dirichlet data satisfy \(u_D \in H^{1/2}(\partial\Omega)\).

The corresponding weak formulation is: find \(u \in H^1(\Omega)\) with \(u = u_D\) on \(\partial\Omega\) such that

\begin{align*} \int_{\Omega} \nabla u \cdot \nabla v \,\mathrm d\mathbf{x} =\int_{\Omega} f\, v \,\mathrm d\mathbf{x} \qquad \text{for all } v \in H_0^1(\Omega). \end{align*}

SIPDG discretization on agglomerated polytopic meshes:

We discretize the weak formulation by a symmetric interior penalty discontinuous Galerkin (SIPDG) method on the agglomerated polytopal mesh \(T_h\), whose elements \(K \in T_h\) are mutually disjoint open polygons (for \(d=2\)) or polyhedra (for \(d=3\)). The mesh skeleton is defined by

\begin{align*} \Gamma := \bigcup_{K \in T_h} \partial K. \end{align*}

The mesh skeleton \(\Gamma\) is decomposed into \((d-1)\)–dimensional simplices \(F\) representing the mesh faces, shared by at most two elements. These are distinct from elemental interfaces, which are defined as the simply connected components of the intersection between the boundary of an element and either a neighboring element or \(\partial \Omega\). As such, an interface between two elements may consist of more than one face, separated by hanging nodes/edges shared by those two elements only. We denote by \(\Gamma_{\mathrm{int}}\) the union of all interior faces, and by \(\Gamma_{\mathrm D} := \Gamma \cap \partial\Omega\) the union of Dirichlet boundary faces.

In practice, the polytopic mesh is obtained by agglomeration, so that each element \(K \in T_h\) is the union of a collection of leaf cells. For each agglomerated element \(K\), we associate an axis-aligned bounding box \(B_K\). On \(B_K\) we define the standard polynomial space \(Q_p(B_K)\) spanned by tensor-product Lagrange polynomials of degree \(p\) in each coordinate direction. Since \(K \subset B_K\), the basis on \(K\) is defined by restricting each basis function to \(K\). In the implementation, this corresponds to using the deal.II finite element FE_DGQ on the bounding box and taking its restriction to the agglomerated element. The global discrete space \(V_h\) is then obtained by assembling these local spaces in a discontinuous manner over all \(K \in T_h\). For \(u_h, v_h \in V_h\) we use the broken gradient \(\nabla_h\) and the standard jump and average operators \([\![\cdot]\!]\) and \(\{\!\!\{\cdot\}\!\!\}\) on faces.

The DG formulation reads: find \(u_h \in V_h\) such that

\begin{align*} B(u_h,v_h) = l(v_h) \qquad \forall\, v_h \in V_h, \end{align*}

with

\begin{align*} B(u_h,v_h) &= \int_{\Omega} \nabla_h u_h \cdot \nabla_h v_h \,\mathrm d\mathbf{x} \\ &\quad - \int_{\Gamma} \Bigl( \{\!\!\{\nabla u_h\}\!\!\} \cdot [\![v_h]\!] + \{\!\!\{\nabla v_h\}\!\!\} \cdot [\![u_h]\!] \Bigr)\,\mathrm d s \\ &\quad + \int_{\Gamma} \sigma \,[\![u_h]\!] \cdot [\![v_h]\!] \,\mathrm d s, \end{align*}

and

\begin{align*} l(v_h) = \int_\Omega f\, v_h \,\mathrm d\mathbf{x} + \int_{\Gamma_{\mathrm D}} u_D \bigl(\sigma v_h - \nabla v_h \cdot \mathbf n\bigr)\,\mathrm d s. \end{align*}

The penalty parameter is chosen as

\begin{align*} \sigma(\mathbf x) = C_\sigma \begin{cases} \dfrac{(p+1)(p+d)}{ h_{B_K} }, & \text{if } \mathbf x \in \partial K \cap \partial\Omega, \\[0.5em] \dfrac{(p+1)(p+d)}{\min\{h_{B_K}^+,h_{B_K}^-\}}, & \text{if } \mathbf x \in \Gamma_{\mathrm{int}}, \end{cases} \end{align*}

where \(h_{B_K}^\pm\) denote the diameters of the bounding boxes associated with the two elements sharing the interior face. In this program, we set \(C_\sigma = 10\).

This scheme is well posed and admits optimal-order a priori error estimates. More precisely, assuming that \(u|_K \in H^{s+1}(K)\) for all \(K \in T_h\) and some \(1 \le s \le p\), there exists a constant \(C > 0\), independent of \(h\), such that

\begin{align*} \|u - u_h\|_{L^2(\Omega)} \le C\, h^{s+1} \, |u|_{H^{s+1}(\Omega)}, \end{align*}

and

\begin{align*} \|\nabla(u - u_h)\|_{L^2(\Omega)} \le C\, h^{s} \, |u|_{H^{s+1}(\Omega)}. \end{align*}

We refer to [2] for details of the analysis.

Agglomeration strategies

Agglomeration is a natural mechanism for constructing polytopic meshes. This program supports two strategies for generating agglomerates, corresponding to the choices metis and rtree. In this example, we mainly focus on the rtree strategy, which is the method developed and introduced in the work [1].

R-tree-based agglomeration

Basic idea and data structure

In the rtree option, axis-aligned bounding boxes of all fine cells are inserted into a spatial R-tree. Agglomerates are obtained by grouping the cells whose bounding boxes belong to the same node at a user-selected extraction level of the tree. This purely geometric strategy does not require external graph partitioners and is typically fast and scalable. The number and shape of the agglomerates are determined by the R-tree structure and the chosen level.

This approach is particularly suitable for multilevel methods, where a nested agglomerated hierarchy is desirable.

At the data-structure level, we distinguish leaf nodes and internal nodes:

  • Leaf nodes store the geometric objects (here, mesh cells or their bounding boxes).
  • Internal nodes store:
    • a pointer (or reference) to a child node,
    • a bounding box that encloses all entries contained in that child subtree.

As a result, each internal node represents a spatial grouping of the objects below it.

Design targets

The R-tree is used here as a geometry-aware structure for organizing cell bounding boxes into hierarchical groups. Our construction is guided by the classical R*-tree criteria of Beckmann et al. [3], namely:

  • Minimize box area: reduce the area covered by each bounding box,
  • Minimize overlap: reduce overlap between neighboring boxes,
  • Improve shape-regularity: reduce box perimeters (equivalently, favor more shape-regular boxes).

These criteria improve the spatial quality of the hierarchy and typically lead to better grouping and query behavior.

To make the R-tree construction more intuitive, we first illustrate the relation between geometric objects, their minimum bounding rectangles (MBRs), and the corresponding R-tree hierarchy. The left image shows geometric objects together with their enclosing MBRs, while the right image shows the associated tree structure (leaf and internal nodes). This visual example helps explain how the hierarchical grouping is later used to extract agglomerates.

Agglomeration extraction

The construction of an R-tree spatial index on an arbitrary fine grid provides a natural agglomeration strategy with the following features:

  • it is fully automated, robust, and dimension-independent;
  • it produces a nested hierarchy of agglomerates;
  • the resulting agglomerates are closely aligned with their axis-aligned bounding boxes.

These properties make the R-tree approach an attractive alternative to graph-based agglomeration methods.

Given a collection of fine-level cells, or of cut-cell bounding boxes, the agglomeration procedure is as follows:

  • Step 1: Build the R-tree
    Construct an R-tree from the set of bounding boxes associated with the fine-level cells.
  • Step 2: Select a target level
    Choose a tree level \(l\) with \(1\le l \le L\) to control the agglomeration granularity.
  • Step 3: Collect leaf descendants
    For each node on level \(l\), recursively traverse its children until leaf nodes are reached.
  • Step 4: Agglomerate by common ancestor
    Merge leaf cells that belong to the same subtree, that is, those sharing the same ancestor at level \(l\).

This yields a nested hierarchy with natural parent-child relations across levels and, by repeating the extraction at different levels, produces a sequence of nested agglomerated meshes that can be used in multilevel solvers and preconditioners.

For the agglomeration workflow considered here, the R-tree-based extraction has the following practical features:

  • Level-independent extraction cost (observed): the wall-clock time is approximately constant with respect to the chosen extraction level.
  • Boost.Geometry backend: the implementation relies on the Boost.Geometry R-tree.
  • Custom traversal logic: the hierarchy traversal required for agglomeration is not directly exposed, so a custom node visitor is implemented.

In the present setting, this makes the R-tree approach particularly convenient for constructing nested agglomerated meshes in multilevel finite element and DG settings.

For illustration, the following images show R-tree-based agglomeration on a structured fine mesh:

Figures (3)-(5) show, respectively, the original fine mesh, the blocks induced by the R-tree on the cell bounding boxes, and the corresponding tree structure.

METIS-based partitioning

In the metis option, the adjacency graph of the fine mesh is constructed with one vertex per cell and edges between face-neighboring cells. This graph is then partitioned by the multilevel graph partitioner METIS into a prescribed number of parts, and each part defines one agglomerate; see [4] for details.

Test case:

We consider the Poisson problem on the unit square \(\Omega = (0,1)^2\) with the manufactured exact solution

\begin{align*} u(x,y) = \sin(\pi x)\sin(\pi y). \end{align*}

The corresponding right-hand side is

\begin{align*} f(x,y) = 2\pi^2 \sin(\pi x)\sin(\pi y). \end{align*}

This manufactured solution allows us to compute the global \(L^2\)- and \(H^1\)-seminorm errors of the discrete solution in order to assess the quality of the numerical approximation.

In this example, we start from an unstructured initial mesh and then perform five global refinement steps. The resulting meshes are shown below.

Agglomerates are then constructed from the fine level mesh by METIS and by the R-tree strategy, leading to different polytopal meshes. The following images compare the resulting agglomerates corresponding to two different agglomeration levels (91 and 364 agglomerates).

Comparison of agglomeration strategies

These plots illustrate how the two strategies distribute and shape the agglomerates starting from the same fine level mesh. In particular, the R-tree approach produces geometry-driven groupings induced by the spatial hierarchy, while METIS produces graph-based partitions of the cell adjacency graph.

To assess the discretization accuracy, we next compare the error behavior under mesh refinement or increasing the polynomial degree. The plots below are obtained by collecting the program outputs over multiple runs and post-processing the reported error data.

(12) h-convergence for Q_p elements (p=1,2,3) with METIS and R-tree agglomeration*

The figure reports the \(L^2\)-norm error with respect to the manufactured solution. Optimal convergence rates are observed for all polynomial degrees and for both agglomeration strategies. In addition, the curves associated with the R-tree approach are marginally lower than or comparable to those obtained with METIS-based partitioning.

We next compare p-convergence for the two agglomeration strategies in both the \(L^2\)-norm and the \(H^1\)-seminorm.

(13) Convergence under p-refinement for METIS and R-tree agglomeration (p = 1,2,3,4,5)*

In addition to accuracy, the cost of constructing the agglomerated polytopal meshes is also relevant in practice. The following timing plot compares the wall-clock time required by the R-tree and METIS strategies. The timing values are collected from the program outputs and summarized in post-processing.

(14) Wall-clock time (seconds) for building polytopal grids with R-tree and METIS*

Finally, the program also outputs VTU files for visualization in ParaView. Figures (15) and (16) show the solution computed on 91 agglomerates and then interpolated back onto the fine mesh, for the R-tree and METIS strategies, respectively, visualized in ParaView using Warp By Scalar together with the Surface representation.

References

[1] Marco Feder, Andrea Cangiani and Luca Heltai (2025), R3MG: R-tree based agglomeration of polytopal grids with applications to multilevel methods. DOI: 10.1016/j.jcp.2025.113773

[2] Daniele Antonio Di Pietro and Alexandre Ern (2012), Mathematical Aspects of Discontinuous Galerkin Methods. DOI: 10.1007/978-3-642-22980-0

[3] Norbert Beckmann, Hans-Peter Kriegel, Ralf Schneider and Bernhard Seeger (1990), The R*-tree: an efficient and robust access method for points and rectangles. DOI: 10.1145/93597.98741

[4] George Karypis and Vipin Kumar (1998), A fast and high quality multilevel scheme for partitioning irregular graphs. DOI: 10.1137/S1064827595287997

Annotated version of agglomeration_poisson.cc

  /* -----------------------------------------------------------------------------
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
  * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,
  * Andrea Cangiani
  *
  * This file is part of the deal.II code gallery.
  *
  * -----------------------------------------------------------------------------
  */
*  *  *  struct InterferenceTaperTransform *  

The following headers provide the deal.II functionality needed in this example. Most of them are standard components for mesh handling, finite element mappings, linear algebra, and graphical output. In addition, we include the agglomeration-specific headers that define the data structures and utilities used to construct and manage polytopal agglomerates.

deal.II base utilities.

  #include <deal.II/base/exceptions.h>

Finite element mappings.

  #include <deal.II/fe/mapping_fe.h>

Grid generation, mesh input/output, and mesh-related utilities.

  #include <deal.II/grid/grid_generator.h>
  #include <deal.II/grid/grid_in.h>
  #include <deal.II/grid/grid_out.h>
  #include <deal.II/grid/grid_tools.h>

Linear algebra objects and sparse direct solvers.

  #include <deal.II/lac/precondition.h>
  #include <deal.II/lac/solver_cg.h>
  #include <deal.II/lac/sparse_direct.h>
  #include <deal.II/lac/sparse_matrix.h>

Output of finite element data for visualization.

  #include <deal.II/numerics/data_out.h>

Agglomeration-specific headers used in this example.

  #include <agglomeration_handler.h>
  #include <poly_utils.h>

C++ standard library headers.

  #include <algorithm>
  #include <chrono>

We use the struct ConvergenceInfo to store the number of degrees of freedom together with the corresponding L2 and H1 errors, and print a simple convergence table to the console.

  struct ConvergenceInfo
  {
  ConvergenceInfo() = default;
  void
  add(const std::pair<types::global_dof_index, std::pair<double, double>>
  &dofs_and_errs)
  {
  vec_data.push_back(dofs_and_errs);
  }
  void
  print()
  {
  Assert(vec_data.size() > 0, ExcInternalError());
  std::cout << std::left << "#DoFs, L2 error, H1 error" << std::endl;
  for (const auto &dof_and_errs : vec_data)
  std::cout << std::scientific << dof_and_errs.first << ", "
  << dof_and_errs.second.first << ", "
  << dof_and_errs.second.second << std::endl;
  }
  std::vector<std::pair<types::global_dof_index, std::pair<double, double>>>
  vec_data;
  };
Point< 2 > second
Definition grid_out.cc:4640
Point< 2 > first
Definition grid_out.cc:4639
#define Assert(cond, exc)
STL namespace.

We will compare the performance of three different partitioning strategies: using METIS, using an R-tree based agglomeration, or not performing any partitioning at all.

  enum class PartitionerType
  {
  metis,
  rtree,
  no_partition
  };

We then implement the manufactured right-hand side f(x, y) = 2 π² sin(π x) sin(π y), which corresponds to the exact solution u(x, y) = sin(π x) sin(π y).

  template <int dim>
  class RightHandSide : public Function<dim>
  {
  public:
  RightHandSide()
  : Function<dim>()
  {}
  virtual void
  value_list(const std::vector<Point<dim>> &points,
  std::vector<double> &values,
  const unsigned int /*component*/) const override
  {
  for (unsigned int i = 0; i < values.size(); ++i)
  values[i] = 2 * numbers::PI * numbers::PI *
  std::sin(numbers::PI * points[i][0]) *
  std::sin(numbers::PI * points[i][1]);
  }
  };
virtual void value_list(const std::vector< Point< dim > > &points, std::vector< RangeNumberType > &values, const unsigned int component=0) const
Definition point.h:111
constexpr double PI
Definition numbers.h:240
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)

Exact solution is set as u(x,y) = sin(pi x) sin(pi y). It is used to impose Dirichlet boundary conditions and to evaluate the L2 and H1-seminorm errors. Its gradient is also provided for the computation of the H1 error.

  template <int dim>
  class ExactSolution : public Function<dim>
  {
  public:
  ExactSolution()
  : Function<dim>()
  {
  }
  virtual double
  value(const Point<dim> &p,
  const unsigned int /* component */ = 0) const override
  {
  return std::sin(numbers::PI * p[0]) * std::sin(numbers::PI * p[1]);
  }
  virtual void
  value_list(const std::vector<Point<dim>> &points,
  std::vector<double> &values,
  const unsigned int /*component*/) const override
  {
  for (unsigned int i = 0; i < values.size(); ++i)
  values[i] = this->value(points[i]);
  }
  const unsigned int /* component */ = 0) const override
  {
  Tensor<1, dim> return_value;
  return_value[0] =
  return_value[1] =
  return return_value;
  }
  };
virtual Tensor< 1, dim, RangeNumberType > gradient(const Point< dim > &p, const unsigned int component=0) const
virtual RangeNumberType value(const Point< dim > &p, const unsigned int component=0) const
static ::ExceptionBase & ExcNotImplemented()
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)

The Poisson<dim> class encapsulates the solution of the model Poisson problem

\[ -\Delta u = f \quad \text{in } \Omega, \qquad u = u_D \quad \text{on } \partial\Omega. \]

It sets up a fine triangulation, constructs agglomerated polytopal cells according to the chosen partitioning strategy, assembles the symmetric interior penalty DG discretization on the agglomerated mesh, solves the resulting linear system, and finally postprocesses the numerical solution by writing visualization output and computing global error norms.

  template <int dim>
  class Poisson
  {
  private:
  void
  make_grid();
  void
  setup_agglomeration();
  void
  assemble_system();
  void
  solve();
  void
  output_results();
  FE_DGQ<dim> dg_fe;
  std::unique_ptr<AgglomerationHandler<dim>> ah;
  SparseMatrix<double> system_matrix;
  Vector<double> solution;
  Vector<double> system_rhs;
  std::unique_ptr<GridTools::Cache<dim>> cached_tria;
  std::unique_ptr<const Function<dim>> rhs_function;
  std::unique_ptr<const Function<dim>> analytical_solution;
  public:
  Poisson(const PartitionerType &partitioner_type = PartitionerType::rtree,
  const unsigned int = 0,
  const unsigned int = 0,
  const unsigned int fe_degree = 1);
  void
  run();
  get_n_dofs() const;
  std::pair<double, double>
  get_error() const;
  PartitionerType partitioner_type;
  unsigned int extraction_level;
  unsigned int n_subdomains;
  double penalty_constant = 60.; // 10*(p+1)(p+d) for p = 1 and d = 2 => 60
  double l2_err;
  double semih1_err;
  };

The constructor initializes the Poisson<dim> solver with the selected partitioning strategy, agglomeration parameters, polynomial degree, and the manufactured exact solution and right-hand side.

  template <int dim>
  Poisson<dim>::Poisson(const PartitionerType &partitioner_type,
  const unsigned int extraction_level,
  const unsigned int n_subdomains,
  const unsigned int fe_degree)
  : mapping()
  , dg_fe(fe_degree)
  , partitioner_type(partitioner_type)
  , extraction_level(extraction_level)
  , n_subdomains(n_subdomains)
  , penalty_constant(10. * (fe_degree + 1) * (fe_degree + dim))
  {

Initialize manufactured solution.

  analytical_solution = std::make_unique<ExactSolution<dim>>();
  rhs_function = std::make_unique<const RightHandSide<dim>>();
  constraints.close();
  }

Build the fine triangulation from a Gmsh mesh, apply a global refinement, initialize the cache and agglomeration handler, and define agglomerates according to the selected partitioning strategy.

  template <int dim>
  void
  Poisson<dim>::make_grid()
  {
  GridIn<dim> grid_in;
  grid_in.attach_triangulation(tria);
  std::ifstream gmsh_file(std::string(MESH_DIR) +
  "/unit_square_quad_unstructured.msh");
  grid_in.read_msh(gmsh_file);
  {
  GridOut grid_out;
  std::ofstream out("grid_input_mesh.vtu");
  grid_out.write_vtu(tria, out); // Write the input mesh (before any refinement), for documentation/figures.
  }
  tria.refine_global(5); // Refine the mesh to obtain the fine grid used for agglomeration.
  {
  GridOut grid_out;
  std::ofstream out("grid_fine_mesh_refined.vtu");
  grid_out.write_vtu(tria, out); // Write the refined (fine) mesh used as starting point for agglomeration.
  }
  std::cout << "Size of tria: " << tria.n_active_cells() << std::endl;
  cached_tria = std::make_unique<GridTools::Cache<dim>>(tria, mapping);
  ah = std::make_unique<AgglomerationHandler<dim>>(*cached_tria);
  if (partitioner_type == PartitionerType::metis)
  { // Partition the triangulation with a graph partitioner.
  auto start = std::chrono::system_clock::now();
  tria,
  std::vector<
  std::vector<typename Triangulation<dim>::active_cell_iterator>>
  cells_per_subdomain(n_subdomains);
  for (const auto &cell : tria.active_cell_iterators())
  cells_per_subdomain[cell->subdomain_id()].push_back(cell);
  for (std::size_t i = 0; i < n_subdomains; ++i) // Define one agglomerate for each subdomain
  ah->define_agglomerate(cells_per_subdomain[i]);
  std::chrono::duration<double> wctduration =
  (std::chrono::system_clock::now() - start);
  std::cout << "METIS built in " << wctduration.count()
  << " seconds [wall clock]" << std::endl;
  }
  else if (partitioner_type == PartitionerType::rtree)
  { // Build agglomerates from the R-tree hierarchy
  namespace bgi = boost::geometry::index;
  static constexpr unsigned int max_elem_per_node =
  PolyUtils::constexpr_pow(2, dim);
  std::vector<std::pair<BoundingBox<dim>,
  boxes(tria.n_active_cells());
  unsigned int i = 0;
  for (const auto &cell : tria.active_cell_iterators())
  boxes[i++] = std::make_pair(mapping.get_bounding_box(cell), cell);
  auto start = std::chrono::system_clock::now();
  auto tree = pack_rtree<bgi::rstar<max_elem_per_node>>(boxes);
  CellsAgglomerator<dim, decltype(tree)> agglomerator{tree,
  extraction_level};
  const auto vec_agglomerates = agglomerator.extract_agglomerates();
  for (const auto &agglo : vec_agglomerates) // Flag elements for agglomeration
  ah->define_agglomerate(agglo);
  std::chrono::duration<double> wctduration =
  (std::chrono::system_clock::now() - start);
  std::cout << "R-tree agglomerates built in " << wctduration.count()
  << " seconds [wall clock]" << std::endl;
  }
  else if (partitioner_type == PartitionerType::no_partition)
  {
  }
  else
  {
  Assert(false, ExcMessage("Wrong partitioning."));
  }
  n_subdomains = ah->n_agglomerates();
  std::cout << "N subdomains = " << n_subdomains << std::endl;
  }
***mech_lbc_system increment_interpolation_handlers push_back(scale_z_handler)
void attach_triangulation(Triangulation< dim, spacedim > &tria)
Definition grid_in.cc:155
void partition_triangulation(const unsigned int n_partitions, Triangulation< dim, spacedim > &triangulation, const SparsityTools::Partitioner partitioner=SparsityTools::Partitioner::metis)
unsigned int subdomain_id
Definition types.h:50

To finalize the agglomeration. In the no-partition case, each fine cell is declared as its own agglomerate. The function then distributes the degrees of freedom on the agglomerated mesh, builds the corresponding sparsity pattern, and writes a VTU file visualizing the agglomeration and the partitioning of the fine grid.

  template <int dim>
  void
  Poisson<dim>::setup_agglomeration()
  {
  if (partitioner_type == PartitionerType::no_partition)
  { // No partitioning means that each cell is a master cell
  for (const auto &cell : tria.active_cell_iterators())
  ah->define_agglomerate({cell});
  }
  ah->distribute_agglomerated_dofs(dg_fe);
  ah->create_agglomeration_sparsity_pattern(dsp);
  sparsity.copy_from(dsp);
  {
  std::string partitioner;
  if (partitioner_type == PartitionerType::metis)
  partitioner = "metis";
  else if (partitioner_type == PartitionerType::rtree)
  partitioner = "rtree";
  else
  partitioner = "no_partitioning";
  const std::string filename =
  "grid_" + partitioner + "_" + std::to_string(n_subdomains) + ".vtu";
  std::ofstream output(filename);
  DataOut<dim> data_out;
  data_out.attach_triangulation(tria);
  const auto &rel = ah->get_relationships();
  Vector<float> agglo_relationships(tria.n_active_cells()); // Store the agglomeration relationships on the fine grid by distinguishing master/slave cells
  for (const auto &cell : tria.active_cell_iterators())
  {
  const unsigned int i = cell->active_cell_index();
  agglo_relationships[i] = rel[i];
  }
  Vector<float> agglo_idx(tria.n_active_cells()); // Generate agglo_idx for visualization
  for (const auto &polytope : ah->polytope_iterators())
  {
  const float id = static_cast<float>(polytope->index());
  const auto &patch_of_cells = polytope->get_agglomerate();
  for (const auto &cell : patch_of_cells)
  agglo_idx[cell->active_cell_index()] = id;
  }
  data_out.add_data_vector(agglo_relationships,
  "agglo_relationships",
  data_out.add_data_vector(agglo_idx,
  "agglo_idx",
  data_out.build_patches(mapping);
  data_out.write_vtu(output);
  }
  }
void attach_triangulation(const Triangulation< dim, spacedim > &)
*  *  *  *  std::vector< Number > ThermoPlasticMaterial< dim, ViscoplasticYieldLaw, Number >::get_state_parameters   const

Assemble the global SIPG matrix and right-hand side on the agglomerated mesh.

It initializes the system matrix and right-hand side, sets up FEValues objects on polytopal cells and interfaces, and then adds the volume, boundary, and interior face contributions of the symmetric interior penalty formulation.

  template <int dim>
  void
  Poisson<dim>::assemble_system()
  {
  system_matrix.reinit(sparsity);
  solution.reinit(ah->n_dofs());
  system_rhs.reinit(ah->n_dofs());
  const unsigned int quadrature_degree = dg_fe.get_degree() + 1;
  const unsigned int face_quadrature_degree = dg_fe.get_degree() + 1;
  ah->initialize_fe_values(QGauss<dim>(quadrature_degree),
  QGauss<dim - 1>(face_quadrature_degree));
  const unsigned int dofs_per_cell = ah->n_dofs_per_cell();
  std::cout << "DoFs per cell: " << dofs_per_cell << std::endl;
  FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
  Vector<double> cell_rhs(dofs_per_cell);
@ update_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
@ update_gradients
Shape function gradients.
@ update_quadrature_points
Transformed quadrature points.

Next, we define the four dofsxdofs matrices needed to assemble jumps and averages.

  FullMatrix<double> M11(dofs_per_cell, dofs_per_cell);
  FullMatrix<double> M12(dofs_per_cell, dofs_per_cell);
  FullMatrix<double> M21(dofs_per_cell, dofs_per_cell);
  FullMatrix<double> M22(dofs_per_cell, dofs_per_cell);
  std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
  for (const auto &polytope : ah->polytope_iterators())
  {
  cell_matrix = 0;
  cell_rhs = 0;
  const auto &agglo_values = ah->reinit(polytope);
  polytope->get_dof_indices(local_dof_indices);
  const auto &q_points = agglo_values.get_quadrature_points();
  const unsigned int n_qpoints = q_points.size();
  std::vector<double> rhs(n_qpoints);
  rhs_function->value_list(q_points, rhs);
  for (unsigned int q_index : agglo_values.quadrature_point_indices())
  {
  for (unsigned int i = 0; i < dofs_per_cell; ++i)
  {
  for (unsigned int j = 0; j < dofs_per_cell; ++j)
  {
  cell_matrix(i, j) += agglo_values.shape_grad(i, q_index) *
  agglo_values.shape_grad(j, q_index) *
  agglo_values.JxW(q_index);
  }
  cell_rhs(i) += agglo_values.shape_value(i, q_index) *
  rhs[q_index] * agglo_values.JxW(q_index);
  }
  }
  const unsigned int n_faces = polytope->n_faces();
  AssertThrow(n_faces > 0,
  ExcMessage(
  "Invalid element: at least 4 faces are required."));
  auto polygon_boundary_vertices = polytope->polytope_boundary();
  for (unsigned int f = 0; f < n_faces; ++f)
  {
  if (polytope->at_boundary(f))
  { // std::cout << "at boundary!" << std::endl;
  const auto &fe_face = ah->reinit(polytope, f);
  const unsigned int dofs_per_cell = fe_face.dofs_per_cell;
  const auto &face_q_points = fe_face.get_quadrature_points();
  std::vector<double> analytical_solution_values(
  face_q_points.size());
  analytical_solution->value_list(face_q_points,
  analytical_solution_values,
  1);
  const auto &normals = fe_face.get_normal_vectors(); // Get normal vectors seen from each agglomeration.
  const double penalty =
  penalty_constant / std::fabs(polytope->diameter());
  for (unsigned int q_index : fe_face.quadrature_point_indices())
  {
  for (unsigned int i = 0; i < dofs_per_cell; ++i)
  {
  for (unsigned int j = 0; j < dofs_per_cell; ++j)
  {
  cell_matrix(i, j) +=
  (-fe_face.shape_value(i, q_index) *
  fe_face.shape_grad(j, q_index) *
  normals[q_index] -
  fe_face.shape_grad(i, q_index) * normals[q_index] *
  fe_face.shape_value(j, q_index) +
  (penalty)*fe_face.shape_value(i, q_index) *
  fe_face.shape_value(j, q_index)) *
  fe_face.JxW(q_index);
  }
  cell_rhs(i) +=
  (penalty * analytical_solution_values[q_index] *
  fe_face.shape_value(i, q_index) -
  fe_face.shape_grad(i, q_index) * normals[q_index] *
  analytical_solution_values[q_index]) *
  fe_face.JxW(q_index);
  }
  }
  }
  else
  {
  const auto &neigh_polytope = polytope->neighbor(f);
  if (polytope->index() < neigh_polytope->index()) // This is necessary to loop over internal faces only once.
  {
  unsigned int nofn =
  polytope->neighbor_of_agglomerated_neighbor(f);
  const auto &fe_faces =
  ah->reinit_interface(polytope, neigh_polytope, f, nofn);
  const auto &fe_faces0 = fe_faces.first;
  const auto &fe_faces1 = fe_faces.second;
  std::vector<types::global_dof_index>
  local_dof_indices_neighbor(dofs_per_cell);
  M11 = 0.;
  M12 = 0.;
  M21 = 0.;
  M22 = 0.;
  const auto &normals = fe_faces0.get_normal_vectors();
  const double penalty =
  penalty_constant / std::min(polytope->diameter(), neigh_polytope->diameter());
  for (unsigned int q_index : // M11
  fe_faces0.quadrature_point_indices())
  {
  for (unsigned int i = 0; i < dofs_per_cell; ++i)
  {
  for (unsigned int j = 0; j < dofs_per_cell; ++j)
  {
  M11(i, j) +=
  (-0.5 * fe_faces0.shape_grad(i, q_index) *
  normals[q_index] *
  fe_faces0.shape_value(j, q_index) -
  0.5 * fe_faces0.shape_grad(j, q_index) *
  normals[q_index] *
  fe_faces0.shape_value(i, q_index) +
  (penalty)*fe_faces0.shape_value(i, q_index) *
  fe_faces0.shape_value(j, q_index)) *
  fe_faces0.JxW(q_index);
  M12(i, j) +=
  (0.5 * fe_faces0.shape_grad(i, q_index) *
  normals[q_index] *
  fe_faces1.shape_value(j, q_index) -
  0.5 * fe_faces1.shape_grad(j, q_index) *
  normals[q_index] *
  fe_faces0.shape_value(i, q_index) -
  (penalty)*fe_faces0.shape_value(i, q_index) *
  fe_faces1.shape_value(j, q_index)) *
  fe_faces1.JxW(q_index);
  M21(i, j) += // A10
  (-0.5 * fe_faces1.shape_grad(i, q_index) *
  normals[q_index] *
  fe_faces0.shape_value(j, q_index) +
  0.5 * fe_faces0.shape_grad(j, q_index) *
  normals[q_index] *
  fe_faces1.shape_value(i, q_index) -
  (penalty)*fe_faces1.shape_value(i, q_index) *
  fe_faces0.shape_value(j, q_index)) *
  fe_faces1.JxW(q_index);
  M22(i, j) += // A11
  (0.5 * fe_faces1.shape_grad(i, q_index) *
  normals[q_index] *
  fe_faces1.shape_value(j, q_index) +
  0.5 * fe_faces1.shape_grad(j, q_index) *
  normals[q_index] *
  fe_faces1.shape_value(i, q_index) +
  (penalty)*fe_faces1.shape_value(i, q_index) *
  fe_faces1.shape_value(j, q_index)) *
  fe_faces1.JxW(q_index);
  }
  }
  }
  neigh_polytope->get_dof_indices(local_dof_indices_neighbor);
  constraints.distribute_local_to_global(M11,
  local_dof_indices,
  system_matrix);
  constraints.distribute_local_to_global(
  M12,
  local_dof_indices,
  local_dof_indices_neighbor,
  system_matrix);
  constraints.distribute_local_to_global(
  M21,
  local_dof_indices_neighbor,
  local_dof_indices,
  system_matrix);
  constraints.distribute_local_to_global(
  M22, local_dof_indices_neighbor, system_matrix);
  } // Loop only once through internal faces
  }
  } // Loop over faces of current cell
*  *  for(const auto &cell :triangulation.active_cell_iterators())
#define AssertThrow(cond, exc)
void cell_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const FEValuesBase< dim > &fetest, const ArrayView< const std::vector< double > > &velocity, const double factor=1.)
Definition advection.h:72
::VectorizedArray< Number, width > min(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)

Distribute the local contributions to the global system.

  constraints.distribute_local_to_global(
  cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
  } // Loop over cells
  }

Solve the linear system by means of a sparse direct solver.

  template <int dim>
  void
  Poisson<dim>::solve()
  {
  A_direct.initialize(system_matrix);
  A_direct.vmult(solution, system_rhs);
  }
void initialize(const SparsityPattern &sparsity_pattern)

Write VTU output and compute the global \(L^2\) and \(H^1\)-seminorm errors of the agglomerated DG approximation.

  template <int dim>
  void
  Poisson<dim>::output_results()
  {
  {
  std::string partitioner;
  if (partitioner_type == PartitionerType::metis)
  partitioner = "metis";
  else if (partitioner_type == PartitionerType::rtree)
  partitioner = "rtree";
  else
  partitioner = "no_partitioning";
  const std::string filename = "interpolated_solution_" + partitioner + "_" +
  std::to_string(n_subdomains) + ".vtu";
  std::ofstream output(filename);
  DataOut<dim> data_out;
  Vector<double> interpolated_solution;
  PolyUtils::interpolate_to_fine_grid(*ah,
  interpolated_solution,
  solution,
  true /*on_the_fly*/);
  data_out.attach_dof_handler(ah->output_dh);
  data_out.add_data_vector(interpolated_solution,
  "u",
  Vector<float> agglo_idx(tria.n_active_cells());

Mark fine cells belonging to the same agglomerate.

  for (const auto &polytope : ah->polytope_iterators())
  {
  const types::global_cell_index polytope_index = polytope->index();
  const auto &patch_of_cells = polytope->get_agglomerate(); // Fine cells
  for (const auto &cell : patch_of_cells) // Mark all fine cells belonging to the current agglomerate.
  agglo_idx[cell->active_cell_index()] = polytope_index;
  }
  data_out.add_data_vector(agglo_idx,
  "agglo_idx",
  data_out.build_patches(mapping);
  data_out.write_vtu(output);
  std::vector<double> errors;
  PolyUtils::compute_global_error(*ah,
  solution,
  *analytical_solution,
  errors); // Compute the global L2 and H1-seminorm errors.
  l2_err = errors[0];
  semih1_err = errors[1];
  }
  }
Definition types.h:30

Return the number of degrees of freedom on the agglomerated mesh.

  template <int dim>
  Poisson<dim>::get_n_dofs() const
  {
  return ah->n_dofs();
  }

Return the pair consisting of the \(L^2\) error and the \(H^1\)-seminorm error of the numerical solution.

  template <int dim>
  inline std::pair<double, double>
  Poisson<dim>::get_error() const
  {
  return std::make_pair(l2_err, semih1_err);
  }

Run the full workflow: mesh generation, agglomeration setup, assembly, solution, and postprocessing.

  template <int dim>
  void
  Poisson<dim>::run()
  {
  make_grid();
  setup_agglomeration();
  auto start = std::chrono::high_resolution_clock::now();
  assemble_system();
  auto stop = std::chrono::high_resolution_clock::now();
  auto duration =
  std::chrono::duration_cast<std::chrono::seconds>(stop - start);
  std::cout << "Time taken by assemble_system(): " << duration.count()
  << " seconds" << std::endl;
  solve();
  output_results();
  }

Driver code.

  int
  {
  ConvergenceInfo convergence_info;
  for (unsigned int fe_degree : {1}) //, 2, 3})
  {
  std::cout << "Running with FE degree: " << fe_degree << std::endl;
  Poisson<2> poisson_problem{PartitionerType::rtree, // Three choices: metis, rtree and no_partition
  4 /* extraction_level */,
  91 /* n_subdomains */,
  fe_degree};
  poisson_problem.run();
  convergence_info.add(
  std::make_pair<types::global_dof_index, std::pair<double, double>>(
  poisson_problem.get_n_dofs(), poisson_problem.get_error()));
  std::cout << std::endl;
  }
  std::cout << "Convergence table:" << std::endl;
  convergence_info.print();
  std::cout << std::endl;
  return 0;
  }
*  *  int main(int argc, char **argv)

Annotated version of include/agglomeration_accessor.h

  /* -----------------------------------------------------------------------------
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
  * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,
  * Andrea Cangiani
  *
  * This file is part of the deal.II code gallery.
  *
  * -----------------------------------------------------------------------------
  */
  #ifndef agglomeration_accessor_h
  #define agglomeration_accessor_h
  #include <deal.II/base/config.h>
  #include <deal.II/base/bounding_box.h>
  #include <deal.II/base/iterator_range.h>
  #include <deal.II/grid/filtered_iterator.h>
  #include <vector>
  using namespace dealii;

Forward declarations

  #ifndef DOXYGEN
  template <int, int>
  class AgglomerationHandler;
  template <int, int>
  class AgglomerationIterator;
  #endif
  template <int dim, int spacedim = dim>
  class AgglomerationAccessor
  {
  public:
  using AgglomerationContainer =
  std::vector<typename Triangulation<dim, spacedim>::active_cell_iterator>;
  void
  get_dof_indices(std::vector<types::global_dof_index> &) const;
  unsigned int
  n_faces() const;
  unsigned int
  n_agglomerated_faces() const;
  const AgglomerationIterator<dim, spacedim>
  neighbor(const unsigned int f) const;
  unsigned int
  neighbor_of_agglomerated_neighbor(const unsigned int f) const;
  bool
  at_boundary(const unsigned int f) const;
  const std::vector<typename Triangulation<dim>::active_face_iterator> &
  polytope_boundary() const;
  double
  volume() const;
  double
  diameter() const;
  AgglomerationContainer
  get_agglomerate() const;
  get_bounding_box() const;
  index() const;
  as_dof_handler_iterator(const DoFHandler<dim, spacedim> &dof_handler) const;
  unsigned int
  n_background_cells() const;
  /* Returns true if this polygon is owned by the current processor. On a serial
  * Triangulation this returs always true, but may yield false for a
  * parallel::distributed::Triangulation.
  */
  bool
  is_locally_owned() const;
  id() const;
  subdomain_id() const;
  inline const std::vector<types::global_cell_index> &
  children() const;
  get_fe() const;
  void
  set_active_fe_index(const types::fe_index index) const;
  active_fe_index() const;
  private:
  AgglomerationAccessor();
  AgglomerationAccessor(
  &master_cell,
  const AgglomerationHandler<dim, spacedim> *ah);
  AgglomerationAccessor(
  const CellId &cell_id,
  const AgglomerationHandler<dim, spacedim> *ah);
  ~AgglomerationAccessor() = default;
  CellId present_id;
  types::subdomain_id present_subdomain_id;
  AgglomerationHandler<dim, spacedim> *handler;
  bool
  operator==(const AgglomerationAccessor<dim, spacedim> &other) const;
  bool
  operator!=(const AgglomerationAccessor<dim, spacedim> &other) const;
  void
  next();
  void
  prev();
  const AgglomerationContainer &
  get_slaves() const;
  unsigned int
  n_agglomerated_faces_per_cell(
  const;
  template <int, int>
  friend class AgglomerationIterator;
  };
  template <int dim, int spacedim>
  unsigned int
  AgglomerationAccessor<dim, spacedim>::n_agglomerated_faces_per_cell(
  {
  unsigned int n_neighbors = 0;
  for (const auto &f : cell->face_indices())
  {
  const auto &neighboring_cell = cell->neighbor(f);
  if ((cell->face(f)->at_boundary()) ||
  (neighboring_cell->is_active() &&
  !handler->are_cells_agglomerated(cell, neighboring_cell)))
  {
  ++n_neighbors;
  }
  }
  return n_neighbors;
  }
  template <int dim, int spacedim>
  unsigned int
  AgglomerationAccessor<dim, spacedim>::n_faces() const
  {
  Assert(!handler->is_slave_cell(master_cell),
  ExcMessage("You cannot pass a slave cell."));
  return handler->number_of_agglomerated_faces[present_index];
  }
  template <int dim, int spacedim>
  const AgglomerationIterator<dim, spacedim>
  AgglomerationAccessor<dim, spacedim>::neighbor(const unsigned int f) const
  {
  if (!at_boundary(f))
  {
  if (master_cell->is_ghost())
  {
bool operator!=(const AlignedVector< T > &lhs, const AlignedVector< T > &rhs)
bool operator==(const AlignedVector< T > &lhs, const AlignedVector< T > &rhs)
typename ActiveSelector::active_cell_iterator active_cell_iterator
unsigned short int fe_index
Definition types.h:70
void prev(std::tuple< I1, I2 > &t)

The following path is needed when the present function is called from neighbor_of_neighbor()

  const unsigned int sender_rank = master_cell->subdomain_id();
  const CellId &master_id_ghosted_neighbor =
  handler->recv_ghosted_master_id.at(sender_rank)
  .at(present_id)
  .at(f);

Use the id of the master cell to uniquely identify the neighboring agglomerate

  return {master_cell,
  master_id_ghosted_neighbor,
  handler}; // dummy master?
  }
  const types::global_cell_index polytope_index =
  handler->master2polygon.at(master_cell->active_cell_index());
  const auto &neigh =
  handler->polytope_cache.cell_face_at_boundary.at({polytope_index, f})
  if (neigh->is_locally_owned())
  {
  *neigh, &(handler->agglo_dh));
  return {cell_dh, handler};
  }
  else
  {

Get master_id from the neighboring ghost polytope. This uniquely identifies the neighboring polytope among all processors.

  const CellId &master_id_neighbor =
  handler->polytope_cache.ghosted_master_id.at({present_id, f});

Use the id of the master cell to uniquely identify the neighboring agglomerate

  return {neigh, master_id_neighbor, handler};
  }
  }
  else
  {
  return {};
  }
  }
  template <int dim, int spacedim>
  unsigned int
  AgglomerationAccessor<dim, spacedim>::neighbor_of_agglomerated_neighbor(
  const unsigned int f) const
  {

First, make sure it's not a boundary face.

  if (!at_boundary(f))
  {
  const auto &neigh_polytope =
  neighbor(f); // returns the neighboring master and id
  AssertThrow(neigh_polytope.state() == IteratorState::valid,
  ExcInternalError());
  unsigned int n_faces_agglomerated_neighbor;
@ valid
Iterator points to a valid object.

if it is locally owned, retrieve the number of faces

  if (neigh_polytope->is_locally_owned())
  {
  n_faces_agglomerated_neighbor = neigh_polytope->n_faces();
  }
  else
  {

The neighboring polytope is not locally owned. We need to get the number of its faces from the neighboring rank.

First, retrieve the CellId of the neighboring polytope.

  const CellId &master_id_neighbor = neigh_polytope->id();

Then, get the neighboring rank

  const unsigned int sender_rank = neigh_polytope->subdomain_id();

From the neighboring rank, use the CellId of the neighboring polytope to get the number of its faces.

  n_faces_agglomerated_neighbor =
  handler->recv_n_faces.at(sender_rank).at(master_id_neighbor);
  }

Loop over all faces of neighboring agglomerate

  for (unsigned int f_out = 0; f_out < n_faces_agglomerated_neighbor;
  ++f_out)
  {

Check if same CellId

  if (neigh_polytope->neighbor(f_out).state() == IteratorState::valid)
  if (neigh_polytope->neighbor(f_out)->id() == present_id)
  return f_out;
  }
  }
  else
  {
constexpr unsigned int invalid_unsigned_int
Definition types.h:228

Face is at boundary

---------------------------— inline functions ----------------------—

  template <int dim, int spacedim>
  inline AgglomerationAccessor<dim, spacedim>::AgglomerationAccessor()
  {}
  template <int dim, int spacedim>
  inline AgglomerationAccessor<dim, spacedim>::AgglomerationAccessor(
  const AgglomerationHandler<dim, spacedim> *ah)
  {
  handler = const_cast<AgglomerationHandler<dim, spacedim> *>(ah);
  if (&(*handler->master_cells_container.end()) == std::addressof(cell))
  {
  present_index = handler->master_cells_container.size();
  master_cell = *handler->master_cells_container.end();
  present_id = CellId(); // invalid id (TODO)
  present_subdomain_id = numbers::invalid_subdomain_id;
  }
  else
  {
  present_index = handler->master2polygon.at(cell->active_cell_index());
  master_cell = cell;
  present_id = master_cell->id();
  present_subdomain_id = master_cell->subdomain_id();
  }
  }
  template <int dim, int spacedim>
  inline AgglomerationAccessor<dim, spacedim>::AgglomerationAccessor(
  const CellId &master_cell_id,
  const AgglomerationHandler<dim, spacedim> *ah)
  {
  Assert(neigh_cell->is_ghost(), ExcInternalError());
constexpr types::subdomain_id invalid_subdomain_id
Definition types.h:385

neigh_cell is ghosted

  handler = const_cast<AgglomerationHandler<dim, spacedim> *>(ah);
  master_cell = neigh_cell;

neigh_cell is ghosted, use the CellId of that agglomerate

  present_id = master_cell_id;
  present_subdomain_id = master_cell->subdomain_id();
  }
  template <int dim, int spacedim>
  inline void
  AgglomerationAccessor<dim, spacedim>::get_dof_indices(
  std::vector<types::global_dof_index> &dof_indices) const
  {
  Assert(dof_indices.size() > 0,
  ExcMessage(
  "The vector of DoFs indices must be already properly resized."));
  if (is_locally_owned())
  {

Forward the call to the master cell

  *master_cell, &(handler->agglo_dh));
  master_cell_dh->get_dof_indices(dof_indices);
  }
  else
  {
  const std::vector<types::global_dof_index> &recv_dof_indices =
  handler->recv_ghost_dofs.at(present_subdomain_id).at(present_id);
  std::copy(recv_dof_indices.cbegin(),
  recv_dof_indices.cend(),
  dof_indices.begin());
  }
  }
  template <int dim, int spacedim>
  inline typename AgglomerationAccessor<dim, spacedim>::AgglomerationContainer
  AgglomerationAccessor<dim, spacedim>::get_agglomerate() const
  {
  auto agglomeration = get_slaves();
  agglomeration.push_back(master_cell);
  return agglomeration;
  }
  template <int dim, int spacedim>
  inline const std::vector<typename Triangulation<dim>::active_face_iterator> &
  AgglomerationAccessor<dim, spacedim>::polytope_boundary() const
  {
  return handler->polygon_boundary[master_cell];
  }
  template <int dim, int spacedim>
  inline double
  AgglomerationAccessor<dim, spacedim>::diameter() const
  {
  Assert(!handler->is_slave_cell(master_cell),
  ExcMessage("The present function cannot be called for slave cells."));
  if (handler->is_master_cell(master_cell))
  {
typename ActiveSelector::cell_iterator cell_iterator

Get the bounding box associated with the master cell

  const auto &bdary_pts =
  handler->bboxes[present_index].get_boundary_points();
  return (bdary_pts.second - bdary_pts.first).norm();
  }
  else
  {

Standard deal.II way to get the measure of a cell.

  return master_cell->diameter();
  }
  }
  template <int dim, int spacedim>
  inline const BoundingBox<dim> &
  AgglomerationAccessor<dim, spacedim>::get_bounding_box() const
  {
  if (is_locally_owned())
  return handler->bboxes[present_index];
  else
  return handler->recv_ghosted_bbox.at(present_subdomain_id).at(present_id);
  }
  template <int dim, int spacedim>
  inline double
  AgglomerationAccessor<dim, spacedim>::volume() const
  {
  Assert(!handler->is_slave_cell(master_cell),
  ExcMessage("The present function cannot be called for slave cells."));
  if (handler->is_master_cell(master_cell))
  {
  return handler->bboxes[present_index].volume();
  }
  else
  {
  return master_cell->measure();
  }
  }
  template <int dim, int spacedim>
  inline void
  AgglomerationAccessor<dim, spacedim>::next()
  {

Increment the present index and update the polytope

  ++present_index;

Make sure not to query the CellId if it's past the last

  if (present_index < handler->master_cells_container.size())
  {
  master_cell = handler->master_cells_container[present_index];
  present_id = master_cell->id();
  present_subdomain_id = master_cell->subdomain_id();
  }
  }
  template <int dim, int spacedim>
  inline void
  AgglomerationAccessor<dim, spacedim>::prev()
  {

Decrement the present index and update the polytope

  --present_index;
  master_cell = handler->master_cells_container[present_index];
  present_id = master_cell->id();
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationAccessor<dim, spacedim>::operator==(
  const AgglomerationAccessor<dim, spacedim> &other) const
  {
  return present_index == other.present_index;
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationAccessor<dim, spacedim>::operator!=(
  const AgglomerationAccessor<dim, spacedim> &other) const
  {
  return !(*this == other);
  }
  template <int dim, int spacedim>
  AgglomerationAccessor<dim, spacedim>::index() const
  {
  return present_index;
  }
  template <int dim, int spacedim>
  AgglomerationAccessor<dim, spacedim>::as_dof_handler_iterator(
  const DoFHandler<dim, spacedim> &dof_handler) const
  {

Forward the call to the master cell using the right DoFHandler.

  return master_cell->as_dof_handler_iterator(dof_handler);
  }
  template <int dim, int spacedim>
  inline const typename AgglomerationAccessor<dim,
  spacedim>::AgglomerationContainer &
  AgglomerationAccessor<dim, spacedim>::get_slaves() const
  {
  return handler->master2slaves.at(master_cell->active_cell_index());
  }
  template <int dim, int spacedim>
  inline unsigned int
  AgglomerationAccessor<dim, spacedim>::n_background_cells() const
  {
  AssertThrow(get_agglomerate().size() > 0, ExcMessage("Empty agglomeration."));
  return get_agglomerate().size();
  }
  template <int dim, int spacedim>
  unsigned int
  AgglomerationAccessor<dim, spacedim>::n_agglomerated_faces() const
  {
  const auto &agglomeration = get_agglomerate();
  unsigned int n_neighbors = 0;
  for (const auto &cell : agglomeration)
  n_neighbors += n_agglomerated_faces_per_cell(cell);
  return n_neighbors;
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationAccessor<dim, spacedim>::at_boundary(const unsigned int f) const
  {
  if (master_cell->is_ghost())
  {
  const unsigned int sender_rank = master_cell->subdomain_id();
  return handler->recv_bdary_info.at(sender_rank).at(present_id).at(f);
  }
  else
  {
  Assert(!handler->is_slave_cell(master_cell),
  ExcMessage(
  "This function should not be called for a slave cell."));
  *master_cell, &(handler->agglo_dh));
  return handler->at_boundary(cell_dh, f);
  }
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationAccessor<dim, spacedim>::is_locally_owned() const
  {
  return master_cell->is_locally_owned();
  }
  template <int dim, int spacedim>
  inline CellId
  AgglomerationAccessor<dim, spacedim>::id() const
  {
  return present_id;
  }
  template <int dim, int spacedim>
  AgglomerationAccessor<dim, spacedim>::subdomain_id() const
  {
  return present_subdomain_id;
  }
  template <int dim, int spacedim>
  inline const std::vector<types::global_cell_index> &
  AgglomerationAccessor<dim, spacedim>::children() const
  {
  Assert(!handler->parent_child_info.empty(), ExcInternalError());
  return handler->parent_child_info.at(
  {present_index, handler->present_extraction_level});
  }
  template <int dim, int spacedim>
  AgglomerationAccessor<dim, spacedim>::get_fe() const
  {
  master_cell_as_dof_handler_iterator =
  master_cell->as_dof_handler_iterator(handler->agglo_dh);
  return master_cell_as_dof_handler_iterator->get_fe();
  }
  template <int dim, int spacedim>
  inline void
  AgglomerationAccessor<dim, spacedim>::set_active_fe_index(
  const types::fe_index index) const
  {
  Assert(!handler->is_slave_cell(master_cell),
  ExcMessage("The present function cannot be called for slave cells."));
  master_cell_as_dof_handler_iterator =
  master_cell->as_dof_handler_iterator(handler->agglo_dh);
  master_cell_as_dof_handler_iterator->set_active_fe_index(index);
  }
  template <int dim, int spacedim>
  AgglomerationAccessor<dim, spacedim>::active_fe_index() const
  {
  master_cell_as_dof_handler_iterator =
  master_cell->as_dof_handler_iterator(handler->agglo_dh);
  return master_cell_as_dof_handler_iterator->active_fe_index();
  }
  #endif
std::size_t size
Definition mpi.cc:733

Annotated version of include/agglomeration_handler.h

  /* -----------------------------------------------------------------------------
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
  * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,
  * Andrea Cangiani
  *
  * This file is part of the deal.II code gallery.
  *
  * -----------------------------------------------------------------------------
  */
  #ifndef agglomeration_handler_h
  #define agglomeration_handler_h
  #include <deal.II/base/mpi.h>
  #include <deal.II/base/quadrature.h>
  #include <deal.II/base/enable_observer_pointer.h>
  #include <deal.II/distributed/shared_tria.h>
  #include <deal.II/distributed/tria.h>
  #include <deal.II/dofs/dof_handler.h>
  #include <deal.II/dofs/dof_tools.h>
  #include <deal.II/fe/fe_dgp.h>
  #include <deal.II/fe/fe_dgq.h>
  #include <deal.II/fe/fe_nothing.h>
  #include <deal.II/fe/fe_simplex_p.h>
  #include <deal.II/fe/fe_system.h>
  #include <deal.II/fe/fe_values.h>
  #include <deal.II/fe/mapping_fe_field.h>
  #include <deal.II/fe/mapping_q.h>
  #include <deal.II/grid/grid_tools_cache.h>
  #include <deal.II/grid/tria.h>
  #include <deal.II/hp/fe_collection.h>
  #include <deal.II/lac/dynamic_sparsity_pattern.h>
  #include <deal.II/lac/la_parallel_vector.h>
  #include <deal.II/lac/sparse_matrix.h>
  #include <deal.II/lac/sparsity_pattern.h>
  #include <deal.II/lac/trilinos_sparse_matrix.h>
  #include <deal.II/lac/vector.h>
  #include <deal.II/meshworker/scratch_data.h>
  #include <deal.II/non_matching/fe_immersed_values.h>
  #include <deal.II/non_matching/immersed_surface_quadrature.h>
  #include <agglomeration_iterator.h>
  #include <agglomerator.h>
  #include <mapping_box.h>
  #include <fstream>
  #include <memory>
  using namespace dealii;
Definition hp.h:115

Forward declarations

  template <int dim, int spacedim>
  class AgglomerationHandler;
  namespace dealii
  {
  namespace internal
  {
  template <int, int>
  class AgglomerationHandlerImplementation;
  } // namespace internal
  } // namespace dealii
  namespace dealii
  {
  namespace internal
  {
  template <int dim, int spacedim>
  class PolytopeCache
  {
  public:
  PolytopeCache() = default;
  ~PolytopeCache() = default;
  void
  clear()
  {

clear all the members

  cell_face_at_boundary.clear();
  interface.clear();
  visited_cell_and_faces.clear();
  }
  mutable std::set<std::pair<types::global_cell_index, unsigned int>>
  visited_cell_and_faces;
  mutable std::set<std::pair<CellId, unsigned int>>
  visited_cell_and_faces_id;
  mutable std::map<
  std::pair<types::global_cell_index, unsigned int>,
  std::pair<bool,
  cell_face_at_boundary;
  mutable std::map<std::pair<CellId, unsigned int>, CellId>
  ghosted_master_id;
  mutable std::map<
  std::pair<CellId, CellId>,
  std::vector<
  std::pair<typename Triangulation<dim, spacedim>::active_cell_iterator,
  unsigned int>>>
  interface;
  };
  } // namespace internal
  } // namespace dealii
  template <int dim, int spacedim = dim>
  class AgglomerationHandler : public EnableObserverPointer
  {
  public:
  using agglomeration_iterator = AgglomerationIterator<dim, spacedim>;
  using AgglomerationContainer =
  typename AgglomerationIterator<dim, spacedim>::AgglomerationContainer;
  enum CellAgglomerationType
  {
  master = 0,
  slave = 1
  };
  explicit AgglomerationHandler(
  const GridTools::Cache<dim, spacedim> &cached_tria);
  AgglomerationHandler() = default;
  ~AgglomerationHandler()
  {

disconnect the signal

  tria_listener.disconnect();
  }
  agglomeration_iterator
  begin() const;
  agglomeration_iterator
  agglomeration_iterator
  end() const;
  agglomeration_iterator
  end();
  agglomeration_iterator
  last();
  polytope_iterators() const;
  template <int, int>
  friend class AgglomerationIterator;
  template <int, int>
  friend class AgglomerationAccessor;
  void
  distribute_agglomerated_dofs(const FiniteElement<dim> &fe_space);
  void
  distribute_agglomerated_dofs(
  const hp::FECollection<dim, spacedim> &fe_collection_in);
  void
  initialize_fe_values(
  const Quadrature<dim> &cell_quadrature = QGauss<dim>(1),
  const Quadrature<dim - 1> &face_quadrature = QGauss<dim - 1>(1),
  void
  initialize_fe_values(
  const hp::QCollection<dim> &cell_qcollection =
  const hp::QCollection<dim - 1> &face_qcollection =
  template <typename SparsityPatternType, typename Number = double>
  void
  create_agglomeration_sparsity_pattern(
  SparsityPatternType &sparsity_pattern,
  const bool keep_constrained_dofs = true,
  agglomeration_iterator
  define_agglomerate(const AgglomerationContainer &cells);
  agglomeration_iterator
  define_agglomerate(const AgglomerationContainer &cells,
  const unsigned int fecollection_size);
  get_triangulation() const;
  get_fe() const;
  inline const Mapping<dim> &
  get_mapping() const;
  inline const MappingBox<dim> &
  get_agglomeration_mapping() const;
  inline const std::vector<BoundingBox<dim>> &
  get_local_bboxes() const;
  double
  get_mesh_size() const;
  cell_to_polytope_index(
  const;
  inline decltype(auto)
  get_interface() const;
  template <typename CellIterator>
  inline bool
  is_master_cell(const CellIterator &cell) const;
  inline const std::vector<
  get_slaves_of_idx(types::global_cell_index idx) const;
  get_relationships() const;
  inline std::vector<
  get_agglomerate(
  &master_cell) const;
  get_dof_handler() const;
  unsigned int
  n_agglomerates() const;
  unsigned int
  n_agglomerated_faces_per_cell(
  const;
  reinit(const AgglomerationIterator<dim, spacedim> &polytope) const;
  reinit(const AgglomerationIterator<dim, spacedim> &polytope,
  const unsigned int face_index) const;
  std::pair<const FEValuesBase<dim, spacedim> &,
  reinit_interface(const AgglomerationIterator<dim, spacedim> &polytope_in,
  const AgglomerationIterator<dim, spacedim> &neigh_polytope,
  const unsigned int local_in,
  const unsigned int local_outside) const;
  agglomerated_quadrature(
  const AgglomerationContainer &cells,
  &master_cell) const;
  inline bool
  at_boundary(
  const unsigned int f) const;
  inline unsigned int
  n_dofs_per_cell() const noexcept;
  inline types::global_dof_index
  n_dofs() const noexcept;
  inline const std::vector<typename Triangulation<dim>::active_face_iterator> &
  polytope_boundary(
  const typename Triangulation<dim>::active_cell_iterator &cell);
  DoFHandler<dim, spacedim> agglo_dh;
  DoFHandler<dim, spacedim> output_dh;
  std::unique_ptr<MappingBox<dim>> box_mapping;
  void
  setup_ghost_polytopes();
  void
  exchange_interface_values();
*  iterator end()
*  *  iterator begin()
Abstract base class for mapping classes.
Definition mapping.h:318
UpdateFlags
@ update_default
No update.

TODO: move it to private interface

  mutable std::map<
  std::map<std::pair<CellId, unsigned int>, std::vector<Point<spacedim>>>>
  recv_qpoints;
  mutable std::map<
  std::map<std::pair<CellId, unsigned int>, std::vector<double>>>
  recv_jxws;
  mutable std::map<
  std::map<std::pair<CellId, unsigned int>, std::vector<Tensor<1, spacedim>>>>
  recv_normals;
  mutable std::map<
  std::map<std::pair<CellId, unsigned int>, std::vector<std::vector<double>>>>
  recv_values;
  mutable std::map<types::subdomain_id,
  std::map<std::pair<CellId, unsigned int>,
  std::vector<std::vector<Tensor<1, spacedim>>>>>
  recv_gradients;
  polytope_to_dh_iterator(const types::global_cell_index polytope_index) const;
  template <typename RtreeType>
  void
  connect_hierarchy(const CellsAgglomerator<dim, RtreeType> &agglomerator);
  get_fe_collection() const;
  inline bool
  used_fe_collection() const;
  private:
  void
  initialize_agglomeration_data(
  const std::unique_ptr<GridTools::Cache<dim, spacedim>> &cache_tria);
  void
  update_agglomerate(
  AgglomerationContainer &polytope,
  &master_cell);
  void
  connect_to_tria_signals()
  {

First disconnect existing connections

  tria_listener.disconnect();
  tria_listener = tria->signals.any_change.connect(
  [&]() { this->initialize_agglomeration_data(this->cached_tria); });
  }
  is_slave_cell_of(
  void
  create_bounding_box(const AgglomerationContainer &polytope);
  get_master_idx_of_cell(
  const;
  inline bool
  are_cells_agglomerated(
  &other_cell) const;
  void
  initialize_hp_structure();
  reinit_master(
  const unsigned int face_number,
  &agglo_isv_ptr) const;
  template <typename CellIterator>
  inline bool
  is_slave_cell(const CellIterator &cell) const;
  void
  setup_connectivity_of_agglomeration();
  unsigned int n_agglomerations;
  LinearAlgebra::distributed::Vector<float> master_slave_relationships;
  master_slave_relationships_iterators;
  mutable std::vector<types::global_cell_index> number_of_agglomerated_faces;
  mutable std::map<
  std::vector<typename Triangulation<dim>::active_face_iterator>>
  polygon_boundary;
  std::vector<BoundingBox<spacedim>> bboxes;
unsigned int global_cell_index
Definition types.h:136

////////////////////////////////////////////////////

n_faces

  mutable std::map<types::subdomain_id, std::map<CellId, unsigned int>>
  local_n_faces;
  mutable std::map<types::subdomain_id, std::map<CellId, unsigned int>>
  recv_n_faces;

CellId (including slaves)

  mutable std::map<types::subdomain_id, std::map<CellId, CellId>>
  local_cell_ids_neigh_cell;
  mutable std::map<types::subdomain_id, std::map<CellId, CellId>>
  recv_cell_ids_neigh_cell;

send to neighborign rank the information that

  • current polytope id
  • face f has the following neighboring id.
  mutable std::map<types::subdomain_id,
  std::map<CellId, std::map<unsigned int, CellId>>>
  local_ghosted_master_id;
  mutable std::map<types::subdomain_id,
  std::map<CellId, std::map<unsigned int, CellId>>>
  recv_ghosted_master_id;

CellIds from neighboring rank

  mutable std::map<types::subdomain_id,
  std::map<CellId, std::map<unsigned int, bool>>>
  local_bdary_info;
  mutable std::map<types::subdomain_id,
  std::map<CellId, std::map<unsigned int, bool>>>
  recv_bdary_info;

Exchange neighboring bounding boxes

  mutable std::map<types::subdomain_id, std::map<CellId, BoundingBox<dim>>>
  local_ghosted_bbox;
  mutable std::map<types::subdomain_id, std::map<CellId, BoundingBox<dim>>>
  recv_ghosted_bbox;

Exchange DoF indices with ghosted polytopes

  mutable std::map<types::subdomain_id,
  std::map<CellId, std::vector<types::global_dof_index>>>
  local_ghost_dofs;
  mutable std::map<types::subdomain_id,
  std::map<CellId, std::vector<types::global_dof_index>>>
  recv_ghost_dofs;

Exchange qpoints

  mutable std::map<
  std::map<std::pair<CellId, unsigned int>, std::vector<Point<spacedim>>>>
  local_qpoints;

Exchange jxws

  mutable std::map<
  std::map<std::pair<CellId, unsigned int>, std::vector<double>>>
  local_jxws;

Exchange normals

  mutable std::map<
  std::map<std::pair<CellId, unsigned int>, std::vector<Tensor<1, spacedim>>>>
  local_normals;

Exchange values

  mutable std::map<
  std::map<std::pair<CellId, unsigned int>, std::vector<std::vector<double>>>>
  local_values;
  mutable std::map<types::subdomain_id,
  std::map<std::pair<CellId, unsigned int>,
  std::vector<std::vector<Tensor<1, spacedim>>>>>
  local_gradients;

////////////////////////////////////////////////////

  const Mapping<dim, spacedim> *mapping;
  std::unique_ptr<GridTools::Cache<dim, spacedim>> cached_tria;
  const MPI_Comm communicator;

The FiniteElement space we have on each cell. Currently supported types are FE_DGQ and FE_DGP elements.

  std::unique_ptr<FiniteElement<dim>> fe;
  mutable std::unique_ptr<ScratchData> standard_scratch;
  mutable std::unique_ptr<ScratchData> agglomerated_scratch;
  mutable std::unique_ptr<NonMatching::FEImmersedSurfaceValues<spacedim>>
  agglomerated_isv;
  mutable std::unique_ptr<NonMatching::FEImmersedSurfaceValues<spacedim>>
  agglomerated_isv_neigh;
  mutable std::unique_ptr<NonMatching::FEImmersedSurfaceValues<spacedim>>
  agglomerated_isv_bdary;
  boost::signals2::connection tria_listener;
  UpdateFlags agglomeration_flags;
  const UpdateFlags internal_agglomeration_flags =
  UpdateFlags agglomeration_face_flags;
  const UpdateFlags internal_agglomeration_face_flags =
  Quadrature<dim> agglomeration_quad;
  Quadrature<dim - 1> agglomeration_face_quad;
@ update_normal_vectors
Normal vectors.
@ update_inverse_jacobians
Volume element.

Associate the master cell to the slaves.

  std::unordered_map<
  std::vector<typename Triangulation<dim, spacedim>::active_cell_iterator>>
  master2slaves;

Map the master cell index with the polytope index

  std::map<types::global_cell_index, types::global_cell_index> master2polygon;
  std::vector<typename Triangulation<dim>::active_cell_iterator>
  master_disconnected;

Dummy FiniteElement objects needed only to generate quadratures

  std::unique_ptr<FEValues<dim, spacedim>> no_values;
  std::unique_ptr<FEFaceValues<dim, spacedim>> no_face_values;
  std::vector<typename Triangulation<dim, spacedim>::active_cell_iterator>
  master_cells_container;
  friend class internal::AgglomerationHandlerImplementation<dim, spacedim>;
  internal::PolytopeCache<dim, spacedim> polytope_cache;
  bool hybrid_mesh;
  std::map<std::pair<types::global_cell_index, types::global_cell_index>,
  std::vector<types::global_cell_index>>
  parent_child_info;
  unsigned int present_extraction_level;

Support for hp::FECollection

  bool is_hp_collection = false; // Indicates whether hp::FECollection is used
  std::unique_ptr<hp::FECollection<dim, spacedim>>
  hp_fe_collection; // External input FECollection

Stores quadrature rules; these QCollections should have the same size as hp_fe_collection

  hp::QCollection<dim> agglomeration_quad_collection;
  hp::QCollection<dim - 1> agglomeration_face_quad_collection;
  mapping_collection; // Contains only one mapping object
  dummy_fe_collection; // Similar to dummy_fe, but as an FECollection

containing only dummy_fe Note: The above two variables provide an hp::FECollection interface but actually contain only one element each.

Analogous to no_values and no_face_values, but used when different cells employ different FEs or quadratures

  std::unique_ptr<hp::FEValues<dim, spacedim>> hp_no_values;
  std::unique_ptr<hp::FEFaceValues<dim, spacedim>> hp_no_face_values;
  };

---------------------------— inline functions ----------------------—

  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::get_fe() const
  {
  return *fe;
  }
  template <int dim, int spacedim>
  inline const Mapping<dim> &
  AgglomerationHandler<dim, spacedim>::get_mapping() const
  {
  return *mapping;
  }
  template <int dim, int spacedim>
  inline const MappingBox<dim> &
  AgglomerationHandler<dim, spacedim>::get_agglomeration_mapping() const
  {
  return *box_mapping;
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::get_triangulation() const
  {
  return *tria;
  }
  template <int dim, int spacedim>
  inline const std::vector<BoundingBox<dim>> &
  AgglomerationHandler<dim, spacedim>::get_local_bboxes() const
  {
  return bboxes;
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::cell_to_polytope_index(
  {
  return master2polygon.at(cell->active_cell_index());
  }
  template <int dim, int spacedim>
  inline decltype(auto)
  AgglomerationHandler<dim, spacedim>::get_interface() const
  {
  return polytope_cache.interface;
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::get_relationships() const
  {
  return master_slave_relationships;
  }
  template <int dim, int spacedim>
  inline std::vector<typename Triangulation<dim, spacedim>::active_cell_iterator>
  AgglomerationHandler<dim, spacedim>::get_agglomerate(
  &master_cell) const
  {
  Assert(is_master_cell(master_cell), ExcInternalError());
  auto agglomeration = get_slaves_of_idx(master_cell->active_cell_index());
  agglomeration.push_back(master_cell);
  return agglomeration;
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::get_dof_handler() const
  {
  return agglo_dh;
  }
  template <int dim, int spacedim>
  inline const std::vector<
  AgglomerationHandler<dim, spacedim>::get_slaves_of_idx(
  {
  return master2slaves.at(idx);
  }
  template <int dim, int spacedim>
  template <typename CellIterator>
  inline bool
  AgglomerationHandler<dim, spacedim>::is_master_cell(
  const CellIterator &cell) const
  {
  return master_slave_relationships[cell->global_active_cell_index()] == -1;
  }
  template <int dim, int spacedim>
  template <typename CellIterator>
  inline bool
  AgglomerationHandler<dim, spacedim>::is_slave_cell(
  const CellIterator &cell) const
  {
  return master_slave_relationships[cell->global_active_cell_index()] >= 0;
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationHandler<dim, spacedim>::at_boundary(
  const unsigned int face_index) const
  {
  Assert(!is_slave_cell(cell),
  ExcMessage("This function should not be called for a slave cell."));
  return polytope_cache.cell_face_at_boundary
  .at({master2polygon.at(cell->active_cell_index()), face_index})
  }
  template <int dim, int spacedim>
  inline unsigned int
  AgglomerationHandler<dim, spacedim>::n_dofs_per_cell() const noexcept
  {
  return fe->n_dofs_per_cell();
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::n_dofs() const noexcept
  {
  return agglo_dh.n_dofs();
  }
  template <int dim, int spacedim>
  inline const std::vector<typename Triangulation<dim>::active_face_iterator> &
  AgglomerationHandler<dim, spacedim>::polytope_boundary(
  {
  return polygon_boundary[cell];
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::is_slave_cell_of(
  {
  return master_slave_relationships_iterators.at(cell->active_cell_index());
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::get_master_idx_of_cell(
  {
  auto idx = master_slave_relationships[cell->global_active_cell_index()];
  if (idx == -1)
  return cell->global_active_cell_index();
  else
  return static_cast<types::global_cell_index>(idx);
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationHandler<dim, spacedim>::are_cells_agglomerated(
  const
  {

if different subdomain, then by construction they will not be together if (cell->subdomain_id() != other_cell->subdomain_id()) return false; else

  return (get_master_idx_of_cell(cell) == get_master_idx_of_cell(other_cell));
  }
  template <int dim, int spacedim>
  inline unsigned int
  AgglomerationHandler<dim, spacedim>::n_agglomerates() const
  {
  return n_agglomerations;
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::polytope_to_dh_iterator(
  const types::global_cell_index polytope_index) const
  {
  return master_cells_container[polytope_index]->as_dof_handler_iterator(
  agglo_dh);
  }
  template <int dim, int spacedim>
  AgglomerationIterator<dim, spacedim>
  AgglomerationHandler<dim, spacedim>::begin() const
  {
  Assert(n_agglomerations > 0,
  ExcMessage("No agglomeration has been performed."));
  return {*master_cells_container.begin(), this};
  }
  template <int dim, int spacedim>
  AgglomerationIterator<dim, spacedim>
  AgglomerationHandler<dim, spacedim>::begin()
  {
  Assert(n_agglomerations > 0,
  ExcMessage("No agglomeration has been performed."));
  return {*master_cells_container.begin(), this};
  }
  template <int dim, int spacedim>
  AgglomerationIterator<dim, spacedim>
  AgglomerationHandler<dim, spacedim>::end() const
  {
  Assert(n_agglomerations > 0,
  ExcMessage("No agglomeration has been performed."));
  return {*master_cells_container.end(), this};
  }
  template <int dim, int spacedim>
  AgglomerationIterator<dim, spacedim>
  AgglomerationHandler<dim, spacedim>::end()
  {
  Assert(n_agglomerations > 0,
  ExcMessage("No agglomeration has been performed."));
  return {*master_cells_container.end(), this};
  }
  template <int dim, int spacedim>
  AgglomerationIterator<dim, spacedim>
  AgglomerationHandler<dim, spacedim>::last()
  {
  Assert(n_agglomerations > 0,
  ExcMessage("No agglomeration has been performed."));
  return {master_cells_container.back(), this};
  }
  template <int dim, int spacedim>
  typename AgglomerationHandler<dim, spacedim>::agglomeration_iterator>
  AgglomerationHandler<dim, spacedim>::polytope_iterators() const
  {
  typename AgglomerationHandler<dim, spacedim>::agglomeration_iterator>(
  begin(), end());
  }
  template <int dim, int spacedim>
  template <typename RtreeType>
  void
  AgglomerationHandler<dim, spacedim>::connect_hierarchy(
  const CellsAgglomerator<dim, RtreeType> &agglomerator)
  {
  parent_child_info = agglomerator.parent_node_to_children_nodes;
  present_extraction_level = agglomerator.extraction_level;
  }
  template <int dim, int spacedim>
  AgglomerationHandler<dim, spacedim>::get_fe_collection() const
  {
  return *hp_fe_collection;
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationHandler<dim, spacedim>::used_fe_collection() const
  {
  return is_hp_collection;
  }
  #endif

Annotated version of include/agglomeration_iterator.h

  /* -----------------------------------------------------------------------------
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
  * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,
  * Andrea Cangiani
  *
  * This file is part of the deal.II code gallery.
  *
  * -----------------------------------------------------------------------------
  */
  #ifndef agglomeration_iterator_h
  #define agglomeration_iterator_h
  #include <agglomeration_accessor.h>
  template <int dim, int spacedim = dim>
  class AgglomerationIterator
  {
  public:
  using AgglomerationContainer =
  typename AgglomerationAccessor<dim, spacedim>::AgglomerationContainer;
  AgglomerationIterator();
  AgglomerationIterator(
  const AgglomerationHandler<dim, spacedim> *handler);
  AgglomerationIterator(
  &master_cell,
  const CellId &cell_id,
  const AgglomerationHandler<dim, spacedim> *handler);
  const AgglomerationAccessor<dim, spacedim> &
  operator*() const;
  AgglomerationAccessor<dim, spacedim> &
  const AgglomerationAccessor<dim, spacedim> *
  operator->() const;
  AgglomerationAccessor<dim, spacedim> *
  operator->();
  bool
  operator==(const AgglomerationIterator<dim, spacedim> &) const;
  bool
  operator!=(const AgglomerationIterator<dim, spacedim> &) const;
  AgglomerationIterator &
  AgglomerationIterator
  AgglomerationIterator &
  AgglomerationIterator
  state() const;
  master_cell() const;
  using iterator_category = std::bidirectional_iterator_tag;
  using value_type = AgglomerationAccessor<dim, spacedim>;
  using difference_type = std::ptrdiff_t;
  using pointer = AgglomerationAccessor<dim, spacedim> *;
  using reference = AgglomerationAccessor<dim, spacedim> &;
  private:
  AgglomerationAccessor<dim, spacedim> accessor;
  };
*  *  reference operator*() const
std::ptrdiff_t difference_type
*  *  iterator & operator++()
SynchronousIterators< Iterators > & operator--(SynchronousIterators< Iterators > &a)

---------------------------— inline functions ----------------------—

  template <int dim, int spacedim>
  inline AgglomerationIterator<dim, spacedim>::AgglomerationIterator()
  : accessor()
  {}
  template <int dim, int spacedim>
  inline AgglomerationIterator<dim, spacedim>::AgglomerationIterator(
  &master_cell,
  const AgglomerationHandler<dim, spacedim> *handler)
  : accessor(master_cell, handler)
  {}
  template <int dim, int spacedim>
  inline AgglomerationIterator<dim, spacedim>::AgglomerationIterator(
  const typename Triangulation<dim, spacedim>::active_cell_iterator
  &master_cell,
  const CellId &cell_id,
  const AgglomerationHandler<dim, spacedim> *handler)
  : accessor(master_cell, cell_id, handler)
  {}
  template <int dim, int spacedim>
  inline AgglomerationAccessor<dim, spacedim> &
  AgglomerationIterator<dim, spacedim>::operator*()
  {
  return accessor;
  }
  template <int dim, int spacedim>
  inline AgglomerationAccessor<dim, spacedim> *
  AgglomerationIterator<dim, spacedim>::operator->()
  {
  return &(this->operator*());
  }
  template <int dim, int spacedim>
  inline const AgglomerationAccessor<dim, spacedim> &
  AgglomerationIterator<dim, spacedim>::operator*() const
  {
  return accessor;
  }
  template <int dim, int spacedim>
  inline const AgglomerationAccessor<dim, spacedim> *
  AgglomerationIterator<dim, spacedim>::operator->() const
  {
  return &(this->operator*());
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationIterator<dim, spacedim>::operator!=(
  const AgglomerationIterator<dim, spacedim> &other) const
  {
  return accessor != other.accessor;
  }
  template <int dim, int spacedim>
  inline bool
  AgglomerationIterator<dim, spacedim>::operator==(
  const AgglomerationIterator<dim, spacedim> &other) const
  {
  return accessor == other.accessor;
  }
  template <int dim, int spacedim>
  inline AgglomerationIterator<dim, spacedim> &
  AgglomerationIterator<dim, spacedim>::operator++()
  {
  accessor.next();
  return *this;
  }
  template <int dim, int spacedim>
  inline AgglomerationIterator<dim, spacedim>
  AgglomerationIterator<dim, spacedim>::operator++(int)
  {
  AgglomerationIterator tmp(*this);
  return tmp;
  }
  template <int dim, int spacedim>
  inline AgglomerationIterator<dim, spacedim> &
  AgglomerationIterator<dim, spacedim>::operator--()
  {
  accessor.prev();
  return *this;
  }
  template <int dim, int spacedim>
  inline AgglomerationIterator<dim, spacedim>
  AgglomerationIterator<dim, spacedim>::operator--(int)
  {
  AgglomerationIterator tmp(*this);
  return tmp;
  }
  template <int dim, int spacedim>
  AgglomerationIterator<dim, spacedim>::state() const
  {
  return accessor.master_cell.state();
  }
  template <int dim, int spacedim>
  AgglomerationIterator<dim, spacedim>::master_cell() const
  {
  return accessor.master_cell;
  }
  #endif

Annotated version of include/agglomerator.h

  /* -----------------------------------------------------------------------------
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
  * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,
  * Andrea Cangiani
  *
  * This file is part of the deal.II code gallery.
  *
  * -----------------------------------------------------------------------------
  */
  #ifndef agglomerator_h
  #define agglomerator_h
  #include <deal.II/base/config.h>
  #include <deal.II/base/bounding_box.h>
  #include <boost/geometry/algorithms/distance.hpp>
  #include <boost/geometry/index/rtree.hpp>
  #include <boost/geometry/strategies/strategies.hpp>
  template <int dim, int spacedim>
  class AgglomerationHandler;
  namespace dealii
  {
  namespace internal
  {
  template <typename Value,
  typename Options,
  typename Translator,
  typename Box,
  typename Allocators>
  struct Rtree_visitor
  : public boost::geometry::index::detail::rtree::visitor<
  Value,
  typename Options::parameters_type,
  Box,
  Allocators,
  typename Options::node_tag,
  true>::type
  {
  inline Rtree_visitor(
  const Translator &translator,
  const unsigned int target_level,
  std::vector<std::vector<typename Triangulation<
  boost::geometry::dimension<Box>::value>::active_cell_iterator>>
  &agglomerates_,
  std::vector<types::global_cell_index> &n_nodes_per_level,
  std::map<std::pair<types::global_cell_index, types::global_cell_index>,
  std::vector<types::global_cell_index>> &parent_to_children);
  using InternalNode =
  typename boost::geometry::index::detail::rtree::internal_node<
  Value,
  typename Options::parameters_type,
  Box,
  Allocators,
  typename Options::node_tag>::type;
  using Leaf = typename boost::geometry::index::detail::rtree::leaf<
  Value,
  typename Options::parameters_type,
  Box,
  Allocators,
  typename Options::node_tag>::type;
  inline void
  operator()(const InternalNode &node);
  inline void
  operator()(const Leaf &);
  const Translator &translator;
  size_t level;
  size_t node_counter;
  const size_t target_level;
  std::vector<std::vector<typename Triangulation<
  boost::geometry::dimension<Box>::value>::active_cell_iterator>>
  &agglomerates;
  std::vector<types::global_cell_index> &n_nodes_per_level;
  std::map<std::pair<types::global_cell_index, types::global_cell_index>,
  std::vector<types::global_cell_index>>
  &parent_node_to_children_nodes;
  };
  template <typename Value,
  typename Options,
  typename Translator,
  typename Box,
  typename Allocators>
  Rtree_visitor<Value, Options, Translator, Box, Allocators>::Rtree_visitor(
  const Translator &translator,
  const unsigned int target_level,
  std::vector<std::vector<typename Triangulation<
  boost::geometry::dimension<Box>::value>::active_cell_iterator>>
  &agglomerates_,
  std::vector<types::global_cell_index> &n_nodes_per_level_,
  std::map<std::pair<types::global_cell_index, types::global_cell_index>,
  std::vector<types::global_cell_index>> &parent_to_children)
  : translator(translator)
  , level(0)
  , node_counter(0)
  , target_level(target_level)
  , agglomerates(agglomerates_)
  , n_nodes_per_level(n_nodes_per_level_)
  , parent_node_to_children_nodes(parent_to_children)
  {}
  template <typename Value,
  typename Options,
  typename Translator,
  typename Box,
  typename Allocators>
  void
  Rtree_visitor<Value, Options, Translator, Box, Allocators>::operator()(
  const Rtree_visitor::InternalNode &node)
  {
  using elements_type =
  typename boost::geometry::index::detail::rtree::elements_type<
  InternalNode>::type; // pairs of bounding box and pointer to child
*  *  Point< dim > operator()(const Point< dim > &p) const * 
unsigned int level
Definition grid_out.cc:4642

node

  const elements_type &elements =
  boost::geometry::index::detail::rtree::elements(node);
  if (level < target_level)
  {
  size_t level_backup = level;
  for (typename elements_type::const_iterator it = elements.begin();
  it != elements.end();
  ++it)
  {
  boost::geometry::index::detail::rtree::apply_visitor(*this,
  *it->second);
  }
  level = level_backup;
  }
  else if (level == target_level)
  {
  const auto offset = agglomerates.size();
  agglomerates.resize(offset + 1);
  size_t level_backup = level;
  for (const auto &entry : elements)
  {
  boost::geometry::index::detail::rtree::apply_visitor(
  *this, *entry.second);
  }

Done with node number 'node_counter' on level target_level.

  ++node_counter; // visited all children of an internal node
  n_nodes_per_level[target_level]++;
  level = level_backup;
  }
  else if (level > target_level)
  {

I am on a child (internal) node on a deeper level.

Keep visiting until you go to the leafs.

  size_t level_backup = level;

looping through entries of node

  for (const auto &entry : elements)
  {
  boost::geometry::index::detail::rtree::apply_visitor(
  *this, *entry.second);
  }

done with node on level l > target_level (not just "target_level+1). @code   n_nodes_per_level[level_backup]++;   const types::global_cell_index node_idx =   n_nodes_per_level[level_backup] - 1; // so to start from 0     parent_node_to_children_nodes[{n_nodes_per_level[level_backup - 1],   level_backup - 1}]   .push_back(node_idx);     level = level_backup;   }   }         template <typename Value,   typename Options,   typename Translator,   typename Box,   typename Allocators>   void   Rtree_visitor<Value, Options, Translator, Box, Allocators>::operator()(   const Rtree_visitor::Leaf &leaf)   {   using elements_type =   typename boost::geometry::index::detail::rtree::elements_type<   Leaf>::type; // pairs of bounding box and pointer to child node   const elements_type &elements =   boost::geometry::index::detail::rtree::elements(leaf);     if (level == target_level)   { @endcode If I want to extract from leaf node, i.e. the target_level is the last one where leafs are grouped together. @code   const auto offset = agglomerates.size();   agglomerates.resize(offset + 1);     for (const auto &it : elements)   agglomerates[node_counter].push_back(it.second);     ++node_counter;   n_nodes_per_level[target_level]++;   }   else   {   for (const auto &it : elements)   agglomerates[node_counter].push_back(it.second);       if (level == target_level + 1)   {   const unsigned int node_idx = n_nodes_per_level[level];     parent_node_to_children_nodes[{n_nodes_per_level[level - 1],   level - 1}]   .push_back(node_idx);   n_nodes_per_level[level]++;   }   }   }   } // namespace internal         /**   * Helper class which handles agglomeration based on the R-tree data   * structure. Notice that the R-tree type is assumed to be an R-star-tree.   */   template <int dim, typename RtreeType>   class CellsAgglomerator   {   public:   template <int, int>   friend class ::AgglomerationHandler;     /**   * Constructor. It takes a given rtree and an integer representing the   * index of the level to be extracted.   */   CellsAgglomerator(const RtreeType &rtree,   const unsigned int extraction_level);     /**   * Extract agglomerates based on the current tree and the extraction level.   * This function returns a reference to   */   const std::vector<   std::vector<typename Triangulation<dim>::active_cell_iterator>> &   extract_agglomerates();     /**   * Get total number of levels.   */   inline unsigned int   get_n_levels() const;     /**   * Return the number of nodes present in level @p level.   */   inline types::global_cell_index   get_n_nodes_per_level(const unsigned int level) const;     /**   * This function returns a map which associates to each node on level   * @p extraction_level a list of children.   */   inline const std::map<   std::pair<types::global_cell_index, types::global_cell_index>,   std::vector<types::global_cell_index>> &   get_hierarchy() const;     private:   /**   * Raw pointer to the actual R-tree.   */   RtreeType *rtree;     /**   * Extraction level.   */   const unsigned int extraction_level;     /**   * Store agglomerates obtained after recursive extraction on nodes of   * level @p extraction_level.   */   std::vector<std::vector<typename Triangulation<dim>::active_cell_iterator>>   agglomerates_on_level;     /**   * Vector storing the number of nodes (and, ultimately, agglomerates) for   * each level.   */   std::vector<types::global_cell_index> n_nodes_per_level;     /**   * Map which maps a node parent @n on level @p l to a vector of integers   * which stores the index of children.   */   std::map<std::pair<types::global_cell_index, types::global_cell_index>,   std::vector<types::global_cell_index>>   parent_node_to_children_nodes;   };         template <int dim, typename RtreeType>   CellsAgglomerator<dim, RtreeType>::CellsAgglomerator(   const RtreeType &tree,   const unsigned int extraction_level_)   : extraction_level(extraction_level_)   {   rtree = const_cast<RtreeType *>(&tree);   Assert(n_levels(*rtree), ExcMessage("At least two levels are needed."));   }         template <int dim, typename RtreeType>   const std::vector<   std::vector<typename Triangulation<dim>::active_cell_iterator>> &   CellsAgglomerator<dim, RtreeType>::extract_agglomerates()   {   AssertThrow(extraction_level <= n_levels(*rtree),   ExcInternalError("You are trying to extract level " +   std::to_string(extraction_level) +   " of the tree, but it only has a total of " +   std::to_string(n_levels(*rtree)) +   " levels."));   using RtreeView =   boost::geometry::index::detail::rtree::utilities::view<RtreeType>;   RtreeView rtv(*rtree);     n_nodes_per_level.resize(rtv.depth() +   1); // store how many nodes we have for each level.     if (rtv.depth() == 0)   { @endcode The below algorithm does not work for <tt>rtv.depth()==0</tt>, which might happen if the number entries in the tree is too small. @code   agglomerates_on_level.resize(1);   agglomerates_on_level[0].resize(1);   }   else   {   const unsigned int target_level =   std::min<unsigned int>(extraction_level, rtv.depth());     internal::Rtree_visitor<typename RtreeView::value_type,   typename RtreeView::options_type,   typename RtreeView::translator_type,   typename RtreeView::box_type,   typename RtreeView::allocators_type>   extractor_visitor(rtv.translator(),   target_level,   agglomerates_on_level,   n_nodes_per_level,   parent_node_to_children_nodes);       rtv.apply_visitor(extractor_visitor);   }   return agglomerates_on_level;   }       @endcode ---------------------------&mdash; inline functions ----------------------&mdash; @code   template <int dim, typename RtreeType>   inline unsigned int   CellsAgglomerator<dim, RtreeType>::get_n_levels() const   {   return n_levels(*rtree);   }         template <int dim, typename RtreeType>   inline types::global_cell_index   CellsAgglomerator<dim, RtreeType>::get_n_nodes_per_level(   const unsigned int level) const   {   return n_nodes_per_level[level];   }         template <int dim, typename RtreeType>   inline const std::map<   std::pair<types::global_cell_index, types::global_cell_index>,   std::vector<types::global_cell_index>> &   CellsAgglomerator<dim, RtreeType>::get_hierarchy() const   {   Assert(parent_node_to_children_nodes.size(),   ExcMessage(   "The hierarchy has not been computed. Did you forget to call"   " extract_agglomerates() first?"));   return parent_node_to_children_nodes;   }   } // namespace dealii   #endif @endcode <a name="ann-include/mapping_box.h"></a> <h1>Annotated version of include/mapping_box.h</h1> @code   /* -----------------------------------------------------------------------------   *   * SPDX-License-Identifier: LGPL-2.1-or-later   * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,   * Andrea Cangiani   *   * This file is part of the deal.II code gallery.   *   * -----------------------------------------------------------------------------   */     #ifndef dealii_mapping_box_h   #define dealii_mapping_box_h       #include <deal.II/base/config.h>     #include <deal.II/base/bounding_box.h>   #include <deal.II/base/qprojector.h>     #include <deal.II/fe/mapping.h>     #include <cmath>       DEAL_II_NAMESPACE_OPEN     /**   * @addtogroup mapping   * @{   */     /**   * A class providing a mapping from the reference cell to cells that are   * axiparallel, i.e., that have the shape of rectangles (in 2d) or   * boxes (in 3d) with edges parallel to the coordinate directions. The   * class therefore provides functionality that is equivalent to what,   * for example, MappingQ would provide for such cells. However, knowledge   * of the shape of cells allows this class to be substantially more   * efficient.   *   * Specifically, the mapping is meant for cells for which the mapping from   * the reference to the real cell is a scaling along the coordinate   * directions: The transformation from reference coordinates \hat {\mathbf   * x} to real coordinates \mathbf x on each cell is of the form   * @f{align*}{   * {\mathbf x}(\hat {\mathbf x})   * =   * \begin{pmatrix}   * h_x & 0 \\   * 0 & h_y   * \end{pmatrix}   * \hat{\mathbf x}   * + {\mathbf v}_0   * @f}   * in 2d, and   * @f{align*}{   * {\mathbf x}(\hat {\mathbf x})   * =   * \begin{pmatrix}   * h_x & 0 & 0 \\   * 0 & h_y & 0 \\   * 0 & 0 & h_z   * \end{pmatrix}   * \hat{\mathbf x}   * + {\mathbf v}_0   * @f}   * in 3d, where {\mathbf v}_0 is the bottom left vertex and h_x,h_y,h_z   * are the extents of the cell along the axes.   *   * The class is intended for efficiency, and it does not do a whole lot of   * error checking. If you apply this mapping to a cell that does not conform   * to the requirements above, you will get strange results.   */   template <int dim, int spacedim = dim>   class MappingBox : public Mapping<dim, spacedim>   {   public:   MappingBox(const std::vector<BoundingBox<dim>> &local_boxes,   const std::map<types::global_cell_index, types::global_cell_index>   &polytope_translator); @endcode for documentation, see the Mapping base class @code   virtual std::unique_ptr<Mapping<dim, spacedim>>   clone() const override;     /**   * Return @p true because MappingBox preserves vertex   * locations.   */   virtual bool   preserves_vertex_locations() const override;     virtual bool   is_compatible_with(   #if DEAL_II_VERSION_GTE(9, 8, 0)   const ReferenceCell<dim> &reference_cell   #else   const ReferenceCell &reference_cell   #endif   ) const override;     /**   * @name Mapping points between reference and real cells   * @{   */   @endcode for documentation, see the Mapping base class @code   virtual Point<spacedim>   transform_unit_to_real_cell(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const Point<dim> &p) const override;   @endcode for documentation, see the Mapping base class @code   virtual Point<dim>   transform_real_to_unit_cell(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const Point<spacedim> &p) const override;   @endcode for documentation, see the Mapping base class @code   virtual void   transform_points_real_to_unit_cell(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const ArrayView<const Point<spacedim>> &real_points,   const ArrayView<Point<dim>> &unit_points) const override;     /**   * @}   */     /**   * @name Functions to transform tensors from reference to real coordinates   * @{   */   @endcode for documentation, see the Mapping base class @code   virtual void   transform(const ArrayView<const Tensor<1, dim>> &input,   const MappingKind kind,   const typename Mapping<dim, spacedim>::InternalDataBase &internal,   const ArrayView<Tensor<1, spacedim>> &output) const override;   @endcode for documentation, see the Mapping base class @code   virtual void   transform(const ArrayView<const DerivativeForm<1, dim, spacedim>> &input,   const MappingKind kind,   const typename Mapping<dim, spacedim>::InternalDataBase &internal,   const ArrayView<Tensor<2, spacedim>> &output) const override;   @endcode for documentation, see the Mapping base class @code   virtual void   transform(const ArrayView<const Tensor<2, dim>> &input,   const MappingKind kind,   const typename Mapping<dim, spacedim>::InternalDataBase &internal,   const ArrayView<Tensor<2, spacedim>> &output) const override;   @endcode for documentation, see the Mapping base class @code   virtual void   transform(const ArrayView<const DerivativeForm<2, dim, spacedim>> &input,   const MappingKind kind,   const typename Mapping<dim, spacedim>::InternalDataBase &internal,   const ArrayView<Tensor<3, spacedim>> &output) const override;   @endcode for documentation, see the Mapping base class @code   virtual void   transform(const ArrayView<const Tensor<3, dim>> &input,   const MappingKind kind,   const typename Mapping<dim, spacedim>::InternalDataBase &internal,   const ArrayView<Tensor<3, spacedim>> &output) const override;     /**   * @}   */     /**   * @name Interface with FEValues   * @{   */     /**   * Storage for internal data of the mapping. See Mapping::InternalDataBase   * for an extensive description.   *   * This includes data that is computed once when the object is created (in   * get_data()) as well as data the class wants to store from between the   * call to fill_fe_values(), fill_fe_face_values(), or   * fill_fe_subface_values() until possible later calls from the finite   * element to functions such as transform(). The latter class of member   * variables are marked as 'mutable'.   */   class InternalData : public Mapping<dim, spacedim>::InternalDataBase   {   public:   /**   * Default constructor.   */   InternalData() = default;     /**   * Constructor that initializes the object with a quadrature.   */   InternalData(const Quadrature<dim> &quadrature);   @endcode Documentation see Mapping::InternalDataBase. @code   virtual void   reinit(const UpdateFlags update_flags,   const Quadrature<dim> &quadrature) override;     /**   * Return an estimate (in bytes) for the memory consumption of this object.   */   virtual std::size_t   memory_consumption() const override;     /**   * Extents of the last cell we have seen in the coordinate directions,   * i.e., <i>h<sub>x</sub></i>, <i>h<sub>y</sub></i>, <i>h<sub>z</sub></i>.   */   mutable Tensor<1, dim> cell_extents;     /**   * Traslation term in F(\hat{x})=J\hat{x} + c.   */   mutable Tensor<1, dim> traslation;     /**   * Reciprocal of the extents of the last cell we have seen in the   * coordinate directions, i.e., <i>h<sub>x</sub></i>,   * <i>h<sub>y</sub></i>, <i>h<sub>z</sub></i>.   */   mutable Tensor<1, dim> inverse_cell_extents;     /**   * The volume element   */   mutable double volume_element;     /**   * Location of quadrature points of faces or subfaces in 3d with all   * possible orientations. Can be accessed with the correct offset provided   * via QProjector::DataSetDescriptor. Not needed/used for cells.   */   std::vector<Point<dim>> quadrature_points;   };     private: @endcode documentation can be found in Mapping::requires_update_flags() @code   virtual UpdateFlags   requires_update_flags(const UpdateFlags update_flags) const override;   @endcode documentation can be found in Mapping::get_data() @code   virtual std::unique_ptr<typename Mapping<dim, spacedim>::InternalDataBase>   get_data(const UpdateFlags, const Quadrature<dim> &quadrature) const override;     using Mapping<dim, spacedim>::get_face_data;   @endcode documentation can be found in Mapping::get_subface_data() @code   virtual std::unique_ptr<typename Mapping<dim, spacedim>::InternalDataBase>   get_subface_data(const UpdateFlags flags,   const Quadrature<dim - 1> &quadrature) const override;   @endcode documentation can be found in Mapping::fill_fe_values() @code   virtual CellSimilarity::Similarity   fill_fe_values(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const CellSimilarity::Similarity cell_similarity,   const Quadrature<dim> &quadrature,   const typename Mapping<dim, spacedim>::InternalDataBase &internal_data,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const override;     using Mapping<dim, spacedim>::fill_fe_face_values;   @endcode documentation can be found in Mapping::fill_fe_subface_values() @code   virtual void   fill_fe_subface_values(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const unsigned int face_no,   const unsigned int subface_no,   const Quadrature<dim - 1> &quadrature,   const typename Mapping<dim, spacedim>::InternalDataBase &internal_data,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const override;   @endcode documentation can be found in Mapping::fill_fe_immersed_surface_values() @code   virtual void   fill_fe_immersed_surface_values(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const NonMatching::ImmersedSurfaceQuadrature<dim> &quadrature,   const typename Mapping<dim, spacedim>::InternalDataBase &internal_data,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const override;     /**   * @}   */     /**   * Update the cell_extents field of the incoming InternalData object with the   * size of the incoming cell.   */   void   update_cell_extents(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const CellSimilarity::Similarity cell_similarity,   const InternalData &data) const;     /**   * Compute the quadrature points if the UpdateFlags of the incoming   * InternalData object say that they should be updated.   *   * Called from fill_fe_values.   */   void   maybe_update_cell_quadrature_points(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const InternalData &data,   const ArrayView<const Point<dim>> &unit_quadrature_points,   std::vector<Point<dim>> &quadrature_points) const;     /**   * Compute the normal vectors if the UpdateFlags of the incoming InternalData   * object say that they should be updated.   */   void   maybe_update_normal_vectors(   const unsigned int face_no,   const InternalData &data,   std::vector<Tensor<1, dim>> &normal_vectors) const;     /**   * Since the Jacobian is constant for this mapping all derivatives of the   * Jacobian are identically zero. Fill these quantities with zeros if the   * corresponding update flags say that they should be updated.   */   void   maybe_update_jacobian_derivatives(   const InternalData &data,   const CellSimilarity::Similarity cell_similarity,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const;       /**   * Compute the volume elements if the UpdateFlags of the incoming   * InternalData object say that they should be updated.   */   void   maybe_update_volume_elements(const InternalData &data) const;     /**   * Compute the Jacobians if the UpdateFlags of the incoming   * InternalData object say that they should be updated.   */   void   maybe_update_jacobians(   const InternalData &data,   const CellSimilarity::Similarity cell_similarity,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const;     /**   * Compute the inverse Jacobians if the UpdateFlags of the incoming   * InternalData object say that they should be updated.   */   void   maybe_update_inverse_jacobians(   const InternalData &data,   const CellSimilarity::Similarity cell_similarity,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const;     /**   * Vector of (local) bounding boxes   */   std::vector<BoundingBox<dim>> boxes;     /**   * Map from global cell index to bounding box index   */   std::map<types::global_cell_index, types::global_cell_index>   polytope_translator;   };     /** @} */     DEAL_II_NAMESPACE_CLOSE     #endif @endcode <a name="ann-include/poly_utils.h"></a> <h1>Annotated version of include/poly_utils.h</h1> @code   /* -----------------------------------------------------------------------------   *   * SPDX-License-Identifier: LGPL-2.1-or-later   * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,   * Andrea Cangiani   *   * This file is part of the deal.II code gallery.   *   * -----------------------------------------------------------------------------   */     #ifndef poly_utils_h   #define poly_utils_h     #include <deal.II/base/config.h>     #include <deal.II/base/conditional_ostream.h>   #include <deal.II/base/point.h>   #include <deal.II/base/quadrature.h>   #include <deal.II/base/std_cxx20/iota_view.h>     #include <deal.II/boost_adaptors/bounding_box.h>   #include <deal.II/boost_adaptors/point.h>   #include <deal.II/boost_adaptors/segment.h>     #include <deal.II/distributed/tria.h>     #include <deal.II/dofs/dof_handler.h>     #include <deal.II/fe/fe_dgq.h>   #include <deal.II/fe/fe_values.h>     #include <deal.II/grid/grid_tools.h>     #include <deal.II/lac/dynamic_sparsity_pattern.h>   #include <deal.II/lac/sparse_matrix.h>   #include <deal.II/lac/sparsity_pattern.h>   #include <deal.II/lac/sparsity_tools.h>   #include <deal.II/lac/trilinos_sparse_matrix.h>     #include <deal.II/numerics/vector_tools_common.h>     #include <boost/geometry/algorithms/distance.hpp>   #include <boost/geometry/index/detail/rtree/utilities/print.hpp>   #include <boost/geometry/index/rtree.hpp>   #include <boost/geometry/strategies/strategies.hpp>     #include <memory>     namespace ::PolyUtils::internal   {   /**   * Helper function to compute the position of index @p index in vector @p v.   */   inline types::global_cell_index   get_index(const std::vector<types::global_cell_index> &v,   const types::global_cell_index index)   {   return std::distance(v.begin(), std::find(v.begin(), v.end(), index));   }     /**   * Compute the connectivity graph for locally owned regions of a distributed   * triangulation.   */   template <int dim, int spacedim>   void   get_face_connectivity_of_cells(   const parallel::fullydistributed::Triangulation<dim, spacedim>   &triangulation,   DynamicSparsityPattern &cell_connectivity,   const std::vector<types::global_cell_index> locally_owned_cells)   {   cell_connectivity.reinit(triangulation.n_locally_owned_active_cells(),   triangulation.n_locally_owned_active_cells());   @endcode loop over all cells and their neighbors to build the sparsity pattern. note that it's a bit hard to enter all the connections when a neighbor has children since we would need to find out which of its children is adjacent to the current cell. this problem can be omitted if we only do something if the neighbor has no children &ndash; in that case it is either on the same or a coarser level than we are. in return, we have to add entries in both directions for both cells @code   for (const auto &cell : triangulation.active_cell_iterators())   {   if (cell->is_locally_owned())   {   const unsigned int index = cell->active_cell_index();   cell_connectivity.add(get_index(locally_owned_cells, index),   get_index(locally_owned_cells, index));   for (auto f : cell->face_indices())   if ((cell->at_boundary(f) == false) &&   (cell->neighbor(f)->has_children() == false) &&   cell->neighbor(f)->is_locally_owned())   {   const unsigned int other_index =   cell->neighbor(f)->active_cell_index();     cell_connectivity.add(get_index(locally_owned_cells, index),   get_index(locally_owned_cells,   other_index));   cell_connectivity.add(get_index(locally_owned_cells,   other_index),   get_index(locally_owned_cells, index));   }   }   }   }   } // namespace ::PolyUtils::internal     namespace ::PolyUtils   {   template <typename Value,   typename Options,   typename Translator,   typename Box,   typename Allocators>   struct Rtree_visitor : public boost::geometry::index::detail::rtree::visitor<   Value,   typename Options::parameters_type,   Box,   Allocators,   typename Options::node_tag,   true>::type   {   inline Rtree_visitor(   const Translator &translator,   unsigned int target_level,   std::vector<std::vector<typename Triangulation<   boost::geometry::dimension<Box>::value>::active_cell_iterator>> &boxes,   std::vector<std::vector<unsigned int>> &csr);     /**   * An alias that identifies an InternalNode of the tree.   */   using InternalNode =   typename boost::geometry::index::detail::rtree::internal_node<   Value,   typename Options::parameters_type,   Box,   Allocators,   typename Options::node_tag>::type;     /**   * An alias that identifies a Leaf of the tree.   */   using Leaf = typename boost::geometry::index::detail::rtree::leaf<   Value,   typename Options::parameters_type,   Box,   Allocators,   typename Options::node_tag>::type;     /**   * Implements the visitor interface for InternalNode objects. If the node   * belongs to the level next to @p target_level, then fill the bounding box   * vector for that node.   */   inline void   operator()(const InternalNode &node);     /**   * Implements the visitor interface for Leaf objects.   */   inline void   operator()(const Leaf &);     /**   * Translator interface, required by the boost implementation of the rtree.   */   const Translator &translator;     /**   * Store the level we are currently visiting.   */   size_t level;     /**   * Index used to keep track of the number of different visited nodes during   * recursion/   */   size_t node_counter;     size_t next_level_leafs_processed;   /**   * The level where children are living.   * Before: "we want to extract from the RTree object."   */   const size_t target_level;     /**   * A reference to the input vector of vector of BoundingBox objects. This   * vector v has the following property: v[i] = vector with all   * of the BoundingBox bounded by the i-th node of the Rtree.   */   std::vector<std::vector<typename Triangulation<   boost::geometry::dimension<Box>::value>::active_cell_iterator>>   &agglomerates;     std::vector<std::vector<unsigned int>> &row_ptr;   };     template <typename Value,   typename Options,   typename Translator,   typename Box,   typename Allocators>   Rtree_visitor<Value, Options, Translator, Box, Allocators>::Rtree_visitor(   const Translator &translator,   const unsigned int target_level,   std::vector<std::vector<typename Triangulation<   boost::geometry::dimension<Box>::value>::active_cell_iterator>>   &bb_in_boxes,   std::vector<std::vector<unsigned int>> &csr)   : translator(translator)   , level(0)   , node_counter(0)   , next_level_leafs_processed(0)   , target_level(target_level)   , agglomerates(bb_in_boxes)   , row_ptr(csr)   {}     template <typename Value,   typename Options,   typename Translator,   typename Box,   typename Allocators>   void   Rtree_visitor<Value, Options, Translator, Box, Allocators>::operator()(   const Rtree_visitor::InternalNode &node)   {   using elements_type =   typename boost::geometry::index::detail::rtree::elements_type<   InternalNode>::type; // pairs of bounding box and pointer to child @endcode node @code   const elements_type &elements =   boost::geometry::index::detail::rtree::elements(node);     if (level < target_level)   {   size_t level_backup = level;   ++level;     for (typename elements_type::const_iterator it = elements.begin();   it != elements.end();   ++it)   {   boost::geometry::index::detail::rtree::apply_visitor(*this,   *it->second);   }     level = level_backup;   }   else if (level == target_level)   { @endcode const unsigned int n_children = elements.size(); @code   const auto offset = agglomerates.size();   agglomerates.resize(offset + 1);   row_ptr.resize(row_ptr.size() + 1);   next_level_leafs_processed = 0;   row_ptr.back().push_back(   next_level_leafs_processed); // convention: row_ptr[0]=0   size_t level_backup = level;     ++level;   for (const auto &child : elements)   {   boost::geometry::index::detail::rtree::apply_visitor(*this,   *child.second);   } @endcode Done with node number 'node_counter' @code   ++node_counter; // visited all children of an internal node     level = level_backup;   }   else if (level > target_level)   { @endcode Keep visiting until you go to the leafs. @code   size_t level_backup = level;     ++level;     for (const auto &child : elements)   {   boost::geometry::index::detail::rtree::apply_visitor(*this,   *child.second);   }   level = level_backup;   row_ptr[node_counter].push_back(next_level_leafs_processed);   }   }     template <typename Value,   typename Options,   typename Translator,   typename Box,   typename Allocators>   void   Rtree_visitor<Value, Options, Translator, Box, Allocators>::operator()(   const Rtree_visitor::Leaf &leaf)   {   using elements_type =   typename boost::geometry::index::detail::rtree::elements_type<   Leaf>::type; // pairs of bounding box and pointer to child node   const elements_type &elements =   boost::geometry::index::detail::rtree::elements(leaf);     for (const auto &it : elements)   {   agglomerates[node_counter].push_back(it.second);   }   next_level_leafs_processed += elements.size();   }     template <typename T>   inline constexpr T   constexpr_pow(T num, unsigned int pow)   {   return (pow >= sizeof(unsigned int) * 8) ? 0 :   pow == 0 ? 1 :   num * constexpr_pow(num, pow - 1);   }     namespace internal   {   /**   * Same as the public free function with the same name, but storing   * explicitly the interpolation matrix and performing interpolation through   * matrix-vector product.   */   template <int dim, int spacedim, typename VectorType>   void   interpolate_to_fine_grid(   const AgglomerationHandler<dim, spacedim> &agglomeration_handler,   VectorType &dst,   const VectorType &src)   {   Assert((dim == spacedim), ExcNotImplemented());   Assert(   dst.size() == 0,   ExcMessage(   "The destination vector must the empt upon calling this function."));     using NumberType = typename VectorType::value_type;   constexpr bool is_trilinos_vector =   std::is_same_v<VectorType, TrilinosWrappers::MPI::Vector>;   using MatrixType = std::conditional_t<is_trilinos_vector,   TrilinosWrappers::SparseMatrix,   SparseMatrix<NumberType>>;     MatrixType interpolation_matrix;     [[maybe_unused]]   typename std::conditional_t<!is_trilinos_vector, SparsityPattern, void *>   sp;   @endcode Get some info from the handler @code   const DoFHandler<dim, spacedim> &agglo_dh =   agglomeration_handler.agglo_dh;     DoFHandler<dim, spacedim> *output_dh =   const_cast<DoFHandler<dim, spacedim> *>(   &agglomeration_handler.output_dh);   const FiniteElement<dim, spacedim> &fe = agglomeration_handler.get_fe();   const Mapping<dim> &mapping = agglomeration_handler.get_mapping();   const Triangulation<dim, spacedim> &tria =   agglomeration_handler.get_triangulation();   const auto &bboxes = agglomeration_handler.get_local_bboxes();     std::unique_ptr<FiniteElement<dim>> output_fe;   if (tria.all_reference_cells_are_hyper_cube())   output_fe = std::make_unique<FE_DGQ<dim>>(fe.degree);   else if (tria.all_reference_cells_are_simplex())   output_fe = std::make_unique<FE_SimplexDGP<dim>>(fe.degree);   else   AssertThrow(false, ExcNotImplemented());   @endcode Setup an auxiliary DoFHandler for output purposes @code   output_dh->reinit(tria);   output_dh->distribute_dofs(*output_fe);     const IndexSet &locally_owned_dofs = output_dh->locally_owned_dofs();   const IndexSet locally_relevant_dofs =   DoFTools::extract_locally_relevant_dofs(*output_dh);     const IndexSet &locally_owned_dofs_agglo = agglo_dh.locally_owned_dofs();     DynamicSparsityPattern dsp(output_dh->n_dofs(),   agglo_dh.n_dofs(),   locally_relevant_dofs);     std::vector<types::global_dof_index> agglo_dof_indices(fe.dofs_per_cell);   std::vector<types::global_dof_index> standard_dof_indices(   fe.dofs_per_cell);   std::vector<types::global_dof_index> output_dof_indices(   output_fe->dofs_per_cell);     Quadrature<dim> quad(output_fe->get_unit_support_points());   FEValues<dim, spacedim> output_fe_values(mapping,   *output_fe,   quad,   update_quadrature_points);     for (const auto &cell : agglo_dh.active_cell_iterators())   if (cell->is_locally_owned())   {   if (agglomeration_handler.is_master_cell(cell))   {   auto slaves = agglomeration_handler.get_slaves_of_idx(   cell->active_cell_index());   slaves.emplace_back(cell);     cell->get_dof_indices(agglo_dof_indices);     for (const auto &slave : slaves)   { @endcode addd master-slave relationship @code   const auto slave_output =   slave->as_dof_handler_iterator(*output_dh);   slave_output->get_dof_indices(output_dof_indices);   for (const auto row : output_dof_indices)   dsp.add_entries(row,   agglo_dof_indices.begin(),   agglo_dof_indices.end());   }   }   }     const auto assemble_interpolation_matrix = [&]() {   FullMatrix<NumberType> local_matrix(fe.dofs_per_cell, fe.dofs_per_cell);   std::vector<Point<dim>> reference_q_points(fe.dofs_per_cell);   @endcode Dummy AffineConstraints, only needed for loc2glb @code   AffineConstraints<NumberType> c;   c.close();     for (const auto &cell : agglo_dh.active_cell_iterators())   if (cell->is_locally_owned())   {   if (agglomeration_handler.is_master_cell(cell))   {   auto slaves = agglomeration_handler.get_slaves_of_idx(   cell->active_cell_index());   slaves.emplace_back(cell);     cell->get_dof_indices(agglo_dof_indices);     const types::global_cell_index polytope_index =   agglomeration_handler.cell_to_polytope_index(cell);   @endcode Get the box of this agglomerate. @code   const BoundingBox<dim> &box = bboxes[polytope_index];     for (const auto &slave : slaves)   { @endcode add master-slave relationship @code   const auto slave_output =   slave->as_dof_handler_iterator(*output_dh);     slave_output->get_dof_indices(output_dof_indices);   output_fe_values.reinit(slave_output);     local_matrix = 0.;     const auto &q_points =   output_fe_values.get_quadrature_points();   for (const auto i : output_fe_values.dof_indices())   {   const auto &p = box.real_to_unit(q_points[i]);   for (const auto j : output_fe_values.dof_indices())   {   local_matrix(i, j) = fe.shape_value(j, p);   }   }   c.distribute_local_to_global(local_matrix,   output_dof_indices,   agglo_dof_indices,   interpolation_matrix);   }   }   }   };     if constexpr (std::is_same_v<MatrixType, TrilinosWrappers::SparseMatrix>)   {   const MPI_Comm &communicator = tria.get_mpi_communicator();   SparsityTools::distribute_sparsity_pattern(dsp,   locally_owned_dofs,   communicator,   locally_relevant_dofs);     interpolation_matrix.reinit(locally_owned_dofs,   locally_owned_dofs_agglo,   dsp,   communicator);   dst.reinit(locally_owned_dofs);   assemble_interpolation_matrix();   }   else if constexpr (std::is_same_v<MatrixType, SparseMatrix<NumberType>>)   {   sp.copy_from(dsp);   interpolation_matrix.reinit(sp);   dst.reinit(output_dh->n_dofs());   assemble_interpolation_matrix();   }   else   { @endcode PETSc, LA::d::v options not implemented. @code   (void)agglomeration_handler;   (void)dst;   (void)src;   AssertThrow(false, ExcNotImplemented());   }   @endcode If tria is distributed @code   if (dynamic_cast<const parallel::TriangulationBase<dim, spacedim> *>(   &tria) != nullptr)   interpolation_matrix.compress(VectorOperation::add);   @endcode Finally, perform the interpolation. @code   interpolation_matrix.vmult(dst, src);   }   } // namespace internal     /**   * Given a vector @p src, typically the solution stemming after the   * agglomerate problem has been solved, this function interpolates @p src   * onto the finer grid and stores the result in vector @p dst. The last   * argument @p on_the_fly does not build any interpolation matrix and allows   * computing the entries in @p dst in a matrix-free fashion.   *   * @note Supported parallel types are TrilinosWrappers::SparseMatrix and   * TrilinosWrappers::MPI::Vector.   */   template <int dim, int spacedim, typename VectorType>   void   interpolate_to_fine_grid(   const AgglomerationHandler<dim, spacedim> &agglomeration_handler,   VectorType &dst,   const VectorType &src,   const bool on_the_fly = true)   {   Assert((dim == spacedim), ExcNotImplemented());   Assert(   dst.size() == 0,   ExcMessage(   "The destination vector must the empt upon calling this function."));     using NumberType = typename VectorType::value_type;   static constexpr bool is_trilinos_vector =   std::is_same_v<VectorType, TrilinosWrappers::MPI::Vector>;     static constexpr bool is_supported_vector =   std::is_same_v<VectorType, Vector<NumberType>> || is_trilinos_vector;   static_assert(is_supported_vector);   @endcode First, check for an easy return @code   if (on_the_fly == false)   {   return internal::interpolate_to_fine_grid(agglomeration_handler,   dst,   src);   }   else   { @endcode otherwise, do not create any matrix @code   if (!agglomeration_handler.used_fe_collection())   { @endcode Original version: handle case without hp::FECollection @code   const Triangulation<dim, spacedim> &tria =   agglomeration_handler.get_triangulation();   const Mapping<dim> &mapping = agglomeration_handler.get_mapping();   const FiniteElement<dim, spacedim> &original_fe =   agglomeration_handler.get_fe();   @endcode We use DGQ (on tensor-product meshes) or DGP (on simplex meshes) nodal elements of the same degree as the ones in the agglomeration handler to interpolate the solution onto the finer grid. @code   std::unique_ptr<FiniteElement<dim>> output_fe;   if (tria.all_reference_cells_are_hyper_cube())   output_fe = std::make_unique<FE_DGQ<dim>>(original_fe.degree);   else if (tria.all_reference_cells_are_simplex())   output_fe =   std::make_unique<FE_SimplexDGP<dim>>(original_fe.degree);   else   AssertThrow(false, ExcNotImplemented());     DoFHandler<dim> &output_dh =   const_cast<DoFHandler<dim> &>(agglomeration_handler.output_dh);   output_dh.reinit(tria);   output_dh.distribute_dofs(*output_fe);     if constexpr (std::is_same_v<VectorType,   TrilinosWrappers::MPI::Vector>)   {   const IndexSet &locally_owned_dofs =   output_dh.locally_owned_dofs();   dst.reinit(locally_owned_dofs);   }   else if constexpr (std::is_same_v<VectorType, Vector<NumberType>>)   {   dst.reinit(output_dh.n_dofs());   }   else   { @endcode PETSc, LA::d::v options not implemented. @code   (void)agglomeration_handler;   (void)dst;   (void)src;   AssertThrow(false, ExcNotImplemented());   }     const unsigned int dofs_per_cell =   agglomeration_handler.n_dofs_per_cell();   const unsigned int output_dofs_per_cell =   output_fe->n_dofs_per_cell();   Quadrature<dim> quad(output_fe->get_unit_support_points());   FEValues<dim> output_fe_values(mapping,   *output_fe,   quad,   update_quadrature_points);     std::vector<types::global_dof_index> local_dof_indices(   dofs_per_cell);   std::vector<types::global_dof_index> local_dof_indices_output(   output_dofs_per_cell);     const auto &bboxes = agglomeration_handler.get_local_bboxes();   for (const auto &polytope :   agglomeration_handler.polytope_iterators())   {   if (polytope->is_locally_owned())   {   polytope->get_dof_indices(local_dof_indices);   const BoundingBox<dim> &box = bboxes[polytope->index()];     const auto &deal_cells =   polytope->get_agglomerate(); // fine deal.II cells   for (const auto &cell : deal_cells)   {   const auto slave_output = cell->as_dof_handler_iterator(   agglomeration_handler.output_dh);   slave_output->get_dof_indices(local_dof_indices_output);   output_fe_values.reinit(slave_output);     const auto &qpoints =   output_fe_values.get_quadrature_points();     for (unsigned int j = 0; j < output_dofs_per_cell; ++j)   {   const auto &ref_qpoint =   box.real_to_unit(qpoints[j]);   for (unsigned int i = 0; i < dofs_per_cell; ++i)   dst(local_dof_indices_output[j]) +=   src(local_dof_indices[i]) *   original_fe.shape_value(i, ref_qpoint);   }   }   }   }   }   else   { @endcode Handle the hp::FECollection case @code   const Triangulation<dim, spacedim> &tria =   agglomeration_handler.get_triangulation();   const Mapping<dim> &mapping = agglomeration_handler.get_mapping();   const hp::FECollection<dim, spacedim> &original_fe_collection =   agglomeration_handler.get_fe_collection();   @endcode We use DGQ (on tensor-product meshes) or DGP (on simplex meshes) nodal elements of the same degree as the ones in the agglomeration handler to interpolate the solution onto the finer grid. @code   hp::FECollection<dim, spacedim> output_fe_collection;     Assert(original_fe_collection[0].n_components() >= 1,   ExcMessage("Invalid FE: must have at least one component."));   if (original_fe_collection[0].n_components() == 1)   { @endcode Scalar case @code   for (unsigned int i = 0; i < original_fe_collection.size(); ++i)   {   std::unique_ptr<FiniteElement<dim>> output_fe;   if (tria.all_reference_cells_are_hyper_cube())   output_fe = std::make_unique<FE_DGQ<dim>>(   original_fe_collection[i].degree);   else if (tria.all_reference_cells_are_simplex())   output_fe = std::make_unique<FE_SimplexDGP<dim>>(   original_fe_collection[i].degree);   else   AssertThrow(false, ExcNotImplemented());   output_fe_collection.push_back(*output_fe);   }   }   else if (original_fe_collection[0].n_components() > 1)   { @endcode System case @code   for (unsigned int i = 0; i < original_fe_collection.size(); ++i)   {   std::vector<const FiniteElement<dim, spacedim> *>   base_elements;   std::vector<unsigned int> multiplicities;   for (unsigned int b = 0;   b < original_fe_collection[i].n_base_elements();   ++b)   {   if (dynamic_cast<const FE_Nothing<dim> *>(   &original_fe_collection[i].base_element(b)))   base_elements.push_back(   new FE_Nothing<dim, spacedim>());   else   {   if (tria.all_reference_cells_are_hyper_cube())   base_elements.push_back(new FE_DGQ<dim, spacedim>(   original_fe_collection[i]   .base_element(b)   .degree));   else if (tria.all_reference_cells_are_simplex())   base_elements.push_back(   new FE_SimplexDGP<dim, spacedim>(   original_fe_collection[i]   .base_element(b)   .degree));   else   AssertThrow(false, ExcNotImplemented());   }   multiplicities.push_back(   original_fe_collection[i].element_multiplicity(b));   }     FESystem<dim, spacedim> output_fe_system(base_elements,   multiplicities);   for (const auto *ptr : base_elements)   delete ptr;   output_fe_collection.push_back(output_fe_system);   }   }     DoFHandler<dim> &output_dh =   const_cast<DoFHandler<dim> &>(agglomeration_handler.output_dh);   output_dh.reinit(tria);   for (const auto &polytope :   agglomeration_handler.polytope_iterators())   {   if (polytope->is_locally_owned())   {   const auto &deal_cells =   polytope->get_agglomerate(); // fine deal.II cells   const unsigned int active_fe_idx =   polytope->active_fe_index();     for (const auto &cell : deal_cells)   {   const typename DoFHandler<dim>::active_cell_iterator   slave_cell_dh_iterator =   cell->as_dof_handler_iterator(output_dh);   slave_cell_dh_iterator->set_active_fe_index(   active_fe_idx);   }   }   }   output_dh.distribute_dofs(output_fe_collection);     if constexpr (std::is_same_v<VectorType,   TrilinosWrappers::MPI::Vector>)   {   const IndexSet &locally_owned_dofs =   output_dh.locally_owned_dofs();   dst.reinit(locally_owned_dofs);   }   else if constexpr (std::is_same_v<VectorType, Vector<NumberType>>)   {   dst.reinit(output_dh.n_dofs());   }   else   { @endcode PETSc, LA::d::v options not implemented. @code   (void)agglomeration_handler;   (void)dst;   (void)src;   AssertThrow(false, ExcNotImplemented());   }     const auto &bboxes = agglomeration_handler.get_local_bboxes();   for (const auto &polytope :   agglomeration_handler.polytope_iterators())   {   if (polytope->is_locally_owned())   {   const unsigned int active_fe_idx =   polytope->active_fe_index();   const unsigned int dofs_per_cell =   polytope->get_fe().dofs_per_cell;   const unsigned int output_dofs_per_cell =   output_fe_collection[active_fe_idx].n_dofs_per_cell();   Quadrature<dim> quad(output_fe_collection[active_fe_idx]   .get_unit_support_points());   FEValues<dim> output_fe_values(   mapping,   output_fe_collection[active_fe_idx],   quad,   update_quadrature_points);   std::vector<types::global_dof_index> local_dof_indices(   dofs_per_cell);   std::vector<types::global_dof_index>   local_dof_indices_output(output_dofs_per_cell);     polytope->get_dof_indices(local_dof_indices);   const BoundingBox<dim> &box = bboxes[polytope->index()];     const auto &deal_cells =   polytope->get_agglomerate(); // fine deal.II cells   for (const auto &cell : deal_cells)   {   const auto slave_output = cell->as_dof_handler_iterator(   agglomeration_handler.output_dh);   slave_output->get_dof_indices(local_dof_indices_output);   output_fe_values.reinit(slave_output);     const auto &qpoints =   output_fe_values.get_quadrature_points();     for (unsigned int j = 0; j < output_dofs_per_cell; ++j)   {   const unsigned int component_idx_of_this_dof =   slave_output->get_fe()   .system_to_component_index(j)   .first;   const auto &ref_qpoint =   box.real_to_unit(qpoints[j]);   for (unsigned int i = 0; i < dofs_per_cell; ++i)   dst(local_dof_indices_output[j]) +=   src(local_dof_indices[i]) *   original_fe_collection[active_fe_idx]   .shape_value_component(   i, ref_qpoint, component_idx_of_this_dof);   }   }   }   }   }   }   }     /**   * Similar to VectorTools::compute_global_error(), but customized for   * polytopic elements. Aside from the solution vector and a reference   * function, this function takes in addition a vector @p norms with types   * VectorTools::NormType to be computed and later stored in the last   * argument @p global_errors.   * In case of a parallel vector, the local errors are collected over each   * processor and later a classical reduction operation is performed.   */   template <int dim, typename Number, typename VectorType>   void   compute_global_error(const AgglomerationHandler<dim> &agglomeration_handler,   const VectorType &solution,   const Function<dim, Number> &exact_solution,   const std::vector<VectorTools::NormType> &norms,   std::vector<double> &global_errors)   {   Assert(solution.size() > 0,   ExcNotImplemented(   "Solution vector must be non-empty upon calling this function."));   Assert(std::any_of(norms.cbegin(),   norms.cend(),   [](VectorTools::NormType norm_type) {   return (norm_type ==   VectorTools::NormType::H1_seminorm ||   norm_type == VectorTools::NormType::L2_norm);   }),   ExcMessage("Norm type not supported"));   global_errors.resize(norms.size());   std::fill(global_errors.begin(), global_errors.end(), 0.);   @endcode Vector storing errors local to the current processor. @code   std::vector<double> local_errors(norms.size());   std::fill(local_errors.begin(), local_errors.end(), 0.);   @endcode Get some info from the handler @code   const unsigned int dofs_per_cell = agglomeration_handler.n_dofs_per_cell();     const bool compute_semi_H1 =   std::any_of(norms.cbegin(),   norms.cend(),   [](VectorTools::NormType norm_type) {   return norm_type == VectorTools::NormType::H1_seminorm;   });     std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);   for (const auto &polytope : agglomeration_handler.polytope_iterators())   {   if (polytope->is_locally_owned())   {   const auto &agglo_values = agglomeration_handler.reinit(polytope);   polytope->get_dof_indices(local_dof_indices);     const auto &q_points = agglo_values.get_quadrature_points();   const unsigned int n_qpoints = q_points.size();   std::vector<double> analyical_sol_at_qpoints(n_qpoints);   exact_solution.value_list(q_points, analyical_sol_at_qpoints);   std::vector<Tensor<1, dim>> grad_analyical_sol_at_qpoints(   n_qpoints);     if (compute_semi_H1)   exact_solution.gradient_list(q_points,   grad_analyical_sol_at_qpoints);     for (unsigned int q_index : agglo_values.quadrature_point_indices())   {   double solution_at_qpoint = 0.;   Tensor<1, dim> grad_solution_at_qpoint;   for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   solution_at_qpoint += solution(local_dof_indices[i]) *   agglo_values.shape_value(i, q_index);     if (compute_semi_H1)   grad_solution_at_qpoint +=   solution(local_dof_indices[i]) *   agglo_values.shape_grad(i, q_index);   } @endcode L2 @code   local_errors[0] += std::pow((analyical_sol_at_qpoints[q_index] -   solution_at_qpoint),   2) *   agglo_values.JxW(q_index);   @endcode H1 seminorm @code   if (compute_semi_H1)   for (unsigned int d = 0; d < dim; ++d)   local_errors[1] +=   std::pow((grad_analyical_sol_at_qpoints[q_index][d] -   grad_solution_at_qpoint[d]),   2) *   agglo_values.JxW(q_index);   }   }   }   @endcode Perform reduction and take sqrt of each error @code   global_errors[0] = Utilities::MPI::reduce<double>(   local_errors[0],   agglomeration_handler.get_triangulation().get_mpi_communicator(),   [](const double a, const double b) { return a + b; });     global_errors[0] = std::sqrt(global_errors[0]);     if (compute_semi_H1)   {   global_errors[1] = Utilities::MPI::reduce<double>(   local_errors[1],   agglomeration_handler.get_triangulation().get_mpi_communicator(),   [](const double a, const double b) { return a + b; });   global_errors[1] = std::sqrt(global_errors[1]);   }   }     /**   * Utility function that builds the multilevel hierarchy from the tree level   * @p starting_level. This function fills the vector of   * @p AgglomerationHandlers objects by distributing degrees of freedom on   * each level of the hierarchy. It returns the total number of levels in the   * hierarchy.   */   template <int dim>   unsigned int   construct_agglomerated_levels(   const Triangulation<dim> &tria,   std::vector<std::unique_ptr<AgglomerationHandler<dim>>>   &agglomeration_handlers,   const FE_DGQ<dim> &fe_dg,   const Mapping<dim> &mapping,   const unsigned int starting_tree_level)   {   const auto parallel_tria =   dynamic_cast<const parallel::TriangulationBase<dim> *>(&tria);     GridTools::Cache<dim> cached_tria(tria);   Assert(parallel_tria->n_active_cells() > 0, ExcInternalError());     const MPI_Comm comm = parallel_tria->get_mpi_communicator();   ConditionalOStream pcout(std::cout,   (Utilities::MPI::this_mpi_process(comm) == 0));   @endcode Start building R-tree @code   namespace bgi = boost::geometry::index;   static constexpr unsigned int max_elem_per_node =   constexpr_pow(2, dim); // 2^dim   std::vector<std::pair<BoundingBox<dim>,   typename Triangulation<dim>::active_cell_iterator>>   boxes(parallel_tria->n_locally_owned_active_cells());   unsigned int i = 0;   for (const auto &cell : parallel_tria->active_cell_iterators())   if (cell->is_locally_owned())   boxes[i++] = std::make_pair(mapping.get_bounding_box(cell), cell);     auto tree = pack_rtree<bgi::rstar<max_elem_per_node>>(boxes);   Assert(n_levels(tree) >= 2, ExcMessage("At least two levels are needed."));   pcout << "Total number of available levels: " << n_levels(tree)   << std::endl;     pcout << "Starting level: " << starting_tree_level << std::endl;   const unsigned int total_tree_levels =   n_levels(tree) - starting_tree_level + 1;   @endcode Resize the agglomeration handlers to the right size @code   agglomeration_handlers.resize(total_tree_levels); @endcode Loop through the available levels and set AgglomerationHandlers up. @code   for (unsigned int extraction_level = starting_tree_level;   extraction_level <= n_levels(tree);   ++extraction_level)   {   agglomeration_handlers[extraction_level - starting_tree_level] =   std::make_unique<AgglomerationHandler<dim>>(cached_tria);   CellsAgglomerator<dim, decltype(tree)> agglomerator{tree,   extraction_level};   const auto agglomerates = agglomerator.extract_agglomerates();   agglomeration_handlers[extraction_level - starting_tree_level]   ->connect_hierarchy(agglomerator);   @endcode Flag elements for agglomeration @code   unsigned int agglo_index = 0;   for (unsigned int i = 0; i < agglomerates.size(); ++i)   {   const auto &agglo = agglomerates[i]; // i-th agglomerate   for (const auto &el : agglo)   {   el->set_material_id(agglo_index);   }   ++agglo_index;   }     const unsigned int n_local_agglomerates = agglo_index;   unsigned int total_agglomerates =   Utilities::MPI::sum(n_local_agglomerates, comm);   pcout << "Total agglomerates per (tree) level: " << extraction_level   << ": " << total_agglomerates << std::endl;   @endcode Now, perform agglomeration within each locally owned partition @code   std::vector<   std::vector<typename Triangulation<dim>::active_cell_iterator>>   cells_per_subdomain(n_local_agglomerates);   for (const auto &cell : parallel_tria->active_cell_iterators())   if (cell->is_locally_owned())   cells_per_subdomain[cell->material_id()].push_back(cell);   @endcode For every subdomain, agglomerate elements together @code   for (std::size_t i = 0; i < cells_per_subdomain.size(); ++i)   agglomeration_handlers[extraction_level - starting_tree_level]   ->define_agglomerate(cells_per_subdomain[i]);     agglomeration_handlers[extraction_level - starting_tree_level]   ->initialize_fe_values(QGauss<dim>(fe_dg.degree + 1),   update_values | update_gradients |   update_JxW_values | update_quadrature_points,   QGauss<dim - 1>(fe_dg.degree + 1),   update_JxW_values);   agglomeration_handlers[extraction_level - starting_tree_level]   ->distribute_agglomerated_dofs(fe_dg);   }     return total_tree_levels;   }     /**   * Utility to compute jump terms when the interface is locally owned, i.e.   * both elements are locally owned.   */   template <int dim>   void   assemble_local_jumps_and_averages(FullMatrix<double> &M11,   FullMatrix<double> &M12,   FullMatrix<double> &M21,   FullMatrix<double> &M22,   const FEValuesBase<dim> &fe_faces0,   const FEValuesBase<dim> &fe_faces1,   const double penalty_constant,   const double h_f)   {   const std::vector<Tensor<1, dim>> &normals = fe_faces0.get_normal_vectors();   const unsigned int dofs_per_cell =   M11.m(); // size of local matrices equals the #DoFs   for (unsigned int q_index : fe_faces0.quadrature_point_indices())   {   const Tensor<1, dim> &normal = normals[q_index];   for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   for (unsigned int j = 0; j < dofs_per_cell; ++j)   {   M11(i, j) += (-0.5 * fe_faces0.shape_grad(i, q_index) * normal *   fe_faces0.shape_value(j, q_index) -   0.5 * fe_faces0.shape_grad(j, q_index) * normal *   fe_faces0.shape_value(i, q_index) +   (penalty_constant / h_f) *   fe_faces0.shape_value(i, q_index) *   fe_faces0.shape_value(j, q_index)) *   fe_faces0.JxW(q_index);   M12(i, j) += (0.5 * fe_faces0.shape_grad(i, q_index) * normal *   fe_faces1.shape_value(j, q_index) -   0.5 * fe_faces1.shape_grad(j, q_index) * normal *   fe_faces0.shape_value(i, q_index) -   (penalty_constant / h_f) *   fe_faces0.shape_value(i, q_index) *   fe_faces1.shape_value(j, q_index)) *   fe_faces1.JxW(q_index);   M21(i, j) += (-0.5 * fe_faces1.shape_grad(i, q_index) * normal *   fe_faces0.shape_value(j, q_index) +   0.5 * fe_faces0.shape_grad(j, q_index) * normal *   fe_faces1.shape_value(i, q_index) -   (penalty_constant / h_f) *   fe_faces1.shape_value(i, q_index) *   fe_faces0.shape_value(j, q_index)) *   fe_faces1.JxW(q_index);   M22(i, j) += (0.5 * fe_faces1.shape_grad(i, q_index) * normal *   fe_faces1.shape_value(j, q_index) +   0.5 * fe_faces1.shape_grad(j, q_index) * normal *   fe_faces1.shape_value(i, q_index) +   (penalty_constant / h_f) *   fe_faces1.shape_value(i, q_index) *   fe_faces1.shape_value(j, q_index)) *   fe_faces1.JxW(q_index);   }   }   }   }   /**   * Same as above, but for a ghosted neighbor.   */   template <int dim>   void   assemble_local_jumps_and_averages_ghost(   FullMatrix<double> &M11,   FullMatrix<double> &M12,   FullMatrix<double> &M21,   FullMatrix<double> &M22,   const FEValuesBase<dim> &fe_faces0,   const std::vector<std::vector<double>> &recv_values,   const std::vector<std::vector<Tensor<1, dim>>> &recv_gradients,   const std::vector<double> &recv_jxws,   const double penalty_constant,   const double h_f)   {   Assert(   (recv_values.size() > 0 && recv_gradients.size() && recv_jxws.size()),   ExcMessage("Not possible to assemble jumps and averages at a ghosted "   "interface."));   const unsigned int dofs_per_cell = M11.m();   const std::vector<Tensor<1, dim>> &normals = fe_faces0.get_normal_vectors();   for (unsigned int q_index : fe_faces0.quadrature_point_indices())   {   const Tensor<1, dim> &normal = normals[q_index];   for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   for (unsigned int j = 0; j < dofs_per_cell; ++j)   {   M11(i, j) += (-0.5 * fe_faces0.shape_grad(i, q_index) * normal *   fe_faces0.shape_value(j, q_index) -   0.5 * fe_faces0.shape_grad(j, q_index) * normal *   fe_faces0.shape_value(i, q_index) +   (penalty_constant / h_f) *   fe_faces0.shape_value(i, q_index) *   fe_faces0.shape_value(j, q_index)) *   fe_faces0.JxW(q_index);   M12(i, j) += (0.5 * fe_faces0.shape_grad(i, q_index) * normal *   recv_values[j][q_index] -   0.5 * recv_gradients[j][q_index] * normal *   fe_faces0.shape_value(i, q_index) -   (penalty_constant / h_f) *   fe_faces0.shape_value(i, q_index) *   recv_values[j][q_index]) *   recv_jxws[q_index];   M21(i, j) +=   (-0.5 * recv_gradients[i][q_index] * normal *   fe_faces0.shape_value(j, q_index) +   0.5 * fe_faces0.shape_grad(j, q_index) * normal *   recv_values[i][q_index] -   (penalty_constant / h_f) * recv_values[i][q_index] *   fe_faces0.shape_value(j, q_index)) *   recv_jxws[q_index];   M22(i, j) +=   (0.5 * recv_gradients[i][q_index] * normal *   recv_values[j][q_index] +   0.5 * recv_gradients[j][q_index] * normal *   recv_values[i][q_index] +   (penalty_constant / h_f) * recv_values[i][q_index] *   recv_values[j][q_index]) *   recv_jxws[q_index];   }   }   }   }     /**   * Utility function to assemble the SIPDG Laplace matrix.   * @note Supported matrix types are Trilinos types and native SparseMatrix   * objects provided by deal.II.   */   template <int dim, typename MatrixType>   void   assemble_dg_matrix(MatrixType &system_matrix,   const FiniteElement<dim> &fe_dg,   const AgglomerationHandler<dim> &ah)   {   static_assert(   (std::is_same_v<MatrixType, TrilinosWrappers::SparseMatrix> ||   std::is_same_v<MatrixType,   SparseMatrix<typename MatrixType::value_type>>));     Assert((dynamic_cast<const FE_DGQ<dim> *>(&fe_dg) ||   dynamic_cast<const FE_DGP<dim> *>(&fe_dg) ||   dynamic_cast<const FE_SimplexDGP<dim> *>(&fe_dg)),   ExcMessage("FE type not supported."));     AffineConstraints constraints;   constraints.close();   const double penalty_constant =   10 * (fe_dg.degree + dim) * (fe_dg.degree + 1);   TrilinosWrappers::SparsityPattern dsp;   const_cast<AgglomerationHandler<dim> &>(ah)   .create_agglomeration_sparsity_pattern(dsp);   system_matrix.reinit(dsp);   const unsigned int dofs_per_cell = fe_dg.n_dofs_per_cell();   FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);   FullMatrix<double> M11(dofs_per_cell, dofs_per_cell);   FullMatrix<double> M12(dofs_per_cell, dofs_per_cell);   FullMatrix<double> M21(dofs_per_cell, dofs_per_cell);   FullMatrix<double> M22(dofs_per_cell, dofs_per_cell);   std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);   std::vector<types::global_dof_index> local_dof_indices_neighbor(   dofs_per_cell);     for (const auto &polytope : ah.polytope_iterators())   {   if (polytope->is_locally_owned())   {   cell_matrix = 0.;   const auto &agglo_values = ah.reinit(polytope);   for (unsigned int q_index : agglo_values.quadrature_point_indices())   {   for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   for (unsigned int j = 0; j < dofs_per_cell; ++j)   {   cell_matrix(i, j) +=   agglo_values.shape_grad(i, q_index) *   agglo_values.shape_grad(j, q_index) *   agglo_values.JxW(q_index);   }   }   } @endcode get volumetric DoFs @code   polytope->get_dof_indices(local_dof_indices); @endcode Assemble face terms @code   unsigned int n_faces = polytope->n_faces();   const double h_f = polytope->diameter();   for (unsigned int f = 0; f < n_faces; ++f)   {   if (polytope->at_boundary(f))   { @endcode Get normal vectors seen from each agglomeration. @code   const auto &fe_face = ah.reinit(polytope, f);   const auto &normals = fe_face.get_normal_vectors();   for (unsigned int q_index :   fe_face.quadrature_point_indices())   {   const Tensor<1, dim> &normal = normals[q_index];   for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   for (unsigned int j = 0; j < dofs_per_cell; ++j)   {   cell_matrix(i, j) +=   (-fe_face.shape_value(i, q_index) *   fe_face.shape_grad(j, q_index) * normal -   fe_face.shape_grad(i, q_index) * normal *   fe_face.shape_value(j, q_index) +   (penalty_constant / h_f) *   fe_face.shape_value(i, q_index) *   fe_face.shape_value(j, q_index)) *   fe_face.JxW(q_index);   }   }   }   }   else   {   const auto &neigh_polytope = polytope->neighbor(f);   if (polytope->id() < neigh_polytope->id())   {   unsigned int nofn =   polytope->neighbor_of_agglomerated_neighbor(f);   Assert(neigh_polytope->neighbor(nofn)->id() ==   polytope->id(),   ExcMessage("Mismatch."));   const auto &fe_faces = ah.reinit_interface(   polytope, neigh_polytope, f, nofn);   const auto &fe_faces0 = fe_faces.first;   if (neigh_polytope->is_locally_owned())   { @endcode use both fevalues @code   const auto &fe_faces1 = fe_faces.second;   M11 = 0.;   M12 = 0.;   M21 = 0.;   M22 = 0.;   assemble_local_jumps_and_averages(M11,   M12,   M21,   M22,   fe_faces0,   fe_faces1,   penalty_constant,   h_f); @endcode distribute DoFs accordingly fluxes @code   neigh_polytope->get_dof_indices(   local_dof_indices_neighbor);   constraints.distribute_local_to_global(   M11, local_dof_indices, system_matrix);   constraints.distribute_local_to_global(   M12,   local_dof_indices,   local_dof_indices_neighbor,   system_matrix);   constraints.distribute_local_to_global(   M21,   local_dof_indices_neighbor,   local_dof_indices,   system_matrix);   constraints.distribute_local_to_global(   M22, local_dof_indices_neighbor, system_matrix);   }   else   { @endcode neigh polytope is ghosted, so retrieve necessary metadata. @code   types::subdomain_id neigh_rank =   neigh_polytope->subdomain_id();   const auto &recv_jxws =   ah.recv_jxws.at(neigh_rank)   .at({neigh_polytope->id(), nofn});   const auto &recv_values =   ah.recv_values.at(neigh_rank)   .at({neigh_polytope->id(), nofn});   const auto &recv_gradients =   ah.recv_gradients.at(neigh_rank)   .at({neigh_polytope->id(), nofn});   M11 = 0.;   M12 = 0.;   M21 = 0.;   M22 = 0.; @endcode there's no FEFaceValues on the other side (it's ghosted), so we just pass the actual data we have recevied from the neighboring ghosted polytope @code   assemble_local_jumps_and_averages_ghost(   M11,   M12,   M21,   M22,   fe_faces0,   recv_values,   recv_gradients,   recv_jxws,   penalty_constant,   h_f); @endcode distribute DoFs accordingly fluxes @code   neigh_polytope->get_dof_indices(   local_dof_indices_neighbor);   constraints.distribute_local_to_global(   M11, local_dof_indices, system_matrix);   constraints.distribute_local_to_global(   M12,   local_dof_indices,   local_dof_indices_neighbor,   system_matrix);   constraints.distribute_local_to_global(   M21,   local_dof_indices_neighbor,   local_dof_indices,   system_matrix);   constraints.distribute_local_to_global(   M22, local_dof_indices_neighbor, system_matrix);   } // ghosted polytope case   } // only once   } // internal face   } // face loop   constraints.distribute_local_to_global(cell_matrix,   local_dof_indices,   system_matrix);   } // locally owned polytopes   }   system_matrix.compress(VectorOperation::add);   }     /**   * Compute SIPDG matrix as well as rhs vector.   * @note Hardcoded for f=1 and simplex elements.   * TODO: Pass Function object for boundary conditions and forcing term.   */   template <int dim, typename MatrixType, typename VectorType>   void   assemble_dg_matrix_on_standard_mesh(MatrixType &system_matrix,   VectorType &system_rhs,   const Mapping<dim> &mapping,   const FiniteElement<dim> &fe_dg,   const DoFHandler<dim> &dof_handler)   {   static_assert(   (std::is_same_v<MatrixType, TrilinosWrappers::SparseMatrix> ||   std::is_same_v<MatrixType,   SparseMatrix<typename MatrixType::value_type>>));     Assert((dynamic_cast<const FE_SimplexDGP<dim> *>(&fe_dg) != nullptr),   ExcNotImplemented(   "Implemented only for simplex meshes for the time being."));     Assert(dof_handler.get_triangulation().all_reference_cells_are_simplex(),   ExcNotImplemented());     const double penalty_constant = .5 * fe_dg.degree * (fe_dg.degree + 1);   AffineConstraints<typename MatrixType::value_type> constraints;   constraints.close();     const IndexSet &locally_owned_dofs = dof_handler.locally_owned_dofs();   const IndexSet locally_relevant_dofs =   DoFTools::extract_locally_relevant_dofs(dof_handler);     DynamicSparsityPattern dsp(locally_relevant_dofs);   DoFTools::make_flux_sparsity_pattern(dof_handler, dsp);   SparsityTools::distribute_sparsity_pattern(dsp,   dof_handler.locally_owned_dofs(),   dof_handler.get_communicator(),   locally_relevant_dofs);     system_matrix.reinit(locally_owned_dofs,   locally_owned_dofs,   dsp,   dof_handler.get_communicator());     system_rhs.reinit(locally_owned_dofs, dof_handler.get_communicator());     const unsigned int quadrature_degree = fe_dg.degree + 1;   FEFaceValues<dim> fe_faces0(mapping,   fe_dg,   QGaussSimplex<dim - 1>(quadrature_degree),   update_values | update_JxW_values |   update_gradients | update_quadrature_points |   update_normal_vectors);     FEValues<dim> fe_values(mapping,   fe_dg,   QGaussSimplex<dim>(quadrature_degree),   update_values | update_JxW_values |   update_gradients | update_quadrature_points);     FEFaceValues<dim> fe_faces1(mapping,   fe_dg,   QGaussSimplex<dim - 1>(quadrature_degree),   update_values | update_JxW_values |   update_gradients | update_quadrature_points |   update_normal_vectors);   const unsigned int dofs_per_cell = fe_dg.n_dofs_per_cell();     FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);   Vector<double> cell_rhs(dofs_per_cell);     FullMatrix<double> M11(dofs_per_cell, dofs_per_cell);   FullMatrix<double> M12(dofs_per_cell, dofs_per_cell);   FullMatrix<double> M21(dofs_per_cell, dofs_per_cell);   FullMatrix<double> M22(dofs_per_cell, dofs_per_cell);     std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);   @endcode Loop over standard deal.II cells @code   for (const auto &cell : dof_handler.active_cell_iterators())   {   if (cell->is_locally_owned())   {   cell_matrix = 0.;   cell_rhs = 0.;     fe_values.reinit(cell);   @endcode const auto &q_points = fe_values.get_quadrature_points(); const unsigned int n_qpoints = q_points.size(); @code   for (unsigned int q_index : fe_values.quadrature_point_indices())   {   for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   for (unsigned int j = 0; j < dofs_per_cell; ++j)   {   cell_matrix(i, j) += fe_values.shape_grad(i, q_index) *   fe_values.shape_grad(j, q_index) *   fe_values.JxW(q_index);   }   cell_rhs(i) +=   fe_values.shape_value(i, q_index) * 1. *   fe_values.JxW(q_index); // TODO: pass functional   }   }   @endcode distribute volumetric DoFs @code   cell->get_dof_indices(local_dof_indices);   double hf = 0.;   for (const auto f : cell->face_indices())   {   const double extent1 =   cell->measure() / cell->face(f)->measure();     if (cell->face(f)->at_boundary())   {   hf = (1. / extent1 + 1. / extent1);   fe_faces0.reinit(cell, f);     const auto &normals = fe_faces0.get_normal_vectors();   for (unsigned int q_index :   fe_faces0.quadrature_point_indices())   {   for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   for (unsigned int j = 0; j < dofs_per_cell; ++j)   {   cell_matrix(i, j) +=   (-fe_faces0.shape_value(i, q_index) *   fe_faces0.shape_grad(j, q_index) *   normals[q_index] -   fe_faces0.shape_grad(i, q_index) *   normals[q_index] *   fe_faces0.shape_value(j, q_index) +   (penalty_constant * hf) *   fe_faces0.shape_value(i, q_index) *   fe_faces0.shape_value(j, q_index)) *   fe_faces0.JxW(q_index);   }   cell_rhs(i) +=   0.; // TODO: add bdary conditions functional   }   }   }   else   {   const auto &neigh_cell = cell->neighbor(f);   if (cell->global_active_cell_index() <   neigh_cell->global_active_cell_index())   {   const double extent2 =   neigh_cell->measure() /   neigh_cell->face(cell->neighbor_of_neighbor(f))   ->measure();   hf = (1. / extent1 + 1. / extent2);   fe_faces0.reinit(cell, f);   fe_faces1.reinit(neigh_cell,   cell->neighbor_of_neighbor(f));     std::vector<types::global_dof_index>   local_dof_indices_neighbor(dofs_per_cell);     M11 = 0.;   M12 = 0.;   M21 = 0.;   M22 = 0.;     const auto &normals = fe_faces0.get_normal_vectors(); @endcode M11 @code   for (unsigned int q_index :   fe_faces0.quadrature_point_indices())   {   for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   for (unsigned int j = 0; j < dofs_per_cell; ++j)   {   M11(i, j) +=   (-0.5 * fe_faces0.shape_grad(i, q_index) *   normals[q_index] *   fe_faces0.shape_value(j, q_index) -   0.5 * fe_faces0.shape_grad(j, q_index) *   normals[q_index] *   fe_faces0.shape_value(i, q_index) +   (penalty_constant * hf) *   fe_faces0.shape_value(i, q_index) *   fe_faces0.shape_value(j, q_index)) *   fe_faces0.JxW(q_index);     M12(i, j) +=   (0.5 * fe_faces0.shape_grad(i, q_index) *   normals[q_index] *   fe_faces1.shape_value(j, q_index) -   0.5 * fe_faces1.shape_grad(j, q_index) *   normals[q_index] *   fe_faces0.shape_value(i, q_index) -   (penalty_constant * hf) *   fe_faces0.shape_value(i, q_index) *   fe_faces1.shape_value(j, q_index)) *   fe_faces1.JxW(q_index);   @endcode A10 @code   M21(i, j) +=   (-0.5 * fe_faces1.shape_grad(i, q_index) *   normals[q_index] *   fe_faces0.shape_value(j, q_index) +   0.5 * fe_faces0.shape_grad(j, q_index) *   normals[q_index] *   fe_faces1.shape_value(i, q_index) -   (penalty_constant * hf) *   fe_faces1.shape_value(i, q_index) *   fe_faces0.shape_value(j, q_index)) *   fe_faces1.JxW(q_index);   @endcode A11 @code   M22(i, j) +=   (0.5 * fe_faces1.shape_grad(i, q_index) *   normals[q_index] *   fe_faces1.shape_value(j, q_index) +   0.5 * fe_faces1.shape_grad(j, q_index) *   normals[q_index] *   fe_faces1.shape_value(i, q_index) +   (penalty_constant * hf) *   fe_faces1.shape_value(i, q_index) *   fe_faces1.shape_value(j, q_index)) *   fe_faces1.JxW(q_index);   }   }   }   @endcode distribute DoFs accordingly @code   neigh_cell->get_dof_indices(local_dof_indices_neighbor);     constraints.distribute_local_to_global(   M11, local_dof_indices, system_matrix);   constraints.distribute_local_to_global(   M12,   local_dof_indices,   local_dof_indices_neighbor,   system_matrix);   constraints.distribute_local_to_global(   M21,   local_dof_indices_neighbor,   local_dof_indices,   system_matrix);   constraints.distribute_local_to_global(   M22, local_dof_indices_neighbor, system_matrix);     } // check idx neighbors   } // over faces   }   constraints.distribute_local_to_global(cell_matrix,   cell_rhs,   local_dof_indices,   system_matrix,   system_rhs);   }   }   system_matrix.compress(VectorOperation::add);   system_rhs.compress(VectorOperation::add);   }     } // namespace ::PolyUtils     #endif @endcode <a name="ann-source/agglomeration_handler.cc"></a> <h1>Annotated version of source/agglomeration_handler.cc</h1> @code   /* -----------------------------------------------------------------------------   *   * SPDX-License-Identifier: LGPL-2.1-or-later   * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,   * Andrea Cangiani   *   * This file is part of the deal.II code gallery.   *   * -----------------------------------------------------------------------------   */     #include <deal.II/base/quadrature_lib.h>   #include <deal.II/lac/sparsity_tools.h>     #include <agglomeration_handler.h>     template <int dim, int spacedim>   AgglomerationHandler<dim, spacedim>::AgglomerationHandler(   const GridTools::Cache<dim, spacedim> &cache_tria)   : cached_tria(std::make_unique<GridTools::Cache<dim, spacedim>>(   cache_tria.get_triangulation(),   cache_tria.get_mapping()))   , communicator(cache_tria.get_triangulation().get_mpi_communicator())   {   Assert(dim == spacedim, ExcNotImplemented("Not available with codim > 0"));   Assert(dim == 2 || dim == 3, ExcImpossibleInDim(1));   Assert((dynamic_cast<const parallel::shared::Triangulation<dim, spacedim> *>(   &cached_tria->get_triangulation()) == nullptr),   ExcNotImplemented());   Assert(cached_tria->get_triangulation().n_active_cells() > 0,   ExcMessage(   "The triangulation must not be empty upon calling this function."));     n_agglomerations = 0;   hybrid_mesh = false;   initialize_agglomeration_data(cached_tria);   }         template <int dim, int spacedim>   typename AgglomerationHandler<dim, spacedim>::agglomeration_iterator   AgglomerationHandler<dim, spacedim>::define_agglomerate(   const AgglomerationContainer &cells)   {   Assert(cells.size() > 0, ExcMessage("No cells to be agglomerated."));     if (cells.size() == 1)   hybrid_mesh = true; // mesh is made also by classical cells   @endcode First index drives the selection of the master cell. After that, store the master cell. @code   const types::global_cell_index global_master_idx =   cells[0]->global_active_cell_index();   const types::global_cell_index master_idx = cells[0]->active_cell_index();   master_cells_container.push_back(cells[0]);   master_slave_relationships[global_master_idx] = -1;     const typename DoFHandler<dim>::active_cell_iterator cell_dh =   cells[0]->as_dof_handler_iterator(agglo_dh);   cell_dh->set_active_fe_index(CellAgglomerationType::master);   @endcode Store slave cells and save the relationship with the parent @code   std::vector<typename Triangulation<dim, spacedim>::active_cell_iterator>   slaves;   slaves.reserve(cells.size() - 1); @endcode exclude first cell since it's the master cell @code   for (auto it = ++cells.begin(); it != cells.end(); ++it)   {   slaves.push_back(*it);   master_slave_relationships[(*it)->global_active_cell_index()] =   global_master_idx; // mark each slave   master_slave_relationships_iterators[(*it)->active_cell_index()] =   cells[0];     const typename DoFHandler<dim>::active_cell_iterator cell =   (*it)->as_dof_handler_iterator(agglo_dh);   cell->set_active_fe_index(CellAgglomerationType::slave); // slave cell   @endcode If we have a p::d::T, check that all cells are in the same subdomain. If serial, just check that the subdomain_id is invalid. @code   Assert(((*it)->subdomain_id() == tria->locally_owned_subdomain() ||   tria->locally_owned_subdomain() == numbers::invalid_subdomain_id),   ExcInternalError());   }     master_slave_relationships_iterators[master_idx] =   cells[0]; // set iterator to master cell   @endcode Store the slaves of each master @code   master2slaves[master_idx] = slaves; @endcode Save to which polygon this agglomerate correspond @code   master2polygon[master_idx] = n_agglomerations;     ++n_agglomerations; // an agglomeration has been performed, record it     create_bounding_box(cells); // fill the vector of bboxes   @endcode Finally, return a polygonal iterator to the polytope just constructed. @code   return {cells[0], this};   }     template <int dim, int spacedim>   typename AgglomerationHandler<dim, spacedim>::agglomeration_iterator   AgglomerationHandler<dim, spacedim>::define_agglomerate(   const AgglomerationContainer &cells,   const unsigned int fecollection_size)   {   Assert(cells.size() > 0, ExcMessage("No cells to be agglomerated."));     if (cells.size() == 1)   hybrid_mesh = true; // mesh is made also by classical cells   @endcode First index drives the selection of the master cell. After that, store the master cell. @code   const types::global_cell_index global_master_idx =   cells[0]->global_active_cell_index();   const types::global_cell_index master_idx = cells[0]->active_cell_index();   master_cells_container.push_back(cells[0]);   master_slave_relationships[global_master_idx] = -1;     const typename DoFHandler<dim>::active_cell_iterator cell_dh =   cells[0]->as_dof_handler_iterator(agglo_dh);   cell_dh->set_active_fe_index(CellAgglomerationType::master);   @endcode Store slave cells and save the relationship with the parent @code   std::vector<typename Triangulation<dim, spacedim>::active_cell_iterator>   slaves;   slaves.reserve(cells.size() - 1); @endcode exclude first cell since it's the master cell @code   for (auto it = ++cells.begin(); it != cells.end(); ++it)   {   slaves.push_back(*it);   master_slave_relationships[(*it)->global_active_cell_index()] =   global_master_idx; // mark each slave   master_slave_relationships_iterators[(*it)->active_cell_index()] =   cells[0];     const typename DoFHandler<dim>::active_cell_iterator cell =   (*it)->as_dof_handler_iterator(agglo_dh);   cell->set_active_fe_index(   fecollection_size); // slave cell (the last index)   @endcode If we have a p::d::T, check that all cells are in the same subdomain. If serial, just check that the subdomain_id is invalid. @code   Assert(((*it)->subdomain_id() == tria->locally_owned_subdomain() ||   tria->locally_owned_subdomain() == numbers::invalid_subdomain_id),   ExcInternalError());   }     master_slave_relationships_iterators[master_idx] =   cells[0]; // set iterator to master cell   @endcode Store the slaves of each master @code   master2slaves[master_idx] = slaves; @endcode Save to which polygon this agglomerate correspond @code   master2polygon[master_idx] = n_agglomerations;     ++n_agglomerations; // an agglomeration has been performed, record it     create_bounding_box(cells); // fill the vector of bboxes   @endcode Finally, return a polygonal iterator to the polytope just constructed. @code   return {cells[0], this};   }       template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::initialize_fe_values(   const Quadrature<dim> &cell_quadrature,   const UpdateFlags &flags,   const Quadrature<dim - 1> &face_quadrature,   const UpdateFlags &face_flags)   {   agglomeration_quad = cell_quadrature;   agglomeration_flags = flags;   agglomeration_face_quad = face_quadrature;   agglomeration_face_flags = face_flags | internal_agglomeration_face_flags;       no_values =   std::make_unique<FEValues<dim>>(*mapping,   dummy_fe,   agglomeration_quad,   update_quadrature_points |   update_JxW_values); // only for quadrature   no_face_values = std::make_unique<FEFaceValues<dim>>(   *mapping,   dummy_fe,   agglomeration_face_quad,   update_quadrature_points | update_JxW_values |   update_normal_vectors); // only for quadrature   }     template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::initialize_fe_values(   const hp::QCollection<dim> &cell_qcollection,   const UpdateFlags &flags,   const hp::QCollection<dim - 1> &face_qcollection,   const UpdateFlags &face_flags)   {   agglomeration_quad_collection = cell_qcollection;   agglomeration_flags = flags;   agglomeration_face_quad_collection = face_qcollection;   agglomeration_face_flags = face_flags | internal_agglomeration_face_flags;     mapping_collection = hp::MappingCollection<dim>(*mapping);   dummy_fe_collection = hp::FECollection<dim, spacedim>(dummy_fe);   hp_no_values = std::make_unique<hp::FEValues<dim>>(   mapping_collection,   dummy_fe_collection,   agglomeration_quad_collection,   update_quadrature_points | update_JxW_values); // only for quadrature     hp_no_face_values = std::make_unique<hp::FEFaceValues<dim>>(   mapping_collection,   dummy_fe_collection,   agglomeration_face_quad_collection,   update_quadrature_points | update_JxW_values |   update_normal_vectors); // only for quadrature   }         template <int dim, int spacedim>   unsigned int   AgglomerationHandler<dim, spacedim>::n_agglomerated_faces_per_cell(   const typename Triangulation<dim, spacedim>::active_cell_iterator &cell) const   {   unsigned int n_neighbors = 0;   for (const auto &f : cell->face_indices())   {   const auto &neighboring_cell = cell->neighbor(f);   if ((cell->face(f)->at_boundary()) ||   (neighboring_cell->is_active() &&   !are_cells_agglomerated(cell, neighboring_cell)))   {   ++n_neighbors;   }   }   return n_neighbors;   }         template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::initialize_agglomeration_data(   const std::unique_ptr<GridTools::Cache<dim, spacedim>> &cache_tria)   {   tria = &(cache_tria->get_triangulation());   mapping = &(cache_tria->get_mapping());     agglo_dh.reinit(*tria);     if (const auto parallel_tria = dynamic_cast<   const ::parallel::TriangulationBase<dim, spacedim> *>(&*tria))   {   const std::weak_ptr<const Utilities::MPI::Partitioner> cells_partitioner =   parallel_tria->global_active_cell_index_partitioner();   master_slave_relationships.reinit(   cells_partitioner.lock()->locally_owned_range(), communicator);   }   else   {   master_slave_relationships.reinit(tria->n_active_cells(), MPI_COMM_SELF);   }     polytope_cache.clear();   bboxes.clear();   @endcode First, update the pointer @code   cached_tria = std::make_unique<GridTools::Cache<dim, spacedim>>(   cache_tria->get_triangulation(), cache_tria->get_mapping());     connect_to_tria_signals();   n_agglomerations = 0;   }         template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::distribute_agglomerated_dofs(   const FiniteElement<dim> &fe_space)   {   if (dynamic_cast<const FE_DGQ<dim> *>(&fe_space))   fe = std::make_unique<FE_DGQ<dim>>(fe_space.degree);   else if (dynamic_cast<const FE_SimplexDGP<dim> *>(&fe_space))   fe = std::make_unique<FE_SimplexDGP<dim>>(fe_space.degree);   else   AssertThrow(   false,   ExcNotImplemented(   "Currently, this interface supports only DGQ and DGP bases."));     box_mapping = std::make_unique<MappingBox<dim>>(   bboxes,   master2polygon); // construct bounding box mapping     if (hybrid_mesh)   { @endcode the mesh is composed by standard and agglomerate cells. initialize classes needed for standard cells in order to treat that finite element space as defined on a standard shape and not on the BoundingBox. @code   standard_scratch =   std::make_unique<ScratchData>(*mapping,   *fe,   QGauss<dim>(2 * fe_space.degree + 2),   internal_agglomeration_flags);   }       fe_collection.push_back(*fe); // master   fe_collection.push_back(   FE_Nothing<dim, spacedim>(fe->reference_cell())); // slave     initialize_hp_structure();   @endcode in case the tria is distributed, communicate ghost information with neighboring ranks @code   const bool needs_ghost_info =   dynamic_cast<const parallel::TriangulationBase<dim, spacedim> *>(&*tria) !=   nullptr;   if (needs_ghost_info)   setup_ghost_polytopes();     setup_connectivity_of_agglomeration();     if (needs_ghost_info)   exchange_interface_values();   }     template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::distribute_agglomerated_dofs(   const hp::FECollection<dim, spacedim> &fe_collection_in)   {   is_hp_collection = true;     hp_fe_collection = std::make_unique<hp::FECollection<dim, spacedim>>(   fe_collection_in); // copy the input collection     box_mapping = std::make_unique<MappingBox<dim>>(   bboxes,   master2polygon); // construct bounding box mapping       if (hybrid_mesh)   {   AssertThrow(false,   ExcNotImplemented(   "Hybrid mesh is not implemented for hp::FECollection."));   }     for (unsigned int i = 0; i < fe_collection_in.size(); ++i)   {   if (dynamic_cast<const FESystem<dim> *>(&fe_collection_in[i]))   { @endcode System case @code   for (unsigned int b = 0; b < fe_collection_in[i].n_base_elements();   ++b)   {   if (!(dynamic_cast<const FE_DGQ<dim> *>(   &fe_collection_in[i].base_element(b)) ||   dynamic_cast<const FE_SimplexDGP<dim> *>(   &fe_collection_in[i].base_element(b)) ||   dynamic_cast<const FE_Nothing<dim> *>(   &fe_collection_in[i].base_element(b))))   AssertThrow(   false,   ExcNotImplemented(   "Currently, this interface supports only DGQ and DGP bases."));   }   }   else   { @endcode Scalar case @code   if (!(dynamic_cast<const FE_DGQ<dim> *>(&fe_collection_in[i]) ||   dynamic_cast<const FE_SimplexDGP<dim> *>(&fe_collection_in[i])))   AssertThrow(   false,   ExcNotImplemented(   "Currently, this interface supports only DGQ and DGP bases."));   }   fe_collection.push_back(fe_collection_in[i]);   }     Assert(fe_collection[0].n_components() >= 1,   ExcMessage("Invalid FE: must have at least one component."));   if (fe_collection[0].n_components() == 1)   {   fe_collection.push_back(FE_Nothing<dim, spacedim>());   }   else if (fe_collection[0].n_components() > 1)   {   std::vector<const FiniteElement<dim, spacedim> *> base_elements;   std::vector<unsigned int> multiplicities;   for (unsigned int b = 0; b < fe_collection[0].n_base_elements(); ++b)   {   base_elements.push_back(new FE_Nothing<dim, spacedim>());   multiplicities.push_back(fe_collection[0].element_multiplicity(b));   }   FESystem<dim, spacedim> fe_system_nothing(base_elements, multiplicities);   for (const auto *ptr : base_elements)   delete ptr;   fe_collection.push_back(fe_system_nothing);   }     initialize_hp_structure();   @endcode in case the tria is distributed, communicate ghost information with neighboring ranks @code   const bool needs_ghost_info =   dynamic_cast<const parallel::TriangulationBase<dim, spacedim> *>(&*tria) !=   nullptr;   if (needs_ghost_info)   setup_ghost_polytopes();     setup_connectivity_of_agglomeration();     if (needs_ghost_info)   exchange_interface_values();   }     template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::create_bounding_box(   const AgglomerationContainer &polytope)   {   Assert(n_agglomerations > 0,   ExcMessage("No agglomeration has been performed."));   Assert(dim > 1, ExcNotImplemented());     std::vector<Point<spacedim>> pts; // store all the vertices   for (const auto &cell : polytope)   for (const auto i : cell->vertex_indices())   pts.push_back(cell->vertex(i));     bboxes.emplace_back(pts);   }         template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::setup_connectivity_of_agglomeration()   {   Assert(master_cells_container.size() > 0,   ExcMessage("No agglomeration has been performed."));   Assert(   agglo_dh.n_dofs() > 0,   ExcMessage(   "The DoFHandler associated to the agglomeration has not been initialized."   "It's likely that you forgot to distribute the DoFs. You may want"   "to check if a call to `initialize_hp_structure()` has been done."));     number_of_agglomerated_faces.resize(master2polygon.size(), 0);   for (const auto &cell : master_cells_container)   {   internal::AgglomerationHandlerImplementation<dim, spacedim>::   setup_master_neighbor_connectivity(cell, *this);   }     if (Utilities::MPI::job_supports_mpi())   { @endcode communicate the number of faces @code   recv_n_faces = Utilities::MPI::some_to_some(communicator, local_n_faces);   @endcode send information about boundaries and neighboring polytopes id @code   recv_bdary_info =   Utilities::MPI::some_to_some(communicator, local_bdary_info);     recv_ghosted_master_id =   Utilities::MPI::some_to_some(communicator, local_ghosted_master_id);   }   }         template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::exchange_interface_values()   {   const unsigned int dofs_per_cell = fe->dofs_per_cell;   for (const auto &polytope : polytope_iterators())   {   if (polytope->is_locally_owned())   {   const unsigned int n_faces = polytope->n_faces();   for (unsigned int f = 0; f < n_faces; ++f)   {   if (!polytope->at_boundary(f))   {   const auto &neigh_polytope = polytope->neighbor(f);   if (!neigh_polytope->is_locally_owned())   { @endcode Neighboring polytope is ghosted. Compute shape functions at the interface @code   const auto &current_fe = reinit(polytope, f);     std::vector<Point<spacedim>> qpoints_to_send =   current_fe.get_quadrature_points();     const std::vector<double> &jxws_to_send =   current_fe.get_JxW_values();     const std::vector<Tensor<1, spacedim>> &normals_to_send =   current_fe.get_normal_vectors();       const types::subdomain_id neigh_rank =   neigh_polytope->subdomain_id();     std::pair<CellId, unsigned int> cell_and_face{   polytope->id(), f}; @endcode Prepare data to send @code   local_qpoints[neigh_rank].emplace(cell_and_face,   qpoints_to_send);     local_jxws[neigh_rank].emplace(cell_and_face,   jxws_to_send);     local_normals[neigh_rank].emplace(cell_and_face,   normals_to_send);       const unsigned int n_qpoints = qpoints_to_send.size();   @endcode TODO: check <tt>agglomeration_flags</tt> before computing values and gradients. @code   std::vector<std::vector<double>> values_per_qpoints(   dofs_per_cell);     std::vector<std::vector<Tensor<1, spacedim>>>   gradients_per_qpoints(dofs_per_cell);     for (unsigned int i = 0; i < dofs_per_cell; ++i)   {   values_per_qpoints[i].resize(n_qpoints);   gradients_per_qpoints[i].resize(n_qpoints);   for (unsigned int q = 0; q < n_qpoints; ++q)   {   values_per_qpoints[i][q] =   current_fe.shape_value(i, q);   gradients_per_qpoints[i][q] =   current_fe.shape_grad(i, q);   }   }     local_values[neigh_rank].emplace(cell_and_face,   values_per_qpoints);   local_gradients[neigh_rank].emplace(   cell_and_face, gradients_per_qpoints);   }   }   }   }   }   @endcode Finally, exchange with neighboring ranks @code   recv_qpoints = Utilities::MPI::some_to_some(communicator, local_qpoints);   recv_jxws = Utilities::MPI::some_to_some(communicator, local_jxws);   recv_normals = Utilities::MPI::some_to_some(communicator, local_normals);   recv_values = Utilities::MPI::some_to_some(communicator, local_values);   recv_gradients = Utilities::MPI::some_to_some(communicator, local_gradients);   }         template <int dim, int spacedim>   Quadrature<dim>   AgglomerationHandler<dim, spacedim>::agglomerated_quadrature(   const typename AgglomerationHandler<dim, spacedim>::AgglomerationContainer   &cells,   const typename Triangulation<dim, spacedim>::active_cell_iterator   &master_cell) const   {   Assert(is_master_cell(master_cell),   ExcMessage("This must be a master cell."));     std::vector<Point<dim>> vec_pts;   std::vector<double> vec_JxWs;     if (!is_hp_collection)   { @endcode Original version: handle case without hp::FECollection @code   for (const auto &dummy_cell : cells)   {   no_values->reinit(dummy_cell);   auto q_points = no_values->get_quadrature_points(); // real qpoints   const auto &JxWs = no_values->get_JxW_values();     std::transform(q_points.begin(),   q_points.end(),   std::back_inserter(vec_pts),   [&](const Point<spacedim> &p) { return p; });   std::transform(JxWs.begin(),   JxWs.end(),   std::back_inserter(vec_JxWs),   [&](const double w) { return w; });   }   }   else   { @endcode Handle the hp::FECollection case @code   const auto &master_cell_as_dh_iterator =   master_cell->as_dof_handler_iterator(agglo_dh);   for (const auto &dummy_cell : cells)   { @endcode The following verbose call is necessary to handle cases where different slave cells on different polytopes use different quadrature rules. If the hp::QCollection contains multiple elements, calling hp_no_values->reinit(dummy_cell) won't work because it cannot infer the correct quadrature rule. By explicitly passing the active FE index as q_index, and setting mapping_index and fe_index to 0, we ensure that the dummy cell uses the same quadrature rule as its corresponding master cell. This assumes a one-to-one correspondence between hp::QCollection and hp::FECollection, which is the convention in deal.II. However, this implementation does not support cases where hp::QCollection and hp::FECollection have different sizes. TODO: Refactor the architecture to better handle numerical integration for hp::QCollection. @code   hp_no_values->reinit(dummy_cell,   master_cell_as_dh_iterator->active_fe_index(),   0,   0);   auto q_points = hp_no_values->get_present_fe_values()   .get_quadrature_points(); // real qpoints   const auto &JxWs =   hp_no_values->get_present_fe_values().get_JxW_values();     std::transform(q_points.begin(),   q_points.end(),   std::back_inserter(vec_pts),   [&](const Point<spacedim> &p) { return p; });   std::transform(JxWs.begin(),   JxWs.end(),   std::back_inserter(vec_JxWs),   [&](const double w) { return w; });   }   }   @endcode Map back each point in real space by using the map associated to the bounding box. @code   std::vector<Point<dim>> unit_points(vec_pts.size());   const auto &bbox =   bboxes[master2polygon.at(master_cell->active_cell_index())];   unit_points.reserve(vec_pts.size());     for (unsigned int i = 0; i < vec_pts.size(); i++)   unit_points[i] = bbox.real_to_unit(vec_pts[i]);     return Quadrature<dim>(unit_points, vec_JxWs);   }         template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::initialize_hp_structure()   {   Assert(agglo_dh.get_triangulation().n_cells() > 0,   ExcMessage(   "Triangulation must not be empty upon calling this function."));   Assert(n_agglomerations > 0,   ExcMessage("No agglomeration has been performed."));     agglo_dh.distribute_dofs(fe_collection); @endcode euler_mapping = std::make_unique< MappingFEField<dim, spacedim, LinearAlgebra::distributed::Vector<double>>>( euler_dh, euler_vector); @code   }         template <int dim, int spacedim>   const FEValues<dim, spacedim> &   AgglomerationHandler<dim, spacedim>::reinit(   const AgglomerationIterator<dim, spacedim> &polytope) const   { @endcode Assert(euler_mapping, ExcMessage("The mapping describing the physical element stemming from " "agglomeration has not been set up.")); @code   const auto &deal_cell = polytope->as_dof_handler_iterator(agglo_dh);   @endcode First check if the polytope is made just by a single cell. If so, use classical FEValues if (polytope->n_background_cells() == 1) return standard_scratch->reinit(deal_cell); @code   const auto &agglo_cells = polytope->get_agglomerate();     Quadrature<dim> agglo_quad = agglomerated_quadrature(agglo_cells, deal_cell);     if (!is_hp_collection)   { @endcode Original version: handle case without hp::FECollection @code   agglomerated_scratch = std::make_unique<ScratchData>(*box_mapping,   fe_collection[0],   agglo_quad,   agglomeration_flags);   }   else   { @endcode Handle the hp::FECollection case @code   agglomerated_scratch = std::make_unique<ScratchData>(*box_mapping,   polytope->get_fe(),   agglo_quad,   agglomeration_flags);   }   return agglomerated_scratch->reinit(deal_cell);   }         template <int dim, int spacedim>   const FEValuesBase<dim, spacedim> &   AgglomerationHandler<dim, spacedim>::reinit_master(   const typename DoFHandler<dim, spacedim>::active_cell_iterator &cell,   const unsigned int face_index,   std::unique_ptr<NonMatching::FEImmersedSurfaceValues<spacedim>>   &agglo_isv_ptr) const   {   return internal::AgglomerationHandlerImplementation<dim, spacedim>::   reinit_master(cell, face_index, agglo_isv_ptr, *this);   }         template <int dim, int spacedim>   const FEValuesBase<dim, spacedim> &   AgglomerationHandler<dim, spacedim>::reinit(   const AgglomerationIterator<dim, spacedim> &polytope,   const unsigned int face_index) const   { @endcode Assert(euler_mapping, ExcMessage("The mapping describing the physical element stemming from " "agglomeration has not been set up.")); @code   const auto &deal_cell = polytope->as_dof_handler_iterator(agglo_dh);   Assert(is_master_cell(deal_cell), ExcMessage("This should be true."));     return internal::AgglomerationHandlerImplementation<dim, spacedim>::   reinit_master(deal_cell, face_index, agglomerated_isv_bdary, *this);   }         template <int dim, int spacedim>   std::pair<const FEValuesBase<dim, spacedim> &,   const FEValuesBase<dim, spacedim> &>   AgglomerationHandler<dim, spacedim>::reinit_interface(   const AgglomerationIterator<dim, spacedim> &polytope_in,   const AgglomerationIterator<dim, spacedim> &neigh_polytope,   const unsigned int local_in,   const unsigned int local_neigh) const   { @endcode If current and neighboring polytopes are both locally owned, then compute the jump in the classical way without needing information about ghosted entities. @code   if (polytope_in->is_locally_owned() && neigh_polytope->is_locally_owned())   {   const auto &cell_in = polytope_in->as_dof_handler_iterator(agglo_dh);   const auto &neigh_cell =   neigh_polytope->as_dof_handler_iterator(agglo_dh);     const auto &fe_in =   internal::AgglomerationHandlerImplementation<dim, spacedim>::   reinit_master(cell_in, local_in, agglomerated_isv, *this);   const auto &fe_out =   internal::AgglomerationHandlerImplementation<dim, spacedim>::   reinit_master(neigh_cell, local_neigh, agglomerated_isv_neigh, *this);   std::pair<const FEValuesBase<dim, spacedim> &,   const FEValuesBase<dim, spacedim> &>   my_p(fe_in, fe_out);     return my_p;   }   else   {   Assert((polytope_in->is_locally_owned() &&   !neigh_polytope->is_locally_owned()),   ExcInternalError());     const auto &cell = polytope_in->as_dof_handler_iterator(agglo_dh);   const auto &bbox = bboxes[master2polygon.at(cell->active_cell_index())]; @endcode const double bbox_measure = bbox.volume(); @code   const unsigned int neigh_rank = neigh_polytope->subdomain_id();   const CellId &neigh_id = neigh_polytope->id();   @endcode Retrieve qpoints,JxWs, normals sent previously from the neighboring rank. @code   std::vector<Point<spacedim>> &real_qpoints =   recv_qpoints.at(neigh_rank).at({neigh_id, local_neigh});     const auto &JxWs = recv_jxws.at(neigh_rank).at({neigh_id, local_neigh});     std::vector<Tensor<1, spacedim>> &normals =   recv_normals.at(neigh_rank).at({neigh_id, local_neigh});   @endcode Apply the necessary scalings due to the bbox. @code   std::vector<Point<spacedim>> final_unit_q_points;   std::transform(real_qpoints.begin(),   real_qpoints.end(),   std::back_inserter(final_unit_q_points),   [&](const Point<spacedim> &p) {   return bbox.real_to_unit(p);   });   @endcode std::vector<double> scale_factors(final_unit_q_points.size()); std::vector<double> scaled_weights(final_unit_q_points.size()); std::vector<Tensor<1, dim>> scaled_normals(final_unit_q_points.size()); Since we received normal vectors from a neighbor, we have to swap the // sign of the vector in order to have outward normals. for (unsigned int q = 0; q < final_unit_q_points.size(); ++q) { for (unsigned int direction = 0; direction < spacedim; ++direction) scaled_normals[q][direction] = normals[q][direction] * (bbox.side_length(direction)); scaled_normals[q] *= -1; scaled_weights[q] = (JxWs[q] * scaled_normals[q].norm()) / bbox_measure; scaled_normals[q] /= scaled_normals[q].norm(); } @code   for (unsigned int q = 0; q < final_unit_q_points.size(); ++q)   normals[q] *= -1;       NonMatching::ImmersedSurfaceQuadrature<dim, spacedim> surface_quad(   final_unit_q_points, JxWs, normals);     agglomerated_isv =   std::make_unique<NonMatching::FEImmersedSurfaceValues<spacedim>>(   *box_mapping, *fe, surface_quad, agglomeration_face_flags);       agglomerated_isv->reinit(cell);     std::pair<const FEValuesBase<dim, spacedim> &,   const FEValuesBase<dim, spacedim> &>   my_p(*agglomerated_isv, *agglomerated_isv);     return my_p;   }   }         template <int dim, int spacedim>   template <typename SparsityPatternType, typename Number>   void   AgglomerationHandler<dim, spacedim>::create_agglomeration_sparsity_pattern(   SparsityPatternType &dsp,   const AffineConstraints<Number> &constraints,   const bool keep_constrained_dofs,   const types::subdomain_id subdomain_id)   {   Assert(n_agglomerations > 0,   ExcMessage("The agglomeration has not been set up correctly."));   Assert(dsp.empty(),   ExcMessage(   "The Sparsity pattern must be empty upon calling this function."));     const IndexSet &locally_owned_dofs = agglo_dh.locally_owned_dofs();   const IndexSet locally_relevant_dofs =   DoFTools::extract_locally_relevant_dofs(agglo_dh);     if constexpr (std::is_same_v<SparsityPatternType, DynamicSparsityPattern>)   dsp.reinit(locally_owned_dofs.size(),   locally_owned_dofs.size(),   locally_relevant_dofs);   else if constexpr (std::is_same_v<SparsityPatternType,   TrilinosWrappers::SparsityPattern>)   dsp.reinit(locally_owned_dofs, communicator);   else   AssertThrow(false, ExcNotImplemented());   @endcode Create the sparsity pattern corresponding only to volumetric terms. The fluxes needed by DG methods will be filled later. @code   DoFTools::make_sparsity_pattern(   agglo_dh, dsp, constraints, keep_constrained_dofs, subdomain_id);       if (!is_hp_collection)   { @endcode Original version: handle case without hp::FECollection @code   const unsigned int dofs_per_cell = agglo_dh.get_fe(0).n_dofs_per_cell();   std::vector<types::global_dof_index> current_dof_indices(dofs_per_cell);   std::vector<types::global_dof_index> neighbor_dof_indices(dofs_per_cell);   @endcode Loop over all locally owned polytopes, find the neighbor (also ghosted) and add fluxes to the sparsity pattern. @code   for (const auto &polytope : polytope_iterators())   {   if (polytope->is_locally_owned())   {   const unsigned int n_current_faces = polytope->n_faces();   polytope->get_dof_indices(current_dof_indices);   for (unsigned int f = 0; f < n_current_faces; ++f)   {   const auto &neigh_polytope = polytope->neighbor(f);   if (neigh_polytope.state() == IteratorState::valid)   {   neigh_polytope->get_dof_indices(neighbor_dof_indices);   constraints.add_entries_local_to_global(   current_dof_indices,   neighbor_dof_indices,   dsp,   keep_constrained_dofs,   {});   }   }   }   }   }   else   { @endcode Handle the hp::FECollection case Loop over all locally owned polytopes, find the neighbor (also ghosted) and add fluxes to the sparsity pattern. @code   for (const auto &polytope : polytope_iterators())   {   if (polytope->is_locally_owned())   {   const unsigned int current_dofs_per_cell =   polytope->get_fe().dofs_per_cell;   std::vector<types::global_dof_index> current_dof_indices(   current_dofs_per_cell);     const unsigned int n_current_faces = polytope->n_faces();   polytope->get_dof_indices(current_dof_indices);   for (unsigned int f = 0; f < n_current_faces; ++f)   {   const auto &neigh_polytope = polytope->neighbor(f);   if (neigh_polytope.state() == IteratorState::valid)   {   const unsigned int neighbor_dofs_per_cell =   neigh_polytope->get_fe().dofs_per_cell;   std::vector<types::global_dof_index> neighbor_dof_indices(   neighbor_dofs_per_cell);     neigh_polytope->get_dof_indices(neighbor_dof_indices);   constraints.add_entries_local_to_global(   current_dof_indices,   neighbor_dof_indices,   dsp,   keep_constrained_dofs,   {});   }   }   }   }   }         if constexpr (std::is_same_v<SparsityPatternType,   TrilinosWrappers::SparsityPattern>)   dsp.compress();   }         template <int dim, int spacedim>   void   AgglomerationHandler<dim, spacedim>::setup_ghost_polytopes()   {   [[maybe_unused]] const auto parallel_triangulation =   dynamic_cast<const parallel::TriangulationBase<dim, spacedim> *>(&*tria);   Assert(parallel_triangulation != nullptr, ExcInternalError());     const unsigned int n_dofs_per_cell = fe->dofs_per_cell;   std::vector<types::global_dof_index> global_dof_indices(n_dofs_per_cell);   for (const auto &polytope : polytope_iterators())   if (polytope->is_locally_owned())   {   const CellId &master_cell_id = polytope->id();     const auto polytope_dh = polytope->as_dof_handler_iterator(agglo_dh);   polytope_dh->get_dof_indices(global_dof_indices);       const auto &agglomerate = polytope->get_agglomerate();     for (const auto &cell : agglomerate)   { @endcode interior, locally owned, cell @code   for (const auto &f : cell->face_indices())   {   if (!cell->at_boundary(f))   {   const auto &neighbor = cell->neighbor(f);   if (neighbor->is_ghost())   { @endcode key of the map: the rank to which send the data @code   const types::subdomain_id neigh_rank =   neighbor->subdomain_id();   @endcode inform the "standard" neighbor about the neighboring id and its master cell @code   local_cell_ids_neigh_cell[neigh_rank].emplace(   cell->id(), master_cell_id);   @endcode inform the neighboring rank that this master cell (hence polytope) has the following DoF indices @code   local_ghost_dofs[neigh_rank].emplace(   master_cell_id, global_dof_indices);   @endcode ...same for bounding boxes @code   const auto &bbox = bboxes[polytope->index()];   local_ghosted_bbox[neigh_rank].emplace(master_cell_id,   bbox);   }   }   }   }   }     recv_cell_ids_neigh_cell =   Utilities::MPI::some_to_some(communicator, local_cell_ids_neigh_cell);   @endcode Exchange with neighboring ranks the neighboring bounding boxes @code   recv_ghosted_bbox =   Utilities::MPI::some_to_some(communicator, local_ghosted_bbox);   @endcode Exchange with neighboring ranks the neighboring ghosted DoFs @code   recv_ghost_dofs =   Utilities::MPI::some_to_some(communicator, local_ghost_dofs);   }         namespace dealii   {   namespace internal   {   template <int dim, int spacedim>   class AgglomerationHandlerImplementation   {   public:   static const FEValuesBase<dim, spacedim> &   reinit_master(   const typename DoFHandler<dim, spacedim>::active_cell_iterator &cell,   const unsigned int face_index,   std::unique_ptr<NonMatching::FEImmersedSurfaceValues<spacedim>>   &agglo_isv_ptr,   const AgglomerationHandler<dim, spacedim> &handler)   {   Assert(handler.is_master_cell(cell),   ExcMessage("This cell must be a master one."));     AgglomerationIterator<dim, spacedim> it{cell, &handler};   const auto &neigh_polytope = it->neighbor(face_index);     const CellId polytope_in_id = cell->id();   @endcode Retrieve the bounding box of the agglomeration @code   const auto &bbox =   handler.bboxes[handler.master2polygon.at(cell->active_cell_index())];     CellId polytope_out_id;   if (neigh_polytope.state() == IteratorState::valid)   polytope_out_id = neigh_polytope->id();   else   polytope_out_id = polytope_in_id; // on the boundary. Same id     const auto &common_face = handler.polytope_cache.interface.at(   {polytope_in_id, polytope_out_id});     std::vector<Point<spacedim>> final_unit_q_points;   std::vector<double> final_weights;   std::vector<Tensor<1, dim>> final_normals;     if (!handler.is_hp_collection)   { @endcode Original version: handle case without hp::FECollection @code   const unsigned int expected_qpoints =   common_face.size() * handler.agglomeration_face_quad.size();   final_unit_q_points.reserve(expected_qpoints);   final_weights.reserve(expected_qpoints);   final_normals.reserve(expected_qpoints);       for (const auto &[deal_cell, local_face_idx] : common_face)   {   handler.no_face_values->reinit(deal_cell, local_face_idx);     const auto &q_points =   handler.no_face_values->get_quadrature_points();   const auto &JxWs = handler.no_face_values->get_JxW_values();   const auto &normals =   handler.no_face_values->get_normal_vectors();     const unsigned int n_qpoints_agglo = q_points.size();     for (unsigned int q = 0; q < n_qpoints_agglo; ++q)   {   final_unit_q_points.push_back(   bbox.real_to_unit(q_points[q]));   final_weights.push_back(JxWs[q]);   final_normals.push_back(normals[q]);   }   }   }   else   { @endcode Handle the hp::FECollection case @code   unsigned int higher_order_quad_index = cell->active_fe_index();   if (neigh_polytope.state() == IteratorState::valid)   if (handler   .agglomeration_face_quad_collection[cell->active_fe_index()]   .size() <   handler   .agglomeration_face_quad_collection[neigh_polytope   ->active_fe_index()]   .size())   higher_order_quad_index = neigh_polytope->active_fe_index();     const unsigned int expected_qpoints =   common_face.size() *   handler   .agglomeration_face_quad_collection[higher_order_quad_index]   .size();   final_unit_q_points.reserve(expected_qpoints);   final_weights.reserve(expected_qpoints);   final_normals.reserve(expected_qpoints);     for (const auto &[deal_cell, local_face_idx] : common_face)   {   handler.hp_no_face_values->reinit(   deal_cell, local_face_idx, higher_order_quad_index, 0, 0);     const auto &q_points =   handler.hp_no_face_values->get_present_fe_values()   .get_quadrature_points();   const auto &JxWs =   handler.hp_no_face_values->get_present_fe_values()   .get_JxW_values();   const auto &normals =   handler.hp_no_face_values->get_present_fe_values()   .get_normal_vectors();     const unsigned int n_qpoints_agglo = q_points.size();     for (unsigned int q = 0; q < n_qpoints_agglo; ++q)   {   final_unit_q_points.push_back(   bbox.real_to_unit(q_points[q]));   final_weights.push_back(JxWs[q]);   final_normals.push_back(normals[q]);   }   }   }       NonMatching::ImmersedSurfaceQuadrature<dim, spacedim> surface_quad(   final_unit_q_points, final_weights, final_normals);     if (!handler.is_hp_collection)   {   agglo_isv_ptr =   std::make_unique<NonMatching::FEImmersedSurfaceValues<spacedim>>(   *(handler.box_mapping),   *(handler.fe),   surface_quad,   handler.agglomeration_face_flags);   }   else   {   agglo_isv_ptr =   std::make_unique<NonMatching::FEImmersedSurfaceValues<spacedim>>(   *(handler.box_mapping),   cell->get_fe(),   surface_quad,   handler.agglomeration_face_flags);   }     agglo_isv_ptr->reinit(cell);     return *agglo_isv_ptr;   }         /**   * Given an agglomeration described by the master cell `master_cell`,   * this function:   * - enumerates the faces of the agglomeration   * - stores who is the neighbor, the local face indices from outside and   * inside*/   static void   setup_master_neighbor_connectivity(   const typename Triangulation<dim, spacedim>::active_cell_iterator   &master_cell,   const AgglomerationHandler<dim, spacedim> &handler)   {   Assert(   handler.master_slave_relationships[master_cell   ->global_active_cell_index()] ==   -1,   ExcMessage("The present cell with index " +   std::to_string(master_cell->global_active_cell_index()) +   "is not a master one."));     const auto &agglomeration = handler.get_agglomerate(master_cell);   const types::global_cell_index current_polytope_index =   handler.master2polygon.at(master_cell->active_cell_index());     CellId current_polytope_id = master_cell->id();       std::set<types::global_cell_index> visited_polygonal_neighbors;     std::map<unsigned int, CellId> face_to_neigh_id;     std::map<unsigned int, bool> is_face_at_boundary;   @endcode same as above, but with CellId @code   std::set<CellId> visited_polygonal_neighbors_id;   unsigned int ghost_counter = 0;     for (const auto &cell : agglomeration)   {   const types::global_cell_index cell_index =   cell->active_cell_index();     const CellId cell_id = cell->id();     for (const auto f : cell->face_indices())   {   const auto &neighboring_cell = cell->neighbor(f);     const bool valid_neighbor =   neighboring_cell.state() == IteratorState::valid;     if (valid_neighbor)   {   if (neighboring_cell->is_locally_owned() &&   !handler.are_cells_agglomerated(cell, neighboring_cell))   { @endcode - cell is not on the boundary, - it's not agglomerated with the neighbor. If so, it's a neighbor of the present agglomeration std::cout << " (from rank) " << Utilities::MPI::this_mpi_process( handler.communicator) << std::endl; std::cout << "neighbor locally owned? " << std::boolalpha << neighboring_cell->is_locally_owned() << std::endl; if (neighboring_cell->is_ghost()) handler.ghosted_indices.push_back( neighboring_cell->active_cell_index()); a new face of the agglomeration has been discovered. @code   handler.polygon_boundary[master_cell].push_back(   cell->face(f));   @endcode global index of neighboring deal.II cell @code   const types::global_cell_index neighboring_cell_index =   neighboring_cell->active_cell_index();   @endcode master cell for the neighboring polytope @code   const auto &master_of_neighbor =   handler.master_slave_relationships_iterators.at(   neighboring_cell_index);     const auto nof = cell->neighbor_of_neighbor(f);     if (handler.is_slave_cell(neighboring_cell))   { @endcode index of the neighboring polytope @code   const types::global_cell_index   neighbor_polytope_index =   handler.master2polygon.at(   master_of_neighbor->active_cell_index());     CellId neighbor_polytope_id =   master_of_neighbor->id();     if (visited_polygonal_neighbors.find(   neighbor_polytope_index) ==   std::end(visited_polygonal_neighbors))   { @endcode found a neighbor @code   const unsigned int n_face =   handler.number_of_agglomerated_faces   [current_polytope_index];     handler.polytope_cache.cell_face_at_boundary[{   current_polytope_index, n_face}] = {   false, master_of_neighbor};     is_face_at_boundary[n_face] = true;     ++handler.number_of_agglomerated_faces   [current_polytope_index];     visited_polygonal_neighbors.insert(   neighbor_polytope_index);   }       if (handler.polytope_cache.visited_cell_and_faces   .find({cell_index, f}) ==   std::end(handler.polytope_cache   .visited_cell_and_faces))   {   handler.polytope_cache   .interface[{current_polytope_id,   neighbor_polytope_id}]   .emplace_back(cell, f);     handler.polytope_cache.visited_cell_and_faces   .insert({cell_index, f});   }       if (handler.polytope_cache.visited_cell_and_faces   .find({neighboring_cell_index, nof}) ==   std::end(handler.polytope_cache   .visited_cell_and_faces))   {   handler.polytope_cache   .interface[{neighbor_polytope_id,   current_polytope_id}]   .emplace_back(neighboring_cell, nof);     handler.polytope_cache.visited_cell_and_faces   .insert({neighboring_cell_index, nof});   }   }   else   { @endcode neighboring cell is a master save the pair of neighboring cells @code   const types::global_cell_index   neighbor_polytope_index =   handler.master2polygon.at(   neighboring_cell_index);     CellId neighbor_polytope_id =   neighboring_cell->id();     if (visited_polygonal_neighbors.find(   neighbor_polytope_index) ==   std::end(visited_polygonal_neighbors))   { @endcode found a neighbor @code   const unsigned int n_face =   handler.number_of_agglomerated_faces   [current_polytope_index];       handler.polytope_cache.cell_face_at_boundary[{   current_polytope_index, n_face}] = {   false, neighboring_cell};     is_face_at_boundary[n_face] = true;     ++handler.number_of_agglomerated_faces   [current_polytope_index];     visited_polygonal_neighbors.insert(   neighbor_polytope_index);   }         if (handler.polytope_cache.visited_cell_and_faces   .find({cell_index, f}) ==   std::end(handler.polytope_cache   .visited_cell_and_faces))   {   handler.polytope_cache   .interface[{current_polytope_id,   neighbor_polytope_id}]   .emplace_back(cell, f);     handler.polytope_cache.visited_cell_and_faces   .insert({cell_index, f});   }     if (handler.polytope_cache.visited_cell_and_faces   .find({neighboring_cell_index, nof}) ==   std::end(handler.polytope_cache   .visited_cell_and_faces))   {   handler.polytope_cache   .interface[{neighbor_polytope_id,   current_polytope_id}]   .emplace_back(neighboring_cell, nof);     handler.polytope_cache.visited_cell_and_faces   .insert({neighboring_cell_index, nof});   }   }   }   else if (neighboring_cell->is_ghost())   {   const auto nof = cell->neighbor_of_neighbor(f);   @endcode from neighboring rank,receive the association between standard cell ids and neighboring polytope. This tells to the current rank that the neighboring cell has the following CellId as master cell. @code   const auto &check_neigh_poly_ids =   handler.recv_cell_ids_neigh_cell.at(   neighboring_cell->subdomain_id());     const CellId neighboring_cell_id =   neighboring_cell->id();     const CellId &check_neigh_polytope_id =   check_neigh_poly_ids.at(neighboring_cell_id);   @endcode const auto master_index = master_indices[ghost_counter]; @code   if (visited_polygonal_neighbors_id.find(   check_neigh_polytope_id) ==   std::end(visited_polygonal_neighbors_id))   {   handler.polytope_cache.cell_face_at_boundary[{   current_polytope_index,   handler.number_of_agglomerated_faces   [current_polytope_index]}] = {false,   neighboring_cell};     @endcode record the cell id of the neighboring polytope @code   handler.polytope_cache.ghosted_master_id[{   current_polytope_id,   handler.number_of_agglomerated_faces   [current_polytope_index]}] =   check_neigh_polytope_id;       const unsigned int n_face =   handler.number_of_agglomerated_faces   [current_polytope_index];     face_to_neigh_id[n_face] = check_neigh_polytope_id;     is_face_at_boundary[n_face] = false;     @endcode increment number of faces @code   ++handler.number_of_agglomerated_faces   [current_polytope_index];     visited_polygonal_neighbors_id.insert(   check_neigh_polytope_id);   @endcode ghosted polytope has been found, increment ghost counter @code   ++ghost_counter;   }         if (handler.polytope_cache.visited_cell_and_faces_id   .find({cell_id, f}) ==   std::end(   handler.polytope_cache.visited_cell_and_faces_id))   {   handler.polytope_cache   .interface[{current_polytope_id,   check_neigh_polytope_id}]   .emplace_back(cell, f);   @endcode std::cout << "ADDED (" << cell->active_cell_index() << ") BETWEEN " << current_polytope_id << " e " << check_neigh_polytope_id << std::endl; @code   handler.polytope_cache.visited_cell_and_faces_id   .insert({cell_id, f});   }       if (handler.polytope_cache.visited_cell_and_faces_id   .find({neighboring_cell_id, nof}) ==   std::end(   handler.polytope_cache.visited_cell_and_faces_id))   {   handler.polytope_cache   .interface[{check_neigh_polytope_id,   current_polytope_id}]   .emplace_back(neighboring_cell, nof);     handler.polytope_cache.visited_cell_and_faces_id   .insert({neighboring_cell_id, nof});   }   }   }   else if (cell->face(f)->at_boundary())   { @endcode Boundary face of a boundary cell. Note that the neighboring cell must be invalid. @code   handler.polygon_boundary[master_cell].push_back(   cell->face(f));     if (visited_polygonal_neighbors.find(   std::numeric_limits<unsigned int>::max()) ==   std::end(visited_polygonal_neighbors))   { @endcode boundary face. Notice that <tt>neighboring_cell</tt> is invalid here. @code   handler.polytope_cache.cell_face_at_boundary[{   current_polytope_index,   handler.number_of_agglomerated_faces   [current_polytope_index]}] = {true,   neighboring_cell};     const unsigned int n_face =   handler.number_of_agglomerated_faces   [current_polytope_index];     is_face_at_boundary[n_face] = true;     ++handler.number_of_agglomerated_faces   [current_polytope_index];     visited_polygonal_neighbors.insert(   std::numeric_limits<unsigned int>::max());   }         if (handler.polytope_cache.visited_cell_and_faces.find(   {cell_index, f}) ==   std::end(handler.polytope_cache.visited_cell_and_faces))   {   handler.polytope_cache   .interface[{current_polytope_id, current_polytope_id}]   .emplace_back(cell, f);     handler.polytope_cache.visited_cell_and_faces.insert(   {cell_index, f});   }   }   } // loop over faces   } // loop over all cells of agglomerate         if (ghost_counter > 0)   {   const auto parallel_triangulation = dynamic_cast<   const ::parallel::TriangulationBase<dim, spacedim> *>(   &(*handler.tria));     const unsigned int n_faces_current_poly =   handler.number_of_agglomerated_faces[current_polytope_index];   @endcode Communicate to neighboring ranks that current_polytope_id has a number of faces equal to n_faces_current_poly faces: current_polytope_id -> n_faces_current_poly @code   for (const unsigned int neigh_rank :   parallel_triangulation->ghost_owners())   {   handler.local_n_faces[neigh_rank].emplace(current_polytope_id,   n_faces_current_poly);     handler.local_bdary_info[neigh_rank].emplace(   current_polytope_id, is_face_at_boundary);     handler.local_ghosted_master_id[neigh_rank].emplace(   current_polytope_id, face_to_neigh_id);   }   }   }   };         } // namespace internal   } // namespace dealii         template class AgglomerationHandler<1>;   template void   AgglomerationHandler<1>::create_agglomeration_sparsity_pattern(   DynamicSparsityPattern &sparsity_pattern,   const AffineConstraints<double> &constraints,   const bool keep_constrained_dofs,   const types::subdomain_id subdomain_id);     template void   AgglomerationHandler<1>::create_agglomeration_sparsity_pattern(   TrilinosWrappers::SparsityPattern &sparsity_pattern,   const AffineConstraints<double> &constraints,   const bool keep_constrained_dofs,   const types::subdomain_id subdomain_id);     template class AgglomerationHandler<2>;   template void   AgglomerationHandler<2>::create_agglomeration_sparsity_pattern(   DynamicSparsityPattern &sparsity_pattern,   const AffineConstraints<double> &constraints,   const bool keep_constrained_dofs,   const types::subdomain_id subdomain_id);     template void   AgglomerationHandler<2>::create_agglomeration_sparsity_pattern(   TrilinosWrappers::SparsityPattern &sparsity_pattern,   const AffineConstraints<double> &constraints,   const bool keep_constrained_dofs,   const types::subdomain_id subdomain_id);     template class AgglomerationHandler<3>;   template void   AgglomerationHandler<3>::create_agglomeration_sparsity_pattern(   DynamicSparsityPattern &sparsity_pattern,   const AffineConstraints<double> &constraints,   const bool keep_constrained_dofs,   const types::subdomain_id subdomain_id);     template void   AgglomerationHandler<3>::create_agglomeration_sparsity_pattern(   TrilinosWrappers::SparsityPattern &sparsity_pattern,   const AffineConstraints<double> &constraints,   const bool keep_constrained_dofs,   const types::subdomain_id subdomain_id); @endcode <a name="ann-source/mapping_box.cc"></a> <h1>Annotated version of source/mapping_box.cc</h1> @code   /* -----------------------------------------------------------------------------   *   * SPDX-License-Identifier: LGPL-2.1-or-later   * Copyright (C) 2025 by Marco Feder, Pasquale Claudio Africa, Xinping Gui,   * Andrea Cangiani   *   * This file is part of the deal.II code gallery.   *   * -----------------------------------------------------------------------------   */     #include <deal.II/base/array_view.h>   #include <deal.II/base/memory_consumption.h>   #include <deal.II/base/qprojector.h>   #include <deal.II/base/quadrature.h>   #include <deal.II/base/signaling_nan.h>   #include <deal.II/base/tensor.h>     #include <deal.II/dofs/dof_accessor.h>     #include <deal.II/fe/fe_values.h>     #include <deal.II/grid/tria.h>   #include <deal.II/grid/tria_iterator.h>     #include <deal.II/lac/full_matrix.h>     #include <mapping_box.h>     #include <algorithm>     DEAL_II_NAMESPACE_OPEN     DeclExceptionMsg(   ExcCellNotAssociatedWithBox,   "You are using MappingBox, but the incoming element is not associated with a"   "Bounding Box Cartesian.");         /**   * Return whether the incoming element has a BoundingBox associated to it.   * Simplicial and quad-hex meshes are supported.   */   template <typename CellType>   bool   has_box(const CellType &cell,   const std::map<types::global_cell_index, types::global_cell_index>   &translator)   {   Assert((cell->reference_cell().is_hyper_cube() ||   cell->reference_cell().is_simplex()),   ExcNotImplemented());   Assert((translator.find(cell->active_cell_index()) != translator.cend()),   ExcCellNotAssociatedWithBox());     return true;   }         template <int dim, int spacedim>   MappingBox<dim, spacedim>::MappingBox(   const std::vector<BoundingBox<dim>> &input_boxes,   const std::map<types::global_cell_index, types::global_cell_index>   &global_to_polytope)   {   Assert(input_boxes.size() > 0,   ExcMessage("Invalid number of bounding boxes."));   @endcode copy boxes and map @code   boxes.resize(input_boxes.size());   for (unsigned int i = 0; i < input_boxes.size(); ++i)   boxes[i] = input_boxes[i];   polytope_translator = global_to_polytope;   }         template <int dim, int spacedim>   MappingBox<dim, spacedim>::InternalData::InternalData(const Quadrature<dim> &q)   : cell_extents(numbers::signaling_nan<Tensor<1, dim>>())   , traslation(numbers::signaling_nan<Tensor<1, dim>>())   , inverse_cell_extents(numbers::signaling_nan<Tensor<1, dim>>())   , volume_element(numbers::signaling_nan<double>())   , quadrature_points(q.get_points())   {}         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::InternalData::reinit(const UpdateFlags update_flags,   const Quadrature<dim> &)   { @endcode store the flags in the internal data object so we can access them in fill_fe_*_values(). use the transitive hull of the required flags @code   this->update_each = update_flags;   }         template <int dim, int spacedim>   std::size_t   MappingBox<dim, spacedim>::InternalData::memory_consumption() const   {   return (Mapping<dim, spacedim>::InternalDataBase::memory_consumption() +   MemoryConsumption::memory_consumption(cell_extents) +   MemoryConsumption::memory_consumption(traslation) +   MemoryConsumption::memory_consumption(inverse_cell_extents) +   MemoryConsumption::memory_consumption(volume_element));   }         template <int dim, int spacedim>   bool   MappingBox<dim, spacedim>::preserves_vertex_locations() const   {   return true;   }         template <int dim, int spacedim>   bool   MappingBox<dim, spacedim>::is_compatible_with(   #if DEAL_II_VERSION_GTE(9, 8, 0)   const ReferenceCell<dim> &reference_cell   #else   const ReferenceCell &reference_cell   #endif   ) const   {   Assert(dim == reference_cell.get_dimension(),   ExcMessage("The dimension of your mapping (" +   Utilities::to_string(dim) +   ") and the reference cell cell_type (" +   Utilities::to_string(reference_cell.get_dimension()) +   " ) do not agree."));     return reference_cell.is_hyper_cube() || reference_cell.is_simplex();   }         template <int dim, int spacedim>   UpdateFlags   MappingBox<dim, spacedim>::requires_update_flags(const UpdateFlags in) const   { @endcode this mapping is pretty simple in that it can basically compute every piece of information wanted by FEValues without requiring computing any other quantities. boundary forms are one exception since they can be computed from the normal vectors without much further ado @code   UpdateFlags out = in;   if (out & update_boundary_forms)   out |= update_normal_vectors;     return out;   }         template <int dim, int spacedim>   std::unique_ptr<typename Mapping<dim, spacedim>::InternalDataBase>   MappingBox<dim, spacedim>::get_data(const UpdateFlags update_flags,   const Quadrature<dim> &q) const   {   std::unique_ptr<typename Mapping<dim, spacedim>::InternalDataBase> data_ptr =   std::make_unique<InternalData>();   data_ptr->reinit(requires_update_flags(update_flags), q);     return data_ptr;   }         template <int dim, int spacedim>   std::unique_ptr<typename Mapping<dim, spacedim>::InternalDataBase>   MappingBox<dim, spacedim>::get_subface_data(   const UpdateFlags update_flags,   const Quadrature<dim - 1> &quadrature) const   {   (void)update_flags;   (void)quadrature;   DEAL_II_NOT_IMPLEMENTED();   return {};   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::update_cell_extents(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const CellSimilarity::Similarity cell_similarity,   const InternalData &data) const   { @endcode Compute start point and sizes along axes. The vertices to be looked at are 1, 2, 4 compared to the base vertex 0. @code   if (cell_similarity != CellSimilarity::translation)   {   const BoundingBox<dim> &current_box =   boxes[polytope_translator.at(cell->active_cell_index())];   const std::pair<Point<dim>, Point<dim>> &bdary_points =   current_box.get_boundary_points();     for (unsigned int d = 0; d < dim; ++d)   {   const double cell_extent_d = current_box.side_length(d);   data.cell_extents[d] = cell_extent_d;     data.traslation[d] =   .5 * (bdary_points.first[d] +   bdary_points.second[d]); // midpoint of each interval     Assert(cell_extent_d != 0.,   ExcMessage("Cell does not appear to be Cartesian!"));   data.inverse_cell_extents[d] = 1. / cell_extent_d;   }   }   }         namespace   {   template <int dim>   void   transform_quadrature_points(   const BoundingBox<dim> &box,   const ArrayView<const Point<dim>> &unit_quadrature_points,   std::vector<Point<dim>> &quadrature_points)   {   for (unsigned int i = 0; i < quadrature_points.size(); ++i)   quadrature_points[i] = box.unit_to_real(unit_quadrature_points[i]);   }   } // namespace         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::maybe_update_cell_quadrature_points(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const InternalData &data,   const ArrayView<const Point<dim>> &unit_quadrature_points,   std::vector<Point<dim>> &quadrature_points) const   {   if (data.update_each & update_quadrature_points)   transform_quadrature_points(   boxes[polytope_translator.at(cell->active_cell_index())],   unit_quadrature_points,   quadrature_points);   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::maybe_update_normal_vectors(   const unsigned int face_no,   const InternalData &data,   std::vector<Tensor<1, dim>> &normal_vectors) const   { @endcode compute normal vectors. All normals on a face have the same value. @code   if (data.update_each & update_normal_vectors)   {   Assert(face_no < GeometryInfo<dim>::faces_per_cell, ExcInternalError());   std::fill(normal_vectors.begin(),   normal_vectors.end(),   GeometryInfo<dim>::unit_normal_vector[face_no]);   }   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::maybe_update_jacobian_derivatives(   const InternalData &data,   const CellSimilarity::Similarity cell_similarity,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const   {   if (cell_similarity != CellSimilarity::translation)   {   if (data.update_each & update_jacobian_grads)   for (unsigned int i = 0; i < output_data.jacobian_grads.size(); ++i)   output_data.jacobian_grads[i] = DerivativeForm<2, dim, spacedim>();     if (data.update_each & update_jacobian_pushed_forward_grads)   for (unsigned int i = 0;   i < output_data.jacobian_pushed_forward_grads.size();   ++i)   output_data.jacobian_pushed_forward_grads[i] = Tensor<3, spacedim>();     if (data.update_each & update_jacobian_2nd_derivatives)   for (unsigned int i = 0;   i < output_data.jacobian_2nd_derivatives.size();   ++i)   output_data.jacobian_2nd_derivatives[i] =   DerivativeForm<3, dim, spacedim>();     if (data.update_each & update_jacobian_pushed_forward_2nd_derivatives)   for (unsigned int i = 0;   i < output_data.jacobian_pushed_forward_2nd_derivatives.size();   ++i)   output_data.jacobian_pushed_forward_2nd_derivatives[i] =   Tensor<4, spacedim>();     if (data.update_each & update_jacobian_3rd_derivatives)   for (unsigned int i = 0;   i < output_data.jacobian_3rd_derivatives.size();   ++i)   output_data.jacobian_3rd_derivatives[i] =   DerivativeForm<4, dim, spacedim>();     if (data.update_each & update_jacobian_pushed_forward_3rd_derivatives)   for (unsigned int i = 0;   i < output_data.jacobian_pushed_forward_3rd_derivatives.size();   ++i)   output_data.jacobian_pushed_forward_3rd_derivatives[i] =   Tensor<5, spacedim>();   }   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::maybe_update_volume_elements(   const InternalData &data) const   {   if (data.update_each & update_volume_elements)   {   double volume = data.cell_extents[0];   for (unsigned int d = 1; d < dim; ++d)   volume *= data.cell_extents[d];   data.volume_element = volume;   }   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::maybe_update_jacobians(   const InternalData &data,   const CellSimilarity::Similarity cell_similarity,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const   { @endcode "compute" Jacobian at the quadrature points, which are all the same @code   if (data.update_each & update_jacobians)   if (cell_similarity != CellSimilarity::translation)   for (unsigned int i = 0; i < output_data.jacobians.size(); ++i)   {   output_data.jacobians[i] = DerivativeForm<1, dim, spacedim>();   for (unsigned int j = 0; j < dim; ++j)   output_data.jacobians[i][j][j] = data.cell_extents[j];   }   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::maybe_update_inverse_jacobians(   const InternalData &data,   const CellSimilarity::Similarity cell_similarity,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const   { @endcode "compute" inverse Jacobian at the quadrature points, which are all the same @code   if (data.update_each & update_inverse_jacobians)   if (cell_similarity != CellSimilarity::translation)   for (unsigned int i = 0; i < output_data.inverse_jacobians.size(); ++i)   {   output_data.inverse_jacobians[i] = Tensor<2, dim>();   for (unsigned int j = 0; j < dim; ++j)   output_data.inverse_jacobians[i][j][j] =   data.inverse_cell_extents[j];   }   }         template <int dim, int spacedim>   CellSimilarity::Similarity   MappingBox<dim, spacedim>::fill_fe_values(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const CellSimilarity::Similarity cell_similarity,   const Quadrature<dim> &quadrature,   const typename Mapping<dim, spacedim>::InternalDataBase &internal_data,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const   {   Assert(has_box(cell, polytope_translator), ExcCellNotAssociatedWithBox());   @endcode convert data object to internal data for this class. fails with an exception if that is not possible @code   Assert(dynamic_cast<const InternalData *>(&internal_data) != nullptr,   ExcInternalError());   const InternalData &data = static_cast<const InternalData &>(internal_data);       update_cell_extents(cell, cell_similarity, data);     maybe_update_cell_quadrature_points(cell,   data,   quadrature.get_points(),   output_data.quadrature_points);   @endcode compute Jacobian determinant. all values are equal and are the product of the local lengths in each coordinate direction @code   if (data.update_each & (update_JxW_values | update_volume_elements))   if (cell_similarity != CellSimilarity::translation)   {   double J = data.cell_extents[0];   for (unsigned int d = 1; d < dim; ++d)   J *= data.cell_extents[d];   data.volume_element = J;   if (data.update_each & update_JxW_values)   for (unsigned int i = 0; i < output_data.JxW_values.size(); ++i)   output_data.JxW_values[i] = quadrature.weight(i);   }       maybe_update_jacobians(data, cell_similarity, output_data);   maybe_update_jacobian_derivatives(data, cell_similarity, output_data);   maybe_update_inverse_jacobians(data, cell_similarity, output_data);     return cell_similarity;   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::fill_fe_subface_values(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const unsigned int face_no,   const unsigned int subface_no,   const Quadrature<dim - 1> &quadrature,   const typename Mapping<dim, spacedim>::InternalDataBase &internal_data,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const   {   (void)cell;   (void)face_no;   (void)subface_no;   (void)quadrature;   (void)internal_data;   (void)output_data;   DEAL_II_NOT_IMPLEMENTED();   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::fill_fe_immersed_surface_values(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const NonMatching::ImmersedSurfaceQuadrature<dim> &quadrature,   const typename Mapping<dim, spacedim>::InternalDataBase &internal_data,   internal::FEValuesImplementation::MappingRelatedData<dim, spacedim>   &output_data) const   {   AssertDimension(dim, spacedim);   Assert(has_box(cell, polytope_translator), ExcCellNotAssociatedWithBox());   @endcode Convert data object to internal data for this class. Fails with an exception if that is not possible. @code   Assert(dynamic_cast<const InternalData *>(&internal_data) != nullptr,   ExcInternalError());   const InternalData &data = static_cast<const InternalData &>(internal_data);       update_cell_extents(cell, CellSimilarity::none, data);     maybe_update_cell_quadrature_points(cell,   data,   quadrature.get_points(),   output_data.quadrature_points);     if (data.update_each & update_normal_vectors)   for (unsigned int i = 0; i < output_data.normal_vectors.size(); ++i)   output_data.normal_vectors[i] = quadrature.normal_vector(i);     if (data.update_each & update_JxW_values)   for (unsigned int i = 0; i < output_data.JxW_values.size(); ++i)   output_data.JxW_values[i] = quadrature.weight(i);     maybe_update_volume_elements(data);   maybe_update_jacobians(data, CellSimilarity::none, output_data);   maybe_update_jacobian_derivatives(data, CellSimilarity::none, output_data);   maybe_update_inverse_jacobians(data, CellSimilarity::none, output_data);   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::transform(   const ArrayView<const Tensor<1, dim>> &input,   const MappingKind mapping_kind,   const typename Mapping<dim, spacedim>::InternalDataBase &mapping_data,   const ArrayView<Tensor<1, spacedim>> &output) const   {   AssertDimension(input.size(), output.size());   Assert(dynamic_cast<const InternalData *>(&mapping_data) != nullptr,   ExcInternalError());   const InternalData &data = static_cast<const InternalData &>(mapping_data);     switch (mapping_kind)   {   case mapping_covariant:   {   Assert(data.update_each & update_covariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d = 0; d < dim; ++d)   output[i][d] = input[i][d] * data.inverse_cell_extents[d];   return;   }     case mapping_contravariant:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d = 0; d < dim; ++d)   output[i][d] = input[i][d] * data.cell_extents[d];   return;   }   case mapping_piola:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));   Assert(data.update_each & update_volume_elements,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_volume_elements"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d = 0; d < dim; ++d)   output[i][d] =   input[i][d] * data.cell_extents[d] / data.volume_element;   return;   }   default:   DEAL_II_NOT_IMPLEMENTED();   }   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::transform(   const ArrayView<const DerivativeForm<1, dim, spacedim>> &input,   const MappingKind mapping_kind,   const typename Mapping<dim, spacedim>::InternalDataBase &mapping_data,   const ArrayView<Tensor<2, spacedim>> &output) const   {   AssertDimension(input.size(), output.size());   Assert(dynamic_cast<const InternalData *>(&mapping_data) != nullptr,   ExcInternalError());   const InternalData &data = static_cast<const InternalData &>(mapping_data);     switch (mapping_kind)   {   case mapping_covariant:   {   Assert(data.update_each & update_covariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] =   input[i][d1][d2] * data.inverse_cell_extents[d2];   return;   }     case mapping_contravariant:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] * data.cell_extents[d2];   return;   }     case mapping_covariant_gradient:   {   Assert(data.update_each & update_covariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] *   data.inverse_cell_extents[d2] *   data.inverse_cell_extents[d1];   return;   }     case mapping_contravariant_gradient:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] * data.cell_extents[d2] *   data.inverse_cell_extents[d1];   return;   }     case mapping_piola:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));   Assert(data.update_each & update_volume_elements,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_volume_elements"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] * data.cell_extents[d2] /   data.volume_element;   return;   }     case mapping_piola_gradient:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));   Assert(data.update_each & update_volume_elements,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_volume_elements"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] * data.cell_extents[d2] *   data.inverse_cell_extents[d1] /   data.volume_element;   return;   }     default:   DEAL_II_NOT_IMPLEMENTED();   }   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::transform(   const ArrayView<const Tensor<2, dim>> &input,   const MappingKind mapping_kind,   const typename Mapping<dim, spacedim>::InternalDataBase &mapping_data,   const ArrayView<Tensor<2, spacedim>> &output) const   {   AssertDimension(input.size(), output.size());   Assert(dynamic_cast<const InternalData *>(&mapping_data) != nullptr,   ExcInternalError());   const InternalData &data = static_cast<const InternalData &>(mapping_data);     switch (mapping_kind)   {   case mapping_covariant:   {   Assert(data.update_each & update_covariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] =   input[i][d1][d2] * data.inverse_cell_extents[d2];   return;   }     case mapping_contravariant:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] * data.cell_extents[d2];   return;   }     case mapping_covariant_gradient:   {   Assert(data.update_each & update_covariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] *   data.inverse_cell_extents[d2] *   data.inverse_cell_extents[d1];   return;   }     case mapping_contravariant_gradient:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] * data.cell_extents[d2] *   data.inverse_cell_extents[d1];   return;   }     case mapping_piola:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));   Assert(data.update_each & update_volume_elements,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_volume_elements"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] * data.cell_extents[d2] /   data.volume_element;   return;   }     case mapping_piola_gradient:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));   Assert(data.update_each & update_volume_elements,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_volume_elements"));     for (unsigned int i = 0; i < output.size(); ++i)   for (unsigned int d1 = 0; d1 < dim; ++d1)   for (unsigned int d2 = 0; d2 < dim; ++d2)   output[i][d1][d2] = input[i][d1][d2] * data.cell_extents[d2] *   data.inverse_cell_extents[d1] /   data.volume_element;   return;   }     default:   DEAL_II_NOT_IMPLEMENTED();   }   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::transform(   const ArrayView<const DerivativeForm<2, dim, spacedim>> &input,   const MappingKind mapping_kind,   const typename Mapping<dim, spacedim>::InternalDataBase &mapping_data,   const ArrayView<Tensor<3, spacedim>> &output) const   {   AssertDimension(input.size(), output.size());   Assert(dynamic_cast<const InternalData *>(&mapping_data) != nullptr,   ExcInternalError());   const InternalData &data = static_cast<const InternalData &>(mapping_data);     switch (mapping_kind)   {   case mapping_covariant_gradient:   {   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));     for (unsigned int q = 0; q < output.size(); ++q)   for (unsigned int i = 0; i < spacedim; ++i)   for (unsigned int j = 0; j < spacedim; ++j)   for (unsigned int k = 0; k < spacedim; ++k)   {   output[q][i][j][k] = input[q][i][j][k] *   data.inverse_cell_extents[j] *   data.inverse_cell_extents[k];   }   return;   }   default:   DEAL_II_NOT_IMPLEMENTED();   }   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::transform(   const ArrayView<const Tensor<3, dim>> &input,   const MappingKind mapping_kind,   const typename Mapping<dim, spacedim>::InternalDataBase &mapping_data,   const ArrayView<Tensor<3, spacedim>> &output) const   {   AssertDimension(input.size(), output.size());   Assert(dynamic_cast<const InternalData *>(&mapping_data) != nullptr,   ExcInternalError());   const InternalData &data = static_cast<const InternalData &>(mapping_data);     switch (mapping_kind)   {   case mapping_contravariant_hessian:   {   Assert(data.update_each & update_covariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));     for (unsigned int q = 0; q < output.size(); ++q)   for (unsigned int i = 0; i < spacedim; ++i)   for (unsigned int j = 0; j < spacedim; ++j)   for (unsigned int k = 0; k < spacedim; ++k)   {   output[q][i][j][k] = input[q][i][j][k] *   data.cell_extents[i] *   data.inverse_cell_extents[j] *   data.inverse_cell_extents[k];   }   return;   }     case mapping_covariant_hessian:   {   Assert(data.update_each & update_covariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));     for (unsigned int q = 0; q < output.size(); ++q)   for (unsigned int i = 0; i < spacedim; ++i)   for (unsigned int j = 0; j < spacedim; ++j)   for (unsigned int k = 0; k < spacedim; ++k)   {   output[q][i][j][k] = input[q][i][j][k] *   (data.inverse_cell_extents[i] *   data.inverse_cell_extents[j]) *   data.inverse_cell_extents[k];   }     return;   }     case mapping_piola_hessian:   {   Assert(data.update_each & update_covariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_covariant_transformation"));   Assert(data.update_each & update_contravariant_transformation,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_contravariant_transformation"));   Assert(data.update_each & update_volume_elements,   typename FEValuesBase<dim>::ExcAccessToUninitializedField(   "update_volume_elements"));     for (unsigned int q = 0; q < output.size(); ++q)   for (unsigned int i = 0; i < spacedim; ++i)   for (unsigned int j = 0; j < spacedim; ++j)   for (unsigned int k = 0; k < spacedim; ++k)   {   output[q][i][j][k] =   input[q][i][j][k] *   (data.cell_extents[i] / data.volume_element *   data.inverse_cell_extents[j]) *   data.inverse_cell_extents[k];   }     return;   }     default:   DEAL_II_NOT_IMPLEMENTED();   }   }         template <int dim, int spacedim>   Point<spacedim>   MappingBox<dim, spacedim>::transform_unit_to_real_cell(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const Point<dim> &p) const   {   Assert(has_box(cell, polytope_translator), ExcCellNotAssociatedWithBox());   Assert(dim == spacedim, ExcNotImplemented());     return boxes[polytope_translator.at(cell->active_cell_index())].unit_to_real(   p);   }         template <int dim, int spacedim>   Point<dim>   MappingBox<dim, spacedim>::transform_real_to_unit_cell(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const Point<spacedim> &p) const   {   Assert(has_box(cell, polytope_translator), ExcCellNotAssociatedWithBox());   Assert(dim == spacedim, ExcNotImplemented());     return boxes[polytope_translator.at(cell->active_cell_index())].real_to_unit(   p);   }         template <int dim, int spacedim>   void   MappingBox<dim, spacedim>::transform_points_real_to_unit_cell(   const typename Triangulation<dim, spacedim>::cell_iterator &cell,   const ArrayView<const Point<spacedim>> &real_points,   const ArrayView<Point<dim>> &unit_points) const   {   Assert(has_box(cell, polytope_translator), ExcCellNotAssociatedWithBox());   AssertDimension(real_points.size(), unit_points.size());     if (dim != spacedim)   DEAL_II_NOT_IMPLEMENTED();   for (unsigned int i = 0; i < real_points.size(); ++i)   unit_points[i] =   boxes[polytope_translator.at(cell->active_cell_index())].real_to_unit(   real_points[i]);   }         template <int dim, int spacedim>   std::unique_ptr<Mapping<dim, spacedim>>   MappingBox<dim, spacedim>::clone() const   {   return std::make_unique<MappingBox<dim, spacedim>>(*this);   }    


explicit instantiations

  template class MappingBox<1>;
  template class MappingBox<2>;
  template class MappingBox<3>;
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39