From 298d6cefd7f9976eecf2c708cd7d6ee413a6f232 Mon Sep 17 00:00:00 2001
From: Stephane Del Pino <stephane.delpino44@gmail.com>
Date: Wed, 5 Jul 2023 08:15:11 +0200
Subject: [PATCH] [ci-skip] Begin Escobar smoother implementation

---
 src/geometry/TriangleTransformation.hpp |   7 +
 src/language/modules/SchemeModule.cpp   |  25 +
 src/mesh/CMakeLists.txt                 |   1 +
 src/mesh/MeshSmootherEscobar.cpp        | 654 ++++++++++++++++++++++++
 src/mesh/MeshSmootherEscobar.hpp        |  50 ++
 5 files changed, 737 insertions(+)
 create mode 100644 src/mesh/MeshSmootherEscobar.cpp
 create mode 100644 src/mesh/MeshSmootherEscobar.hpp

diff --git a/src/geometry/TriangleTransformation.hpp b/src/geometry/TriangleTransformation.hpp
index 9210e0bda..240d0c919 100644
--- a/src/geometry/TriangleTransformation.hpp
+++ b/src/geometry/TriangleTransformation.hpp
@@ -19,6 +19,13 @@ class TriangleTransformation<2>
   double m_jacobian_determinant;
 
  public:
+  PUGS_INLINE
+  TinyMatrix<Dimension>
+  jacobian() const
+  {
+    return m_jacobian;
+  }
+
   PUGS_INLINE
   TinyVector<Dimension>
   operator()(const TinyVector<2>& x) const
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index 813bde93a..fb6e96002 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -17,6 +17,7 @@
 #include <mesh/MeshDataManager.hpp>
 #include <mesh/MeshRandomizer.hpp>
 #include <mesh/MeshSmoother.hpp>
+#include <mesh/MeshSmootherEscobar.hpp>
 #include <scheme/AcousticSolver.hpp>
 #include <scheme/AxisBoundaryConditionDescriptor.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
@@ -306,6 +307,30 @@ SchemeModule::SchemeModule()
 
                                             ));
 
+  this->_addBuiltinFunction("smoothMeshEscobar",
+                            std::function(
+
+                              [](std::shared_ptr<const IMesh> p_mesh,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list) -> std::shared_ptr<const IMesh> {
+                                MeshSmootherEscobarHandler handler;
+                                return handler.getSmoothedMesh(p_mesh, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("qualityEscobar",
+                            std::function(
+#warning REMOVE BEFORE MERGING
+                              [](std::shared_ptr<const IMesh> p_mesh,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list) -> std::shared_ptr<const ItemValueVariant> {
+                                MeshSmootherEscobarHandler handler;
+                                return handler.getQuality(p_mesh, bc_descriptor_list);
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("fixed", std::function(
 
                                        [](std::shared_ptr<const IBoundaryDescriptor> boundary)
diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt
index ee69a9710..54c7afbf2 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -39,5 +39,6 @@ add_library(
   MeshNodeInterface.cpp
   MeshRandomizer.cpp
   MeshSmoother.cpp
+  MeshSmootherEscobar.cpp
   MeshTransformer.cpp
   SynchronizerManager.cpp)
diff --git a/src/mesh/MeshSmootherEscobar.cpp b/src/mesh/MeshSmootherEscobar.cpp
new file mode 100644
index 000000000..793153c0a
--- /dev/null
+++ b/src/mesh/MeshSmootherEscobar.cpp
@@ -0,0 +1,654 @@
+#include <mesh/MeshSmootherEscobar.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <geometry/TriangleTransformation.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshCellZone.hpp>
+#include <mesh/MeshFlatNodeBoundary.hpp>
+#include <mesh/MeshLineNodeBoundary.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <scheme/AxisBoundaryConditionDescriptor.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <scheme/FixedBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <utils/RandomEngine.hpp>
+
+#include <variant>
+
+template <size_t Dimension>
+class MeshSmootherEscobarHandler::MeshSmootherEscobar
+{
+ private:
+  using Rd               = TinyVector<Dimension>;
+  using Rdxd             = TinyMatrix<Dimension>;
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+
+  const MeshType& m_given_mesh;
+
+  class AxisBoundaryCondition;
+  class FixedBoundaryCondition;
+  class SymmetryBoundaryCondition;
+
+  using BoundaryCondition = std::variant<AxisBoundaryCondition, FixedBoundaryCondition, SymmetryBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+  BoundaryConditionList m_boundary_condition_list;
+
+  BoundaryConditionList
+  _getBCList(const MeshType& mesh,
+             const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+  {
+    BoundaryConditionList bc_list;
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::axis: {
+        if constexpr (Dimension == 1) {
+          bc_list.emplace_back(FixedBoundaryCondition{getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())});
+        } else {
+          bc_list.emplace_back(
+            AxisBoundaryCondition{getMeshLineNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())});
+        }
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        bc_list.emplace_back(
+          SymmetryBoundaryCondition{getMeshFlatNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())});
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::fixed: {
+        bc_list.emplace_back(FixedBoundaryCondition{getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())});
+        break;
+      }
+      default: {
+        std::ostringstream error_msg;
+        error_msg << *bc_descriptor << " is an invalid boundary condition for mesh smoother";
+        throw NormalError(error_msg.str());
+      }
+      }
+    }
+
+    return bc_list;
+  }
+
+  void
+  _applyBC(NodeValue<Rd>& shift) const
+  {
+    for (auto&& boundary_condition : m_boundary_condition_list) {
+      std::visit(
+        [&](auto&& bc) {
+          using BCType = std::decay_t<decltype(bc)>;
+          if constexpr (std::is_same_v<BCType, SymmetryBoundaryCondition>) {
+            const Rd& n = bc.outgoingNormal();
+
+            const Rdxd I   = identity;
+            const Rdxd nxn = tensorProduct(n, n);
+            const Rdxd P   = I - nxn;
+
+            const Array<const NodeId>& node_list = bc.nodeList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(const size_t i_node) {
+                const NodeId node_id = node_list[i_node];
+
+                shift[node_id] = P * shift[node_id];
+              });
+
+          } else if constexpr (std::is_same_v<BCType, AxisBoundaryCondition>) {
+            if constexpr (Dimension > 1) {
+              const Rd& t = bc.direction();
+
+              const Rdxd txt = tensorProduct(t, t);
+
+              const Array<const NodeId>& node_list = bc.nodeList();
+              parallel_for(
+                node_list.size(), PUGS_LAMBDA(const size_t i_node) {
+                  const NodeId node_id = node_list[i_node];
+
+                  shift[node_id] = txt * shift[node_id];
+                });
+            } else {
+              throw UnexpectedError("AxisBoundaryCondition make no sense in dimension 1");
+            }
+
+          } else if constexpr (std::is_same_v<BCType, FixedBoundaryCondition>) {
+            const Array<const NodeId>& node_list = bc.nodeList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(const size_t i_node) {
+                const NodeId node_id = node_list[i_node];
+                shift[node_id]       = zero;
+              });
+
+          } else {
+            throw UnexpectedError("invalid boundary condition type");
+          }
+        },
+        boundary_condition);
+    }
+  }
+
+  NodeValue<Rd>
+  _getDisplacement() const
+  {
+    const ConnectivityType& connectivity = m_given_mesh.connectivity();
+    NodeValue<const Rd> given_xr         = m_given_mesh.xr();
+
+    auto node_to_cell_matrix        = connectivity.nodeToCellMatrix();
+    auto cell_to_node_matrix        = connectivity.cellToNodeMatrix();
+    auto node_number_in_their_cells = connectivity.nodeLocalNumbersInTheirCells();
+
+    NodeValue<double> max_delta_xr{connectivity};
+    parallel_for(
+      connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+        const Rd& x0 = given_xr[node_id];
+
+        const auto& node_cell_list = node_to_cell_matrix[node_id];
+        double min_distance_2      = std::numeric_limits<double>::max();
+
+        for (size_t i_cell = 0; i_cell < node_cell_list.size(); ++i_cell) {
+          const size_t i_cell_node = node_number_in_their_cells(node_id, i_cell);
+
+          const CellId cell_id       = node_cell_list[i_cell];
+          const auto& cell_node_list = cell_to_node_matrix[cell_id];
+
+          for (size_t i_node = 0; i_node < cell_node_list.size(); ++i_node) {
+            if (i_node != i_cell_node) {
+              const NodeId cell_node_id = cell_node_list[i_node];
+              const Rd delta            = x0 - given_xr[cell_node_id];
+              min_distance_2            = std::min(min_distance_2, dot(delta, delta));
+            }
+          }
+        }
+        double max_delta = std::sqrt(min_distance_2);
+
+        max_delta_xr[node_id] = max_delta;
+      });
+
+    NodeValue<Rd> shift_r{connectivity};
+
+    parallel_for(
+      m_given_mesh.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+        const auto& node_cell_list = node_to_cell_matrix[node_id];
+        Rd mean_position(zero);
+        size_t number_of_neighbours = 0;
+
+        for (size_t i_cell = 0; i_cell < node_cell_list.size(); ++i_cell) {
+          const size_t i_cell_node = node_number_in_their_cells(node_id, i_cell);
+
+          const CellId cell_id       = node_cell_list[i_cell];
+          const auto& cell_node_list = cell_to_node_matrix[cell_id];
+          for (size_t i_node = 0; i_node < cell_node_list.size(); ++i_node) {
+            if (i_node != i_cell_node) {
+              const NodeId cell_node_id = cell_node_list[i_node];
+              mean_position += given_xr[cell_node_id];
+              number_of_neighbours++;
+            }
+          }
+        }
+        mean_position    = 1. / number_of_neighbours * mean_position;
+        shift_r[node_id] = mean_position - given_xr[node_id];
+      });
+
+    this->_applyBC(shift_r);
+
+    synchronize(shift_r);
+
+    return shift_r;
+  }
+
+ public:
+  std::shared_ptr<const ItemValueVariant>
+  getQuality() const
+  {
+    if constexpr (Dimension == 2) {
+      const ConnectivityType& connectivity = m_given_mesh.connectivity();
+      NodeValue<const Rd> xr               = m_given_mesh.xr();
+
+      auto cell_to_node_matrix        = connectivity.cellToNodeMatrix();
+      auto node_to_cell_matrix        = connectivity.nodeToCellMatrix();
+      auto node_number_in_their_cells = connectivity.nodeLocalNumbersInTheirCells();
+
+      auto is_boundary_node = connectivity.isBoundaryNode();
+      NodeValue<double> quality{connectivity};
+
+      constexpr double eps = 1E-15;
+      quality.fill(2);
+
+      auto f_inner = [=](const NodeId node_id, const TinyVector<Dimension>& x) -> double {
+        auto cell_list           = node_to_cell_matrix[node_id];
+        auto node_number_in_cell = node_number_in_their_cells[node_id];
+
+        const double alpha = 2 * std::acos(-1) / cell_list.size();
+        const TinyMatrix<Dimension> W{1, std::cos(alpha), 0, std::sin(alpha)};
+
+        const TinyMatrix<Dimension> inv_W = inverse(W);
+
+        SmallArray<TinyMatrix<Dimension>> S(cell_list.size());
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          const size_t i_cell_node   = node_number_in_cell[i_cell];
+          auto cell_node_list        = cell_to_node_matrix[cell_list[i_cell]];
+          const size_t cell_nb_nodes = cell_node_list.size();
+
+          TriangleTransformation<Dimension> T(x,   //
+                                              xr[cell_node_list[(i_cell_node + 1) % cell_nb_nodes]],
+                                              xr[cell_node_list[(i_cell_node + cell_nb_nodes - 1) % cell_nb_nodes]]);
+          S[i_cell] = T.jacobian() * inv_W;
+        }
+
+        SmallArray<double> sigma(S.size());
+        for (size_t j = 0; j < S.size(); ++j) {
+          sigma[j] = det(S[j]);
+        }
+
+        const double sigma_min = min(sigma);
+
+        const double delta =
+          (sigma_min < eps) ? std::max(std::sqrt(eps * (eps - sigma_min)), std::sqrt(eps) * std::abs(sigma_min)) : 0;
+
+        double f = 0;
+        for (size_t j = 0; j < S.size(); ++j) {
+          const TinyMatrix<Dimension> Sigma = sigma[j] * inverse(S[j]);
+
+          const double Sj_norm    = std::sqrt(trace(transpose(S[j]) * S[j]));
+          const double Sigma_norm = std::sqrt(trace(transpose(Sigma) * Sigma));
+
+          f += Sj_norm * Sigma_norm / (sigma[j] + std::sqrt(sigma[j] * sigma[j] + 4 * delta * delta));
+        }
+        return f;
+      };
+
+      parallel_for(
+        connectivity.numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
+          auto cell_list           = node_to_cell_matrix[node_id];
+          auto node_number_in_cell = node_number_in_their_cells[node_id];
+
+          if (is_boundary_node[node_id]) {
+            quality[node_id] = 1;
+          } else {
+            TinyVector x     = xr[node_id];
+            quality[node_id] = f_inner(node_id, x);
+
+            TinyMatrix<Dimension> B = identity;
+          }
+        });
+
+      return std::make_shared<ItemValueVariant>(quality);
+    } else {
+      throw NotImplementedError("Dimension != 2");
+    }
+  }
+
+  std::shared_ptr<const IMesh>
+  getSmoothedMesh() const
+  {
+    NodeValue<const Rd> given_xr = m_given_mesh.xr();
+
+    NodeValue<Rd> xr = this->_getDisplacement();
+
+    parallel_for(
+      m_given_mesh.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) { xr[node_id] += given_xr[node_id]; });
+
+    return std::make_shared<MeshType>(m_given_mesh.shared_connectivity(), xr);
+  }
+
+  std::shared_ptr<const IMesh>
+  getSmoothedMesh(const FunctionSymbolId& function_symbol_id) const
+  {
+    NodeValue<const Rd> given_xr = m_given_mesh.xr();
+
+    NodeValue<const bool> is_displaced =
+      InterpolateItemValue<bool(const Rd)>::interpolate(function_symbol_id, given_xr);
+
+    NodeValue<Rd> xr = this->_getDisplacement();
+
+    parallel_for(
+      m_given_mesh.numberOfNodes(),
+      PUGS_LAMBDA(const NodeId node_id) { xr[node_id] = is_displaced[node_id] * xr[node_id] + given_xr[node_id]; });
+
+    return std::make_shared<MeshType>(m_given_mesh.shared_connectivity(), xr);
+  }
+
+  std::shared_ptr<const IMesh>
+  getSmoothedMesh(const std::vector<std::shared_ptr<const IZoneDescriptor>>& zone_descriptor_list) const
+  {
+    NodeValue<const Rd> given_xr = m_given_mesh.xr();
+
+    auto node_to_cell_matrix = m_given_mesh.connectivity().nodeToCellMatrix();
+
+    NodeValue<bool> is_displaced{m_given_mesh.connectivity()};
+    is_displaced.fill(false);
+
+    for (size_t i_zone = 0; i_zone < zone_descriptor_list.size(); ++i_zone) {
+      MeshCellZone<Dimension> cell_zone = getMeshCellZone(m_given_mesh, *zone_descriptor_list[i_zone]);
+      const auto cell_list              = cell_zone.cellList();
+      CellValue<bool> is_zone_cell{m_given_mesh.connectivity()};
+      is_zone_cell.fill(false);
+      parallel_for(
+        cell_list.size(), PUGS_LAMBDA(const size_t i_cell) { is_zone_cell[cell_list[i_cell]] = true; });
+      parallel_for(
+        m_given_mesh.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+          auto node_cell_list = node_to_cell_matrix[node_id];
+          bool displace       = true;
+          for (size_t i_node_cell = 0; i_node_cell < node_cell_list.size(); ++i_node_cell) {
+            const CellId cell_id = node_cell_list[i_node_cell];
+            displace &= is_zone_cell[cell_id];
+          }
+          if (displace) {
+            is_displaced[node_id] = true;
+          }
+        });
+    }
+    synchronize(is_displaced);
+    NodeValue<Rd> xr = this->_getDisplacement();
+
+    parallel_for(
+      m_given_mesh.numberOfNodes(),
+      PUGS_LAMBDA(const NodeId node_id) { xr[node_id] = is_displaced[node_id] * xr[node_id] + given_xr[node_id]; });
+
+    return std::make_shared<MeshType>(m_given_mesh.shared_connectivity(), xr);
+  }
+
+  std::shared_ptr<const IMesh>
+  getSmoothedMesh(
+    const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const
+  {
+    NodeValue<const Rd> given_xr = m_given_mesh.xr();
+
+    auto node_to_cell_matrix = m_given_mesh.connectivity().nodeToCellMatrix();
+
+    NodeValue<bool> is_displaced{m_given_mesh.connectivity()};
+    is_displaced.fill(false);
+
+    for (size_t i_zone = 0; i_zone < discrete_function_variant_list.size(); ++i_zone) {
+      auto is_zone_cell = discrete_function_variant_list[i_zone]->get<DiscreteFunctionP0<Dimension, const double>>();
+
+      parallel_for(
+        m_given_mesh.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+          auto node_cell_list = node_to_cell_matrix[node_id];
+          bool displace       = true;
+          for (size_t i_node_cell = 0; i_node_cell < node_cell_list.size(); ++i_node_cell) {
+            const CellId cell_id = node_cell_list[i_node_cell];
+            displace &= (is_zone_cell[cell_id] != 0);
+          }
+          if (displace) {
+            is_displaced[node_id] = true;
+          }
+        });
+    }
+    synchronize(is_displaced);
+    NodeValue<Rd> xr = this->_getDisplacement();
+
+    parallel_for(
+      m_given_mesh.numberOfNodes(),
+      PUGS_LAMBDA(const NodeId node_id) { xr[node_id] = is_displaced[node_id] * xr[node_id] + given_xr[node_id]; });
+
+    return std::make_shared<MeshType>(m_given_mesh.shared_connectivity(), xr);
+  }
+
+  MeshSmootherEscobar(const MeshSmootherEscobar&) = delete;
+  MeshSmootherEscobar(MeshSmootherEscobar&&)      = delete;
+
+  MeshSmootherEscobar(const MeshType& given_mesh,
+                      const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+    : m_given_mesh(given_mesh), m_boundary_condition_list(this->_getBCList(given_mesh, bc_descriptor_list))
+  {}
+
+  ~MeshSmootherEscobar() = default;
+};
+
+template <size_t Dimension>
+class MeshSmootherEscobarHandler::MeshSmootherEscobar<Dimension>::AxisBoundaryCondition
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const MeshLineNodeBoundary<Dimension> m_mesh_line_node_boundary;
+
+ public:
+  const Rd&
+  direction() const
+  {
+    return m_mesh_line_node_boundary.direction();
+  }
+
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_line_node_boundary.nodeList();
+  }
+
+  AxisBoundaryCondition(MeshLineNodeBoundary<Dimension>&& mesh_line_node_boundary)
+    : m_mesh_line_node_boundary(mesh_line_node_boundary)
+  {
+    ;
+  }
+
+  ~AxisBoundaryCondition() = default;
+};
+
+template <>
+class MeshSmootherEscobarHandler::MeshSmootherEscobar<1>::AxisBoundaryCondition
+{
+ public:
+  AxisBoundaryCondition()  = default;
+  ~AxisBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class MeshSmootherEscobarHandler::MeshSmootherEscobar<Dimension>::FixedBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary<Dimension> m_mesh_node_boundary;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  FixedBoundaryCondition(MeshNodeBoundary<Dimension>&& mesh_node_boundary) : m_mesh_node_boundary{mesh_node_boundary} {}
+
+  ~FixedBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class MeshSmootherEscobarHandler::MeshSmootherEscobar<Dimension>::SymmetryBoundaryCondition
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const MeshFlatNodeBoundary<Dimension> m_mesh_flat_node_boundary;
+
+ public:
+  const Rd&
+  outgoingNormal() const
+  {
+    return m_mesh_flat_node_boundary.outgoingNormal();
+  }
+
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_flat_node_boundary.nodeList();
+  }
+
+  SymmetryBoundaryCondition(MeshFlatNodeBoundary<Dimension>&& mesh_flat_node_boundary)
+    : m_mesh_flat_node_boundary(mesh_flat_node_boundary)
+  {
+    ;
+  }
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+std::shared_ptr<const ItemValueVariant>
+MeshSmootherEscobarHandler::getQuality(
+  const std::shared_ptr<const IMesh>& mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+{
+  switch (mesh->dimension()) {
+  case 1: {
+    constexpr size_t Dimension = 1;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getQuality();
+  }
+  case 2: {
+    constexpr size_t Dimension = 2;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getQuality();
+  }
+  case 3: {
+    constexpr size_t Dimension = 3;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getQuality();
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
+std::shared_ptr<const IMesh>
+MeshSmootherEscobarHandler::getSmoothedMesh(
+  const std::shared_ptr<const IMesh>& mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+{
+  switch (mesh->dimension()) {
+  case 1: {
+    constexpr size_t Dimension = 1;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh();
+  }
+  case 2: {
+    constexpr size_t Dimension = 2;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh();
+  }
+  case 3: {
+    constexpr size_t Dimension = 3;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh();
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
+std::shared_ptr<const IMesh>
+MeshSmootherEscobarHandler::getSmoothedMesh(
+  const std::shared_ptr<const IMesh>& mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const FunctionSymbolId& function_symbol_id) const
+{
+  switch (mesh->dimension()) {
+  case 1: {
+    constexpr size_t Dimension = 1;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(function_symbol_id);
+  }
+  case 2: {
+    constexpr size_t Dimension = 2;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(function_symbol_id);
+  }
+  case 3: {
+    constexpr size_t Dimension = 3;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(function_symbol_id);
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
+std::shared_ptr<const IMesh>
+MeshSmootherEscobarHandler::getSmoothedMesh(
+  const std::shared_ptr<const IMesh>& mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const std::vector<std::shared_ptr<const IZoneDescriptor>>& smoothing_zone_list) const
+{
+  switch (mesh->dimension()) {
+  case 1: {
+    constexpr size_t Dimension = 1;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(smoothing_zone_list);
+  }
+  case 2: {
+    constexpr size_t Dimension = 2;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(smoothing_zone_list);
+  }
+  case 3: {
+    constexpr size_t Dimension = 3;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(smoothing_zone_list);
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
+std::shared_ptr<const IMesh>
+MeshSmootherEscobarHandler::getSmoothedMesh(
+  const std::shared_ptr<const IMesh>& mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const
+{
+  if (not hasSameMesh(discrete_function_variant_list)) {
+    throw NormalError("discrete functions are not defined on the same mesh");
+  }
+
+  std::shared_ptr<const IMesh> common_mesh = getCommonMesh(discrete_function_variant_list);
+
+  if (common_mesh != mesh) {
+    throw NormalError("discrete functions are not defined on the smoothed mesh");
+  }
+
+  switch (mesh->dimension()) {
+  case 1: {
+    constexpr size_t Dimension = 1;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(discrete_function_variant_list);
+  }
+  case 2: {
+    constexpr size_t Dimension = 2;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(discrete_function_variant_list);
+  }
+  case 3: {
+    constexpr size_t Dimension = 3;
+    using MeshType             = Mesh<Connectivity<Dimension>>;
+    MeshSmootherEscobar smoother(dynamic_cast<const MeshType&>(*mesh), bc_descriptor_list);
+    return smoother.getSmoothedMesh(discrete_function_variant_list);
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
diff --git a/src/mesh/MeshSmootherEscobar.hpp b/src/mesh/MeshSmootherEscobar.hpp
new file mode 100644
index 000000000..f60ac7de0
--- /dev/null
+++ b/src/mesh/MeshSmootherEscobar.hpp
@@ -0,0 +1,50 @@
+#ifndef MESH_SMOOTHER_ESCOBAR_HPP
+#define MESH_SMOOTHER_ESCOBAR_HPP
+
+#include <mesh/IMesh.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+
+#include <memory>
+#include <vector>
+
+class FunctionSymbolId;
+class IZoneDescriptor;
+class DiscreteFunctionVariant;
+class ItemValueVariant;
+
+class MeshSmootherEscobarHandler
+{
+ private:
+  template <size_t Dimension>
+  class MeshSmootherEscobar;
+
+ public:
+  std::shared_ptr<const ItemValueVariant> getQuality(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const;
+
+  std::shared_ptr<const IMesh> getSmoothedMesh(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const;
+
+  std::shared_ptr<const IMesh> getSmoothedMesh(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+    const FunctionSymbolId& function_symbol_id) const;
+
+  std::shared_ptr<const IMesh> getSmoothedMesh(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+    const std::vector<std::shared_ptr<const IZoneDescriptor>>& smoothing_zone_list) const;
+
+  std::shared_ptr<const IMesh> getSmoothedMesh(
+    const std::shared_ptr<const IMesh>& mesh,
+    const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+    const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& smoothing_zone_list) const;
+
+  MeshSmootherEscobarHandler()                             = default;
+  MeshSmootherEscobarHandler(MeshSmootherEscobarHandler&&) = default;
+  ~MeshSmootherEscobarHandler()                            = default;
+};
+
+#endif   // MESH_SMOOTHER_ESCOBAR_HPP
-- 
GitLab