From d1862a0ad2bb83c0fadacd33a73b2918fca6454b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Mon, 19 Jul 2021 19:15:06 +0200
Subject: [PATCH] Reorganize Mesh*NodeBoundary classes

---
 src/mesh/CMakeLists.txt           |   3 +
 src/mesh/MeshFlatNodeBoundary.cpp | 296 ++++++++++
 src/mesh/MeshFlatNodeBoundary.hpp |  60 ++
 src/mesh/MeshLineNodeBoundary.cpp | 143 +++++
 src/mesh/MeshLineNodeBoundary.hpp |  65 +++
 src/mesh/MeshNodeBoundary.cpp     | 323 +++++++++++
 src/mesh/MeshNodeBoundary.hpp     | 885 +-----------------------------
 src/mesh/MeshRandomizer.hpp       |   2 +
 src/scheme/AcousticSolver.cpp     |   1 +
 9 files changed, 903 insertions(+), 875 deletions(-)
 create mode 100644 src/mesh/MeshFlatNodeBoundary.cpp
 create mode 100644 src/mesh/MeshFlatNodeBoundary.hpp
 create mode 100644 src/mesh/MeshLineNodeBoundary.cpp
 create mode 100644 src/mesh/MeshLineNodeBoundary.hpp
 create mode 100644 src/mesh/MeshNodeBoundary.cpp

diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt
index 6c0088831..2d429282e 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -17,6 +17,9 @@ add_library(
   LogicalConnectivityBuilder.cpp
   MeshBuilderBase.cpp
   MeshDataManager.cpp
+  MeshFlatNodeBoundary.cpp
+  MeshLineNodeBoundary.cpp
+  MeshNodeBoundary.cpp
   SynchronizerManager.cpp)
 
 # Additional dependencies
diff --git a/src/mesh/MeshFlatNodeBoundary.cpp b/src/mesh/MeshFlatNodeBoundary.cpp
new file mode 100644
index 000000000..08e0c800d
--- /dev/null
+++ b/src/mesh/MeshFlatNodeBoundary.cpp
@@ -0,0 +1,296 @@
+#include <mesh/MeshFlatNodeBoundary.hpp>
+
+template <size_t Dimension>
+void
+MeshFlatNodeBoundary<Dimension>::_checkBoundaryIsFlat(const TinyVector<Dimension, double>& normal,
+                                                      const TinyVector<Dimension, double>& origin,
+                                                      const double length,
+                                                      const Mesh<Connectivity<Dimension>>& mesh) const
+{
+  const NodeValue<const Rd>& xr = mesh.xr();
+
+  bool is_bad = false;
+
+  parallel_for(this->m_node_list.size(), [=, &is_bad](int r) {
+    const Rd& x = xr[this->m_node_list[r]];
+    if (dot(x - origin, normal) > 1E-13 * length) {
+      is_bad = true;
+    }
+  });
+
+  if (parallel::allReduceOr(is_bad)) {
+    std::ostringstream ost;
+    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
+        << ": boundary is not flat!";
+    throw NormalError(ost.str());
+  }
+}
+
+template <>
+TinyVector<1, double>
+MeshFlatNodeBoundary<1>::_getNormal(const Mesh<Connectivity<1>>& mesh)
+{
+  using R = TinyVector<1, double>;
+
+  const size_t number_of_bc_nodes = [&]() {
+    size_t number_of_bc_nodes = 0;
+    auto node_is_owned        = mesh.connectivity().nodeIsOwned();
+    for (size_t i_node = 0; i_node < m_node_list.size(); ++i_node) {
+      number_of_bc_nodes += (node_is_owned[m_node_list[i_node]]);
+    }
+    return parallel::allReduceMax(number_of_bc_nodes);
+  }();
+
+  if (number_of_bc_nodes != 1) {
+    std::ostringstream ost;
+    ost << "invalid boundary " << rang::fgB::yellow << m_boundary_name << rang::style::reset
+        << ": node boundaries in 1D require to have exactly 1 node";
+    throw NormalError(ost.str());
+  }
+
+  return R{1};
+}
+
+template <>
+TinyVector<2, double>
+MeshFlatNodeBoundary<2>::_getNormal(const Mesh<Connectivity<2>>& mesh)
+{
+  using R2 = TinyVector<2, double>;
+
+  std::array<R2, 2> bounds = this->_getBounds(mesh);
+
+  const R2& xmin = bounds[0];
+  const R2& xmax = bounds[1];
+
+  if (xmin == xmax) {
+    std::ostringstream ost;
+    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
+        << ": unable to compute normal";
+    throw NormalError(ost.str());
+  }
+
+  R2 dx = xmax - xmin;
+  dx *= 1. / l2Norm(dx);
+
+  const R2 normal(-dx[1], dx[0]);
+
+  this->_checkBoundaryIsFlat(normal, 0.5 * (xmin + xmax), l2Norm(xmax - xmin), mesh);
+
+  return normal;
+}
+
+template <>
+TinyVector<3, double>
+MeshFlatNodeBoundary<3>::_getNormal(const Mesh<Connectivity<3>>& mesh)
+{
+  using R3 = TinyVector<3, double>;
+
+  std::array<R3, 6> bounds = this->_getBounds(mesh);
+
+  const R3& xmin = bounds[0];
+  const R3& ymin = bounds[1];
+  const R3& zmin = bounds[2];
+  const R3& xmax = bounds[3];
+  const R3& ymax = bounds[4];
+  const R3& zmax = bounds[5];
+
+  const R3 u = xmax - xmin;
+  const R3 v = ymax - ymin;
+  const R3 w = zmax - zmin;
+
+  const R3 uv        = crossProduct(u, v);
+  const double uv_l2 = dot(uv, uv);
+
+  R3 normal        = uv;
+  double normal_l2 = uv_l2;
+
+  const R3 uw        = crossProduct(u, w);
+  const double uw_l2 = dot(uw, uw);
+
+  if (uw_l2 > uv_l2) {
+    normal    = uw;
+    normal_l2 = uw_l2;
+  }
+
+  const R3 vw        = crossProduct(v, w);
+  const double vw_l2 = dot(vw, vw);
+
+  if (vw_l2 > normal_l2) {
+    normal    = vw;
+    normal_l2 = vw_l2;
+  }
+
+  if (normal_l2 == 0) {
+    std::ostringstream ost;
+    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
+        << ": unable to compute normal";
+    throw NormalError(ost.str());
+  }
+
+  const double length = sqrt(normal_l2);
+
+  normal *= 1. / length;
+
+  this->_checkBoundaryIsFlat(normal, 1. / 6. * (xmin + xmax + ymin + ymax + zmin + zmax), length, mesh);
+
+  return normal;
+}
+
+template <>
+TinyVector<1, double>
+MeshFlatNodeBoundary<1>::_getOutgoingNormal(const Mesh<Connectivity<1>>& mesh)
+{
+  using R = TinyVector<1, double>;
+
+  const R normal = this->_getNormal(mesh);
+
+  double max_height = 0;
+
+  if (m_node_list.size() > 0) {
+    const NodeValue<const R>& xr    = mesh.xr();
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    const auto& node_to_cell_matrix = mesh.connectivity().nodeToCellMatrix();
+
+    const NodeId r0      = m_node_list[0];
+    const CellId j0      = node_to_cell_matrix[r0][0];
+    const auto& j0_nodes = cell_to_node_matrix[j0];
+
+    for (size_t r = 0; r < j0_nodes.size(); ++r) {
+      const double height = dot(xr[j0_nodes[r]] - xr[r0], normal);
+      if (std::abs(height) > std::abs(max_height)) {
+        max_height = height;
+      }
+    }
+  }
+
+  Array<double> max_height_array = parallel::allGather(max_height);
+  for (size_t i = 0; i < max_height_array.size(); ++i) {
+    const double height = max_height_array[i];
+    if (std::abs(height) > std::abs(max_height)) {
+      max_height = height;
+    }
+  }
+
+  if (max_height > 0) {
+    return -normal;
+  } else {
+    return normal;
+  }
+}
+
+template <>
+TinyVector<2, double>
+MeshFlatNodeBoundary<2>::_getOutgoingNormal(const Mesh<Connectivity<2>>& mesh)
+{
+  using R2 = TinyVector<2, double>;
+
+  const R2 normal = this->_getNormal(mesh);
+
+  double max_height = 0;
+
+  if (m_node_list.size() > 0) {
+    const NodeValue<const R2>& xr   = mesh.xr();
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    const auto& node_to_cell_matrix = mesh.connectivity().nodeToCellMatrix();
+
+    const NodeId r0      = m_node_list[0];
+    const CellId j0      = node_to_cell_matrix[r0][0];
+    const auto& j0_nodes = cell_to_node_matrix[j0];
+    for (size_t r = 0; r < j0_nodes.size(); ++r) {
+      const double height = dot(xr[j0_nodes[r]] - xr[r0], normal);
+      if (std::abs(height) > std::abs(max_height)) {
+        max_height = height;
+      }
+    }
+  }
+
+  Array<double> max_height_array = parallel::allGather(max_height);
+  for (size_t i = 0; i < max_height_array.size(); ++i) {
+    const double height = max_height_array[i];
+    if (std::abs(height) > std::abs(max_height)) {
+      max_height = height;
+    }
+  }
+
+  if (max_height > 0) {
+    return -normal;
+  } else {
+    return normal;
+  }
+}
+
+template <>
+TinyVector<3, double>
+MeshFlatNodeBoundary<3>::_getOutgoingNormal(const Mesh<Connectivity<3>>& mesh)
+{
+  using R3 = TinyVector<3, double>;
+
+  const R3 normal = this->_getNormal(mesh);
+
+  double max_height = 0;
+
+  if (m_node_list.size() > 0) {
+    const NodeValue<const R3>& xr   = mesh.xr();
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    const auto& node_to_cell_matrix = mesh.connectivity().nodeToCellMatrix();
+
+    const NodeId r0      = m_node_list[0];
+    const CellId j0      = node_to_cell_matrix[r0][0];
+    const auto& j0_nodes = cell_to_node_matrix[j0];
+
+    for (size_t r = 0; r < j0_nodes.size(); ++r) {
+      const double height = dot(xr[j0_nodes[r]] - xr[r0], normal);
+      if (std::abs(height) > std::abs(max_height)) {
+        max_height = height;
+      }
+    }
+  }
+
+  Array<double> max_height_array = parallel::allGather(max_height);
+  for (size_t i = 0; i < max_height_array.size(); ++i) {
+    const double height = max_height_array[i];
+    if (std::abs(height) > std::abs(max_height)) {
+      max_height = height;
+    }
+  }
+
+  if (max_height > 0) {
+    return -normal;
+  } else {
+    return normal;
+  }
+}
+
+template <size_t Dimension>
+MeshFlatNodeBoundary<Dimension>
+getMeshFlatNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const IBoundaryDescriptor& boundary_descriptor)
+{
+  for (size_t i_ref_node_list = 0; i_ref_node_list < mesh.connectivity().template numberOfRefItemList<ItemType::node>();
+       ++i_ref_node_list) {
+    const auto& ref_node_list = mesh.connectivity().template refItemList<ItemType::node>(i_ref_node_list);
+    const RefId& ref          = ref_node_list.refId();
+    if (ref == boundary_descriptor) {
+      return MeshFlatNodeBoundary<Dimension>{mesh, ref_node_list};
+    }
+  }
+  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
+       ++i_ref_face_list) {
+    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
+    const RefId& ref          = ref_face_list.refId();
+    if (ref == boundary_descriptor) {
+      return MeshFlatNodeBoundary<Dimension>{mesh, ref_face_list};
+    }
+  }
+
+  std::ostringstream ost;
+  ost << "cannot find surface with name " << rang::fgB::red << boundary_descriptor << rang::style::reset;
+
+  throw NormalError(ost.str());
+}
+
+template MeshFlatNodeBoundary<1> getMeshFlatNodeBoundary(const Mesh<Connectivity<1>>&, const IBoundaryDescriptor&);
+template MeshFlatNodeBoundary<2> getMeshFlatNodeBoundary(const Mesh<Connectivity<2>>&, const IBoundaryDescriptor&);
+template MeshFlatNodeBoundary<3> getMeshFlatNodeBoundary(const Mesh<Connectivity<3>>&, const IBoundaryDescriptor&);
diff --git a/src/mesh/MeshFlatNodeBoundary.hpp b/src/mesh/MeshFlatNodeBoundary.hpp
new file mode 100644
index 000000000..334cf03aa
--- /dev/null
+++ b/src/mesh/MeshFlatNodeBoundary.hpp
@@ -0,0 +1,60 @@
+#ifndef MESH_FLAT_NODE_BOUNDARY_HPP
+#define MESH_FLAT_NODE_BOUNDARY_HPP
+
+#include <mesh/MeshNodeBoundary.hpp>
+
+template <size_t Dimension>
+class MeshFlatNodeBoundary final : public MeshNodeBoundary<Dimension>   // clazy:exclude=copyable-polymorphic
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const Rd m_outgoing_normal;
+
+  Rd _getNormal(const Mesh<Connectivity<Dimension>>& mesh);
+
+  void _checkBoundaryIsFlat(const TinyVector<Dimension, double>& normal,
+                            const TinyVector<Dimension, double>& origin,
+                            const double length,
+                            const Mesh<Connectivity<Dimension>>& mesh) const;
+
+  Rd _getOutgoingNormal(const Mesh<Connectivity<Dimension>>& mesh);
+
+ public:
+  const Rd&
+  outgoingNormal() const
+  {
+    return m_outgoing_normal;
+  }
+
+  MeshFlatNodeBoundary& operator=(const MeshFlatNodeBoundary&) = default;
+  MeshFlatNodeBoundary& operator=(MeshFlatNodeBoundary&&) = default;
+
+  template <size_t MeshDimension>
+  friend MeshFlatNodeBoundary<MeshDimension> getMeshFlatNodeBoundary(const Mesh<Connectivity<MeshDimension>>& mesh,
+                                                                     const IBoundaryDescriptor& boundary_descriptor);
+
+ private:
+  template <typename MeshType>
+  MeshFlatNodeBoundary(const MeshType& mesh, const RefFaceList& ref_face_list)
+    : MeshNodeBoundary<Dimension>(mesh, ref_face_list), m_outgoing_normal(_getOutgoingNormal(mesh))
+  {}
+
+  template <typename MeshType>
+  MeshFlatNodeBoundary(const MeshType& mesh, const RefNodeList& ref_node_list)
+    : MeshNodeBoundary<Dimension>(mesh, ref_node_list), m_outgoing_normal(_getOutgoingNormal(mesh))
+  {}
+
+ public:
+  MeshFlatNodeBoundary()                            = default;
+  MeshFlatNodeBoundary(const MeshFlatNodeBoundary&) = default;
+  MeshFlatNodeBoundary(MeshFlatNodeBoundary&&)      = default;
+  ~MeshFlatNodeBoundary()                           = default;
+};
+
+template <size_t Dimension>
+MeshFlatNodeBoundary<Dimension> getMeshFlatNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh,
+                                                        const IBoundaryDescriptor& boundary_descriptor);
+
+#endif   // MESH_FLAT_NODE_BOUNDARY_HPP
diff --git a/src/mesh/MeshLineNodeBoundary.cpp b/src/mesh/MeshLineNodeBoundary.cpp
new file mode 100644
index 000000000..391a7aeca
--- /dev/null
+++ b/src/mesh/MeshLineNodeBoundary.cpp
@@ -0,0 +1,143 @@
+#include <mesh/MeshLineNodeBoundary.hpp>
+
+template <size_t Dimension>
+void
+MeshLineNodeBoundary<Dimension>::_checkBoundaryIsLine(const TinyVector<Dimension, double>& direction,
+                                                      const TinyVector<Dimension, double>& origin,
+                                                      const double length,
+                                                      const Mesh<Connectivity<Dimension>>& mesh) const
+{
+  using Rdxd = TinyMatrix<Dimension>;
+
+  const NodeValue<const Rd>& xr = mesh.xr();
+
+  Rdxd P = Rdxd{identity} - tensorProduct(direction, direction);
+
+  bool is_bad = false;
+  parallel_for(this->m_node_list.size(), [=, &is_bad](int node_id) {
+    const Rd& x    = xr[this->m_node_list[node_id]];
+    const Rd delta = x - origin;
+    if (dot(P * delta, direction) > 1E-13 * length) {
+      is_bad = true;
+    }
+  });
+
+  if (parallel::allReduceOr(is_bad)) {
+    std::ostringstream ost;
+    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
+        << ": boundary is not a line!";
+    throw NormalError(ost.str());
+  }
+}
+
+template <>
+template <>
+TinyVector<1>
+MeshLineNodeBoundary<1>::_getDirection(const Mesh<Connectivity<1>>&)
+{
+  throw UnexpectedError("MeshLineNodeBoundary makes no sense in dimension 1");
+}
+
+template <>
+template <>
+TinyVector<2>
+MeshLineNodeBoundary<2>::_getDirection(const Mesh<Connectivity<2>>& mesh)
+{
+  using R2 = TinyVector<2, double>;
+
+  std::array<R2, 2> bounds = this->_getBounds(mesh);
+
+  const R2& xmin = bounds[0];
+  const R2& xmax = bounds[1];
+
+  if (xmin == xmax) {
+    std::ostringstream ost;
+    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
+        << ": unable to compute direction";
+    throw NormalError(ost.str());
+  }
+
+  R2 direction        = xmax - xmin;
+  const double length = l2Norm(direction);
+  direction *= 1. / length;
+
+  this->_checkBoundaryIsLine(direction, xmin, length, mesh);
+
+  return direction;
+}
+
+template <>
+template <>
+TinyVector<3>
+MeshLineNodeBoundary<3>::_getDirection(const Mesh<Connectivity<3>>& mesh)
+{
+  using R3 = TinyVector<3, double>;
+
+  std::array<R3, 6> bounds = this->_getBounds(mesh);
+
+  const R3& xmin = bounds[0];
+  const R3& ymin = bounds[1];
+  const R3& zmin = bounds[2];
+  const R3& xmax = bounds[3];
+  const R3& ymax = bounds[4];
+  const R3& zmax = bounds[5];
+
+  const R3 u = xmax - xmin;
+  const R3 v = ymax - ymin;
+  const R3 w = zmax - zmin;
+
+  R3 direction = u;
+  if (dot(v, v) > dot(direction, direction)) {
+    direction = v;
+  }
+
+  if (dot(w, w) > dot(direction, direction)) {
+    direction = w;
+  }
+
+  const double length = l2Norm(direction);
+  direction *= 1. / length;
+
+  this->_checkBoundaryIsLine(direction, xmin, length, mesh);
+
+  return direction;
+}
+
+template <size_t Dimension>
+MeshLineNodeBoundary<Dimension>
+getMeshLineNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const IBoundaryDescriptor& boundary_descriptor)
+{
+  for (size_t i_ref_node_list = 0; i_ref_node_list < mesh.connectivity().template numberOfRefItemList<ItemType::node>();
+       ++i_ref_node_list) {
+    const auto& ref_node_list = mesh.connectivity().template refItemList<ItemType::node>(i_ref_node_list);
+    const RefId& ref          = ref_node_list.refId();
+    if (ref == boundary_descriptor) {
+      return MeshLineNodeBoundary<Dimension>{mesh, ref_node_list};
+    }
+  }
+  for (size_t i_ref_edge_list = 0; i_ref_edge_list < mesh.connectivity().template numberOfRefItemList<ItemType::edge>();
+       ++i_ref_edge_list) {
+    const auto& ref_edge_list = mesh.connectivity().template refItemList<ItemType::edge>(i_ref_edge_list);
+    const RefId& ref          = ref_edge_list.refId();
+    if (ref == boundary_descriptor) {
+      return MeshLineNodeBoundary<Dimension>{mesh, ref_edge_list};
+    }
+  }
+  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
+       ++i_ref_face_list) {
+    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
+    const RefId& ref          = ref_face_list.refId();
+    if (ref == boundary_descriptor) {
+      return MeshLineNodeBoundary<Dimension>{mesh, ref_face_list};
+    }
+  }
+
+  std::ostringstream ost;
+  ost << "cannot find surface with name " << rang::fgB::red << boundary_descriptor << rang::style::reset;
+
+  throw NormalError(ost.str());
+}
+
+template MeshLineNodeBoundary<1> getMeshLineNodeBoundary(const Mesh<Connectivity<1>>&, const IBoundaryDescriptor&);
+template MeshLineNodeBoundary<2> getMeshLineNodeBoundary(const Mesh<Connectivity<2>>&, const IBoundaryDescriptor&);
+template MeshLineNodeBoundary<3> getMeshLineNodeBoundary(const Mesh<Connectivity<3>>&, const IBoundaryDescriptor&);
diff --git a/src/mesh/MeshLineNodeBoundary.hpp b/src/mesh/MeshLineNodeBoundary.hpp
new file mode 100644
index 000000000..0ae35d058
--- /dev/null
+++ b/src/mesh/MeshLineNodeBoundary.hpp
@@ -0,0 +1,65 @@
+#ifndef MESH_LINE_NODE_BOUNDARY_HPP
+#define MESH_LINE_NODE_BOUNDARY_HPP
+
+#include <algebra/TinyMatrix.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+
+template <size_t Dimension>
+class MeshLineNodeBoundary final : public MeshNodeBoundary<Dimension>   // clazy:exclude=copyable-polymorphic
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const Rd m_direction;
+
+  template <size_t MeshDimension>
+  TinyVector<MeshDimension> _getDirection(const Mesh<Connectivity<MeshDimension>>&);
+
+  void _checkBoundaryIsLine(const TinyVector<Dimension, double>& direction,
+                            const TinyVector<Dimension, double>& origin,
+                            const double length,
+                            const Mesh<Connectivity<Dimension>>& mesh) const;
+
+ public:
+  template <size_t MeshDimension>
+  friend MeshLineNodeBoundary<MeshDimension> getMeshLineNodeBoundary(const Mesh<Connectivity<MeshDimension>>& mesh,
+                                                                     const IBoundaryDescriptor& boundary_descriptor);
+
+  PUGS_INLINE
+  const Rd&
+  direction() const
+  {
+    return m_direction;
+  }
+
+  MeshLineNodeBoundary& operator=(const MeshLineNodeBoundary&) = default;
+  MeshLineNodeBoundary& operator=(MeshLineNodeBoundary&&) = default;
+
+ private:
+  MeshLineNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const RefEdgeList& ref_edge_list)
+    : MeshNodeBoundary<Dimension>(mesh, ref_edge_list), m_direction(_getDirection(mesh))
+  {
+    ;
+  }
+
+  MeshLineNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const RefFaceList& ref_face_list)
+    : MeshNodeBoundary<Dimension>(mesh, ref_face_list), m_direction(_getDirection(mesh))
+  {}
+
+  MeshLineNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const RefNodeList& ref_node_list)
+    : MeshNodeBoundary<Dimension>(mesh, ref_node_list), m_direction(_getDirection(mesh))
+  {}
+
+ public:
+  MeshLineNodeBoundary()                            = default;
+  MeshLineNodeBoundary(const MeshLineNodeBoundary&) = default;
+  MeshLineNodeBoundary(MeshLineNodeBoundary&&)      = default;
+  ~MeshLineNodeBoundary()                           = default;
+};
+
+template <size_t Dimension>
+MeshLineNodeBoundary<Dimension> getMeshLineNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh,
+                                                        const IBoundaryDescriptor& boundary_descriptor);
+
+#endif   // MESH_LINE_NODE_BOUNDARY_HPP
diff --git a/src/mesh/MeshNodeBoundary.cpp b/src/mesh/MeshNodeBoundary.cpp
new file mode 100644
index 000000000..94fed7f5c
--- /dev/null
+++ b/src/mesh/MeshNodeBoundary.cpp
@@ -0,0 +1,323 @@
+#include <mesh/MeshNodeBoundary.hpp>
+
+template <>
+std::array<TinyVector<2>, 2>
+MeshNodeBoundary<2>::_getBounds(const Mesh<Connectivity<2>>& mesh) const
+{
+  using R2 = TinyVector<2, double>;
+
+  const NodeValue<const R2>& xr = mesh.xr();
+
+  std::array<R2, 2> bounds;
+  R2& xmin = bounds[0];
+  R2& xmax = bounds[1];
+
+  xmin = R2{std::numeric_limits<double>::max(), std::numeric_limits<double>::max()};
+  xmax = R2{-std::numeric_limits<double>::max(), -std::numeric_limits<double>::max()};
+
+  auto update_xmin = [](const R2& x, R2& xmin) {
+    if ((x[0] < xmin[0]) or ((x[0] == xmin[0]) and (x[1] < xmin[1]))) {
+      xmin = x;
+    }
+  };
+
+  auto update_xmax = [](const R2& x, R2& xmax) {
+    if ((x[0] > xmax[0]) or ((x[0] == xmax[0]) and (x[1] > xmax[1]))) {
+      xmax = x;
+    }
+  };
+
+  for (size_t r = 0; r < m_node_list.size(); ++r) {
+    const R2& x = xr[m_node_list[r]];
+    update_xmin(x, xmin);
+    update_xmax(x, xmax);
+  }
+
+  if (parallel::size() > 1) {
+    Array<R2> xmin_array = parallel::allGather(xmin);
+    Array<R2> xmax_array = parallel::allGather(xmax);
+    for (size_t i = 0; i < xmin_array.size(); ++i) {
+      update_xmin(xmin_array[i], xmin);
+    }
+    for (size_t i = 0; i < xmax_array.size(); ++i) {
+      update_xmax(xmax_array[i], xmax);
+    }
+  }
+
+  return bounds;
+}
+
+template <>
+std::array<TinyVector<3>, 6>
+MeshNodeBoundary<3>::_getBounds(const Mesh<Connectivity<3>>& mesh) const
+{
+  using R3 = TinyVector<3, double>;
+
+  auto update_xmin = [](const R3& x, R3& xmin) {
+    // XMIN: X.xmin X.ymax X.zmax
+    if ((x[0] < xmin[0]) or ((x[0] == xmin[0]) and (x[1] > xmin[1])) or
+        ((x[0] == xmin[0]) and (x[1] == xmin[1]) and (x[2] > xmin[2]))) {
+      xmin = x;
+    }
+  };
+
+  auto update_xmax = [](const R3& x, R3& xmax) {
+    // XMAX: X.xmax X.ymin X.zmin
+    if ((x[0] > xmax[0]) or ((x[0] == xmax[0]) and (x[1] < xmax[1])) or
+        ((x[0] == xmax[0]) and (x[1] == xmax[1]) and (x[2] < xmax[2]))) {
+      xmax = x;
+    }
+  };
+
+  auto update_ymin = [](const R3& x, R3& ymin) {
+    // YMIN: X.ymin X.zmax X.xmin
+    if ((x[1] < ymin[1]) or ((x[1] == ymin[1]) and (x[2] > ymin[2])) or
+        ((x[1] == ymin[1]) and (x[2] == ymin[2]) and (x[0] < ymin[0]))) {
+      ymin = x;
+    }
+  };
+
+  auto update_ymax = [](const R3& x, R3& ymax) {
+    // YMAX: X.ymax X.zmin X.xmax
+    if ((x[1] > ymax[1]) or ((x[1] == ymax[1]) and (x[2] < ymax[2])) or
+        ((x[1] == ymax[1]) and (x[2] == ymax[1]) and (x[0] > ymax[0]))) {
+      ymax = x;
+    }
+  };
+
+  auto update_zmin = [](const R3& x, R3& zmin) {
+    // ZMIN: X.zmin X.xmin X.ymin
+    if ((x[2] < zmin[2]) or ((x[2] == zmin[2]) and (x[2] < zmin[2])) or
+        ((x[1] == zmin[1]) and (x[2] == zmin[1]) and (x[0] < zmin[0]))) {
+      zmin = x;
+    }
+  };
+
+  auto update_zmax = [](const R3& x, R3& zmax) {
+    // ZMAX: X.zmax X.xmax X.ymax
+    if ((x[2] > zmax[2]) or ((x[2] == zmax[2]) and (x[2] > zmax[2])) or
+        ((x[1] == zmax[1]) and (x[2] == zmax[1]) and (x[0] > zmax[0]))) {
+      zmax = x;
+    }
+  };
+
+  std::array<R3, 6> bounds;
+  R3& xmin = bounds[0];
+  R3& ymin = bounds[1];
+  R3& zmin = bounds[2];
+  R3& xmax = bounds[3];
+  R3& ymax = bounds[4];
+  R3& zmax = bounds[5];
+
+  xmin = R3{std::numeric_limits<double>::max(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max()};
+  ymin = xmin;
+  zmin = xmin;
+
+  xmax = -xmin;
+  ymax = xmax;
+  zmax = xmax;
+
+  const NodeValue<const R3>& xr = mesh.xr();
+
+  for (size_t r = 0; r < m_node_list.size(); ++r) {
+    const R3& x = xr[m_node_list[r]];
+    update_xmin(x, xmin);
+    update_xmax(x, xmax);
+    update_ymin(x, ymin);
+    update_ymax(x, ymax);
+    update_zmin(x, zmin);
+    update_zmax(x, zmax);
+  }
+
+  if (parallel::size() > 0) {
+    Array<const R3> xmin_array = parallel::allGather(xmin);
+    Array<const R3> xmax_array = parallel::allGather(xmax);
+    Array<const R3> ymin_array = parallel::allGather(ymin);
+    Array<const R3> ymax_array = parallel::allGather(ymax);
+    Array<const R3> zmin_array = parallel::allGather(zmin);
+    Array<const R3> zmax_array = parallel::allGather(zmax);
+
+    for (size_t i = 0; i < xmin_array.size(); ++i) {
+      update_xmin(xmin_array[i], xmin);
+    }
+    for (size_t i = 0; i < ymin_array.size(); ++i) {
+      update_ymin(ymin_array[i], ymin);
+    }
+    for (size_t i = 0; i < zmin_array.size(); ++i) {
+      update_zmin(zmin_array[i], zmin);
+    }
+    for (size_t i = 0; i < xmax_array.size(); ++i) {
+      update_xmax(xmax_array[i], xmax);
+    }
+    for (size_t i = 0; i < ymax_array.size(); ++i) {
+      update_ymax(ymax_array[i], ymax);
+    }
+    for (size_t i = 0; i < zmax_array.size(); ++i) {
+      update_zmax(zmax_array[i], zmax);
+    }
+  }
+
+  return bounds;
+}
+
+template <size_t Dimension>
+MeshNodeBoundary<Dimension>::MeshNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh,
+                                              const RefFaceList& ref_face_list)
+  : m_boundary_name(ref_face_list.refId().tagName())
+{
+  const auto& face_to_cell_matrix = mesh.connectivity().faceToCellMatrix();
+
+  const Array<const FaceId>& face_list = ref_face_list.list();
+
+  bool is_bad = false;
+  parallel_for(face_list.size(), [=, &is_bad](int l) {
+    const auto& face_cells = face_to_cell_matrix[face_list[l]];
+    if (face_cells.size() > 1) {
+      is_bad = true;
+    }
+  });
+
+  if (parallel::allReduceOr(is_bad)) {
+    std::ostringstream ost;
+    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
+        << ": inner faces cannot be used to define mesh boundaries";
+    throw NormalError(ost.str());
+  }
+
+  Kokkos::vector<unsigned int> node_ids;
+  // not enough but should reduce significantly the number of resizing
+  node_ids.reserve(Dimension * face_list.size());
+  const auto& face_to_node_matrix = mesh.connectivity().faceToNodeMatrix();
+
+  for (size_t l = 0; l < face_list.size(); ++l) {
+    const FaceId face_number = face_list[l];
+    const auto& face_nodes   = face_to_node_matrix[face_number];
+
+    for (size_t r = 0; r < face_nodes.size(); ++r) {
+      node_ids.push_back(face_nodes[r]);
+    }
+  }
+  std::sort(node_ids.begin(), node_ids.end());
+  auto last = std::unique(node_ids.begin(), node_ids.end());
+  node_ids.resize(std::distance(node_ids.begin(), last));
+
+  Array<NodeId> node_list(node_ids.size());
+  parallel_for(
+    node_ids.size(), PUGS_LAMBDA(int r) { node_list[r] = node_ids[r]; });
+  m_node_list = node_list;
+}
+
+template <size_t Dimension>
+MeshNodeBoundary<Dimension>::MeshNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh,
+                                              const RefEdgeList& ref_edge_list)
+  : m_boundary_name(ref_edge_list.refId().tagName())
+{
+  const auto& edge_to_face_matrix = mesh.connectivity().edgeToFaceMatrix();
+  const auto& face_to_cell_matrix = mesh.connectivity().faceToCellMatrix();
+
+  auto edge_is_owned = mesh.connectivity().edgeIsOwned();
+
+  const Array<const EdgeId>& edge_list = ref_edge_list.list();
+
+  bool is_bad = false;
+  parallel_for(edge_list.size(), [=, &is_bad](int l) {
+    const EdgeId edge_id = edge_list[l];
+    if (edge_is_owned[edge_id]) {
+      const auto& edge_faces             = edge_to_face_matrix[edge_id];
+      bool is_connected_to_boundary_face = false;
+      for (size_t i_edge_face = 0; i_edge_face < edge_faces.size(); ++i_edge_face) {
+        const FaceId edge_face_id = edge_faces[i_edge_face];
+        if (face_to_cell_matrix[edge_face_id].size() == 1) {
+          is_connected_to_boundary_face = true;
+        }
+      }
+      if (not is_connected_to_boundary_face) {
+        is_bad = true;
+      }
+    }
+  });
+
+  if (parallel::allReduceOr(is_bad)) {
+    std::ostringstream ost;
+    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
+        << ": inner edges cannot be used to define mesh boundaries";
+    throw NormalError(ost.str());
+  }
+
+  Kokkos::vector<unsigned int> node_ids;
+  node_ids.reserve(2 * edge_list.size());
+  const auto& edge_to_node_matrix = mesh.connectivity().edgeToNodeMatrix();
+
+  for (size_t l = 0; l < edge_list.size(); ++l) {
+    const EdgeId edge_number = edge_list[l];
+    const auto& edge_nodes   = edge_to_node_matrix[edge_number];
+
+    for (size_t r = 0; r < edge_nodes.size(); ++r) {
+      node_ids.push_back(edge_nodes[r]);
+    }
+  }
+  std::sort(node_ids.begin(), node_ids.end());
+  auto last = std::unique(node_ids.begin(), node_ids.end());
+  node_ids.resize(std::distance(node_ids.begin(), last));
+
+  Array<NodeId> node_list(node_ids.size());
+  parallel_for(
+    node_ids.size(), PUGS_LAMBDA(int r) { node_list[r] = node_ids[r]; });
+  m_node_list = node_list;
+}
+
+template <size_t Dimension>
+MeshNodeBoundary<Dimension>::MeshNodeBoundary(const Mesh<Connectivity<Dimension>>&, const RefNodeList& ref_node_list)
+  : m_node_list(ref_node_list.list()), m_boundary_name(ref_node_list.refId().tagName())
+{}
+
+template MeshNodeBoundary<1>::MeshNodeBoundary(const Mesh<Connectivity<1>>&, const RefFaceList&);
+template MeshNodeBoundary<2>::MeshNodeBoundary(const Mesh<Connectivity<2>>&, const RefFaceList&);
+template MeshNodeBoundary<3>::MeshNodeBoundary(const Mesh<Connectivity<3>>&, const RefFaceList&);
+
+template MeshNodeBoundary<1>::MeshNodeBoundary(const Mesh<Connectivity<1>>&, const RefEdgeList&);
+template MeshNodeBoundary<2>::MeshNodeBoundary(const Mesh<Connectivity<2>>&, const RefEdgeList&);
+template MeshNodeBoundary<3>::MeshNodeBoundary(const Mesh<Connectivity<3>>&, const RefEdgeList&);
+
+template MeshNodeBoundary<1>::MeshNodeBoundary(const Mesh<Connectivity<1>>&, const RefNodeList&);
+template MeshNodeBoundary<2>::MeshNodeBoundary(const Mesh<Connectivity<2>>&, const RefNodeList&);
+template MeshNodeBoundary<3>::MeshNodeBoundary(const Mesh<Connectivity<3>>&, const RefNodeList&);
+
+template <size_t Dimension>
+MeshNodeBoundary<Dimension>
+getMeshNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const IBoundaryDescriptor& boundary_descriptor)
+{
+  for (size_t i_ref_node_list = 0; i_ref_node_list < mesh.connectivity().template numberOfRefItemList<ItemType::node>();
+       ++i_ref_node_list) {
+    const auto& ref_node_list = mesh.connectivity().template refItemList<ItemType::node>(i_ref_node_list);
+    const RefId& ref          = ref_node_list.refId();
+    if (ref == boundary_descriptor) {
+      return MeshNodeBoundary<Dimension>{mesh, ref_node_list};
+    }
+  }
+  for (size_t i_ref_edge_list = 0; i_ref_edge_list < mesh.connectivity().template numberOfRefItemList<ItemType::edge>();
+       ++i_ref_edge_list) {
+    const auto& ref_edge_list = mesh.connectivity().template refItemList<ItemType::edge>(i_ref_edge_list);
+    const RefId& ref          = ref_edge_list.refId();
+    if (ref == boundary_descriptor) {
+      return MeshNodeBoundary<Dimension>{mesh, ref_edge_list};
+    }
+  }
+  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
+       ++i_ref_face_list) {
+    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
+    const RefId& ref          = ref_face_list.refId();
+    if (ref == boundary_descriptor) {
+      return MeshNodeBoundary<Dimension>{mesh, ref_face_list};
+    }
+  }
+
+  std::ostringstream ost;
+  ost << "cannot find surface with name " << rang::fgB::red << boundary_descriptor << rang::style::reset;
+
+  throw NormalError(ost.str());
+}
+
+template MeshNodeBoundary<1> getMeshNodeBoundary(const Mesh<Connectivity<1>>&, const IBoundaryDescriptor&);
+template MeshNodeBoundary<2> getMeshNodeBoundary(const Mesh<Connectivity<2>>&, const IBoundaryDescriptor&);
+template MeshNodeBoundary<3> getMeshNodeBoundary(const Mesh<Connectivity<3>>&, const IBoundaryDescriptor&);
diff --git a/src/mesh/MeshNodeBoundary.hpp b/src/mesh/MeshNodeBoundary.hpp
index ab98ded0e..927e00d91 100644
--- a/src/mesh/MeshNodeBoundary.hpp
+++ b/src/mesh/MeshNodeBoundary.hpp
@@ -2,10 +2,8 @@
 #define MESH_NODE_BOUNDARY_HPP
 
 #include <Kokkos_Vector.hpp>
-#include <algebra/TinyMatrix.hpp>
 #include <algebra/TinyVector.hpp>
 #include <mesh/Connectivity.hpp>
-#include <mesh/ConnectivityMatrix.hpp>
 #include <mesh/IBoundaryDescriptor.hpp>
 #include <mesh/ItemValue.hpp>
 #include <mesh/Mesh.hpp>
@@ -21,13 +19,13 @@ class MeshNodeBoundary   // clazy:exclude=copyable-polymorphic
   Array<const NodeId> m_node_list;
   std::string m_boundary_name;
 
-  template <typename MeshType>
-  std::array<TinyVector<Dimension>, Dimension*(Dimension - 1)> _getBounds(const MeshType& mesh) const;
+  std::array<TinyVector<Dimension>, Dimension*(Dimension - 1)> _getBounds(
+    const Mesh<Connectivity<Dimension>>& mesh) const;
 
  public:
-  template <typename MeshType>
-  friend MeshNodeBoundary<MeshType::Dimension> getMeshNodeBoundary(const MeshType& mesh,
-                                                                   const IBoundaryDescriptor& boundary_descriptor);
+  template <size_t MeshDimension>
+  friend MeshNodeBoundary<MeshDimension> getMeshNodeBoundary(const Mesh<Connectivity<MeshDimension>>& mesh,
+                                                             const IBoundaryDescriptor& boundary_descriptor);
 
   MeshNodeBoundary& operator=(const MeshNodeBoundary&) = default;
   MeshNodeBoundary& operator=(MeshNodeBoundary&&) = default;
@@ -39,119 +37,9 @@ class MeshNodeBoundary   // clazy:exclude=copyable-polymorphic
   }
 
  protected:
-  template <typename MeshType>
-  MeshNodeBoundary(const MeshType& mesh, const RefFaceList& ref_face_list)
-    : m_boundary_name(ref_face_list.refId().tagName())
-  {
-    static_assert(Dimension == MeshType::Dimension);
-    const auto& face_to_cell_matrix = mesh.connectivity().faceToCellMatrix();
-
-    const Array<const FaceId>& face_list = ref_face_list.list();
-
-    bool is_bad = false;
-    parallel_for(face_list.size(), [=, &is_bad](int l) {
-      const auto& face_cells = face_to_cell_matrix[face_list[l]];
-      if (face_cells.size() > 1) {
-        is_bad = true;
-      }
-    });
-
-    if (parallel::allReduceOr(is_bad)) {
-      std::ostringstream ost;
-      ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
-          << ": inner faces cannot be used to define mesh boundaries";
-      throw NormalError(ost.str());
-    }
-
-    Kokkos::vector<unsigned int> node_ids;
-    // not enough but should reduce significantly the number of resizing
-    node_ids.reserve(Dimension * face_list.size());
-    const auto& face_to_node_matrix = mesh.connectivity().faceToNodeMatrix();
-
-    for (size_t l = 0; l < face_list.size(); ++l) {
-      const FaceId face_number = face_list[l];
-      const auto& face_nodes   = face_to_node_matrix[face_number];
-
-      for (size_t r = 0; r < face_nodes.size(); ++r) {
-        node_ids.push_back(face_nodes[r]);
-      }
-    }
-    std::sort(node_ids.begin(), node_ids.end());
-    auto last = std::unique(node_ids.begin(), node_ids.end());
-    node_ids.resize(std::distance(node_ids.begin(), last));
-
-    Array<NodeId> node_list(node_ids.size());
-    parallel_for(
-      node_ids.size(), PUGS_LAMBDA(int r) { node_list[r] = node_ids[r]; });
-    m_node_list = node_list;
-  }
-
-  template <typename MeshType>
-  MeshNodeBoundary(const MeshType& mesh, const RefEdgeList& ref_edge_list)
-    : m_boundary_name(ref_edge_list.refId().tagName())
-  {
-    static_assert(Dimension == MeshType::Dimension);
-    const auto& edge_to_face_matrix = mesh.connectivity().edgeToFaceMatrix();
-    const auto& face_to_cell_matrix = mesh.connectivity().faceToCellMatrix();
-
-    auto edge_is_owned = mesh.connectivity().edgeIsOwned();
-
-    const Array<const EdgeId>& edge_list = ref_edge_list.list();
-
-    bool is_bad = false;
-    parallel_for(edge_list.size(), [=, &is_bad](int l) {
-      const EdgeId edge_id = edge_list[l];
-      if (edge_is_owned[edge_id]) {
-        const auto& edge_faces             = edge_to_face_matrix[edge_id];
-        bool is_connected_to_boundary_face = false;
-        for (size_t i_edge_face = 0; i_edge_face < edge_faces.size(); ++i_edge_face) {
-          const FaceId edge_face_id = edge_faces[i_edge_face];
-          if (face_to_cell_matrix[edge_face_id].size() == 1) {
-            is_connected_to_boundary_face = true;
-          }
-        }
-        if (not is_connected_to_boundary_face) {
-          is_bad = true;
-        }
-      }
-    });
-
-    if (parallel::allReduceOr(is_bad)) {
-      std::ostringstream ost;
-      ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
-          << ": inner edges cannot be used to define mesh boundaries";
-      throw NormalError(ost.str());
-    }
-
-    Kokkos::vector<unsigned int> node_ids;
-    node_ids.reserve(2 * edge_list.size());
-    const auto& edge_to_node_matrix = mesh.connectivity().edgeToNodeMatrix();
-
-    for (size_t l = 0; l < edge_list.size(); ++l) {
-      const EdgeId edge_number = edge_list[l];
-      const auto& edge_nodes   = edge_to_node_matrix[edge_number];
-
-      for (size_t r = 0; r < edge_nodes.size(); ++r) {
-        node_ids.push_back(edge_nodes[r]);
-      }
-    }
-    std::sort(node_ids.begin(), node_ids.end());
-    auto last = std::unique(node_ids.begin(), node_ids.end());
-    node_ids.resize(std::distance(node_ids.begin(), last));
-
-    Array<NodeId> node_list(node_ids.size());
-    parallel_for(
-      node_ids.size(), PUGS_LAMBDA(int r) { node_list[r] = node_ids[r]; });
-    m_node_list = node_list;
-  }
-
-  template <typename MeshType>
-  MeshNodeBoundary(const MeshType&, const RefNodeList& ref_node_list)
-    : m_node_list(ref_node_list.list()), m_boundary_name(ref_node_list.refId().tagName())
-
-  {
-    static_assert(Dimension == MeshType::Dimension);
-  }
+  MeshNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const RefFaceList& ref_face_list);
+  MeshNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh, const RefEdgeList& ref_edge_list);
+  MeshNodeBoundary(const Mesh<Connectivity<Dimension>>&, const RefNodeList& ref_node_list);
 
  public:
   MeshNodeBoundary(const MeshNodeBoundary&) = default;
@@ -161,761 +49,8 @@ class MeshNodeBoundary   // clazy:exclude=copyable-polymorphic
   virtual ~MeshNodeBoundary() = default;
 };
 
-template <typename MeshType>
-MeshNodeBoundary<MeshType::Dimension>
-getMeshNodeBoundary(const MeshType& mesh, const IBoundaryDescriptor& boundary_descriptor)
-{
-  for (size_t i_ref_node_list = 0; i_ref_node_list < mesh.connectivity().template numberOfRefItemList<ItemType::node>();
-       ++i_ref_node_list) {
-    const auto& ref_node_list = mesh.connectivity().template refItemList<ItemType::node>(i_ref_node_list);
-    const RefId& ref          = ref_node_list.refId();
-    if (ref == boundary_descriptor) {
-      return MeshNodeBoundary<MeshType::Dimension>{mesh, ref_node_list};
-    }
-  }
-  for (size_t i_ref_edge_list = 0; i_ref_edge_list < mesh.connectivity().template numberOfRefItemList<ItemType::edge>();
-       ++i_ref_edge_list) {
-    const auto& ref_edge_list = mesh.connectivity().template refItemList<ItemType::edge>(i_ref_edge_list);
-    const RefId& ref          = ref_edge_list.refId();
-    if (ref == boundary_descriptor) {
-      return MeshNodeBoundary<MeshType::Dimension>{mesh, ref_edge_list};
-    }
-  }
-  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
-       ++i_ref_face_list) {
-    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
-    const RefId& ref          = ref_face_list.refId();
-    if (ref == boundary_descriptor) {
-      return MeshNodeBoundary<MeshType::Dimension>{mesh, ref_face_list};
-    }
-  }
-
-  std::ostringstream ost;
-  ost << "cannot find surface with name " << rang::fgB::red << boundary_descriptor << rang::style::reset;
-
-  throw NormalError(ost.str());
-}
-
-template <>
-template <typename MeshType>
-std::array<TinyVector<2>, 2>
-MeshNodeBoundary<2>::_getBounds(const MeshType& mesh) const
-{
-  static_assert(MeshType::Dimension == 2);
-
-  using R2 = TinyVector<2, double>;
-
-  const NodeValue<const R2>& xr = mesh.xr();
-
-  std::array<R2, 2> bounds;
-  R2& xmin = bounds[0];
-  R2& xmax = bounds[1];
-
-  xmin = R2{std::numeric_limits<double>::max(), std::numeric_limits<double>::max()};
-  xmax = R2{-std::numeric_limits<double>::max(), -std::numeric_limits<double>::max()};
-
-  auto update_xmin = [](const R2& x, R2& xmin) {
-    if ((x[0] < xmin[0]) or ((x[0] == xmin[0]) and (x[1] < xmin[1]))) {
-      xmin = x;
-    }
-  };
-
-  auto update_xmax = [](const R2& x, R2& xmax) {
-    if ((x[0] > xmax[0]) or ((x[0] == xmax[0]) and (x[1] > xmax[1]))) {
-      xmax = x;
-    }
-  };
-
-  for (size_t r = 0; r < m_node_list.size(); ++r) {
-    const R2& x = xr[m_node_list[r]];
-    update_xmin(x, xmin);
-    update_xmax(x, xmax);
-  }
-
-  if (parallel::size() > 1) {
-    Array<R2> xmin_array = parallel::allGather(xmin);
-    Array<R2> xmax_array = parallel::allGather(xmax);
-    for (size_t i = 0; i < xmin_array.size(); ++i) {
-      update_xmin(xmin_array[i], xmin);
-    }
-    for (size_t i = 0; i < xmax_array.size(); ++i) {
-      update_xmax(xmax_array[i], xmax);
-    }
-  }
-
-  return bounds;
-}
-
-template <>
-template <typename MeshType>
-std::array<TinyVector<3>, 6>
-MeshNodeBoundary<3>::_getBounds(const MeshType& mesh) const
-{
-  static_assert(MeshType::Dimension == 3);
-
-  using R3 = TinyVector<3, double>;
-
-  auto update_xmin = [](const R3& x, R3& xmin) {
-    // XMIN: X.xmin X.ymax X.zmax
-    if ((x[0] < xmin[0]) or ((x[0] == xmin[0]) and (x[1] > xmin[1])) or
-        ((x[0] == xmin[0]) and (x[1] == xmin[1]) and (x[2] > xmin[2]))) {
-      xmin = x;
-    }
-  };
-
-  auto update_xmax = [](const R3& x, R3& xmax) {
-    // XMAX: X.xmax X.ymin X.zmin
-    if ((x[0] > xmax[0]) or ((x[0] == xmax[0]) and (x[1] < xmax[1])) or
-        ((x[0] == xmax[0]) and (x[1] == xmax[1]) and (x[2] < xmax[2]))) {
-      xmax = x;
-    }
-  };
-
-  auto update_ymin = [](const R3& x, R3& ymin) {
-    // YMIN: X.ymin X.zmax X.xmin
-    if ((x[1] < ymin[1]) or ((x[1] == ymin[1]) and (x[2] > ymin[2])) or
-        ((x[1] == ymin[1]) and (x[2] == ymin[2]) and (x[0] < ymin[0]))) {
-      ymin = x;
-    }
-  };
-
-  auto update_ymax = [](const R3& x, R3& ymax) {
-    // YMAX: X.ymax X.zmin X.xmax
-    if ((x[1] > ymax[1]) or ((x[1] == ymax[1]) and (x[2] < ymax[2])) or
-        ((x[1] == ymax[1]) and (x[2] == ymax[1]) and (x[0] > ymax[0]))) {
-      ymax = x;
-    }
-  };
-
-  auto update_zmin = [](const R3& x, R3& zmin) {
-    // ZMIN: X.zmin X.xmin X.ymin
-    if ((x[2] < zmin[2]) or ((x[2] == zmin[2]) and (x[2] < zmin[2])) or
-        ((x[1] == zmin[1]) and (x[2] == zmin[1]) and (x[0] < zmin[0]))) {
-      zmin = x;
-    }
-  };
-
-  auto update_zmax = [](const R3& x, R3& zmax) {
-    // ZMAX: X.zmax X.xmax X.ymax
-    if ((x[2] > zmax[2]) or ((x[2] == zmax[2]) and (x[2] > zmax[2])) or
-        ((x[1] == zmax[1]) and (x[2] == zmax[1]) and (x[0] > zmax[0]))) {
-      zmax = x;
-    }
-  };
-
-  std::array<R3, 6> bounds;
-  R3& xmin = bounds[0];
-  R3& ymin = bounds[1];
-  R3& zmin = bounds[2];
-  R3& xmax = bounds[3];
-  R3& ymax = bounds[4];
-  R3& zmax = bounds[5];
-
-  xmin = R3{std::numeric_limits<double>::max(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max()};
-  ymin = xmin;
-  zmin = xmin;
-
-  xmax = -xmin;
-  ymax = xmax;
-  zmax = xmax;
-
-  const NodeValue<const R3>& xr = mesh.xr();
-
-  for (size_t r = 0; r < m_node_list.size(); ++r) {
-    const R3& x = xr[m_node_list[r]];
-    update_xmin(x, xmin);
-    update_xmax(x, xmax);
-    update_ymin(x, ymin);
-    update_ymax(x, ymax);
-    update_zmin(x, zmin);
-    update_zmax(x, zmax);
-  }
-
-  if (parallel::size() > 0) {
-    Array<const R3> xmin_array = parallel::allGather(xmin);
-    Array<const R3> xmax_array = parallel::allGather(xmax);
-    Array<const R3> ymin_array = parallel::allGather(ymin);
-    Array<const R3> ymax_array = parallel::allGather(ymax);
-    Array<const R3> zmin_array = parallel::allGather(zmin);
-    Array<const R3> zmax_array = parallel::allGather(zmax);
-
-    for (size_t i = 0; i < xmin_array.size(); ++i) {
-      update_xmin(xmin_array[i], xmin);
-    }
-    for (size_t i = 0; i < ymin_array.size(); ++i) {
-      update_ymin(ymin_array[i], ymin);
-    }
-    for (size_t i = 0; i < zmin_array.size(); ++i) {
-      update_zmin(zmin_array[i], zmin);
-    }
-    for (size_t i = 0; i < xmax_array.size(); ++i) {
-      update_xmax(xmax_array[i], xmax);
-    }
-    for (size_t i = 0; i < ymax_array.size(); ++i) {
-      update_ymax(ymax_array[i], ymax);
-    }
-    for (size_t i = 0; i < zmax_array.size(); ++i) {
-      update_zmax(zmax_array[i], zmax);
-    }
-  }
-
-  return bounds;
-}
-
-template <size_t Dimension>
-class MeshFlatNodeBoundary : public MeshNodeBoundary<Dimension>   // clazy:exclude=copyable-polymorphic
-{
- public:
-  using Rd = TinyVector<Dimension, double>;
-
- private:
-  const Rd m_outgoing_normal;
-
-  template <typename MeshType>
-  PUGS_INLINE Rd _getNormal(const MeshType& mesh);
-
-  template <typename MeshType>
-  PUGS_INLINE void
-  _checkBoundaryIsFlat(const TinyVector<Dimension, double>& normal,
-                       const TinyVector<Dimension, double>& origin,
-                       const double length,
-                       const MeshType& mesh) const
-  {
-    static_assert(MeshType::Dimension == Dimension);
-
-    const NodeValue<const Rd>& xr = mesh.xr();
-
-    bool is_bad = false;
-
-    parallel_for(this->m_node_list.size(), [=, &is_bad](int r) {
-      const Rd& x = xr[this->m_node_list[r]];
-      if (dot(x - origin, normal) > 1E-13 * length) {
-        is_bad = true;
-      }
-    });
-
-    if (parallel::allReduceOr(is_bad)) {
-      std::ostringstream ost;
-      ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
-          << ": boundary is not flat!";
-      throw NormalError(ost.str());
-    }
-  }
-
-  template <typename MeshType>
-  PUGS_INLINE Rd _getOutgoingNormal(const MeshType& mesh);
-
- public:
-  const Rd&
-  outgoingNormal() const
-  {
-    return m_outgoing_normal;
-  }
-
-  MeshFlatNodeBoundary& operator=(const MeshFlatNodeBoundary&) = default;
-  MeshFlatNodeBoundary& operator=(MeshFlatNodeBoundary&&) = default;
-
-  template <typename MeshType>
-  friend MeshFlatNodeBoundary<MeshType::Dimension> getMeshFlatNodeBoundary(
-    const MeshType& mesh,
-    const IBoundaryDescriptor& boundary_descriptor);
-
- private:
-  template <typename MeshType>
-  MeshFlatNodeBoundary(const MeshType& mesh, const RefFaceList& ref_face_list)
-    : MeshNodeBoundary<Dimension>(mesh, ref_face_list), m_outgoing_normal(_getOutgoingNormal(mesh))
-  {
-    ;
-  }
-
-  template <typename MeshType>
-  MeshFlatNodeBoundary(const MeshType& mesh, const RefNodeList& ref_node_list)
-    : MeshNodeBoundary<Dimension>(mesh, ref_node_list), m_outgoing_normal(_getOutgoingNormal(mesh))
-  {
-    ;
-  }
-
- public:
-  MeshFlatNodeBoundary()                            = default;
-  MeshFlatNodeBoundary(const MeshFlatNodeBoundary&) = default;
-  MeshFlatNodeBoundary(MeshFlatNodeBoundary&&)      = default;
-  virtual ~MeshFlatNodeBoundary()                   = default;
-};
-
-template <typename MeshType>
-MeshFlatNodeBoundary<MeshType::Dimension>
-getMeshFlatNodeBoundary(const MeshType& mesh, const IBoundaryDescriptor& boundary_descriptor)
-{
-  for (size_t i_ref_node_list = 0; i_ref_node_list < mesh.connectivity().template numberOfRefItemList<ItemType::node>();
-       ++i_ref_node_list) {
-    const auto& ref_node_list = mesh.connectivity().template refItemList<ItemType::node>(i_ref_node_list);
-    const RefId& ref          = ref_node_list.refId();
-    if (ref == boundary_descriptor) {
-      return MeshFlatNodeBoundary<MeshType::Dimension>{mesh, ref_node_list};
-    }
-  }
-  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
-       ++i_ref_face_list) {
-    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
-    const RefId& ref          = ref_face_list.refId();
-    if (ref == boundary_descriptor) {
-      return MeshFlatNodeBoundary<MeshType::Dimension>{mesh, ref_face_list};
-    }
-  }
-
-  std::ostringstream ost;
-  ost << "cannot find surface with name " << rang::fgB::red << boundary_descriptor << rang::style::reset;
-
-  throw NormalError(ost.str());
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<1, double>
-MeshFlatNodeBoundary<1>::_getNormal(const MeshType& mesh)
-{
-  static_assert(MeshType::Dimension == 1);
-  using R = TinyVector<1, double>;
-
-  const size_t number_of_bc_nodes = [&]() {
-    size_t number_of_bc_nodes = 0;
-    auto node_is_owned        = mesh.connectivity().nodeIsOwned();
-    for (size_t i_node = 0; i_node < m_node_list.size(); ++i_node) {
-      number_of_bc_nodes += (node_is_owned[m_node_list[i_node]]);
-    }
-    return parallel::allReduceMax(number_of_bc_nodes);
-  }();
-
-  if (number_of_bc_nodes != 1) {
-    std::ostringstream ost;
-    ost << "invalid boundary " << rang::fgB::yellow << m_boundary_name << rang::style::reset
-        << ": node boundaries in 1D require to have exactly 1 node";
-    throw NormalError(ost.str());
-  }
-
-  return R{1};
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<2, double>
-MeshFlatNodeBoundary<2>::_getNormal(const MeshType& mesh)
-{
-  static_assert(MeshType::Dimension == 2);
-  using R2 = TinyVector<2, double>;
-
-  std::array<R2, 2> bounds = this->_getBounds(mesh);
-
-  const R2& xmin = bounds[0];
-  const R2& xmax = bounds[1];
-
-  if (xmin == xmax) {
-    std::ostringstream ost;
-    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
-        << ": unable to compute normal";
-    throw NormalError(ost.str());
-  }
-
-  R2 dx = xmax - xmin;
-  dx *= 1. / l2Norm(dx);
-
-  const R2 normal(-dx[1], dx[0]);
-
-  this->_checkBoundaryIsFlat(normal, 0.5 * (xmin + xmax), l2Norm(xmax - xmin), mesh);
-
-  return normal;
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<3, double>
-MeshFlatNodeBoundary<3>::_getNormal(const MeshType& mesh)
-{
-  static_assert(MeshType::Dimension == 3);
-
-  using R3 = TinyVector<3, double>;
-
-  std::array<R3, 6> bounds = this->_getBounds(mesh);
-
-  const R3& xmin = bounds[0];
-  const R3& ymin = bounds[1];
-  const R3& zmin = bounds[2];
-  const R3& xmax = bounds[3];
-  const R3& ymax = bounds[4];
-  const R3& zmax = bounds[5];
-
-  const R3 u = xmax - xmin;
-  const R3 v = ymax - ymin;
-  const R3 w = zmax - zmin;
-
-  const R3 uv        = crossProduct(u, v);
-  const double uv_l2 = dot(uv, uv);
-
-  R3 normal        = uv;
-  double normal_l2 = uv_l2;
-
-  const R3 uw        = crossProduct(u, w);
-  const double uw_l2 = dot(uw, uw);
-
-  if (uw_l2 > uv_l2) {
-    normal    = uw;
-    normal_l2 = uw_l2;
-  }
-
-  const R3 vw        = crossProduct(v, w);
-  const double vw_l2 = dot(vw, vw);
-
-  if (vw_l2 > normal_l2) {
-    normal    = vw;
-    normal_l2 = vw_l2;
-  }
-
-  if (normal_l2 == 0) {
-    std::ostringstream ost;
-    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
-        << ": unable to compute normal";
-    throw NormalError(ost.str());
-  }
-
-  const double length = sqrt(normal_l2);
-
-  normal *= 1. / length;
-
-  this->_checkBoundaryIsFlat(normal, 1. / 6. * (xmin + xmax + ymin + ymax + zmin + zmax), length, mesh);
-
-  return normal;
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<1, double>
-MeshFlatNodeBoundary<1>::_getOutgoingNormal(const MeshType& mesh)
-{
-  static_assert(MeshType::Dimension == 1);
-  using R = TinyVector<1, double>;
-
-  const R normal = this->_getNormal(mesh);
-
-  double max_height = 0;
-
-  if (m_node_list.size() > 0) {
-    const NodeValue<const R>& xr    = mesh.xr();
-    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
-
-    const auto& node_to_cell_matrix = mesh.connectivity().nodeToCellMatrix();
-
-    const NodeId r0      = m_node_list[0];
-    const CellId j0      = node_to_cell_matrix[r0][0];
-    const auto& j0_nodes = cell_to_node_matrix[j0];
-
-    for (size_t r = 0; r < j0_nodes.size(); ++r) {
-      const double height = dot(xr[j0_nodes[r]] - xr[r0], normal);
-      if (std::abs(height) > std::abs(max_height)) {
-        max_height = height;
-      }
-    }
-  }
-
-  Array<double> max_height_array = parallel::allGather(max_height);
-  for (size_t i = 0; i < max_height_array.size(); ++i) {
-    const double height = max_height_array[i];
-    if (std::abs(height) > std::abs(max_height)) {
-      max_height = height;
-    }
-  }
-
-  if (max_height > 0) {
-    return -normal;
-  } else {
-    return normal;
-  }
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<2, double>
-MeshFlatNodeBoundary<2>::_getOutgoingNormal(const MeshType& mesh)
-{
-  static_assert(MeshType::Dimension == 2);
-  using R2 = TinyVector<2, double>;
-
-  const R2 normal = this->_getNormal(mesh);
-
-  double max_height = 0;
-
-  if (m_node_list.size() > 0) {
-    const NodeValue<const R2>& xr   = mesh.xr();
-    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
-
-    const auto& node_to_cell_matrix = mesh.connectivity().nodeToCellMatrix();
-
-    const NodeId r0      = m_node_list[0];
-    const CellId j0      = node_to_cell_matrix[r0][0];
-    const auto& j0_nodes = cell_to_node_matrix[j0];
-    for (size_t r = 0; r < j0_nodes.size(); ++r) {
-      const double height = dot(xr[j0_nodes[r]] - xr[r0], normal);
-      if (std::abs(height) > std::abs(max_height)) {
-        max_height = height;
-      }
-    }
-  }
-
-  Array<double> max_height_array = parallel::allGather(max_height);
-  for (size_t i = 0; i < max_height_array.size(); ++i) {
-    const double height = max_height_array[i];
-    if (std::abs(height) > std::abs(max_height)) {
-      max_height = height;
-    }
-  }
-
-  if (max_height > 0) {
-    return -normal;
-  } else {
-    return normal;
-  }
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<3, double>
-MeshFlatNodeBoundary<3>::_getOutgoingNormal(const MeshType& mesh)
-{
-  static_assert(MeshType::Dimension == 3);
-  using R3 = TinyVector<3, double>;
-
-  const R3 normal = this->_getNormal(mesh);
-
-  double max_height = 0;
-
-  if (m_node_list.size() > 0) {
-    const NodeValue<const R3>& xr   = mesh.xr();
-    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
-
-    const auto& node_to_cell_matrix = mesh.connectivity().nodeToCellMatrix();
-
-    const NodeId r0      = m_node_list[0];
-    const CellId j0      = node_to_cell_matrix[r0][0];
-    const auto& j0_nodes = cell_to_node_matrix[j0];
-
-    for (size_t r = 0; r < j0_nodes.size(); ++r) {
-      const double height = dot(xr[j0_nodes[r]] - xr[r0], normal);
-      if (std::abs(height) > std::abs(max_height)) {
-        max_height = height;
-      }
-    }
-  }
-
-  Array<double> max_height_array = parallel::allGather(max_height);
-  for (size_t i = 0; i < max_height_array.size(); ++i) {
-    const double height = max_height_array[i];
-    if (std::abs(height) > std::abs(max_height)) {
-      max_height = height;
-    }
-  }
-
-  if (max_height > 0) {
-    return -normal;
-  } else {
-    return normal;
-  }
-}
-
 template <size_t Dimension>
-class MeshLineNodeBoundary : public MeshNodeBoundary<Dimension>   // clazy:exclude=copyable-polymorphic
-{
- public:
-  using Rd = TinyVector<Dimension, double>;
-
- private:
-  const Rd m_direction;
-
-  template <typename MeshType>
-  PUGS_INLINE TinyVector<Dimension> _getDirection(const MeshType&);
-
-  template <typename MeshType>
-  PUGS_INLINE void
-  _checkBoundaryIsLine(const TinyVector<Dimension, double>& direction,
-                       const TinyVector<Dimension, double>& origin,
-                       const double length,
-                       const MeshType& mesh) const
-  {
-    static_assert(MeshType::Dimension == Dimension);
-    using Rdxd = TinyMatrix<Dimension>;
-
-    const NodeValue<const Rd>& xr = mesh.xr();
-
-    Rdxd P = Rdxd{identity} - tensorProduct(direction, direction);
-
-    bool is_bad = false;
-    parallel_for(this->m_node_list.size(), [=, &is_bad](int r) {
-      const Rd& x    = xr[this->m_node_list[r]];
-      const Rd delta = x - origin;
-      if (dot(P * delta, direction) > 1E-13 * length) {
-        is_bad = true;
-      }
-    });
-
-    if (parallel::allReduceOr(is_bad)) {
-      std::ostringstream ost;
-      ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
-          << ": boundary is not a line!";
-      throw NormalError(ost.str());
-    }
-  }
-
- public:
-  template <typename MeshType>
-  friend MeshLineNodeBoundary<MeshType::Dimension> getMeshLineNodeBoundary(
-    const MeshType& mesh,
-    const IBoundaryDescriptor& boundary_descriptor);
-
-  PUGS_INLINE
-  const Rd&
-  direction() const
-  {
-    return m_direction;
-  }
-
-  MeshLineNodeBoundary& operator=(const MeshLineNodeBoundary&) = default;
-  MeshLineNodeBoundary& operator=(MeshLineNodeBoundary&&) = default;
-
- private:
-  template <typename MeshType>
-  MeshLineNodeBoundary(const MeshType& mesh, const RefEdgeList& ref_edge_list)
-    : MeshNodeBoundary<Dimension>(mesh, ref_edge_list), m_direction(_getDirection(mesh))
-  {
-    ;
-  }
-
-  template <typename MeshType>
-  MeshLineNodeBoundary(const MeshType& mesh, const RefFaceList& ref_face_list)
-    : MeshNodeBoundary<Dimension>(mesh, ref_face_list), m_direction(_getDirection(mesh))
-  {
-    ;
-  }
-
-  template <typename MeshType>
-  MeshLineNodeBoundary(const MeshType& mesh, const RefNodeList& ref_node_list)
-    : MeshNodeBoundary<Dimension>(mesh, ref_node_list), m_direction(_getDirection(mesh))
-  {
-    ;
-  }
-
- public:
-  MeshLineNodeBoundary()                            = default;
-  MeshLineNodeBoundary(const MeshLineNodeBoundary&) = default;
-  MeshLineNodeBoundary(MeshLineNodeBoundary&&)      = default;
-  virtual ~MeshLineNodeBoundary()                   = default;
-};
-
-template <typename MeshType>
-MeshLineNodeBoundary<MeshType::Dimension>
-getMeshLineNodeBoundary(const MeshType& mesh, const IBoundaryDescriptor& boundary_descriptor)
-{
-  for (size_t i_ref_node_list = 0; i_ref_node_list < mesh.connectivity().template numberOfRefItemList<ItemType::node>();
-       ++i_ref_node_list) {
-    const auto& ref_node_list = mesh.connectivity().template refItemList<ItemType::node>(i_ref_node_list);
-    const RefId& ref          = ref_node_list.refId();
-    if (ref == boundary_descriptor) {
-      return MeshLineNodeBoundary<MeshType::Dimension>{mesh, ref_node_list};
-    }
-  }
-  for (size_t i_ref_edge_list = 0; i_ref_edge_list < mesh.connectivity().template numberOfRefItemList<ItemType::edge>();
-       ++i_ref_edge_list) {
-    const auto& ref_edge_list = mesh.connectivity().template refItemList<ItemType::edge>(i_ref_edge_list);
-    const RefId& ref          = ref_edge_list.refId();
-    if (ref == boundary_descriptor) {
-      return MeshLineNodeBoundary<MeshType::Dimension>{mesh, ref_edge_list};
-    }
-  }
-  for (size_t i_ref_face_list = 0; i_ref_face_list < mesh.connectivity().template numberOfRefItemList<ItemType::face>();
-       ++i_ref_face_list) {
-    const auto& ref_face_list = mesh.connectivity().template refItemList<ItemType::face>(i_ref_face_list);
-    const RefId& ref          = ref_face_list.refId();
-    if (ref == boundary_descriptor) {
-      return MeshLineNodeBoundary<MeshType::Dimension>{mesh, ref_face_list};
-    }
-  }
-
-  std::ostringstream ost;
-  ost << "cannot find surface with name " << rang::fgB::red << boundary_descriptor << rang::style::reset;
-
-  throw NormalError(ost.str());
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<1>
-MeshLineNodeBoundary<1>::_getDirection(const MeshType&)
-{
-  throw UnexpectedError("MeshLineNodeBoundary::_getDirection make no sense in dimension 1");
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<2>
-MeshLineNodeBoundary<2>::_getDirection(const MeshType& mesh)
-{
-  using R2 = TinyVector<2, double>;
-
-  std::array<R2, 2> bounds = this->_getBounds(mesh);
-
-  const R2& xmin = bounds[0];
-  const R2& xmax = bounds[1];
-
-  if (xmin == xmax) {
-    std::ostringstream ost;
-    ost << "invalid boundary " << rang::fgB::yellow << this->m_boundary_name << rang::style::reset
-        << ": unable to compute direction";
-    throw NormalError(ost.str());
-  }
-
-  R2 direction        = xmax - xmin;
-  const double length = l2Norm(direction);
-  direction *= 1. / length;
-
-  this->_checkBoundaryIsLine(direction, xmin, length, mesh);
-
-  return direction;
-}
-
-template <>
-template <typename MeshType>
-PUGS_INLINE TinyVector<3>
-MeshLineNodeBoundary<3>::_getDirection(const MeshType& mesh)
-{
-  static_assert(MeshType::Dimension == 3);
-
-  using R3 = TinyVector<3, double>;
-
-  std::array<R3, 6> bounds = this->_getBounds(mesh);
-
-  const R3& xmin = bounds[0];
-  const R3& ymin = bounds[1];
-  const R3& zmin = bounds[2];
-  const R3& xmax = bounds[3];
-  const R3& ymax = bounds[4];
-  const R3& zmax = bounds[5];
-
-  const R3 u = xmax - xmin;
-  const R3 v = ymax - ymin;
-  const R3 w = zmax - zmin;
-
-  R3 direction = u;
-  if (dot(v, v) > dot(direction, direction)) {
-    direction = v;
-  }
-
-  if (dot(w, w) > dot(direction, direction)) {
-    direction = w;
-  }
-
-  const double length = l2Norm(direction);
-  direction *= 1. / length;
-
-  this->_checkBoundaryIsLine(direction, xmin, length, mesh);
-
-  return direction;
-}
+MeshNodeBoundary<Dimension> getMeshNodeBoundary(const Mesh<Connectivity<Dimension>>& mesh,
+                                                const IBoundaryDescriptor& boundary_descriptor);
 
 #endif   // MESH_NODE_BOUNDARY_HPP
diff --git a/src/mesh/MeshRandomizer.hpp b/src/mesh/MeshRandomizer.hpp
index aa2258c59..fb29175a8 100644
--- a/src/mesh/MeshRandomizer.hpp
+++ b/src/mesh/MeshRandomizer.hpp
@@ -7,6 +7,8 @@
 #include <mesh/Connectivity.hpp>
 #include <mesh/ItemValueUtils.hpp>
 #include <mesh/Mesh.hpp>
+#include <mesh/MeshFlatNodeBoundary.hpp>
+#include <mesh/MeshLineNodeBoundary.hpp>
 #include <mesh/MeshNodeBoundary.hpp>
 #include <scheme/AxisBoundaryConditionDescriptor.hpp>
 #include <scheme/FixedBoundaryConditionDescriptor.hpp>
diff --git a/src/scheme/AcousticSolver.cpp b/src/scheme/AcousticSolver.cpp
index 7bd97aef4..dedfbd2af 100644
--- a/src/scheme/AcousticSolver.cpp
+++ b/src/scheme/AcousticSolver.cpp
@@ -2,6 +2,7 @@
 
 #include <language/utils/InterpolateItemValue.hpp>
 #include <mesh/ItemValueUtils.hpp>
+#include <mesh/MeshFlatNodeBoundary.hpp>
 #include <mesh/MeshNodeBoundary.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
 #include <scheme/DiscreteFunctionP0.hpp>
-- 
GitLab