diff --git a/src/algebra/CMakeLists.txt b/src/algebra/CMakeLists.txt
index 6310f48dcf69eb93e2c1178e05d32dbc7fe5afad..58b2548ffef0ec4da9e919697627929eb2b1e29e 100644
--- a/src/algebra/CMakeLists.txt
+++ b/src/algebra/CMakeLists.txt
@@ -2,6 +2,7 @@
 
 add_library(
   PugsAlgebra
+  LeastSquareSolver.cpp
   EigenvalueSolver.cpp
   LinearSolver.cpp
   LinearSolverOptions.cpp
diff --git a/src/algebra/LeastSquareSolver.cpp b/src/algebra/LeastSquareSolver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d376881c629a66d6c2864d882956ee83cd4fc5de
--- /dev/null
+++ b/src/algebra/LeastSquareSolver.cpp
@@ -0,0 +1,27 @@
+#include <algebra/LeastSquareSolver.hpp>
+
+#include <algebra/CG.hpp>
+
+template <typename T>
+void
+LeastSquareSolver::solveLocalSystem(const SmallMatrix<T>& A, SmallVector<T>& x, const SmallVector<T>& b)
+{
+  if (A.numberOfRows() >= A.numberOfColumns()) {
+    const SmallMatrix tA = transpose(A);
+    const SmallMatrix B  = tA * A;
+    const SmallVector y  = tA * b;
+    CG{B, x, y, 1e-12, 10, false};
+  } else {
+    const SmallMatrix tA = transpose(A);
+    const SmallMatrix B  = A * tA;
+    SmallVector<double> y{A.numberOfRows()};
+    y = zero;
+    CG{B, y, b, 1e-12, 10, false};
+
+    x = tA * y;
+  }
+}
+
+template void LeastSquareSolver::solveLocalSystem(const SmallMatrix<double>&,
+                                                  SmallVector<double>&,
+                                                  const SmallVector<double>&);
diff --git a/src/algebra/LeastSquareSolver.hpp b/src/algebra/LeastSquareSolver.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b23ec9b7d160c43ee7a4f9e1b38251c8489a9f20
--- /dev/null
+++ b/src/algebra/LeastSquareSolver.hpp
@@ -0,0 +1,17 @@
+#ifndef LEAST_SQUARE_SOLVER_HPP
+#define LEAST_SQUARE_SOLVER_HPP
+
+#include <algebra/SmallMatrix.hpp>
+#include <algebra/SmallVector.hpp>
+
+class LeastSquareSolver
+{
+ public:
+  template <typename T>
+  void solveLocalSystem(const SmallMatrix<T>& A, SmallVector<T>& x, const SmallVector<T>& b);
+
+  LeastSquareSolver()  = default;
+  ~LeastSquareSolver() = default;
+};
+
+#endif   // LEAST_SQUARE_SOLVER_HPP
diff --git a/src/language/algorithms/CMakeLists.txt b/src/language/algorithms/CMakeLists.txt
index 63c2374987289e7e5c7352dbc773d77ef4e675fb..50783e8ea26f53d4eb5e2b9df92447b1ddd134fc 100644
--- a/src/language/algorithms/CMakeLists.txt
+++ b/src/language/algorithms/CMakeLists.txt
@@ -1,7 +1,13 @@
 # ------------------- Source files --------------------
 
 add_library(PugsLanguageAlgorithms
-  INTERFACE # THIS SHOULD BE REMOVED IF FILES ARE FINALY PROVIDED
+  ElasticityDiamondAlgorithm.cpp
+  UnsteadyElasticity.cpp
+  Heat5PointsAlgorithm.cpp
+  HeatDiamondAlgorithm.cpp
+  HeatDiamondAlgorithm2.cpp
+  ParabolicHeat.cpp
+#  INTERFACE # THIS SHOULD BE REMOVED IF FILES ARE FINALY PROVIDED
   )
 
 
diff --git a/src/language/algorithms/ElasticityDiamondAlgorithm.cpp b/src/language/algorithms/ElasticityDiamondAlgorithm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b14557ebd7599b9375c12d8ebe6f03311a9342fd
--- /dev/null
+++ b/src/language/algorithms/ElasticityDiamondAlgorithm.cpp
@@ -0,0 +1,874 @@
+#include <language/algorithms/ElasticityDiamondAlgorithm.hpp>
+
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LinearSolver.hpp>
+#include <algebra/SmallMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <algebra/Vector.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+
+template <size_t Dimension>
+ElasticityDiamondScheme<Dimension>::ElasticityDiamondScheme(
+  std::shared_ptr<const IMesh> i_mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const FunctionSymbolId& lambda_id,
+  const FunctionSymbolId& mu_id,
+  const FunctionSymbolId& f_id,
+  const FunctionSymbolId& U_id)
+{
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  using BoundaryCondition =
+    std::variant<DirichletBoundaryCondition, NormalStrainBoundaryCondition, SymmetryBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+  BoundaryConditionList boundary_condition_list;
+
+  std::cout << "number of bc descr = " << bc_descriptor_list.size() << '\n';
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+  NodeValue<bool> is_dirichlet{mesh->connectivity()};
+  is_dirichlet.fill(false);
+  NodeValue<TinyVector<Dimension>> dirichlet_value{mesh->connectivity()};
+  {
+    TinyVector<Dimension> nan_tiny_vector;
+    for (size_t i = 0; i < Dimension; ++i) {
+      nan_tiny_vector[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+    dirichlet_value.fill(nan_tiny_vector);
+  }
+
+  for (const auto& bc_descriptor : bc_descriptor_list) {
+    bool is_valid_boundary_condition = true;
+
+    switch (bc_descriptor->type()) {
+    case IBoundaryConditionDescriptor::Type::symmetry: {
+      const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor =
+        dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+      if constexpr (Dimension > 1) {
+        MeshFaceBoundary mesh_face_boundary = getMeshFaceBoundary(*mesh, sym_bc_descriptor.boundaryDescriptor());
+        boundary_condition_list.push_back(SymmetryBoundaryCondition{mesh_face_boundary.faceList()});
+
+      } else {
+        throw NotImplementedError("Symmetry conditions are not supported in 1d");
+      }
+
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::dirichlet: {
+      const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+        dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+      if (dirichlet_bc_descriptor.name() == "dirichlet") {
+        if constexpr (Dimension > 1) {
+          MeshFaceBoundary mesh_face_boundary =
+            getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+          MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+          const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+            TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                          mesh_face_boundary.faceList());
+
+          boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+        } else {
+          throw NotImplementedError("Dirichlet conditions are not supported in 1d");
+        }
+      } else if (dirichlet_bc_descriptor.name() == "normal_strain") {
+        if constexpr (Dimension > 1) {
+          MeshFaceBoundary mesh_face_boundary =
+            getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+          MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+          const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+            TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                          mesh_face_boundary.faceList());
+          boundary_condition_list.push_back(NormalStrainBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+        } else {
+          throw NotImplementedError("Normal strain conditions are not supported in 1d");
+        }
+
+      } else {
+        is_valid_boundary_condition = false;
+      }
+      break;
+    }
+    default: {
+      is_valid_boundary_condition = false;
+    }
+    }
+    if (not is_valid_boundary_condition) {
+      std::ostringstream error_msg;
+      error_msg << *bc_descriptor << " is an invalid boundary condition for elasticity equation";
+      throw NormalError(error_msg.str());
+    }
+  }
+
+  if constexpr (Dimension > 1) {
+    const CellValue<const size_t> cell_dof_number = [&] {
+      CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+      return compute_cell_dof_number;
+    }();
+    size_t number_of_dof = mesh->numberOfCells();
+
+    const FaceValue<const size_t> face_dof_number = [&] {
+      FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+      compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>) or
+                          (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                          (std::is_same_v<T, DirichletBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id = face_list[i_face];
+                if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                  std::ostringstream os;
+                  os << "The face " << face_id << " is used at least twice for boundary conditions";
+                  throw NormalError(os.str());
+                } else {
+                  compute_face_dof_number[face_id] = number_of_dof++;
+                }
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return compute_face_dof_number;
+    }();
+
+    const auto& primal_face_to_node_matrix             = mesh->connectivity().faceToNodeMatrix();
+    const auto& face_to_cell_matrix                    = mesh->connectivity().faceToCellMatrix();
+    const FaceValue<const bool> primal_face_is_neumann = [&] {
+      FaceValue<bool> face_is_neumann{mesh->connectivity()};
+      face_is_neumann.fill(false);
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id     = face_list[i_face];
+                face_is_neumann[face_id] = true;
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return face_is_neumann;
+    }();
+
+    const FaceValue<const bool> primal_face_is_symmetry = [&] {
+      FaceValue<bool> face_is_symmetry{mesh->connectivity()};
+      face_is_symmetry.fill(false);
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, SymmetryBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id      = face_list[i_face];
+                face_is_symmetry[face_id] = true;
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return face_is_symmetry;
+    }();
+
+    NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+    }
+
+    primal_node_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+          NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+          primal_node_is_on_boundary[node_id] = true;
+        }
+      }
+    }
+
+    FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+    }
+
+    primal_face_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        primal_face_is_on_boundary[face_id] = true;
+      }
+    }
+
+    FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+    }
+
+    primal_face_is_dirichlet.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] && (!primal_face_is_neumann[face_id]) &&
+                                           (!primal_face_is_symmetry[face_id]));
+    }
+    MeshDataType& mesh_data                          = MeshDataManager::instance().getMeshData(*mesh);
+    const NodeValue<const TinyVector<Dimension>>& xr = mesh->xr();
+
+    const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix                  = mesh->connectivity().nodeToFaceMatrix();
+    CellValuePerNode<double> w_rj{mesh->connectivity()};
+    FaceValuePerNode<double> w_rl{mesh->connectivity()};
+
+    const NodeValuePerFace<const TinyVector<Dimension>> primal_nlr = mesh_data.nlr();
+    auto project_to_face = [&](const TinyVector<Dimension>& x, const FaceId face_id) -> const TinyVector<Dimension> {
+      TinyVector<Dimension> proj;
+      const TinyVector<Dimension> nil = primal_nlr(face_id, 0);
+      proj                            = x - dot((x - xl[face_id]), nil) * nil;
+      return proj;
+    };
+
+    for (size_t i = 0; i < w_rl.numberOfValues(); ++i) {
+      w_rl[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+
+    for (NodeId i_node = 0; i_node < mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      if (not primal_node_is_on_boundary[i_node]) {
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size()};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+      } else {
+        int nb_face_used = 0;
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (primal_face_is_on_boundary[face_id]) {
+            nb_face_used++;
+          }
+        }
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size() + nb_face_used};
+        for (size_t j = 0; j < node_to_cell.size() + nb_face_used; j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          int cpt_face = 0;
+          for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+            FaceId face_id = node_to_face_matrix[i_node][i_face];
+            if (primal_face_is_on_boundary[face_id]) {
+              if (primal_face_is_symmetry[face_id]) {
+                for (size_t j = 0; j < face_to_cell_matrix[face_id].size(); ++j) {
+                  const CellId cell_id                 = face_to_cell_matrix[face_id][j];
+                  TinyVector<Dimension> xproj          = project_to_face(xj[cell_id], face_id);
+                  A(i, node_to_cell.size() + cpt_face) = xproj[i - 1];
+                }
+              } else {
+                A(i, node_to_cell.size() + cpt_face) = xl[face_id][i - 1];
+              }
+              cpt_face++;
+            }
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size() + nb_face_used};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+        int cpt_face = node_to_cell.size();
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (primal_face_is_on_boundary[face_id]) {
+            w_rl(i_node, i_face) = x[cpt_face++];
+          }
+        }
+      }
+    }
+
+    {
+      std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+      MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+      std::shared_ptr mapper =
+        DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+      CellValue<double> dual_muj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(mu_id,
+                                                                                                  diamond_mesh_data
+                                                                                                    .xj());
+
+      CellValue<double> dual_lambdaj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(lambda_id,
+                                                                                                  diamond_mesh_data
+                                                                                                    .xj());
+
+      CellValue<TinyVector<Dimension>> Uj = InterpolateItemValue<TinyVector<Dimension>(
+        TinyVector<Dimension>)>::template interpolate<ItemType::cell>(U_id, mesh_data.xj());
+
+      CellValue<TinyVector<Dimension>> fj = InterpolateItemValue<TinyVector<Dimension>(
+        TinyVector<Dimension>)>::template interpolate<ItemType::cell>(f_id, mesh_data.xj());
+
+      const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+      const FaceValue<const double> mes_l = [&] {
+        if constexpr (Dimension == 1) {
+          FaceValue<double> compute_mes_l{mesh->connectivity()};
+          compute_mes_l.fill(1);
+          return compute_mes_l;
+        } else {
+          return mesh_data.ll();
+        }
+      }();
+
+      const CellValue<const double> dual_mes_l_j = [=] {
+        CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+        mapper->toDualCell(mes_l, compute_mes_j);
+
+        return compute_mes_j;
+      }();
+
+      const CellValue<const double> primal_Vj   = mesh_data.Vj();
+      FaceValue<const CellId> face_dual_cell_id = [=]() {
+        FaceValue<CellId> computed_face_dual_cell_id{mesh->connectivity()};
+        CellValue<CellId> dual_cell_id{diamond_mesh->connectivity()};
+        parallel_for(
+          diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { dual_cell_id[cell_id] = cell_id; });
+
+        mapper->fromDualCell(dual_cell_id, computed_face_dual_cell_id);
+
+        return computed_face_dual_cell_id;
+      }();
+
+      NodeValue<const NodeId> dual_node_primal_node_id = [=]() {
+        CellValue<NodeId> cell_ignored_id{mesh->connectivity()};
+        cell_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+        NodeValue<NodeId> node_primal_id{mesh->connectivity()};
+
+        parallel_for(
+          mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { node_primal_id[node_id] = node_id; });
+
+        NodeValue<NodeId> computed_dual_node_primal_node_id{diamond_mesh->connectivity()};
+
+        mapper->toDualNode(node_primal_id, cell_ignored_id, computed_dual_node_primal_node_id);
+
+        return computed_dual_node_primal_node_id;
+      }();
+
+      CellValue<NodeId> primal_cell_dual_node_id = [=]() {
+        CellValue<NodeId> cell_id{mesh->connectivity()};
+        NodeValue<NodeId> node_ignored_id{mesh->connectivity()};
+        node_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+        NodeValue<NodeId> dual_node_id{diamond_mesh->connectivity()};
+
+        parallel_for(
+          diamond_mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { dual_node_id[node_id] = node_id; });
+
+        CellValue<NodeId> computed_primal_cell_dual_node_id{mesh->connectivity()};
+
+        mapper->fromDualNode(dual_node_id, node_ignored_id, cell_id);
+
+        return cell_id;
+      }();
+      const auto& dual_Cjr                     = diamond_mesh_data.Cjr();
+      FaceValue<TinyVector<Dimension>> dualClj = [&] {
+        FaceValue<TinyVector<Dimension>> computedClj{mesh->connectivity()};
+        const auto& dual_node_to_cell_matrix = diamond_mesh->connectivity().nodeToCellMatrix();
+        const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+        parallel_for(
+          mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+            const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+            for (size_t i = 0; i < primal_face_to_cell.size(); i++) {
+              CellId cell_id            = primal_face_to_cell[i];
+              const NodeId dual_node_id = primal_cell_dual_node_id[cell_id];
+              for (size_t i_dual_cell = 0; i_dual_cell < dual_node_to_cell_matrix[dual_node_id].size(); i_dual_cell++) {
+                const CellId dual_cell_id = dual_node_to_cell_matrix[dual_node_id][i_dual_cell];
+                if (face_dual_cell_id[face_id] == dual_cell_id) {
+                  for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                       i_dual_node++) {
+                    const NodeId final_dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                    if (final_dual_node_id == dual_node_id) {
+                      computedClj[face_id] = dual_Cjr(dual_cell_id, i_dual_node);
+                    }
+                  }
+                }
+              }
+            }
+          });
+        return computedClj;
+      }();
+
+      FaceValue<TinyVector<Dimension>> nlj = [&] {
+        FaceValue<TinyVector<Dimension>> computedNlj{mesh->connectivity()};
+        parallel_for(
+          mesh->numberOfFaces(),
+          PUGS_LAMBDA(FaceId face_id) { computedNlj[face_id] = 1. / l2Norm(dualClj[face_id]) * dualClj[face_id]; });
+        return computedNlj;
+      }();
+
+      FaceValue<const double> alpha_lambda_l = [&] {
+        CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+        parallel_for(
+          diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+            alpha_j[diamond_cell_id] = dual_lambdaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+          });
+
+        FaceValue<double> computed_alpha_l{mesh->connectivity()};
+        mapper->fromDualCell(alpha_j, computed_alpha_l);
+        return computed_alpha_l;
+      }();
+
+      FaceValue<const double> alpha_mu_l = [&] {
+        CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+        parallel_for(
+          diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+            alpha_j[diamond_cell_id] = dual_muj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+          });
+
+        FaceValue<double> computed_alpha_l{mesh->connectivity()};
+        mapper->fromDualCell(alpha_j, computed_alpha_l);
+        return computed_alpha_l;
+      }();
+
+      const TinyMatrix<Dimension> I = identity;
+
+      const Array<int> non_zeros{number_of_dof * Dimension};
+      non_zeros.fill(Dimension * Dimension);
+      CRSMatrixDescriptor<double> S(number_of_dof * Dimension, number_of_dof * Dimension, non_zeros);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        const double beta_mu_l          = l2Norm(dualClj[face_id]) * alpha_mu_l[face_id] * mes_l[face_id];
+        const double beta_lambda_l      = l2Norm(dualClj[face_id]) * alpha_lambda_l[face_id] * mes_l[face_id];
+        const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+        for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+          const CellId i_id                      = primal_face_to_cell[i_cell];
+          const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+          const TinyVector<Dimension> nil = [&] {
+            if (is_face_reversed_for_cell_i) {
+              return -nlj[face_id];
+            } else {
+              return nlj[face_id];
+            }
+          }();
+          for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+            const CellId j_id = primal_face_to_cell[j_cell];
+            TinyMatrix<Dimension> M =
+              beta_mu_l * I + beta_mu_l * tensorProduct(nil, nil) + beta_lambda_l * tensorProduct(nil, nil);
+            TinyMatrix<Dimension> N = tensorProduct(nil, nil);
+
+            if (i_cell == j_cell) {
+              for (size_t i = 0; i < Dimension; ++i) {
+                for (size_t j = 0; j < Dimension; ++j) {
+                  S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) += M(i, j);
+                  if (primal_face_is_neumann[face_id]) {
+                    S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) -= M(i, j);
+                  }
+                  if (primal_face_is_symmetry[face_id]) {
+                    S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) +=
+                      -((i == j) ? 1 : 0) + N(i, j);
+                    S(face_dof_number[face_id] * Dimension + i, face_dof_number[face_id] * Dimension + j) +=
+                      (i == j) ? 1 : 0;
+                  }
+                }
+              }
+            } else {
+              for (size_t i = 0; i < Dimension; ++i) {
+                for (size_t j = 0; j < Dimension; ++j) {
+                  S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) -= M(i, j);
+                }
+              }
+            }
+          }
+        }
+      }
+
+      const auto& dual_cell_to_node_matrix   = diamond_mesh->connectivity().cellToNodeMatrix();
+      const auto& primal_node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        const double alpha_mu_face_id     = mes_l[face_id] * alpha_mu_l[face_id];
+        const double alpha_lambda_face_id = mes_l[face_id] * alpha_lambda_l[face_id];
+
+        for (size_t i_face_cell = 0; i_face_cell < face_to_cell_matrix[face_id].size(); ++i_face_cell) {
+          CellId i_id                            = face_to_cell_matrix[face_id][i_face_cell];
+          const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+          for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+            NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+
+            const TinyVector<Dimension> nil = [&] {
+              if (is_face_reversed_for_cell_i) {
+                return -nlj[face_id];
+              } else {
+                return nlj[face_id];
+              }
+            }();
+
+            CellId dual_cell_id = face_dual_cell_id[face_id];
+
+            for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size(); ++i_dual_node) {
+              const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+              if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+
+                TinyMatrix<Dimension> M = alpha_mu_face_id * dot(Clr, nil) * I +
+                                          alpha_mu_face_id * tensorProduct(Clr, nil) +
+                                          alpha_lambda_face_id * tensorProduct(nil, Clr);
+
+                for (size_t j_cell = 0; j_cell < primal_node_to_cell_matrix[node_id].size(); ++j_cell) {
+                  CellId j_id = primal_node_to_cell_matrix[node_id][j_cell];
+                  for (size_t i = 0; i < Dimension; ++i) {
+                    for (size_t j = 0; j < Dimension; ++j) {
+                      S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) -=
+                        w_rj(node_id, j_cell) * M(i, j);
+                      if (primal_face_is_neumann[face_id]) {
+                        S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) +=
+                          w_rj(node_id, j_cell) * M(i, j);
+                      }
+                    }
+                  }
+                }
+                if (primal_node_is_on_boundary[node_id]) {
+                  for (size_t l_face = 0; l_face < node_to_face_matrix[node_id].size(); ++l_face) {
+                    FaceId l_id = node_to_face_matrix[node_id][l_face];
+                    if (primal_face_is_on_boundary[l_id]) {
+                      for (size_t i = 0; i < Dimension; ++i) {
+                        for (size_t j = 0; j < Dimension; ++j) {
+                          S(cell_dof_number[i_id] * Dimension + i, face_dof_number[l_id] * Dimension + j) -=
+                            w_rl(node_id, l_face) * M(i, j);
+                        }
+                      }
+                      if (primal_face_is_neumann[face_id]) {
+                        for (size_t i = 0; i < Dimension; ++i) {
+                          for (size_t j = 0; j < Dimension; ++j) {
+                            S(face_dof_number[face_id] * Dimension + i, face_dof_number[l_id] * Dimension + j) +=
+                              w_rl(node_id, l_face) * M(i, j);
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+            //            }
+          }
+        }
+      }
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (primal_face_is_dirichlet[face_id]) {
+          for (size_t i = 0; i < Dimension; ++i) {
+            S(face_dof_number[face_id] * Dimension + i, face_dof_number[face_id] * Dimension + i) += 1;
+          }
+        }
+      }
+
+      CRSMatrix A{S.getCRSMatrix()};
+      Vector<double> b{number_of_dof * Dimension};
+      b = zero;
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < Dimension; ++i) {
+          b[(cell_dof_number[cell_id] * Dimension) + i] = primal_Vj[cell_id] * fj[cell_id][i];
+        }
+      }
+
+      // Dirichlet
+      NodeValue<bool> node_tag{mesh->connectivity()};
+      node_tag.fill(false);
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
+              const auto& face_list  = bc.faceList();
+              const auto& value_list = bc.valueList();
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id = face_list[i_face];
+
+                for (size_t i = 0; i < Dimension; ++i) {
+                  b[(face_dof_number[face_id] * Dimension) + i] += value_list[i_face][i];
+                }
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+              const auto& face_list  = bc.faceList();
+              const auto& value_list = bc.valueList();
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                FaceId face_id = face_list[i_face];
+                for (size_t i = 0; i < Dimension; ++i) {
+                  b[face_dof_number[face_id] * Dimension + i] += mes_l[face_id] * value_list[i_face][i];   // sign
+                }
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      Vector<double> U{number_of_dof * Dimension};
+      U = zero;
+      CellValue<TinyVector<Dimension>> Speed{mesh->connectivity()};
+      FaceValue<TinyVector<Dimension>> Ul = InterpolateItemValue<TinyVector<Dimension>(
+        TinyVector<Dimension>)>::template interpolate<ItemType::face>(U_id, mesh_data.xl());
+      FaceValue<TinyVector<Dimension>> Speed_face{mesh->connectivity()};
+
+      Vector r = A * U - b;
+      std::cout << "initial (real) residu = " << std::sqrt(dot(r, r)) << '\n';
+
+      LinearSolver solver;
+      solver.solveLocalSystem(A, U, b);
+
+      r = A * U - b;
+
+      std::cout << "final (real) residu = " << std::sqrt(dot(r, r)) << '\n';
+
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < Dimension; ++i) {
+          Speed[cell_id][i] = U[(cell_dof_number[cell_id] * Dimension) + i];
+        }
+      }
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        for (size_t i = 0; i < Dimension; ++i) {
+          if (primal_face_is_on_boundary[face_id]) {
+            Speed_face[face_id][i] = U[(face_dof_number[face_id] * Dimension) + i];
+          } else {
+            Speed_face[face_id][i] = Ul[face_id][i];
+          }
+        }
+      }
+      Vector<double> Uexacte{mesh->numberOfCells() * Dimension};
+      for (CellId j = 0; j < mesh->numberOfCells(); ++j) {
+        for (size_t l = 0; l < Dimension; ++l) {
+          Uexacte[(cell_dof_number[j] * Dimension) + l] = Uj[j][l];
+        }
+      }
+
+      Vector<double> error{mesh->numberOfCells() * Dimension};
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < Dimension; ++i) {
+          error[(cell_id * Dimension) + i] = (Speed[cell_id][i] - Uj[cell_id][i]) * sqrt(primal_Vj[cell_id]);
+        }
+      }
+      Vector<double> error_face{mesh->numberOfFaces() * Dimension};
+      parallel_for(
+        mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+          if (primal_face_is_on_boundary[face_id]) {
+            for (size_t i = 0; i < Dimension; ++i) {
+              error_face[face_id * Dimension + i] = (Speed_face[face_id][i] - Ul[face_id][i]) * sqrt(mes_l[face_id]);
+            }
+          } else {
+            error_face[face_id] = 0;
+          }
+        });
+
+      std::cout << "||Error||_2 (cell)= " << std::sqrt(dot(error, error)) << "\n";
+      std::cout << "||Error||_2 (face)= " << std::sqrt(dot(error_face, error_face)) << "\n";
+      std::cout << "||Error||_2 (total)= " << std::sqrt(dot(error, error)) + std::sqrt(dot(error_face, error_face))
+                << "\n";
+
+      NodeValue<TinyVector<3>> ur3d{mesh->connectivity()};
+      ur3d.fill(zero);
+
+      parallel_for(
+        mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
+          TinyVector<Dimension> x = zero;
+          const auto node_cells   = node_to_cell_matrix[node_id];
+          for (size_t i_cell = 0; i_cell < node_cells.size(); ++i_cell) {
+            CellId cell_id = node_cells[i_cell];
+            x += w_rj(node_id, i_cell) * Speed[cell_id];
+          }
+          const auto node_faces = node_to_face_matrix[node_id];
+          for (size_t i_face = 0; i_face < node_faces.size(); ++i_face) {
+            FaceId face_id = node_faces[i_face];
+            if (primal_face_is_on_boundary[face_id]) {
+              x += w_rl(node_id, i_face) * Speed_face[face_id];
+            }
+          }
+          for (size_t i = 0; i < Dimension; ++i) {
+            ur3d[node_id][i] = x[i];
+          }
+        });
+    }
+  } else {
+    throw NotImplementedError("not done in 1d");
+  }
+}
+
+template <size_t Dimension>
+class ElasticityDiamondScheme<Dimension>::DirichletBoundaryCondition
+{
+ private:
+  const Array<const TinyVector<Dimension>> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const TinyVector<Dimension>>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  DirichletBoundaryCondition(const Array<const FaceId>& face_list, const Array<const TinyVector<Dimension>>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~DirichletBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class ElasticityDiamondScheme<Dimension>::NormalStrainBoundaryCondition
+{
+ private:
+  const Array<const TinyVector<Dimension>> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const TinyVector<Dimension>>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NormalStrainBoundaryCondition(const Array<const FaceId>& face_list,
+                                const Array<const TinyVector<Dimension>>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~NormalStrainBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class ElasticityDiamondScheme<Dimension>::SymmetryBoundaryCondition
+{
+ private:
+  const Array<const TinyVector<Dimension>> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+ public:
+  SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+template ElasticityDiamondScheme<1>::ElasticityDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
+
+template ElasticityDiamondScheme<2>::ElasticityDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
+
+template ElasticityDiamondScheme<3>::ElasticityDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
diff --git a/src/language/algorithms/ElasticityDiamondAlgorithm.hpp b/src/language/algorithms/ElasticityDiamondAlgorithm.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..24988916ec799ecaa3e227ebc39d54878bfef8d3
--- /dev/null
+++ b/src/language/algorithms/ElasticityDiamondAlgorithm.hpp
@@ -0,0 +1,32 @@
+#ifndef ELASTICITY_DIAMOND_ALGORITHM_HPP
+#define ELASTICITY_DIAMOND_ALGORITHM_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <language/utils/FunctionSymbolId.hpp>
+#include <memory>
+#include <mesh/IMesh.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+#include <variant>
+#include <vector>
+
+template <size_t Dimension>
+class ElasticityDiamondScheme
+{
+ private:
+  class DirichletBoundaryCondition;
+  class NormalStrainBoundaryCondition;
+  class SymmetryBoundaryCondition;
+
+ public:
+  ElasticityDiamondScheme(std::shared_ptr<const IMesh> i_mesh,
+                          const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+                          const FunctionSymbolId& lambda_id,
+                          const FunctionSymbolId& mu_id,
+                          const FunctionSymbolId& f_id,
+                          const FunctionSymbolId& U_id);
+};
+
+#endif   // ELASTICITY_DIAMOND_ALGORITHM2_HPP
diff --git a/src/language/algorithms/Heat5PointsAlgorithm.cpp b/src/language/algorithms/Heat5PointsAlgorithm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ffd1e810277ca05104cc00dd183c283dcf81f9a0
--- /dev/null
+++ b/src/language/algorithms/Heat5PointsAlgorithm.cpp
@@ -0,0 +1,207 @@
+#include <language/algorithms/Heat5PointsAlgorithm.hpp>
+
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LinearSolver.hpp>
+#include <algebra/LinearSolverOptions.hpp>
+#include <algebra/SmallMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <algebra/Vector.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
+
+template <size_t Dimension>
+Heat5PointsAlgorithm<Dimension>::Heat5PointsAlgorithm(
+  std::shared_ptr<const IMesh> i_mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const FunctionSymbolId& T_id,
+  const FunctionSymbolId& kappa_id,
+  const FunctionSymbolId& f_id)
+{
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+  std::cout << "number of bc descr = " << bc_descriptor_list.size() << '\n';
+
+  if constexpr (Dimension == 2) {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+    CellValue<double> Tj =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(T_id, mesh_data.xj());
+
+    NodeValue<double> Tr(mesh->connectivity());
+    const NodeValue<const TinyVector<Dimension>>& xr = mesh->xr();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = mesh->connectivity().nodeToCellMatrix();
+    CellValuePerNode<double> w_rj{mesh->connectivity()};
+
+    for (NodeId i_node = 0; i_node < mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+      for (size_t j = 0; j < node_to_cell.size(); j++) {
+        A(0, j) = 1;
+      }
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          const CellId J = node_to_cell[j];
+          A(i, j)        = xj[J][i - 1];
+        }
+      }
+      SmallVector<double> x{node_to_cell.size()};
+      x = zero;
+
+      LeastSquareSolver ls_solver;
+      ls_solver.solveLocalSystem(A, x, b);
+
+      Tr[i_node] = 0;
+      for (size_t j = 0; j < node_to_cell.size(); j++) {
+        Tr[i_node] += x[j] * Tj[node_to_cell[j]];
+        w_rj(i_node, j) = x[j];
+      }
+    }
+
+    {
+      std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+      MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+      std::shared_ptr mapper =
+        DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+      NodeValue<double> Trd{diamond_mesh->connectivity()};
+
+      mapper->toDualNode(Tr, Tj, Trd);
+
+      CellValue<double> kappaj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(kappa_id,
+                                                                                                  mesh_data.xj());
+
+      CellValue<double> dual_kappaj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(kappa_id,
+                                                                                                  diamond_mesh_data
+                                                                                                    .xj());
+
+      const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+      const FaceValue<const double> mes_l = [&] {
+        if constexpr (Dimension == 1) {
+          FaceValue<double> compute_mes_l{mesh->connectivity()};
+          compute_mes_l.fill(1);
+          return compute_mes_l;
+        } else {
+          return mesh_data.ll();
+        }
+      }();
+
+      const CellValue<const double> dual_mes_l_j = [=] {
+        CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+        mapper->toDualCell(mes_l, compute_mes_j);
+
+        return compute_mes_j;
+      }();
+
+      FaceValue<const double> alpha_l = [&] {
+        CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+        parallel_for(
+          diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+            alpha_j[diamond_cell_id] =
+              dual_mes_l_j[diamond_cell_id] * dual_kappaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+          });
+
+        FaceValue<double> computed_alpha_l{mesh->connectivity()};
+        mapper->fromDualCell(alpha_j, computed_alpha_l);
+
+        return computed_alpha_l;
+      }();
+
+      const Array<int> non_zeros{mesh->numberOfCells()};
+      non_zeros.fill(Dimension);
+      CRSMatrixDescriptor<double> S(mesh->numberOfCells(), mesh->numberOfCells(), non_zeros);
+
+      const auto& face_to_cell_matrix = mesh->connectivity().faceToCellMatrix();
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+
+        const double beta_l = 0.5 * alpha_l[face_id] * mes_l[face_id];
+
+        for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+          const CellId cell_id1 = primal_face_to_cell[i_cell];
+          for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+            const CellId cell_id2 = primal_face_to_cell[j_cell];
+            if (i_cell == j_cell) {
+              S(cell_id1, cell_id2) -= beta_l;
+            } else {
+              S(cell_id1, cell_id2) += beta_l;
+            }
+          }
+        }
+      }
+      CellValue<double> fj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(f_id, mesh_data.xj());
+
+      const CellValue<const double> primal_Vj = mesh_data.Vj();
+      CRSMatrix A{S.getCRSMatrix()};
+      Vector<double> b{mesh->numberOfCells()};
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { b[cell_id] = fj[cell_id] * primal_Vj[cell_id]; });
+
+      Vector<double> T{mesh->numberOfCells()};
+      T = zero;
+
+      LinearSolver solver;
+      solver.solveLocalSystem(A, T, b);
+
+      CellValue<double> Temperature{mesh->connectivity()};
+
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { Temperature[cell_id] = T[cell_id]; });
+
+      Vector<double> error{mesh->numberOfCells()};
+      parallel_for(
+        mesh->numberOfCells(),
+        PUGS_LAMBDA(CellId cell_id) { error[cell_id] = (Temperature[cell_id] - Tj[cell_id]) * primal_Vj[cell_id]; });
+
+      std::cout << "||Error||_2 = " << std::sqrt(dot(error, error)) << "\n";
+    }
+  } else {
+    throw NotImplementedError("not done in this dimension");
+  }
+}
+
+template Heat5PointsAlgorithm<1>::Heat5PointsAlgorithm(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
+
+template Heat5PointsAlgorithm<2>::Heat5PointsAlgorithm(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
+
+template Heat5PointsAlgorithm<3>::Heat5PointsAlgorithm(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
diff --git a/src/language/algorithms/Heat5PointsAlgorithm.hpp b/src/language/algorithms/Heat5PointsAlgorithm.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a061972d6c3b33073a296ea9b8fd4d1a456701c5
--- /dev/null
+++ b/src/language/algorithms/Heat5PointsAlgorithm.hpp
@@ -0,0 +1,21 @@
+#ifndef HEAT_5POINTS_ALGORITHM_HPP
+#define HEAT_5POINTS_ALGORITHM_HPP
+
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+
+#include <memory>
+#include <vector>
+
+template <size_t Dimension>
+struct Heat5PointsAlgorithm
+{
+  Heat5PointsAlgorithm(std::shared_ptr<const IMesh> i_mesh,
+                       const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+                       const FunctionSymbolId& T_id,
+                       const FunctionSymbolId& kappa_id,
+                       const FunctionSymbolId& f_id);
+};
+
+#endif   // HEAT_5POINTS_ALGORITHM_HPP
diff --git a/src/language/algorithms/HeatDiamondAlgorithm.cpp b/src/language/algorithms/HeatDiamondAlgorithm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d21c25062dd24234a2e3d619ce1847a9bb1b310b
--- /dev/null
+++ b/src/language/algorithms/HeatDiamondAlgorithm.cpp
@@ -0,0 +1,773 @@
+#include <language/algorithms/HeatDiamondAlgorithm.hpp>
+
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LinearSolver.hpp>
+#include <algebra/SmallMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <algebra/Vector.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/FourierBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <utils/Timer.hpp>
+
+template <size_t Dimension>
+HeatDiamondScheme<Dimension>::HeatDiamondScheme(
+  std::shared_ptr<const IMesh> i_mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const FunctionSymbolId& T_id,
+  const FunctionSymbolId& kappa_id,
+  const FunctionSymbolId& f_id)
+{
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  using BoundaryCondition = std::variant<DirichletBoundaryCondition, FourierBoundaryCondition, NeumannBoundaryCondition,
+                                         SymmetryBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+  BoundaryConditionList boundary_condition_list;
+
+  std::cout << "number of bc descr = " << bc_descriptor_list.size() << '\n';
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+  for (const auto& bc_descriptor : bc_descriptor_list) {
+    bool is_valid_boundary_condition = true;
+
+    switch (bc_descriptor->type()) {
+    case IBoundaryConditionDescriptor::Type::symmetry: {
+      throw NotImplementedError("NIY");
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::dirichlet: {
+      const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+        dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+      if constexpr (Dimension > 1) {
+        MeshFaceBoundary<Dimension> mesh_face_boundary =
+          getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+        const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+        MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
+
+        Array<const double> value_list =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                    mesh_data.xl(),
+                                                                                                    mesh_face_boundary
+                                                                                                      .faceList());
+        boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+      } else {
+        throw NotImplementedError("not implemented in 1d");
+      }
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::fourier: {
+      throw NotImplementedError("NIY");
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::neumann: {
+      const NeumannBoundaryConditionDescriptor& neumann_bc_descriptor =
+        dynamic_cast<const NeumannBoundaryConditionDescriptor&>(*bc_descriptor);
+
+      if constexpr (Dimension > 1) {
+        MeshFaceBoundary<Dimension> mesh_face_boundary =
+          getMeshFaceBoundary(*mesh, neumann_bc_descriptor.boundaryDescriptor());
+
+        const FunctionSymbolId g_id = neumann_bc_descriptor.rhsSymbolId();
+        MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
+
+        Array<const double> value_list =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                    mesh_data.xl(),
+                                                                                                    mesh_face_boundary
+                                                                                                      .faceList());
+        boundary_condition_list.push_back(NeumannBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+      } else {
+        throw NotImplementedError("not implemented in 1d");
+      }
+      break;
+    }
+    default: {
+      is_valid_boundary_condition = false;
+    }
+    }
+    if (not is_valid_boundary_condition) {
+      std::ostringstream error_msg;
+      error_msg << *bc_descriptor << " is an invalid boundary condition for heat equation";
+      throw NormalError(error_msg.str());
+    }
+  }
+
+  if constexpr (Dimension > 1) {
+    const CellValue<const size_t> cell_dof_number = [&] {
+      CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+      return compute_cell_dof_number;
+    }();
+    size_t number_of_dof = mesh->numberOfCells();
+
+    const FaceValue<const size_t> face_dof_number = [&] {
+      FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+      compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                          (std::is_same_v<T, FourierBoundaryCondition>) or
+                          (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                          (std::is_same_v<T, DirichletBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id = face_list[i_face];
+                if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                  std::ostringstream os;
+                  os << "The face " << face_id << " is used at least twice for boundary conditions";
+                  throw NormalError(os.str());
+                } else {
+                  compute_face_dof_number[face_id] = number_of_dof++;
+                }
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return compute_face_dof_number;
+    }();
+
+    const FaceValue<const bool> primal_face_is_neumann = [&] {
+      FaceValue<bool> face_is_neumann{mesh->connectivity()};
+      face_is_neumann.fill(false);
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                          (std::is_same_v<T, FourierBoundaryCondition>) or
+                          (std::is_same_v<T, SymmetryBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id     = face_list[i_face];
+                face_is_neumann[face_id] = true;
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return face_is_neumann;
+    }();
+
+    const auto& primal_face_to_node_matrix = mesh->connectivity().faceToNodeMatrix();
+    const auto& face_to_cell_matrix        = mesh->connectivity().faceToCellMatrix();
+    NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+    }
+
+    primal_node_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+          NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+          primal_node_is_on_boundary[node_id] = true;
+        }
+      }
+    }
+
+    FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+    }
+
+    primal_face_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        primal_face_is_on_boundary[face_id] = true;
+      }
+    }
+
+    FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+    }
+
+    primal_face_is_dirichlet.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] && (!primal_face_is_neumann[face_id]));
+    }
+
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+    CellValue<double> Tj =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(T_id, mesh_data.xj());
+    FaceValue<double> Tl =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(T_id, mesh_data.xl());
+    NodeValue<double> Tr(mesh->connectivity());
+    const NodeValue<const TinyVector<Dimension>>& xr = mesh->xr();
+
+    const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix                  = mesh->connectivity().nodeToFaceMatrix();
+    CellValuePerNode<double> w_rj{mesh->connectivity()};
+    FaceValuePerNode<double> w_rl{mesh->connectivity()};
+
+    for (size_t i = 0; i < w_rl.numberOfValues(); ++i) {
+      w_rl[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+
+    for (NodeId i_node = 0; i_node < mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      if (not primal_node_is_on_boundary[i_node]) {
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        SmallVector<double> x{node_to_cell.size()};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        Tr[i_node] = 0;
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          Tr[i_node] += x[j] * Tj[node_to_cell[j]];
+          w_rj(i_node, j) = x[j];
+        }
+      } else {
+        int nb_face_used = 0;
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (primal_face_is_on_boundary[face_id]) {
+            nb_face_used++;
+          }
+        }
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size() + nb_face_used};
+        for (size_t j = 0; j < node_to_cell.size() + nb_face_used; j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          int cpt_face = 0;
+          for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+            FaceId face_id = node_to_face_matrix[i_node][i_face];
+            if (primal_face_is_on_boundary[face_id]) {
+              A(i, node_to_cell.size() + cpt_face) = xl[face_id][i - 1];
+              cpt_face++;
+            }
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size() + nb_face_used};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        Tr[i_node] = 0;
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          Tr[i_node] += x[j] * Tj[node_to_cell[j]];
+          w_rj(i_node, j) = x[j];
+        }
+        int cpt_face = node_to_cell.size();
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (primal_face_is_on_boundary[face_id]) {
+            w_rl(i_node, i_face) = x[cpt_face++];
+            Tr[i_node] += w_rl(i_node, i_face) * Tl[face_id];
+          }
+        }
+      }
+    }
+
+    {
+      std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+      MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+      std::shared_ptr mapper =
+        DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+      NodeValue<double> Trd{diamond_mesh->connectivity()};
+
+      mapper->toDualNode(Tr, Tj, Trd);
+
+      CellValue<double> kappaj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(kappa_id,
+                                                                                                  mesh_data.xj());
+
+      CellValue<double> dual_kappaj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(kappa_id,
+                                                                                                  diamond_mesh_data
+                                                                                                    .xj());
+
+      const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+      const FaceValue<const double> mes_l = [&] {
+        if constexpr (Dimension == 1) {
+          FaceValue<double> compute_mes_l{mesh->connectivity()};
+          compute_mes_l.fill(1);
+          return compute_mes_l;
+        } else {
+          return mesh_data.ll();
+        }
+      }();
+
+      const CellValue<const double> dual_mes_l_j = [=] {
+        CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+        mapper->toDualCell(mes_l, compute_mes_j);
+
+        return compute_mes_j;
+      }();
+
+      FaceValue<const CellId> face_dual_cell_id = [=]() {
+        FaceValue<CellId> computed_face_dual_cell_id{mesh->connectivity()};
+        CellValue<CellId> dual_cell_id{diamond_mesh->connectivity()};
+        parallel_for(
+          diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { dual_cell_id[cell_id] = cell_id; });
+
+        mapper->fromDualCell(dual_cell_id, computed_face_dual_cell_id);
+
+        return computed_face_dual_cell_id;
+      }();
+
+      NodeValue<const NodeId> dual_node_primal_node_id = [=]() {
+        CellValue<NodeId> cell_ignored_id{mesh->connectivity()};
+        cell_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+        NodeValue<NodeId> node_primal_id{mesh->connectivity()};
+
+        parallel_for(
+          mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { node_primal_id[node_id] = node_id; });
+
+        NodeValue<NodeId> computed_dual_node_primal_node_id{diamond_mesh->connectivity()};
+
+        mapper->toDualNode(node_primal_id, cell_ignored_id, computed_dual_node_primal_node_id);
+
+        return computed_dual_node_primal_node_id;
+      }();
+
+      CellValue<NodeId> primal_cell_dual_node_id = [=]() {
+        CellValue<NodeId> cell_id{mesh->connectivity()};
+        NodeValue<NodeId> node_ignored_id{mesh->connectivity()};
+        node_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+        NodeValue<NodeId> dual_node_id{diamond_mesh->connectivity()};
+
+        parallel_for(
+          diamond_mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { dual_node_id[node_id] = node_id; });
+
+        CellValue<NodeId> computed_primal_cell_dual_node_id{mesh->connectivity()};
+
+        mapper->fromDualNode(dual_node_id, node_ignored_id, cell_id);
+
+        return cell_id;
+      }();
+      Timer my_timer;
+      const auto& dual_Cjr                     = diamond_mesh_data.Cjr();
+      FaceValue<TinyVector<Dimension>> dualClj = [&] {
+        FaceValue<TinyVector<Dimension>> computedClj{mesh->connectivity()};
+        const auto& dual_node_to_cell_matrix = diamond_mesh->connectivity().nodeToCellMatrix();
+        const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+        parallel_for(
+          mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+            const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+            for (size_t i = 0; i < primal_face_to_cell.size(); i++) {
+              CellId cell_id            = primal_face_to_cell[i];
+              const NodeId dual_node_id = primal_cell_dual_node_id[cell_id];
+              for (size_t i_dual_cell = 0; i_dual_cell < dual_node_to_cell_matrix[dual_node_id].size(); i_dual_cell++) {
+                const CellId dual_cell_id = dual_node_to_cell_matrix[dual_node_id][i_dual_cell];
+                if (face_dual_cell_id[face_id] == dual_cell_id) {
+                  for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                       i_dual_node++) {
+                    const NodeId final_dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                    if (final_dual_node_id == dual_node_id) {
+                      computedClj[face_id] = dual_Cjr(dual_cell_id, i_dual_node);
+                    }
+                  }
+                }
+              }
+            }
+          });
+        return computedClj;
+      }();
+
+      FaceValue<TinyVector<Dimension>> nlj = [&] {
+        FaceValue<TinyVector<Dimension>> computedNlj{mesh->connectivity()};
+        parallel_for(
+          mesh->numberOfFaces(),
+          PUGS_LAMBDA(FaceId face_id) { computedNlj[face_id] = 1. / l2Norm(dualClj[face_id]) * dualClj[face_id]; });
+        return computedNlj;
+      }();
+
+      FaceValue<const double> alpha_l = [&] {
+        CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+        parallel_for(
+          diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+            alpha_j[diamond_cell_id] = dual_kappaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+          });
+
+        FaceValue<double> computed_alpha_l{mesh->connectivity()};
+        mapper->fromDualCell(alpha_j, computed_alpha_l);
+
+        return computed_alpha_l;
+      }();
+
+      const Array<int> non_zeros{number_of_dof};
+      non_zeros.fill(Dimension * Dimension);
+      CRSMatrixDescriptor<double> S(number_of_dof, number_of_dof, non_zeros);
+
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+        // const double beta_l             = 1. / Dimension * alpha_l[face_id] * mes_l[face_id];
+        const double beta_l = l2Norm(dualClj[face_id]) * alpha_l[face_id] * mes_l[face_id];
+        for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+          const CellId cell_id1 = primal_face_to_cell[i_cell];
+          for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+            const CellId cell_id2 = primal_face_to_cell[j_cell];
+            if (i_cell == j_cell) {
+              S(cell_dof_number[cell_id1], cell_dof_number[cell_id2]) += beta_l;
+              if (primal_face_is_neumann[face_id]) {
+                S(face_dof_number[face_id], cell_dof_number[cell_id2]) -= beta_l;
+              }
+            } else {
+              S(cell_dof_number[cell_id1], cell_dof_number[cell_id2]) -= beta_l;
+            }
+          }
+        }
+      }
+
+      const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+
+      const auto& primal_node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        const double alpha_face_id = mes_l[face_id] * alpha_l[face_id];
+
+        for (size_t i_face_cell = 0; i_face_cell < face_to_cell_matrix[face_id].size(); ++i_face_cell) {
+          CellId i_id                            = face_to_cell_matrix[face_id][i_face_cell];
+          const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+          for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+            NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+
+            const TinyVector<Dimension> nil = [&] {
+              if (is_face_reversed_for_cell_i) {
+                return -nlj[face_id];
+              } else {
+                return nlj[face_id];
+              }
+            }();
+
+            CellId dual_cell_id = face_dual_cell_id[face_id];
+
+            for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size(); ++i_dual_node) {
+              const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+              if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+
+                const double a_ir = alpha_face_id * dot(nil, Clr);
+
+                for (size_t j_cell = 0; j_cell < primal_node_to_cell_matrix[node_id].size(); ++j_cell) {
+                  CellId j_id = primal_node_to_cell_matrix[node_id][j_cell];
+                  S(cell_dof_number[i_id], cell_dof_number[j_id]) -= w_rj(node_id, j_cell) * a_ir;
+                  if (primal_face_is_neumann[face_id]) {
+                    S(face_dof_number[face_id], cell_dof_number[j_id]) += w_rj(node_id, j_cell) * a_ir;
+                  }
+                }
+                if (primal_node_is_on_boundary[node_id]) {
+                  for (size_t l_face = 0; l_face < node_to_face_matrix[node_id].size(); ++l_face) {
+                    FaceId l_id = node_to_face_matrix[node_id][l_face];
+                    if (primal_face_is_on_boundary[l_id]) {
+                      S(cell_dof_number[i_id], face_dof_number[l_id]) -= w_rl(node_id, l_face) * a_ir;
+                      if (primal_face_is_neumann[face_id]) {
+                        S(face_dof_number[face_id], face_dof_number[l_id]) += w_rl(node_id, l_face) * a_ir;
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (primal_face_is_dirichlet[face_id]) {
+          S(face_dof_number[face_id], face_dof_number[face_id]) += 1;
+        }
+      }
+
+      CellValue<double> fj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(f_id, mesh_data.xj());
+
+      const CellValue<const double> primal_Vj = mesh_data.Vj();
+      CRSMatrix A{S.getCRSMatrix()};
+      Vector<double> b{number_of_dof};
+      b = zero;
+
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+        b[cell_dof_number[cell_id]] = fj[cell_id] * primal_Vj[cell_id];
+      }
+      // Dirichlet on b^L_D
+      {
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
+                const auto& face_list  = bc.faceList();
+                const auto& value_list = bc.valueList();
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id = face_list[i_face];
+                  b[face_dof_number[face_id]] += value_list[i_face];   // sign
+                }
+              }
+            },
+            boundary_condition);
+        }
+      }
+      // EL b^L
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                          (std::is_same_v<T, FourierBoundaryCondition>)) {
+              const auto& face_list  = bc.faceList();
+              const auto& value_list = bc.valueList();
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                FaceId face_id = face_list[i_face];
+                b[face_dof_number[face_id]] += mes_l[face_id] * value_list[i_face];   // sign
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      Vector<double> T{number_of_dof};
+      T = zero;
+
+      LinearSolver solver;
+      solver.solveLocalSystem(A, T, b);
+
+      CellValue<double> Temperature{mesh->connectivity()};
+      FaceValue<double> Temperature_face{mesh->connectivity()};
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { Temperature[cell_id] = T[cell_dof_number[cell_id]]; });
+      parallel_for(
+        mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+          if (primal_face_is_neumann[face_id]) {
+            Temperature_face[face_id] = T[face_dof_number[face_id]];
+          } else {
+            Temperature_face[face_id] = Tl[face_id];
+          }
+        });
+      Vector<double> error{mesh->numberOfCells()};
+      CellValue<double> cell_error{mesh->connectivity()};
+      Vector<double> face_error{mesh->numberOfFaces()};
+      double error_max = 0.;
+      size_t cell_max  = 0;
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+          error[cell_id]      = (Temperature[cell_id] - Tj[cell_id]) * sqrt(primal_Vj[cell_id]);
+          cell_error[cell_id] = (Temperature[cell_id] - Tj[cell_id]);
+        });
+      parallel_for(
+        mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+          if (primal_face_is_on_boundary[face_id]) {
+            face_error[face_id] = (Temperature_face[face_id] - Tl[face_id]) * sqrt(mes_l[face_id]);
+          } else {
+            face_error[face_id] = 0;
+          }
+        });
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); cell_id++) {
+        if (error_max < std::abs(cell_error[cell_id])) {
+          error_max = std::abs(cell_error[cell_id]);
+          cell_max  = cell_id;
+        }
+      }
+
+      std::cout << " ||Error||_max (cell)= " << error_max << " on cell " << cell_max << "\n";
+      std::cout << "||Error||_2 (cell)= " << std::sqrt(dot(error, error)) << "\n";
+      std::cout << "||Error||_2 (face)= " << std::sqrt(dot(face_error, face_error)) << "\n";
+      std::cout << "||Error||_2 (total)= " << std::sqrt(dot(error, error)) + std::sqrt(dot(face_error, face_error))
+                << "\n";
+    }
+  } else {
+    throw NotImplementedError("not implemented in 1d");
+  }
+}
+
+template <size_t Dimension>
+class HeatDiamondScheme<Dimension>::DirichletBoundaryCondition
+{
+ private:
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  DirichletBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~DirichletBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HeatDiamondScheme<Dimension>::NeumannBoundaryCondition
+{
+ private:
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NeumannBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~NeumannBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HeatDiamondScheme<Dimension>::FourierBoundaryCondition
+{
+ private:
+  const Array<const double> m_coef_list;
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  const Array<const double>&
+  coefList() const
+  {
+    return m_coef_list;
+  }
+
+ public:
+  FourierBoundaryCondition(const Array<const FaceId>& face_list,
+                           const Array<const double>& coef_list,
+                           const Array<const double>& value_list)
+    : m_coef_list{coef_list}, m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_coef_list.size() == m_face_list.size());
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~FourierBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HeatDiamondScheme<Dimension>::SymmetryBoundaryCondition
+{
+ private:
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+ public:
+  SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+template HeatDiamondScheme<1>::HeatDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
+
+template HeatDiamondScheme<2>::HeatDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
+
+template HeatDiamondScheme<3>::HeatDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
diff --git a/src/language/algorithms/HeatDiamondAlgorithm.hpp b/src/language/algorithms/HeatDiamondAlgorithm.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9e29d91bc1bc9f37842b8e993a698a4d26d9f68f
--- /dev/null
+++ b/src/language/algorithms/HeatDiamondAlgorithm.hpp
@@ -0,0 +1,29 @@
+#ifndef HEAT_DIAMOND_ALGORITHM_HPP
+#define HEAT_DIAMOND_ALGORITHM_HPP
+
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+
+#include <memory>
+#include <variant>
+#include <vector>
+
+template <size_t Dimension>
+class HeatDiamondScheme
+{
+ private:
+  class DirichletBoundaryCondition;
+  class FourierBoundaryCondition;
+  class NeumannBoundaryCondition;
+  class SymmetryBoundaryCondition;
+
+ public:
+  HeatDiamondScheme(std::shared_ptr<const IMesh> i_mesh,
+                    const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+                    const FunctionSymbolId& T_id,
+                    const FunctionSymbolId& kappa_id,
+                    const FunctionSymbolId& f_id);
+};
+
+#endif   // HEAT_DIAMOND_ALGORITHM_HPP
diff --git a/src/language/algorithms/HeatDiamondAlgorithm2.cpp b/src/language/algorithms/HeatDiamondAlgorithm2.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c36e796780f77ccd58c2fa7559a8c5cab9478d87
--- /dev/null
+++ b/src/language/algorithms/HeatDiamondAlgorithm2.cpp
@@ -0,0 +1,869 @@
+#include <language/algorithms/HeatDiamondAlgorithm2.hpp>
+
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LinearSolver.hpp>
+#include <algebra/SmallMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <algebra/Vector.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/FourierBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <utils/Timer.hpp>
+
+template <size_t Dimension>
+HeatDiamondScheme2<Dimension>::HeatDiamondScheme2(
+  std::shared_ptr<const IMesh> i_mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const FunctionSymbolId& T_id,
+  const FunctionSymbolId& kappa_id,
+  const FunctionSymbolId& f_id)
+{
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  using BoundaryCondition = std::variant<DirichletBoundaryCondition, FourierBoundaryCondition, NeumannBoundaryCondition,
+                                         SymmetryBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+  BoundaryConditionList boundary_condition_list;
+
+  std::cout << "number of bc descr = " << bc_descriptor_list.size() << '\n';
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+  NodeValue<bool> is_dirichlet{mesh->connectivity()};
+  is_dirichlet.fill(false);
+  NodeValue<double> dirichlet_value{mesh->connectivity()};
+  dirichlet_value.fill(std::numeric_limits<double>::signaling_NaN());
+
+  for (const auto& bc_descriptor : bc_descriptor_list) {
+    bool is_valid_boundary_condition = true;
+
+    switch (bc_descriptor->type()) {
+    case IBoundaryConditionDescriptor::Type::symmetry: {
+      throw NotImplementedError("NIY");
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::dirichlet: {
+      const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+        dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+      if constexpr (Dimension > 1) {
+        MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+        const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+        MeshNodeBoundary<Dimension> mesh_node_boundary =
+          getMeshNodeBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+        Array<const double> node_value_list =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::node>(g_id, mesh->xr(),
+                                                                                                    mesh_node_boundary
+                                                                                                      .nodeList());
+
+        MeshFaceBoundary<Dimension> mesh_face_boundary =
+          getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+        Array<const double> face_value_list =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                    mesh_data.xl(),
+                                                                                                    mesh_face_boundary
+                                                                                                      .faceList());
+
+        for (size_t i_node = 0; i_node < mesh_node_boundary.nodeList().size(); ++i_node) {
+          NodeId node_id = mesh_node_boundary.nodeList()[i_node];
+
+          if (not is_dirichlet[node_id]) {
+            is_dirichlet[node_id]    = true;
+            dirichlet_value[node_id] = node_value_list[i_node];
+          }
+        }
+
+        boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), face_value_list,
+                                                                     mesh_node_boundary.nodeList(), node_value_list});
+
+      } else {
+        throw NotImplementedError("not implemented in 1d");
+      }
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::fourier: {
+      throw NotImplementedError("NIY");
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::neumann: {
+      const NeumannBoundaryConditionDescriptor& neumann_bc_descriptor =
+        dynamic_cast<const NeumannBoundaryConditionDescriptor&>(*bc_descriptor);
+
+      if constexpr (Dimension > 1) {
+        MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+        const FunctionSymbolId g_id = neumann_bc_descriptor.rhsSymbolId();
+
+        MeshFaceBoundary<Dimension> mesh_face_boundary =
+          getMeshFaceBoundary(*mesh, neumann_bc_descriptor.boundaryDescriptor());
+
+        Array<const double> face_value_list =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                    mesh_data.xl(),
+                                                                                                    mesh_face_boundary
+                                                                                                      .faceList());
+
+        boundary_condition_list.push_back(NeumannBoundaryCondition{mesh_face_boundary.faceList(), face_value_list});
+      } else {
+        throw NotImplementedError("not implemented in 1d");
+      }
+
+      break;
+    }
+    default: {
+      is_valid_boundary_condition = false;
+    }
+    }
+    if (not is_valid_boundary_condition) {
+      std::ostringstream error_msg;
+      error_msg << *bc_descriptor << " is an invalid boundary condition for heat equation";
+      throw NormalError(error_msg.str());
+    }
+  }
+
+  if constexpr (Dimension > 1) {
+    const CellValue<const size_t> cell_dof_number = [&] {
+      CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+      return compute_cell_dof_number;
+    }();
+    size_t number_of_dof = mesh->numberOfCells();
+
+    const FaceValue<const size_t> face_dof_number = [&] {
+      FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+      compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                          (std::is_same_v<T, FourierBoundaryCondition>) or
+                          (std::is_same_v<T, SymmetryBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id = face_list[i_face];
+                if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                  std::ostringstream os;
+                  os << "The face " << face_id << " is used at least twice for boundary conditions";
+                  throw NormalError(os.str());
+                } else {
+                  compute_face_dof_number[face_id] = number_of_dof++;
+                }
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return compute_face_dof_number;
+    }();
+
+    const FaceValue<const bool> primal_face_is_neumann = [&] {
+      FaceValue<bool> face_is_neumann{mesh->connectivity()};
+      face_is_neumann.fill(false);
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                          (std::is_same_v<T, FourierBoundaryCondition>) or
+                          (std::is_same_v<T, SymmetryBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id     = face_list[i_face];
+                face_is_neumann[face_id] = true;
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return face_is_neumann;
+    }();
+
+    const auto& primal_face_to_node_matrix = mesh->connectivity().faceToNodeMatrix();
+    const auto& face_to_cell_matrix        = mesh->connectivity().faceToCellMatrix();
+    NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+    }
+
+    primal_node_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+          NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+          primal_node_is_on_boundary[node_id] = true;
+        }
+      }
+    }
+
+    FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+    }
+
+    primal_face_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        primal_face_is_on_boundary[face_id] = true;
+      }
+    }
+
+    FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+    }
+
+    primal_face_is_dirichlet.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] && (!primal_face_is_neumann[face_id]));
+    }
+
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+    CellValue<double> Tj =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(T_id, mesh_data.xj());
+    FaceValue<double> Tl =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(T_id, mesh_data.xl());
+    NodeValue<double> Tr(mesh->connectivity());
+    const NodeValue<const TinyVector<Dimension>>& xr = mesh->xr();
+
+    const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix                  = mesh->connectivity().nodeToFaceMatrix();
+    CellValuePerNode<double> w_rj{mesh->connectivity()};
+    FaceValuePerNode<double> w_rl{mesh->connectivity()};
+
+    for (size_t i = 0; i < w_rl.numberOfValues(); ++i) {
+      w_rl[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+
+    for (NodeId i_node = 0; i_node < mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      if (not primal_node_is_on_boundary[i_node]) {
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        SmallVector<double> x{node_to_cell.size()};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        Tr[i_node] = 0;
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          Tr[i_node] += x[j] * Tj[node_to_cell[j]];
+          w_rj(i_node, j) = x[j];
+        }
+      } else {
+        int nb_face_used = 0;
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (primal_face_is_on_boundary[face_id]) {
+            nb_face_used++;
+          }
+        }
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size() + nb_face_used};
+        for (size_t j = 0; j < node_to_cell.size() + nb_face_used; j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          int cpt_face = 0;
+          for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+            FaceId face_id = node_to_face_matrix[i_node][i_face];
+            if (primal_face_is_on_boundary[face_id]) {
+              A(i, node_to_cell.size() + cpt_face) = xl[face_id][i - 1];
+              cpt_face++;
+            }
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size() + nb_face_used};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        Tr[i_node] = 0;
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          Tr[i_node] += x[j] * Tj[node_to_cell[j]];
+          w_rj(i_node, j) = x[j];
+        }
+        int cpt_face = node_to_cell.size();
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (primal_face_is_on_boundary[face_id]) {
+            w_rl(i_node, i_face) = x[cpt_face++];
+            Tr[i_node] += w_rl(i_node, i_face) * Tl[face_id];
+          }
+        }
+      }
+    }
+
+    {
+      std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+      MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+      std::shared_ptr mapper =
+        DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+      NodeValue<double> Trd{diamond_mesh->connectivity()};
+
+      mapper->toDualNode(Tr, Tj, Trd);
+
+      CellValue<double> kappaj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(kappa_id,
+                                                                                                  mesh_data.xj());
+
+      CellValue<double> dual_kappaj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(kappa_id,
+                                                                                                  diamond_mesh_data
+                                                                                                    .xj());
+
+      const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+      const FaceValue<const double> mes_l = [&] {
+        if constexpr (Dimension == 1) {
+          FaceValue<double> compute_mes_l{mesh->connectivity()};
+          compute_mes_l.fill(1);
+          return compute_mes_l;
+        } else {
+          return mesh_data.ll();
+        }
+      }();
+
+      const CellValue<const double> dual_mes_l_j = [=] {
+        CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+        mapper->toDualCell(mes_l, compute_mes_j);
+
+        return compute_mes_j;
+      }();
+
+      FaceValue<const CellId> face_dual_cell_id = [=]() {
+        FaceValue<CellId> computed_face_dual_cell_id{mesh->connectivity()};
+        CellValue<CellId> dual_cell_id{diamond_mesh->connectivity()};
+        parallel_for(
+          diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { dual_cell_id[cell_id] = cell_id; });
+
+        mapper->fromDualCell(dual_cell_id, computed_face_dual_cell_id);
+
+        return computed_face_dual_cell_id;
+      }();
+
+      NodeValue<const NodeId> dual_node_primal_node_id = [=]() {
+        CellValue<NodeId> cell_ignored_id{mesh->connectivity()};
+        cell_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+        NodeValue<NodeId> node_primal_id{mesh->connectivity()};
+
+        parallel_for(
+          mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { node_primal_id[node_id] = node_id; });
+
+        NodeValue<NodeId> computed_dual_node_primal_node_id{diamond_mesh->connectivity()};
+
+        mapper->toDualNode(node_primal_id, cell_ignored_id, computed_dual_node_primal_node_id);
+
+        return computed_dual_node_primal_node_id;
+      }();
+
+      CellValue<NodeId> primal_cell_dual_node_id = [=]() {
+        CellValue<NodeId> cell_id{mesh->connectivity()};
+        NodeValue<NodeId> node_ignored_id{mesh->connectivity()};
+        node_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+        NodeValue<NodeId> dual_node_id{diamond_mesh->connectivity()};
+
+        parallel_for(
+          diamond_mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { dual_node_id[node_id] = node_id; });
+
+        CellValue<NodeId> computed_primal_cell_dual_node_id{mesh->connectivity()};
+
+        mapper->fromDualNode(dual_node_id, node_ignored_id, cell_id);
+
+        return cell_id;
+      }();
+      Timer my_timer;
+      const auto& dual_Cjr                     = diamond_mesh_data.Cjr();
+      FaceValue<TinyVector<Dimension>> dualClj = [&] {
+        FaceValue<TinyVector<Dimension>> computedClj{mesh->connectivity()};
+        const auto& dual_node_to_cell_matrix = diamond_mesh->connectivity().nodeToCellMatrix();
+        const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+        parallel_for(
+          mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+            const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+            for (size_t i = 0; i < primal_face_to_cell.size(); i++) {
+              CellId cell_id            = primal_face_to_cell[i];
+              const NodeId dual_node_id = primal_cell_dual_node_id[cell_id];
+              for (size_t i_dual_cell = 0; i_dual_cell < dual_node_to_cell_matrix[dual_node_id].size(); i_dual_cell++) {
+                const CellId dual_cell_id = dual_node_to_cell_matrix[dual_node_id][i_dual_cell];
+                if (face_dual_cell_id[face_id] == dual_cell_id) {
+                  for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                       i_dual_node++) {
+                    const NodeId final_dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                    if (final_dual_node_id == dual_node_id) {
+                      computedClj[face_id] = dual_Cjr(dual_cell_id, i_dual_node);
+                    }
+                  }
+                }
+              }
+            }
+          });
+        return computedClj;
+      }();
+
+      FaceValue<TinyVector<Dimension>> nlj = [&] {
+        FaceValue<TinyVector<Dimension>> computedNlj{mesh->connectivity()};
+        parallel_for(
+          mesh->numberOfFaces(),
+          PUGS_LAMBDA(FaceId face_id) { computedNlj[face_id] = 1. / l2Norm(dualClj[face_id]) * dualClj[face_id]; });
+        return computedNlj;
+      }();
+
+      FaceValue<const double> alpha_l = [&] {
+        CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+        parallel_for(
+          diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+            alpha_j[diamond_cell_id] = dual_kappaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+          });
+
+        FaceValue<double> computed_alpha_l{mesh->connectivity()};
+        mapper->fromDualCell(alpha_j, computed_alpha_l);
+
+        return computed_alpha_l;
+      }();
+
+      const Array<int> non_zeros{number_of_dof};
+      non_zeros.fill(Dimension * Dimension);
+      CRSMatrixDescriptor<double> S(number_of_dof, number_of_dof, non_zeros);
+
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+        // const double beta_l             = 1. / Dimension * alpha_l[face_id] * mes_l[face_id];
+        const double beta_l = l2Norm(dualClj[face_id]) * alpha_l[face_id] * mes_l[face_id];
+        for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+          const CellId cell_id1 = primal_face_to_cell[i_cell];
+          for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+            const CellId cell_id2 = primal_face_to_cell[j_cell];
+            if (i_cell == j_cell) {
+              S(cell_dof_number[cell_id1], cell_dof_number[cell_id2]) += beta_l;
+              if (primal_face_is_neumann[face_id]) {
+                S(face_dof_number[face_id], cell_dof_number[cell_id2]) -= beta_l;
+              }
+            } else {
+              S(cell_dof_number[cell_id1], cell_dof_number[cell_id2]) -= beta_l;
+            }
+          }
+        }
+      }
+
+      const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+
+      const auto& primal_node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        const double alpha_face_id = mes_l[face_id] * alpha_l[face_id];
+
+        for (size_t i_face_cell = 0; i_face_cell < face_to_cell_matrix[face_id].size(); ++i_face_cell) {
+          CellId i_id                            = face_to_cell_matrix[face_id][i_face_cell];
+          const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+          for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+            NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+
+            const TinyVector<Dimension> nil = [&] {
+              if (is_face_reversed_for_cell_i) {
+                return -nlj[face_id];
+              } else {
+                return nlj[face_id];
+              }
+            }();
+
+            CellId dual_cell_id = face_dual_cell_id[face_id];
+
+            for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size(); ++i_dual_node) {
+              const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+              if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+
+                const double a_ir = alpha_face_id * dot(nil, Clr);
+
+                for (size_t j_cell = 0; j_cell < primal_node_to_cell_matrix[node_id].size(); ++j_cell) {
+                  CellId j_id = primal_node_to_cell_matrix[node_id][j_cell];
+                  S(cell_dof_number[i_id], cell_dof_number[j_id]) -= w_rj(node_id, j_cell) * a_ir;
+                  if (primal_face_is_neumann[face_id]) {
+                    S(face_dof_number[face_id], cell_dof_number[j_id]) += w_rj(node_id, j_cell) * a_ir;
+                  }
+                }
+                if (primal_node_is_on_boundary[node_id]) {
+                  for (size_t l_face = 0; l_face < node_to_face_matrix[node_id].size(); ++l_face) {
+                    FaceId l_id = node_to_face_matrix[node_id][l_face];
+                    // ELIMINATION METTRE AU SECOND MEMBRE
+                    if (primal_face_is_neumann[l_id]) {
+                      S(cell_dof_number[i_id], face_dof_number[l_id]) -= w_rl(node_id, l_face) * a_ir;
+                      if (primal_face_is_neumann[face_id]) {
+                        S(face_dof_number[face_id], face_dof_number[l_id]) += w_rl(node_id, l_face) * a_ir;
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+
+      CellValue<double> fj =
+        InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(f_id, mesh_data.xj());
+
+      const CellValue<const double> primal_Vj = mesh_data.Vj();
+      CRSMatrix A{S.getCRSMatrix()};
+      Vector<double> b{number_of_dof};
+      b = zero;
+
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+        b[cell_dof_number[cell_id]] = fj[cell_id] * primal_Vj[cell_id];
+      }
+      // Dirichlet on b^L_D a Dirichlet face F contribute to the second member J via the node R thanks to A^{JR} A^{RF}
+      // for cell and A^{NR} A^{RF} for Neumann faces
+      {
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
+                const auto& value_list = bc.valueList();
+                const auto& face_list  = bc.faceList();
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  FaceId face_id = face_list[i_face];
+
+                  // loop on Nodes of Dirichlet faces
+                  for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+                    NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+                    // loop on faces of node
+                    const double w_face_node = [&] {
+                      for (size_t i_node_face = 0; i_node_face < node_to_face_matrix[node_id].size(); ++i_node_face) {
+                        FaceId l_id = node_to_face_matrix[node_id][i_node_face];
+                        if (l_id == face_id) {
+                          return w_rl(node_id, i_node_face);
+                        }
+                      }
+                      throw UnexpectedError("unable to get face node weight");
+                    }();
+
+                    for (size_t i_node_face = 0; i_node_face < node_to_face_matrix[node_id].size(); ++i_node_face) {
+                      FaceId l_id         = node_to_face_matrix[node_id][i_node_face];
+                      CellId dual_cell_id = face_dual_cell_id[l_id];
+
+                      for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                           ++i_dual_node) {
+                        const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                        // find the dual node of interest
+                        if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                          // get Cjr
+                          const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+                          const double alpha_face_id      = mes_l[l_id] * alpha_l[l_id];
+                          for (size_t cell_id = 0; cell_id < face_to_cell_matrix[l_id].size(); ++cell_id) {
+                            CellId i_id                            = face_to_cell_matrix[l_id][cell_id];
+                            const bool is_face_reversed_for_cell_i = (dot(dualClj[l_id], xl[l_id] - xj[i_id]) < 0);
+
+                            const TinyVector<Dimension> nil = [&] {
+                              if (is_face_reversed_for_cell_i) {
+                                return -nlj[l_id];
+                              } else {
+                                return nlj[l_id];
+                              }
+                            }();
+
+                            const double a_ir = alpha_face_id * dot(nil, Clr);
+                            // loop on faces of cells
+                            b[cell_dof_number[i_id]] += w_face_node * a_ir * value_list[i_face];
+
+                            // If l_id is Neumann do...
+                            if (primal_face_is_neumann[l_id]) {
+                              b[face_dof_number[l_id]] -= w_face_node * a_ir * value_list[i_face];
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+      }
+      // EL b^L
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                          (std::is_same_v<T, FourierBoundaryCondition>)) {
+              const auto& face_list  = bc.faceList();
+              const auto& value_list = bc.valueList();
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                FaceId face_id = face_list[i_face];
+                Assert(face_to_cell_matrix[face_id].size() == 1);
+                b[face_dof_number[face_id]] += mes_l[face_id] * value_list[i_face];   // sign
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      Vector<double> T{number_of_dof};
+      T = zero;
+
+      LinearSolver solver;
+      solver.solveLocalSystem(A, T, b);
+
+      CellValue<double> Temperature{mesh->connectivity()};
+      FaceValue<double> Temperature_face{mesh->connectivity()};
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { Temperature[cell_id] = T[cell_dof_number[cell_id]]; });
+      parallel_for(
+        mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+          if (primal_face_is_neumann[face_id]) {
+            Temperature_face[face_id] = T[face_dof_number[face_id]];
+          } else {
+            Temperature_face[face_id] = Tl[face_id];
+          }
+        });
+      Vector<double> error{mesh->numberOfCells()};
+      CellValue<double> cell_error{mesh->connectivity()};
+      Vector<double> face_error{mesh->numberOfFaces()};
+      double error_max = 0.;
+      size_t cell_max  = 0;
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+          error[cell_id]      = (Temperature[cell_id] - Tj[cell_id]) * sqrt(primal_Vj[cell_id]);
+          cell_error[cell_id] = (Temperature[cell_id] - Tj[cell_id]);
+        });
+      parallel_for(
+        mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+          // ELIMINATION ENLEVER DIRICHLET
+          if (primal_face_is_neumann[face_id]) {
+            face_error[face_id] = (Temperature_face[face_id] - Tl[face_id]) * sqrt(mes_l[face_id]);
+          } else {
+            face_error[face_id] = 0;
+          }
+        });
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); cell_id++) {
+        if (error_max < std::abs(cell_error[cell_id])) {
+          error_max = std::abs(cell_error[cell_id]);
+          cell_max  = cell_id;
+        }
+      }
+
+      std::cout << " ||Error||_max (cell)= " << error_max << " on cell " << cell_max << "\n";
+      std::cout << "||Error||_2 (cell)= " << std::sqrt(dot(error, error)) << "\n";
+      std::cout << "||Error||_2 (face)= " << std::sqrt(dot(face_error, face_error)) << "\n";
+      std::cout << "||Error||_2 (total)= " << std::sqrt(dot(error, error)) + std::sqrt(dot(face_error, face_error))
+                << "\n";
+    }
+  } else {
+    throw NotImplementedError("not done in 1d");
+  }
+}
+
+template <size_t Dimension>
+class HeatDiamondScheme2<Dimension>::DirichletBoundaryCondition
+{
+ private:
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+  const Array<const double> m_node_value_list;
+  const Array<const NodeId> m_node_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_node_list;
+  }
+
+  const Array<const double>&
+  nodeValueList() const
+  {
+    return m_node_value_list;
+  }
+
+  DirichletBoundaryCondition(const Array<const FaceId>& face_list,
+                             const Array<const double>& value_list,
+                             const Array<const NodeId>& node_list,
+                             const Array<const double>& node_value_list)
+    : m_value_list{value_list}, m_face_list{face_list}, m_node_value_list(node_value_list), m_node_list(node_list)
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+    Assert(m_node_value_list.size() == m_node_list.size());
+  }
+
+  ~DirichletBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HeatDiamondScheme2<Dimension>::NeumannBoundaryCondition
+{
+ private:
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NeumannBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~NeumannBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HeatDiamondScheme2<Dimension>::FourierBoundaryCondition
+{
+ private:
+  const Array<const double> m_coef_list;
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  const Array<const double>&
+  coefList() const
+  {
+    return m_coef_list;
+  }
+
+ public:
+  FourierBoundaryCondition(const Array<const FaceId>& face_list,
+                           const Array<const double>& coef_list,
+                           const Array<const double>& value_list)
+    : m_coef_list{coef_list}, m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_coef_list.size() == m_face_list.size());
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~FourierBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class HeatDiamondScheme2<Dimension>::SymmetryBoundaryCondition
+{
+ private:
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+ public:
+  SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+template HeatDiamondScheme2<1>::HeatDiamondScheme2(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
+
+template HeatDiamondScheme2<2>::HeatDiamondScheme2(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
+
+template HeatDiamondScheme2<3>::HeatDiamondScheme2(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&);
diff --git a/src/language/algorithms/HeatDiamondAlgorithm2.hpp b/src/language/algorithms/HeatDiamondAlgorithm2.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2cf388ed134d443fbcdbc7d3402f4a917610d19c
--- /dev/null
+++ b/src/language/algorithms/HeatDiamondAlgorithm2.hpp
@@ -0,0 +1,29 @@
+#ifndef HEAT_DIAMOND_ALGORITHM2_HPP
+#define HEAT_DIAMOND_ALGORITHM2_HPP
+
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+
+#include <memory>
+#include <variant>
+#include <vector>
+
+template <size_t Dimension>
+class HeatDiamondScheme2
+{
+ private:
+  class DirichletBoundaryCondition;
+  class FourierBoundaryCondition;
+  class NeumannBoundaryCondition;
+  class SymmetryBoundaryCondition;
+
+ public:
+  HeatDiamondScheme2(std::shared_ptr<const IMesh> i_mesh,
+                     const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+                     const FunctionSymbolId& T_id,
+                     const FunctionSymbolId& kappa_id,
+                     const FunctionSymbolId& f_id);
+};
+
+#endif   // HEAT_DIAMOND_ALGORITHM2_HPP
diff --git a/src/language/algorithms/ParabolicHeat.cpp b/src/language/algorithms/ParabolicHeat.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d535751ae11c75f2cf08e20024435d82738010d2
--- /dev/null
+++ b/src/language/algorithms/ParabolicHeat.cpp
@@ -0,0 +1,568 @@
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LinearSolver.hpp>
+#include <algebra/SmallMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <algebra/Vector.hpp>
+#include <language/algorithms/ParabolicHeat.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/FourierBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/ScalarDiamondScheme.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <utils/Timer.hpp>
+
+template <size_t Dimension>
+ParabolicHeatScheme<Dimension>::ParabolicHeatScheme(
+  std::shared_ptr<const IMesh> i_mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const FunctionSymbolId& T_id,
+  const FunctionSymbolId& T_init_id,
+  const FunctionSymbolId& kappa_id,
+  const FunctionSymbolId& f_id,
+  const double& Tf,
+  const double& dt)
+{
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  using BoundaryCondition = std::variant<DirichletBoundaryCondition, FourierBoundaryCondition, NeumannBoundaryCondition,
+                                         SymmetryBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+  BoundaryConditionList boundary_condition_list;
+
+  std::cout << "number of bc descr = " << bc_descriptor_list.size() << '\n';
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+  for (const auto& bc_descriptor : bc_descriptor_list) {
+    bool is_valid_boundary_condition = true;
+
+    switch (bc_descriptor->type()) {
+    case IBoundaryConditionDescriptor::Type::symmetry: {
+      throw NotImplementedError("NIY");
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::dirichlet: {
+      const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+        dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+      if constexpr (Dimension > 1) {
+        MeshFaceBoundary<Dimension> mesh_face_boundary =
+          getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+        const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+        MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
+
+        Array<const double> value_list =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                    mesh_data.xl(),
+                                                                                                    mesh_face_boundary
+                                                                                                      .faceList());
+        boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+      } else {
+        throw NotImplementedError("not implemented in 1d");
+      }
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::fourier: {
+      throw NotImplementedError("NIY");
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::neumann: {
+      const NeumannBoundaryConditionDescriptor& neumann_bc_descriptor =
+        dynamic_cast<const NeumannBoundaryConditionDescriptor&>(*bc_descriptor);
+
+      if constexpr (Dimension > 1) {
+        MeshFaceBoundary<Dimension> mesh_face_boundary =
+          getMeshFaceBoundary(*mesh, neumann_bc_descriptor.boundaryDescriptor());
+
+        const FunctionSymbolId g_id = neumann_bc_descriptor.rhsSymbolId();
+
+        MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+        Array<const double> value_list =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                    mesh_data.xl(),
+                                                                                                    mesh_face_boundary
+                                                                                                      .faceList());
+        boundary_condition_list.push_back(NeumannBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+      } else {
+        throw NotImplementedError("not implemented in 1d");
+      }
+
+      break;
+    }
+    default: {
+      is_valid_boundary_condition = false;
+    }
+    }
+    if (not is_valid_boundary_condition) {
+      std::ostringstream error_msg;
+      error_msg << *bc_descriptor << " is an invalid boundary condition for heat equation";
+      throw NormalError(error_msg.str());
+    }
+  }
+
+  if constexpr (Dimension > 1) {
+    const CellValue<const size_t> cell_dof_number = [&] {
+      CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+      return compute_cell_dof_number;
+    }();
+    size_t number_of_dof = mesh->numberOfCells();
+
+    const FaceValue<const size_t> face_dof_number = [&] {
+      FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+      compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                          (std::is_same_v<T, FourierBoundaryCondition>) or
+                          (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                          (std::is_same_v<T, DirichletBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id = face_list[i_face];
+                if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                  std::ostringstream os;
+                  os << "The face " << face_id << " is used at least twice for boundary conditions";
+                  throw NormalError(os.str());
+                } else {
+                  compute_face_dof_number[face_id] = number_of_dof++;
+                }
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return compute_face_dof_number;
+    }();
+
+    const FaceValue<const bool> primal_face_is_neumann = [&] {
+      FaceValue<bool> face_is_neumann{mesh->connectivity()};
+      face_is_neumann.fill(false);
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                          (std::is_same_v<T, FourierBoundaryCondition>) or
+                          (std::is_same_v<T, SymmetryBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id     = face_list[i_face];
+                face_is_neumann[face_id] = true;
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return face_is_neumann;
+    }();
+
+    const auto& primal_face_to_node_matrix = mesh->connectivity().faceToNodeMatrix();
+    const auto& face_to_cell_matrix        = mesh->connectivity().faceToCellMatrix();
+    NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+    }
+
+    primal_node_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+          NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+          primal_node_is_on_boundary[node_id] = true;
+        }
+      }
+    }
+
+    FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+    }
+
+    primal_face_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        primal_face_is_on_boundary[face_id] = true;
+      }
+    }
+
+    FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+    }
+
+    primal_face_is_dirichlet.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] && (!primal_face_is_neumann[face_id]));
+    }
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+    CellValue<double> Tj =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(T_id, mesh_data.xj());
+    CellValue<double> Temperature =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(T_init_id,
+                                                                                                mesh_data.xj());
+    //{mesh->connectivity()};
+    FaceValue<double> Tl =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(T_id, mesh_data.xl());
+    FaceValue<double> Temperature_face =
+      InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(T_init_id,
+                                                                                                mesh_data.xl());
+
+    double time = 0;
+    Assert(dt > 0, "The time-step have to be positive!");
+    const CellValue<const double> primal_Vj = mesh_data.Vj();
+
+    InterpolationWeightsManager iwm(mesh, primal_face_is_on_boundary, primal_node_is_on_boundary);
+    iwm.compute();
+    CellValuePerNode<double> w_rj = iwm.wrj();
+    FaceValuePerNode<double> w_rl = iwm.wrl();
+    do {
+      double deltat = std::min(dt, Tf - time);
+      std::cout << "Current time = " << time << " time-step = " << deltat << " final time = " << Tf << "\n";
+      LegacyScalarDiamondScheme<Dimension>(i_mesh, bc_descriptor_list, kappa_id, f_id, Temperature, Temperature_face,
+                                           Tf, deltat, w_rj, w_rl);
+      time += deltat;
+    } while (time < Tf && std::abs(time - Tf) > 1e-15);
+    {
+      Vector<double> error{mesh->numberOfCells()};
+      CellValue<double> cell_error{mesh->connectivity()};
+      Vector<double> face_error{mesh->numberOfFaces()};
+      double error_max                    = 0.;
+      size_t cell_max                     = 0;
+      const FaceValue<const double> mes_l = [&] {
+        if constexpr (Dimension == 1) {
+          FaceValue<double> compute_mes_l{mesh->connectivity()};
+          compute_mes_l.fill(1);
+          return compute_mes_l;
+        } else {
+          return mesh_data.ll();
+        }
+      }();
+
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+          error[cell_id]      = (Temperature[cell_id] - Tj[cell_id]) * sqrt(primal_Vj[cell_id]);
+          cell_error[cell_id] = (Temperature[cell_id] - Tj[cell_id]);
+        });
+      parallel_for(
+        mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+          if (primal_face_is_on_boundary[face_id]) {
+            face_error[face_id] = (Temperature_face[face_id] - Tl[face_id]) * sqrt(mes_l[face_id]);
+          } else {
+            face_error[face_id] = 0;
+          }
+        });
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); cell_id++) {
+        if (error_max < std::abs(cell_error[cell_id])) {
+          error_max = std::abs(cell_error[cell_id]);
+          cell_max  = cell_id;
+        }
+      }
+
+      std::cout << " ||Error||_max (cell)= " << error_max << " on cell " << cell_max << "\n";
+      std::cout << "||Error||_2 (cell)= " << std::sqrt(dot(error, error)) << "\n";
+      std::cout << "||Error||_2 (face)= " << std::sqrt(dot(face_error, face_error)) << "\n";
+      std::cout << "||Error||_2 (total)= " << std::sqrt(dot(error, error)) + std::sqrt(dot(face_error, face_error))
+                << "\n";
+    }
+  } else {
+    throw NotImplementedError("not done in 1d");
+  }
+}
+
+template <size_t Dimension>
+class ParabolicHeatScheme<Dimension>::DirichletBoundaryCondition
+{
+ private:
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  DirichletBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~DirichletBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class ParabolicHeatScheme<Dimension>::NeumannBoundaryCondition
+{
+ private:
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NeumannBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~NeumannBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class ParabolicHeatScheme<Dimension>::FourierBoundaryCondition
+{
+ private:
+  const Array<const double> m_coef_list;
+  const Array<const double> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  const Array<const double>&
+  coefList() const
+  {
+    return m_coef_list;
+  }
+
+ public:
+  FourierBoundaryCondition(const Array<const FaceId>& face_list,
+                           const Array<const double>& coef_list,
+                           const Array<const double>& value_list)
+    : m_coef_list{coef_list}, m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_coef_list.size() == m_face_list.size());
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~FourierBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class ParabolicHeatScheme<Dimension>::SymmetryBoundaryCondition
+{
+ private:
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+ public:
+  SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class ParabolicHeatScheme<Dimension>::InterpolationWeightsManager
+{
+ private:
+  std::shared_ptr<const Mesh<Connectivity<Dimension>>> m_mesh;
+  FaceValue<bool> m_primal_face_is_on_boundary;
+  NodeValue<bool> m_primal_node_is_on_boundary;
+  CellValuePerNode<double> m_w_rj;
+  FaceValuePerNode<double> m_w_rl;
+
+ public:
+  InterpolationWeightsManager(std::shared_ptr<const Mesh<Connectivity<Dimension>>> mesh,
+                              FaceValue<bool> primal_face_is_on_boundary,
+                              NodeValue<bool> primal_node_is_on_boundary)
+    : m_mesh(mesh),
+      m_primal_face_is_on_boundary(primal_face_is_on_boundary),
+      m_primal_node_is_on_boundary(primal_node_is_on_boundary)
+  {}
+  ~InterpolationWeightsManager() = default;
+  CellValuePerNode<double>&
+  wrj()
+  {
+    return m_w_rj;
+  }
+  FaceValuePerNode<double>&
+  wrl()
+  {
+    return m_w_rl;
+  }
+  void
+  compute()
+  {
+    using MeshDataType      = MeshData<Dimension>;
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*m_mesh);
+
+    const NodeValue<const TinyVector<Dimension>>& xr = m_mesh->xr();
+
+    const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = m_mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix                  = m_mesh->connectivity().nodeToFaceMatrix();
+    CellValuePerNode<double> w_rj{m_mesh->connectivity()};
+    FaceValuePerNode<double> w_rl{m_mesh->connectivity()};
+
+    for (size_t i = 0; i < w_rl.numberOfValues(); ++i) {
+      w_rl[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+
+    for (NodeId i_node = 0; i_node < m_mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      if (not m_primal_node_is_on_boundary[i_node]) {
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        SmallVector<double> x{node_to_cell.size()};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+      } else {
+        int nb_face_used = 0;
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            nb_face_used++;
+          }
+        }
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size() + nb_face_used};
+        for (size_t j = 0; j < node_to_cell.size() + nb_face_used; j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          int cpt_face = 0;
+          for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+            FaceId face_id = node_to_face_matrix[i_node][i_face];
+            if (m_primal_face_is_on_boundary[face_id]) {
+              A(i, node_to_cell.size() + cpt_face) = xl[face_id][i - 1];
+              cpt_face++;
+            }
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size() + nb_face_used};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+        int cpt_face = node_to_cell.size();
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            w_rl(i_node, i_face) = x[cpt_face++];
+          }
+        }
+      }
+    }
+    m_w_rj = w_rj;
+    m_w_rl = w_rl;
+  }
+};
+
+template ParabolicHeatScheme<1>::ParabolicHeatScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const double&,
+  const double&);
+
+template ParabolicHeatScheme<2>::ParabolicHeatScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const double&,
+  const double&);
+
+template ParabolicHeatScheme<3>::ParabolicHeatScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const double&,
+  const double&);
diff --git a/src/language/algorithms/ParabolicHeat.hpp b/src/language/algorithms/ParabolicHeat.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..84ee713eea6c4616d533ce93b1f34268aad060cc
--- /dev/null
+++ b/src/language/algorithms/ParabolicHeat.hpp
@@ -0,0 +1,33 @@
+#ifndef PARABOLIC_HEAT_HPP
+#define PARABOLIC_HEAT_HPP
+
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+
+#include <memory>
+#include <variant>
+#include <vector>
+
+template <size_t Dimension>
+class ParabolicHeatScheme
+{
+ private:
+  class DirichletBoundaryCondition;
+  class FourierBoundaryCondition;
+  class NeumannBoundaryCondition;
+  class SymmetryBoundaryCondition;
+  class InterpolationWeightsManager;
+
+ public:
+  ParabolicHeatScheme(std::shared_ptr<const IMesh> i_mesh,
+                      const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+                      const FunctionSymbolId& T_id,
+                      const FunctionSymbolId& T_init_id,
+                      const FunctionSymbolId& kappa_id,
+                      const FunctionSymbolId& f_id,
+                      const double& final_time,
+                      const double& dt);
+};
+
+#endif   // HEAT_DIAMOND_ALGORITHM_HPP
diff --git a/src/language/algorithms/UnsteadyElasticity.cpp b/src/language/algorithms/UnsteadyElasticity.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6eb2017c1286f73356ae126f8e19998ae8015de8
--- /dev/null
+++ b/src/language/algorithms/UnsteadyElasticity.cpp
@@ -0,0 +1,613 @@
+#include <language/algorithms/UnsteadyElasticity.hpp>
+
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LinearSolver.hpp>
+#include <algebra/SmallMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <algebra/Vector.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <scheme/VectorDiamondScheme.hpp>
+
+template <size_t Dimension>
+UnsteadyElasticity<Dimension>::UnsteadyElasticity(
+  std::shared_ptr<const IMesh> i_mesh,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+  const FunctionSymbolId& lambda_id,
+  const FunctionSymbolId& mu_id,
+  const FunctionSymbolId& f_id,
+  const FunctionSymbolId& U_id,
+  const FunctionSymbolId& U_init_id,
+  const double& Tf,
+  const double& dt)
+{
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  using BoundaryCondition =
+    std::variant<DirichletBoundaryCondition, NormalStrainBoundaryCondition, SymmetryBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+  BoundaryConditionList boundary_condition_list;
+
+  std::cout << "number of bc descr = " << bc_descriptor_list.size() << '\n';
+  std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+  NodeValue<bool> is_dirichlet{mesh->connectivity()};
+  is_dirichlet.fill(false);
+  NodeValue<TinyVector<Dimension>> dirichlet_value{mesh->connectivity()};
+  {
+    TinyVector<Dimension> nan_tiny_vector;
+    for (size_t i = 0; i < Dimension; ++i) {
+      nan_tiny_vector[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+    dirichlet_value.fill(nan_tiny_vector);
+  }
+
+  for (const auto& bc_descriptor : bc_descriptor_list) {
+    bool is_valid_boundary_condition = true;
+
+    switch (bc_descriptor->type()) {
+    case IBoundaryConditionDescriptor::Type::symmetry: {
+      const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor =
+        dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+      if constexpr (Dimension > 1) {
+        MeshFaceBoundary mesh_face_boundary = getMeshFaceBoundary(*mesh, sym_bc_descriptor.boundaryDescriptor());
+        boundary_condition_list.push_back(SymmetryBoundaryCondition{mesh_face_boundary.faceList()});
+
+      } else {
+        throw NotImplementedError("Symmetry conditions are not supported in 1d");
+      }
+
+      break;
+    }
+    case IBoundaryConditionDescriptor::Type::dirichlet: {
+      const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+        dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+      if (dirichlet_bc_descriptor.name() == "dirichlet") {
+        if constexpr (Dimension > 1) {
+          MeshFaceBoundary mesh_face_boundary =
+            getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+          MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+          const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+            TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                          mesh_face_boundary.faceList());
+
+          boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+        } else {
+          throw NotImplementedError("Dirichlet conditions are not supported in 1d");
+        }
+      } else if (dirichlet_bc_descriptor.name() == "normal_strain") {
+        if constexpr (Dimension > 1) {
+          MeshFaceBoundary mesh_face_boundary =
+            getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+          MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+          const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+            TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                          mesh_face_boundary.faceList());
+          boundary_condition_list.push_back(NormalStrainBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+        } else {
+          throw NotImplementedError("Normal strain conditions are not supported in 1d");
+        }
+
+      } else {
+        is_valid_boundary_condition = false;
+      }
+      break;
+    }
+    default: {
+      is_valid_boundary_condition = false;
+    }
+    }
+    if (not is_valid_boundary_condition) {
+      std::ostringstream error_msg;
+      error_msg << *bc_descriptor << " is an invalid boundary condition for elasticity equation";
+      throw NormalError(error_msg.str());
+    }
+  }
+
+  if constexpr (Dimension > 1) {
+    const CellValue<const size_t> cell_dof_number = [&] {
+      CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+      return compute_cell_dof_number;
+    }();
+    size_t number_of_dof = mesh->numberOfCells();
+
+    const FaceValue<const size_t> face_dof_number = [&] {
+      FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+      compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>) or
+                          (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                          (std::is_same_v<T, DirichletBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id = face_list[i_face];
+                if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                  std::ostringstream os;
+                  os << "The face " << face_id << " is used at least twice for boundary conditions";
+                  throw NormalError(os.str());
+                } else {
+                  compute_face_dof_number[face_id] = number_of_dof++;
+                }
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return compute_face_dof_number;
+    }();
+
+    const auto& primal_face_to_node_matrix             = mesh->connectivity().faceToNodeMatrix();
+    const auto& face_to_cell_matrix                    = mesh->connectivity().faceToCellMatrix();
+    const FaceValue<const bool> primal_face_is_neumann = [&] {
+      FaceValue<bool> face_is_neumann{mesh->connectivity()};
+      face_is_neumann.fill(false);
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id     = face_list[i_face];
+                face_is_neumann[face_id] = true;
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return face_is_neumann;
+    }();
+
+    FaceValue<bool> primal_face_is_symmetry = [&] {
+      FaceValue<bool> face_is_symmetry{mesh->connectivity()};
+      face_is_symmetry.fill(false);
+      for (const auto& boundary_condition : boundary_condition_list) {
+        std::visit(
+          [&](auto&& bc) {
+            using T = std::decay_t<decltype(bc)>;
+            if constexpr ((std::is_same_v<T, SymmetryBoundaryCondition>)) {
+              const auto& face_list = bc.faceList();
+
+              for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                const FaceId face_id      = face_list[i_face];
+                face_is_symmetry[face_id] = true;
+              }
+            }
+          },
+          boundary_condition);
+      }
+
+      return face_is_symmetry;
+    }();
+
+    NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+    }
+
+    primal_node_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+          NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+          primal_node_is_on_boundary[node_id] = true;
+        }
+      }
+    }
+
+    FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+    }
+
+    primal_face_is_on_boundary.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      if (face_to_cell_matrix[face_id].size() == 1) {
+        primal_face_is_on_boundary[face_id] = true;
+      }
+    }
+
+    FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+    if (parallel::size() > 1) {
+      throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+    }
+
+    primal_face_is_dirichlet.fill(false);
+    for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+      primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] && (!primal_face_is_neumann[face_id]) &&
+                                           (!primal_face_is_symmetry[face_id]));
+    }
+    MeshDataType& mesh_data         = MeshDataManager::instance().getMeshData(*mesh);
+    const auto& node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix = mesh->connectivity().nodeToFaceMatrix();
+
+    {
+      CellValue<TinyVector<Dimension>> velocity = InterpolateItemValue<TinyVector<Dimension>(
+        TinyVector<Dimension>)>::template interpolate<ItemType::cell>(U_id, mesh_data.xj());
+      CellValue<TinyVector<Dimension>> Uj       = InterpolateItemValue<TinyVector<Dimension>(
+        TinyVector<Dimension>)>::template interpolate<ItemType::cell>(U_init_id, mesh_data.xj());
+
+      CellValue<TinyVector<Dimension>> fj            = InterpolateItemValue<TinyVector<Dimension>(
+        TinyVector<Dimension>)>::template interpolate<ItemType::cell>(f_id, mesh_data.xj());
+      FaceValue<TinyVector<Dimension>> velocity_face = InterpolateItemValue<TinyVector<Dimension>(
+        TinyVector<Dimension>)>::template interpolate<ItemType::face>(U_id, mesh_data.xl());
+      FaceValue<TinyVector<Dimension>> Ul            = InterpolateItemValue<TinyVector<Dimension>(
+        TinyVector<Dimension>)>::template interpolate<ItemType::face>(U_init_id, mesh_data.xl());
+
+      double time = 0;
+      Assert(dt > 0, "The time-step have to be positive!");
+      const CellValue<const double> primal_Vj = mesh_data.Vj();
+
+      InterpolationWeightsManager iwm(mesh, primal_face_is_on_boundary, primal_node_is_on_boundary,
+                                      primal_face_is_symmetry);
+      iwm.compute();
+      CellValuePerNode<double> w_rj = iwm.wrj();
+      FaceValuePerNode<double> w_rl = iwm.wrl();
+      do {
+        double deltat = std::min(dt, Tf - time);
+        std::cout << "Current time = " << time << " time-step = " << deltat << " final time = " << Tf << "\n";
+        LegacyVectorDiamondScheme<Dimension>(i_mesh, bc_descriptor_list, lambda_id, mu_id, f_id, Uj, Ul, Tf, deltat,
+                                             w_rj, w_rl);
+        time += deltat;
+      } while (time < Tf && std::abs(time - Tf) > 1e-15);
+
+      const FaceValue<const double> mes_l = [&] {
+        if constexpr (Dimension == 1) {
+          FaceValue<double> compute_mes_l{mesh->connectivity()};
+          compute_mes_l.fill(1);
+          return compute_mes_l;
+        } else {
+          return mesh_data.ll();
+        }
+      }();
+
+      Vector<double> Uexacte{mesh->numberOfCells() * Dimension};
+      for (CellId j = 0; j < mesh->numberOfCells(); ++j) {
+        for (size_t l = 0; l < Dimension; ++l) {
+          Uexacte[(cell_dof_number[j] * Dimension) + l] = velocity[j][l];
+        }
+      }
+
+      Vector<double> error{mesh->numberOfCells() * Dimension};
+      for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < Dimension; ++i) {
+          error[(cell_id * Dimension) + i] = (Uj[cell_id][i] - velocity[cell_id][i]) * sqrt(primal_Vj[cell_id]);
+        }
+      }
+      Vector<double> error_face{mesh->numberOfFaces() * Dimension};
+      parallel_for(
+        mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+          if (primal_face_is_on_boundary[face_id]) {
+            for (size_t i = 0; i < Dimension; ++i) {
+              error_face[face_id * Dimension + i] = (Ul[face_id][i] - velocity_face[face_id][i]) * sqrt(mes_l[face_id]);
+            }
+          } else {
+            error_face[face_id] = 0;
+          }
+        });
+
+      std::cout << "||Error||_2 (cell)= " << std::sqrt(dot(error, error)) << "\n";
+      std::cout << "||Error||_2 (face)= " << std::sqrt(dot(error_face, error_face)) << "\n";
+      std::cout << "||Error||_2 (total)= " << std::sqrt(dot(error, error)) + std::sqrt(dot(error_face, error_face))
+                << "\n";
+
+      NodeValue<TinyVector<3>> ur3d{mesh->connectivity()};
+      ur3d.fill(zero);
+
+      parallel_for(
+        mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
+          TinyVector<Dimension> x = zero;
+          const auto node_cells   = node_to_cell_matrix[node_id];
+          for (size_t i_cell = 0; i_cell < node_cells.size(); ++i_cell) {
+            CellId cell_id = node_cells[i_cell];
+            x += w_rj(node_id, i_cell) * Uj[cell_id];
+          }
+          const auto node_faces = node_to_face_matrix[node_id];
+          for (size_t i_face = 0; i_face < node_faces.size(); ++i_face) {
+            FaceId face_id = node_faces[i_face];
+            if (primal_face_is_on_boundary[face_id]) {
+              x += w_rl(node_id, i_face) * Ul[face_id];
+            }
+          }
+          for (size_t i = 0; i < Dimension; ++i) {
+            ur3d[node_id][i] = x[i];
+          }
+        });
+    }
+  } else {
+    throw NotImplementedError("not done in 1d");
+  }
+}
+
+template <size_t Dimension>
+class UnsteadyElasticity<Dimension>::DirichletBoundaryCondition
+{
+ private:
+  const Array<const TinyVector<Dimension>> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const TinyVector<Dimension>>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  DirichletBoundaryCondition(const Array<const FaceId>& face_list, const Array<const TinyVector<Dimension>>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~DirichletBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class UnsteadyElasticity<Dimension>::NormalStrainBoundaryCondition
+{
+ private:
+  const Array<const TinyVector<Dimension>> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+  const Array<const TinyVector<Dimension>>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NormalStrainBoundaryCondition(const Array<const FaceId>& face_list,
+                                const Array<const TinyVector<Dimension>>& value_list)
+    : m_value_list{value_list}, m_face_list{face_list}
+  {
+    Assert(m_value_list.size() == m_face_list.size());
+  }
+
+  ~NormalStrainBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class UnsteadyElasticity<Dimension>::SymmetryBoundaryCondition
+{
+ private:
+  const Array<const TinyVector<Dimension>> m_value_list;
+  const Array<const FaceId> m_face_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_face_list;
+  }
+
+ public:
+  SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+template <size_t Dimension>
+class UnsteadyElasticity<Dimension>::InterpolationWeightsManager
+{
+ private:
+  std::shared_ptr<const Mesh<Connectivity<Dimension>>> m_mesh;
+  FaceValue<bool> m_primal_face_is_on_boundary;
+  NodeValue<bool> m_primal_node_is_on_boundary;
+  FaceValue<bool> m_primal_face_is_symmetry;
+  CellValuePerNode<double> m_w_rj;
+  FaceValuePerNode<double> m_w_rl;
+
+ public:
+  InterpolationWeightsManager(std::shared_ptr<const Mesh<Connectivity<Dimension>>> mesh,
+                              FaceValue<bool> primal_face_is_on_boundary,
+                              NodeValue<bool> primal_node_is_on_boundary,
+                              FaceValue<bool> primal_face_is_symmetry)
+    : m_mesh(mesh),
+      m_primal_face_is_on_boundary(primal_face_is_on_boundary),
+      m_primal_node_is_on_boundary(primal_node_is_on_boundary),
+      m_primal_face_is_symmetry(primal_face_is_symmetry)
+  {}
+  ~InterpolationWeightsManager() = default;
+  CellValuePerNode<double>&
+  wrj()
+  {
+    return m_w_rj;
+  }
+  FaceValuePerNode<double>&
+  wrl()
+  {
+    return m_w_rl;
+  }
+  void
+  compute()
+  {
+    using MeshDataType      = MeshData<Dimension>;
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*m_mesh);
+
+    const NodeValue<const TinyVector<Dimension>>& xr = m_mesh->xr();
+
+    const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = m_mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix                  = m_mesh->connectivity().nodeToFaceMatrix();
+    const auto& face_to_cell_matrix                  = m_mesh->connectivity().faceToCellMatrix();
+
+    CellValuePerNode<double> w_rj{m_mesh->connectivity()};
+    FaceValuePerNode<double> w_rl{m_mesh->connectivity()};
+
+    const NodeValuePerFace<const TinyVector<Dimension>> primal_nlr = mesh_data.nlr();
+    auto project_to_face = [&](const TinyVector<Dimension>& x, const FaceId face_id) -> const TinyVector<Dimension> {
+      TinyVector<Dimension> proj;
+      const TinyVector<Dimension> nil = primal_nlr(face_id, 0);
+      proj                            = x - dot((x - xl[face_id]), nil) * nil;
+      return proj;
+    };
+
+    for (size_t i = 0; i < w_rl.numberOfValues(); ++i) {
+      w_rl[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+
+    for (NodeId i_node = 0; i_node < m_mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      if (not m_primal_node_is_on_boundary[i_node]) {
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size()};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+      } else {
+        int nb_face_used = 0;
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            nb_face_used++;
+          }
+        }
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size() + nb_face_used};
+        for (size_t j = 0; j < node_to_cell.size() + nb_face_used; j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          int cpt_face = 0;
+          for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+            FaceId face_id = node_to_face_matrix[i_node][i_face];
+            if (m_primal_face_is_on_boundary[face_id]) {
+              if (m_primal_face_is_symmetry[face_id]) {
+                for (size_t j = 0; j < face_to_cell_matrix[face_id].size(); ++j) {
+                  const CellId cell_id                 = face_to_cell_matrix[face_id][j];
+                  TinyVector<Dimension> xproj          = project_to_face(xj[cell_id], face_id);
+                  A(i, node_to_cell.size() + cpt_face) = xproj[i - 1];
+                }
+              } else {
+                A(i, node_to_cell.size() + cpt_face) = xl[face_id][i - 1];
+              }
+              cpt_face++;
+            }
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size() + nb_face_used};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+        int cpt_face = node_to_cell.size();
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            w_rl(i_node, i_face) = x[cpt_face++];
+          }
+        }
+      }
+    }
+    m_w_rj = w_rj;
+    m_w_rl = w_rl;
+  }
+};
+
+template UnsteadyElasticity<1>::UnsteadyElasticity(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const double&,
+  const double&);
+
+template UnsteadyElasticity<2>::UnsteadyElasticity(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const double&,
+  const double&);
+
+template UnsteadyElasticity<3>::UnsteadyElasticity(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const double&,
+  const double&);
diff --git a/src/language/algorithms/UnsteadyElasticity.hpp b/src/language/algorithms/UnsteadyElasticity.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f55ef9b32cd544bcc53d1aed05646df510f65c5b
--- /dev/null
+++ b/src/language/algorithms/UnsteadyElasticity.hpp
@@ -0,0 +1,36 @@
+#ifndef UNSTEADY_ELASTICITY_HPP
+#define UNSTEADY_ELASTICITY_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <language/utils/FunctionSymbolId.hpp>
+#include <memory>
+#include <mesh/IMesh.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+#include <variant>
+#include <vector>
+
+template <size_t Dimension>
+class UnsteadyElasticity
+{
+ private:
+  class DirichletBoundaryCondition;
+  class NormalStrainBoundaryCondition;
+  class SymmetryBoundaryCondition;
+  class InterpolationWeightsManager;
+
+ public:
+  UnsteadyElasticity(std::shared_ptr<const IMesh> i_mesh,
+                     const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+                     const FunctionSymbolId& lambda_id,
+                     const FunctionSymbolId& mu_id,
+                     const FunctionSymbolId& f_id,
+                     const FunctionSymbolId& U_id,
+                     const FunctionSymbolId& U_init_id,
+                     const double& Tf,
+                     const double& dt);
+};
+
+#endif   // ELASTICITY_DIAMOND_ALGORITHM2_HPP
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index 5d397223996c592687707711150705056e57879c..90645124a5d3fbb12cef19c4f105eb3f08465661 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -3,6 +3,12 @@
 #include <analysis/GaussLegendreQuadratureDescriptor.hpp>
 #include <analysis/GaussLobattoQuadratureDescriptor.hpp>
 #include <analysis/GaussQuadratureDescriptor.hpp>
+#include <language/algorithms/ElasticityDiamondAlgorithm.hpp>
+#include <language/algorithms/Heat5PointsAlgorithm.hpp>
+#include <language/algorithms/HeatDiamondAlgorithm.hpp>
+#include <language/algorithms/HeatDiamondAlgorithm2.hpp>
+#include <language/algorithms/ParabolicHeat.hpp>
+#include <language/algorithms/UnsteadyElasticity.hpp>
 #include <language/modules/BinaryOperatorRegisterForVh.hpp>
 #include <language/modules/MathFunctionRegisterForVh.hpp>
 #include <language/modules/UnaryOperatorRegisterForVh.hpp>
@@ -36,6 +42,7 @@
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 #include <scheme/NeumannBoundaryConditionDescriptor.hpp>
 #include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <scheme/VectorDiamondScheme.hpp>
 #include <utils/Socket.hpp>
 
 #include <memory>
@@ -371,6 +378,57 @@ SchemeModule::SchemeModule()
 
                               ));
 
+  this->_addBuiltinFunction("dirichlet",
+                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
+                              const IBoundaryConditionDescriptor>(std::shared_ptr<const IBoundaryDescriptor>,
+                                                                  const FunctionSymbolId&)>>(
+
+                              [](std::shared_ptr<const IBoundaryDescriptor> boundary,
+                                 const FunctionSymbolId& g_id) -> std::shared_ptr<const IBoundaryConditionDescriptor> {
+                                return std::make_shared<DirichletBoundaryConditionDescriptor>("dirichlet", boundary,
+                                                                                              g_id);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("normal_strain",
+                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
+                              const IBoundaryConditionDescriptor>(std::shared_ptr<const IBoundaryDescriptor>,
+                                                                  const FunctionSymbolId&)>>(
+
+                              [](std::shared_ptr<const IBoundaryDescriptor> boundary,
+                                 const FunctionSymbolId& g_id) -> std::shared_ptr<const IBoundaryConditionDescriptor> {
+                                return std::make_shared<DirichletBoundaryConditionDescriptor>("normal_strain", boundary,
+                                                                                              g_id);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("fourier",
+                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
+                              const IBoundaryConditionDescriptor>(std::shared_ptr<const IBoundaryDescriptor>,
+                                                                  const FunctionSymbolId&, const FunctionSymbolId&)>>(
+
+                              [](std::shared_ptr<const IBoundaryDescriptor> boundary, const FunctionSymbolId& alpha_id,
+                                 const FunctionSymbolId& g_id) -> std::shared_ptr<const IBoundaryConditionDescriptor> {
+                                return std::make_shared<FourierBoundaryConditionDescriptor>("fourier", boundary,
+                                                                                            alpha_id, g_id);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("neumann",
+                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
+                              const IBoundaryConditionDescriptor>(std::shared_ptr<const IBoundaryDescriptor>,
+                                                                  const FunctionSymbolId&)>>(
+
+                              [](std::shared_ptr<const IBoundaryDescriptor> boundary,
+                                 const FunctionSymbolId& g_id) -> std::shared_ptr<const IBoundaryConditionDescriptor> {
+                                return std::make_shared<NeumannBoundaryConditionDescriptor>("neumann", boundary, g_id);
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("external_fsi_velocity",
                             std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
                               const IBoundaryConditionDescriptor>(std::shared_ptr<const IBoundaryDescriptor>,
@@ -457,6 +515,305 @@ SchemeModule::SchemeModule()
 
                               ));
 
+  this->_addBuiltinFunction("heat", std::make_shared<BuiltinFunctionEmbedder<
+                                      void(std::shared_ptr<const IMesh>,
+                                           const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+                                           const FunctionSymbolId&, const FunctionSymbolId&, const FunctionSymbolId&)>>(
+
+                                      [](std::shared_ptr<const IMesh> p_mesh,
+                                         const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                           bc_descriptor_list,
+                                         const FunctionSymbolId& T_id, const FunctionSymbolId& kappa_id,
+                                         const FunctionSymbolId& f_id) -> void {
+                                        switch (p_mesh->dimension()) {
+                                        case 1: {
+                                          HeatDiamondScheme<1>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                          break;
+                                        }
+                                        case 2: {
+                                          HeatDiamondScheme<2>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                          break;
+                                        }
+                                        case 3: {
+                                          HeatDiamondScheme<3>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                          break;
+                                        }
+                                        default: {
+                                          throw UnexpectedError("invalid mesh dimension");
+                                        }
+                                        }
+                                      }
+
+                                      ));
+
+  this->_addBuiltinFunction("parabolicheat",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              void(std::shared_ptr<const IMesh>,
+                                   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+                                   const FunctionSymbolId&, const FunctionSymbolId&, const FunctionSymbolId&,
+                                   const FunctionSymbolId&, const double&, const double&)>>(
+
+                              [](std::shared_ptr<const IMesh> p_mesh,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const FunctionSymbolId& T_id, const FunctionSymbolId& T_init_id,
+                                 const FunctionSymbolId& kappa_id, const FunctionSymbolId& f_id,
+                                 const double& final_time, const double& dt) -> void {
+                                switch (p_mesh->dimension()) {
+                                case 1: {
+                                  ParabolicHeatScheme<1>{p_mesh, bc_descriptor_list, T_id, T_init_id, kappa_id,
+                                                         f_id,   final_time,         dt};
+                                  break;
+                                }
+                                case 2: {
+                                  ParabolicHeatScheme<2>{p_mesh, bc_descriptor_list, T_id, T_init_id, kappa_id,
+                                                         f_id,   final_time,         dt};
+                                  break;
+                                }
+                                case 3: {
+                                  ParabolicHeatScheme<3>{p_mesh, bc_descriptor_list, T_id, T_init_id, kappa_id,
+                                                         f_id,   final_time,         dt};
+                                  break;
+                                }
+                                default: {
+                                  throw UnexpectedError("invalid mesh dimension");
+                                }
+                                }
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction(
+    "parabolicheat",
+    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
+      const IDiscreteFunction>(const std::shared_ptr<const IDiscreteFunction>&,
+                               const std::shared_ptr<const IDiscreteFunction>&,
+                               const std::shared_ptr<const IDiscreteFunction>&,
+                               const std::shared_ptr<const IDiscreteFunction>&,
+                               const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&)>>(
+
+      [](const std::shared_ptr<const IDiscreteFunction>& alpha,
+         const std::shared_ptr<const IDiscreteFunction>& mub_dual,
+         const std::shared_ptr<const IDiscreteFunction>& mu_dual, const std::shared_ptr<const IDiscreteFunction>& f,
+         const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+        -> const std::shared_ptr<const IDiscreteFunction> {
+        return ScalarDiamondSchemeHandler{alpha, mub_dual, mu_dual, f, bc_descriptor_list}.solution();
+      }
+
+      ));
+
+  this->_addBuiltinFunction("unsteadyelasticity",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              void(std::shared_ptr<const IMesh>,
+                                   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+                                   const FunctionSymbolId&, const FunctionSymbolId&, const FunctionSymbolId&,
+                                   const FunctionSymbolId&, const FunctionSymbolId&, const double&, const double&)>>(
+
+                              [](std::shared_ptr<const IMesh> p_mesh,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const FunctionSymbolId& lambda_id, const FunctionSymbolId& mu_id,
+                                 const FunctionSymbolId& f_id, const FunctionSymbolId& U_id,
+                                 const FunctionSymbolId& U_init_id, const double& final_time,
+                                 const double& dt) -> void {
+                                switch (p_mesh->dimension()) {
+                                case 1: {
+                                  UnsteadyElasticity<1>{p_mesh, bc_descriptor_list, lambda_id,  mu_id, f_id,
+                                                        U_id,   U_init_id,          final_time, dt};
+                                  break;
+                                }
+                                case 2: {
+                                  UnsteadyElasticity<2>{p_mesh, bc_descriptor_list, lambda_id,  mu_id, f_id,
+                                                        U_id,   U_init_id,          final_time, dt};
+                                  break;
+                                }
+                                case 3: {
+                                  UnsteadyElasticity<3>{p_mesh, bc_descriptor_list, lambda_id,  mu_id, f_id,
+                                                        U_id,   U_init_id,          final_time, dt};
+                                  break;
+                                }
+                                default: {
+                                  throw UnexpectedError("invalid mesh dimension");
+                                }
+                                }
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("unsteadyelasticity",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              std::shared_ptr<const IDiscreteFunction>(const std::shared_ptr<const IDiscreteFunction>&,
+                                                                       const std::shared_ptr<const IDiscreteFunction>&,
+                                                                       const std::shared_ptr<const IDiscreteFunction>&,
+                                                                       const std::shared_ptr<const IDiscreteFunction>&,
+                                                                       const std::shared_ptr<const IDiscreteFunction>&,
+                                                                       const std::shared_ptr<const IDiscreteFunction>&,
+                                                                       const std::vector<std::shared_ptr<
+                                                                         const IBoundaryConditionDescriptor>>&)>>(
+
+                              [](const std::shared_ptr<const IDiscreteFunction> alpha,
+                                 const std::shared_ptr<const IDiscreteFunction> lambdab,
+                                 const std::shared_ptr<const IDiscreteFunction> mub,
+                                 const std::shared_ptr<const IDiscreteFunction> lambda,
+                                 const std::shared_ptr<const IDiscreteFunction> mu,
+                                 const std::shared_ptr<const IDiscreteFunction> f,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list) -> const std::shared_ptr<const IDiscreteFunction> {
+                                return VectorDiamondSchemeHandler{alpha, lambdab,           mub, lambda, mu,
+                                                                  f,     bc_descriptor_list}
+                                  .solution();
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("moleculardiffusion",
+                            std::make_shared<BuiltinFunctionEmbedder<std::tuple<
+                              std::shared_ptr<const IDiscreteFunction>,
+                              std::shared_ptr<const IDiscreteFunction>>(const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::vector<std::shared_ptr<
+                                                                          const IBoundaryConditionDescriptor>>&)>>(
+                              [](const std::shared_ptr<const IDiscreteFunction> alpha,
+                                 const std::shared_ptr<const IDiscreteFunction> lambdab,
+                                 const std::shared_ptr<const IDiscreteFunction> mub,
+                                 const std::shared_ptr<const IDiscreteFunction> lambda,
+                                 const std::shared_ptr<const IDiscreteFunction> mu,
+                                 const std::shared_ptr<const IDiscreteFunction> f,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list) -> const std::tuple<std::shared_ptr<const IDiscreteFunction>,
+                                                                           std::shared_ptr<const IDiscreteFunction>> {
+                                return VectorDiamondSchemeHandler{alpha, lambdab,           mub, lambda, mu,
+                                                                  f,     bc_descriptor_list}
+                                  .apply();
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("energybalance",
+                            std::make_shared<BuiltinFunctionEmbedder<std::tuple<
+                              std::shared_ptr<const IDiscreteFunction>,
+                              std::shared_ptr<const IDiscreteFunction>>(const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::shared_ptr<const IDiscreteFunction>&,
+                                                                        const std::vector<std::shared_ptr<
+                                                                          const IBoundaryConditionDescriptor>>&)>>(
+                              [](const std::shared_ptr<const IDiscreteFunction> lambdab,
+                                 const std::shared_ptr<const IDiscreteFunction> mub,
+                                 const std::shared_ptr<const IDiscreteFunction> U,
+                                 const std::shared_ptr<const IDiscreteFunction> dual_U,
+                                 const std::shared_ptr<const IDiscreteFunction> source,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list) -> const std::tuple<std::shared_ptr<const IDiscreteFunction>,
+                                                                           std::shared_ptr<const IDiscreteFunction>> {
+                                return EnergyComputerHandler{lambdab, mub, U, dual_U, source, bc_descriptor_list}
+                                  .computeEnergyUpdate();
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("heat2",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              void(std::shared_ptr<const IMesh>,
+                                   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+                                   const FunctionSymbolId&, const FunctionSymbolId&, const FunctionSymbolId&)>>(
+
+                              [](std::shared_ptr<const IMesh> p_mesh,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const FunctionSymbolId& T_id, const FunctionSymbolId& kappa_id,
+                                 const FunctionSymbolId& f_id) -> void {
+                                switch (p_mesh->dimension()) {
+                                case 1: {
+                                  HeatDiamondScheme2<1>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                  break;
+                                }
+                                case 2: {
+                                  HeatDiamondScheme2<2>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                  break;
+                                }
+                                case 3: {
+                                  HeatDiamondScheme2<3>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                  break;
+                                }
+                                default: {
+                                  throw UnexpectedError("invalid mesh dimension");
+                                }
+                                }
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("elasticity",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              void(std::shared_ptr<const IMesh>,
+                                   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+                                   const FunctionSymbolId&, const FunctionSymbolId&, const FunctionSymbolId&,
+                                   const FunctionSymbolId&)>>(
+
+                              [](std::shared_ptr<const IMesh> p_mesh,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const FunctionSymbolId& lambda_id, const FunctionSymbolId& mu_id,
+                                 const FunctionSymbolId& f_id, const FunctionSymbolId& U_id) -> void {
+                                switch (p_mesh->dimension()) {
+                                case 1: {
+                                  ElasticityDiamondScheme<1>{p_mesh, bc_descriptor_list, lambda_id, mu_id, f_id, U_id};
+                                  break;
+                                }
+                                case 2: {
+                                  ElasticityDiamondScheme<2>{p_mesh, bc_descriptor_list, lambda_id, mu_id, f_id, U_id};
+                                  break;
+                                }
+                                case 3: {
+                                  ElasticityDiamondScheme<3>{p_mesh, bc_descriptor_list, lambda_id, mu_id, f_id, U_id};
+                                  break;
+                                }
+                                default: {
+                                  throw UnexpectedError("invalid mesh dimension");
+                                }
+                                }
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("heat5points",
+                            std::make_shared<BuiltinFunctionEmbedder<
+                              void(std::shared_ptr<const IMesh>,
+                                   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+                                   const FunctionSymbolId&, const FunctionSymbolId&, const FunctionSymbolId)>>(
+
+                              [](std::shared_ptr<const IMesh> p_mesh,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const FunctionSymbolId& T_id, const FunctionSymbolId& kappa_id,
+                                 const FunctionSymbolId& f_id) -> void {
+                                switch (p_mesh->dimension()) {
+                                case 1: {
+                                  Heat5PointsAlgorithm<1>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                  break;
+                                }
+                                case 2: {
+                                  Heat5PointsAlgorithm<2>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                  break;
+                                }
+                                case 3: {
+                                  Heat5PointsAlgorithm<3>{p_mesh, bc_descriptor_list, T_id, kappa_id, f_id};
+                                  break;
+                                }
+                                default: {
+                                  throw UnexpectedError("invalid mesh dimension");
+                                }
+                                }
+                              }
+
+                              ));
+
   this
     ->_addBuiltinFunction("lagrangian",
                           std::make_shared<BuiltinFunctionEmbedder<
diff --git a/src/scheme/CMakeLists.txt b/src/scheme/CMakeLists.txt
index 115d7bc08ce9aa72dced807b9120431f17543864..25e1405a5e9d2f9603a5f50a6abf04350596c58b 100644
--- a/src/scheme/CMakeLists.txt
+++ b/src/scheme/CMakeLists.txt
@@ -7,4 +7,6 @@ add_library(
   DiscreteFunctionInterpoler.cpp
   DiscreteFunctionUtils.cpp
   DiscreteFunctionVectorIntegrator.cpp
-  DiscreteFunctionVectorInterpoler.cpp)
+  DiscreteFunctionVectorInterpoler.cpp
+  ScalarDiamondScheme.cpp
+  VectorDiamondScheme.cpp)
diff --git a/src/scheme/ScalarDiamondScheme.cpp b/src/scheme/ScalarDiamondScheme.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..07561e6f5abfee4461cd72d2e634bce94f7f12e8
--- /dev/null
+++ b/src/scheme/ScalarDiamondScheme.cpp
@@ -0,0 +1,851 @@
+#include <scheme/ScalarDiamondScheme.hpp>
+
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+
+template <size_t Dimension>
+class ScalarDiamondSchemeHandler::InterpolationWeightsManager
+{
+ private:
+  std::shared_ptr<const Mesh<Connectivity<Dimension>>> m_mesh;
+  FaceValue<bool> m_primal_face_is_on_boundary;
+  NodeValue<bool> m_primal_node_is_on_boundary;
+  CellValuePerNode<double> m_w_rj;
+  FaceValuePerNode<double> m_w_rl;
+
+ public:
+  CellValuePerNode<double>&
+  wrj()
+  {
+    return m_w_rj;
+  }
+
+  FaceValuePerNode<double>&
+  wrl()
+  {
+    return m_w_rl;
+  }
+
+  void
+  compute()
+  {
+    using MeshDataType      = MeshData<Dimension>;
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*m_mesh);
+
+    const NodeValue<const TinyVector<Dimension>>& xr = m_mesh->xr();
+
+    const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = m_mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix                  = m_mesh->connectivity().nodeToFaceMatrix();
+    CellValuePerNode<double> w_rj{m_mesh->connectivity()};
+    FaceValuePerNode<double> w_rl{m_mesh->connectivity()};
+
+    for (size_t i = 0; i < w_rl.numberOfValues(); ++i) {
+      w_rl[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+
+    for (NodeId i_node = 0; i_node < m_mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      if (not m_primal_node_is_on_boundary[i_node]) {
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        SmallVector<double> x{node_to_cell.size()};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+      } else {
+        int nb_face_used = 0;
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            nb_face_used++;
+          }
+        }
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size() + nb_face_used};
+        for (size_t j = 0; j < node_to_cell.size() + nb_face_used; j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          int cpt_face = 0;
+          for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+            FaceId face_id = node_to_face_matrix[i_node][i_face];
+            if (m_primal_face_is_on_boundary[face_id]) {
+              A(i, node_to_cell.size() + cpt_face) = xl[face_id][i - 1];
+              cpt_face++;
+            }
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size() + nb_face_used};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+        int cpt_face = node_to_cell.size();
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            w_rl(i_node, i_face) = x[cpt_face++];
+          }
+        }
+      }
+    }
+    m_w_rj = w_rj;
+    m_w_rl = w_rl;
+  }
+
+  InterpolationWeightsManager(std::shared_ptr<const Mesh<Connectivity<Dimension>>> mesh,
+                              FaceValue<bool> primal_face_is_on_boundary,
+                              NodeValue<bool> primal_node_is_on_boundary)
+    : m_mesh(mesh),
+      m_primal_face_is_on_boundary(primal_face_is_on_boundary),
+      m_primal_node_is_on_boundary(primal_node_is_on_boundary)
+  {}
+
+  ~InterpolationWeightsManager() = default;
+};
+
+class ScalarDiamondSchemeHandler::IScalarDiamondScheme
+{
+ public:
+  virtual std::shared_ptr<const IDiscreteFunction> getSolution() const = 0;
+
+  IScalarDiamondScheme()          = default;
+  virtual ~IScalarDiamondScheme() = default;
+};
+
+template <size_t Dimension>
+class ScalarDiamondSchemeHandler::ScalarDiamondScheme : public ScalarDiamondSchemeHandler::IScalarDiamondScheme
+{
+ private:
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_solution;
+
+  class DirichletBoundaryCondition
+  {
+   private:
+    const Array<const double> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const double>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    DirichletBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~DirichletBoundaryCondition() = default;
+  };
+
+  class NeumannBoundaryCondition
+  {
+   private:
+    const Array<const double> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const double>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    NeumannBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~NeumannBoundaryCondition() = default;
+  };
+
+  class FourierBoundaryCondition
+  {
+   private:
+    const Array<const double> m_coef_list;
+    const Array<const double> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const double>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    const Array<const double>&
+    coefList() const
+    {
+      return m_coef_list;
+    }
+
+   public:
+    FourierBoundaryCondition(const Array<const FaceId>& face_list,
+                             const Array<const double>& coef_list,
+                             const Array<const double>& value_list)
+      : m_coef_list{coef_list}, m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_coef_list.size() == m_face_list.size());
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~FourierBoundaryCondition() = default;
+  };
+
+  class SymmetryBoundaryCondition
+  {
+   private:
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+   public:
+    SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+    ~SymmetryBoundaryCondition() = default;
+  };
+
+ public:
+  std::shared_ptr<const IDiscreteFunction>
+  getSolution() const final
+  {
+    return m_solution;
+  }
+
+  ScalarDiamondScheme(const std::shared_ptr<const MeshType>& mesh,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& alpha,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mub,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mu,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& f,
+                      const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+  {
+    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mu->mesh(),
+           "diffusion coefficient is not defined on the dual mesh!");
+    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mub->mesh(),
+           "boundary diffusion coefficient is not defined on the dual mesh!");
+
+    using BoundaryCondition = std::variant<DirichletBoundaryCondition, FourierBoundaryCondition,
+                                           NeumannBoundaryCondition, SymmetryBoundaryCondition>;
+
+    using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+    BoundaryConditionList boundary_condition_list;
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        throw NotImplementedError("NIY");
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::dirichlet: {
+        const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+          dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+        if constexpr (Dimension > 1) {
+          MeshFaceBoundary<Dimension> mesh_face_boundary =
+            getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+          const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+          MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
+
+          Array<const double> value_list =
+            InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                      mesh_data.xl(),
+                                                                                                      mesh_face_boundary
+                                                                                                        .faceList());
+
+          boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+
+        } else {
+          throw NotImplementedError("Dirichlet BC in 1d");
+        }
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::fourier: {
+        throw NotImplementedError("NIY");
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::neumann: {
+        const NeumannBoundaryConditionDescriptor& neumann_bc_descriptor =
+          dynamic_cast<const NeumannBoundaryConditionDescriptor&>(*bc_descriptor);
+
+        if constexpr (Dimension > 1) {
+          MeshFaceBoundary<Dimension> mesh_face_boundary =
+            getMeshFaceBoundary(*mesh, neumann_bc_descriptor.boundaryDescriptor());
+
+          const FunctionSymbolId g_id = neumann_bc_descriptor.rhsSymbolId();
+          MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
+
+          Array<const double> value_list =
+            InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                      mesh_data.xl(),
+                                                                                                      mesh_face_boundary
+                                                                                                        .faceList());
+
+          boundary_condition_list.push_back(NeumannBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+
+        } else {
+          throw NotImplementedError("Dirichlet BC in 1d");
+        }
+        break;
+      }
+      default: {
+        is_valid_boundary_condition = false;
+      }
+      }
+      if (not is_valid_boundary_condition) {
+        std::ostringstream error_msg;
+        error_msg << *bc_descriptor << " is an invalid boundary condition for heat equation";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    if constexpr (Dimension > 1) {
+      const CellValue<const size_t> cell_dof_number = [&] {
+        CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+        return compute_cell_dof_number;
+      }();
+      size_t number_of_dof = mesh->numberOfCells();
+
+      const FaceValue<const size_t> face_dof_number = [&] {
+        FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+        compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                            (std::is_same_v<T, FourierBoundaryCondition>) or
+                            (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                            (std::is_same_v<T, DirichletBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id = face_list[i_face];
+                  if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                    std::ostringstream os;
+                    os << "The face " << face_id << " is used at least twice for boundary conditions";
+                    throw NormalError(os.str());
+                  } else {
+                    compute_face_dof_number[face_id] = number_of_dof++;
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return compute_face_dof_number;
+      }();
+
+      const FaceValue<const bool> primal_face_is_neumann = [&] {
+        FaceValue<bool> face_is_neumann{mesh->connectivity()};
+        face_is_neumann.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                            (std::is_same_v<T, FourierBoundaryCondition>) or
+                            (std::is_same_v<T, SymmetryBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id     = face_list[i_face];
+                  face_is_neumann[face_id] = true;
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return face_is_neumann;
+      }();
+
+      const auto& primal_face_to_node_matrix = mesh->connectivity().faceToNodeMatrix();
+      const auto& face_to_cell_matrix        = mesh->connectivity().faceToCellMatrix();
+      NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+      }
+
+      primal_node_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+            NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+            primal_node_is_on_boundary[node_id] = true;
+          }
+        }
+      }
+
+      FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+      }
+
+      primal_face_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          primal_face_is_on_boundary[face_id] = true;
+        }
+      }
+
+      FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+      }
+
+      primal_face_is_dirichlet.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] && (!primal_face_is_neumann[face_id]));
+      }
+
+      InterpolationWeightsManager iwm(mesh, primal_face_is_on_boundary, primal_node_is_on_boundary);
+      iwm.compute();
+      CellValuePerNode<double> w_rj = iwm.wrj();
+      FaceValuePerNode<double> w_rl = iwm.wrl();
+
+      MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+      const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+      const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+      const auto& node_to_face_matrix                  = mesh->connectivity().nodeToFaceMatrix();
+
+      {
+        std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+        MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+        std::shared_ptr mapper =
+          DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+        CellValue<const double> dual_kappaj  = dual_mu->cellValues();
+        CellValue<const double> dual_kappajb = dual_mub->cellValues();
+
+        const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+        const FaceValue<const double> mes_l = [&] {
+          if constexpr (Dimension == 1) {
+            FaceValue<double> compute_mes_l{mesh->connectivity()};
+            compute_mes_l.fill(1);
+            return compute_mes_l;
+          } else {
+            return mesh_data.ll();
+          }
+        }();
+
+        const CellValue<const double> dual_mes_l_j = [=] {
+          CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+          mapper->toDualCell(mes_l, compute_mes_j);
+
+          return compute_mes_j;
+        }();
+
+        FaceValue<const CellId> face_dual_cell_id = [=]() {
+          FaceValue<CellId> computed_face_dual_cell_id{mesh->connectivity()};
+          CellValue<CellId> dual_cell_id{diamond_mesh->connectivity()};
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { dual_cell_id[cell_id] = cell_id; });
+
+          mapper->fromDualCell(dual_cell_id, computed_face_dual_cell_id);
+
+          return computed_face_dual_cell_id;
+        }();
+
+        NodeValue<const NodeId> dual_node_primal_node_id = [=]() {
+          CellValue<NodeId> cell_ignored_id{mesh->connectivity()};
+          cell_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> node_primal_id{mesh->connectivity()};
+
+          parallel_for(
+            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { node_primal_id[node_id] = node_id; });
+
+          NodeValue<NodeId> computed_dual_node_primal_node_id{diamond_mesh->connectivity()};
+
+          mapper->toDualNode(node_primal_id, cell_ignored_id, computed_dual_node_primal_node_id);
+
+          return computed_dual_node_primal_node_id;
+        }();
+
+        CellValue<NodeId> primal_cell_dual_node_id = [=]() {
+          CellValue<NodeId> cell_id{mesh->connectivity()};
+          NodeValue<NodeId> node_ignored_id{mesh->connectivity()};
+          node_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> dual_node_id{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { dual_node_id[node_id] = node_id; });
+
+          CellValue<NodeId> computed_primal_cell_dual_node_id{mesh->connectivity()};
+
+          mapper->fromDualNode(dual_node_id, node_ignored_id, cell_id);
+
+          return cell_id;
+        }();
+        const auto& dual_Cjr                     = diamond_mesh_data.Cjr();
+        FaceValue<TinyVector<Dimension>> dualClj = [&] {
+          FaceValue<TinyVector<Dimension>> computedClj{mesh->connectivity()};
+          const auto& dual_node_to_cell_matrix = diamond_mesh->connectivity().nodeToCellMatrix();
+          const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+          parallel_for(
+            mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+              const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+              for (size_t i = 0; i < primal_face_to_cell.size(); i++) {
+                CellId cell_id            = primal_face_to_cell[i];
+                const NodeId dual_node_id = primal_cell_dual_node_id[cell_id];
+                for (size_t i_dual_cell = 0; i_dual_cell < dual_node_to_cell_matrix[dual_node_id].size();
+                     i_dual_cell++) {
+                  const CellId dual_cell_id = dual_node_to_cell_matrix[dual_node_id][i_dual_cell];
+                  if (face_dual_cell_id[face_id] == dual_cell_id) {
+                    for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                         i_dual_node++) {
+                      const NodeId final_dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                      if (final_dual_node_id == dual_node_id) {
+                        computedClj[face_id] = dual_Cjr(dual_cell_id, i_dual_node);
+                      }
+                    }
+                  }
+                }
+              }
+            });
+          return computedClj;
+        }();
+
+        FaceValue<TinyVector<Dimension>> nlj = [&] {
+          FaceValue<TinyVector<Dimension>> computedNlj{mesh->connectivity()};
+          parallel_for(
+            mesh->numberOfFaces(),
+            PUGS_LAMBDA(FaceId face_id) { computedNlj[face_id] = 1. / l2Norm(dualClj[face_id]) * dualClj[face_id]; });
+          return computedNlj;
+        }();
+
+        FaceValue<const double> alpha_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_kappaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        FaceValue<const double> alphab_l = [&] {
+          CellValue<double> alpha_jb{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_jb[diamond_cell_id] = dual_kappajb[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_lb{mesh->connectivity()};
+          mapper->fromDualCell(alpha_jb, computed_alpha_lb);
+          return computed_alpha_lb;
+        }();
+
+        const CellValue<const double> primal_Vj = mesh_data.Vj();
+
+        const Array<int> non_zeros{number_of_dof};
+        non_zeros.fill(Dimension);
+        CRSMatrixDescriptor<double> S(number_of_dof, number_of_dof, non_zeros);
+
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+          const double beta_l             = l2Norm(dualClj[face_id]) * alpha_l[face_id] * mes_l[face_id];
+          const double betab_l            = l2Norm(dualClj[face_id]) * alphab_l[face_id] * mes_l[face_id];
+          for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+            const CellId cell_id1 = primal_face_to_cell[i_cell];
+            for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+              const CellId cell_id2 = primal_face_to_cell[j_cell];
+              if (i_cell == j_cell) {
+                S(cell_dof_number[cell_id1], cell_dof_number[cell_id2]) += beta_l;
+                if (primal_face_is_neumann[face_id]) {
+                  S(face_dof_number[face_id], cell_dof_number[cell_id2]) -= betab_l;
+                }
+              } else {
+                S(cell_dof_number[cell_id1], cell_dof_number[cell_id2]) -= beta_l;
+              }
+            }
+          }
+        }
+
+        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+          const size_t j = cell_dof_number[cell_id];
+          S(j, j) += (*alpha)[cell_id] * primal_Vj[cell_id];
+        }
+
+        const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+
+        const auto& primal_node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          const double alpha_face_id  = mes_l[face_id] * alpha_l[face_id];
+          const double alphab_face_id = mes_l[face_id] * alphab_l[face_id];
+
+          for (size_t i_face_cell = 0; i_face_cell < face_to_cell_matrix[face_id].size(); ++i_face_cell) {
+            CellId i_id                            = face_to_cell_matrix[face_id][i_face_cell];
+            const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+            for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+              NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+
+              const TinyVector<Dimension> nil = [&] {
+                if (is_face_reversed_for_cell_i) {
+                  return -nlj[face_id];
+                } else {
+                  return nlj[face_id];
+                }
+              }();
+
+              CellId dual_cell_id = face_dual_cell_id[face_id];
+
+              for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size(); ++i_dual_node) {
+                const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                  const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+
+                  const double a_ir  = alpha_face_id * dot(nil, Clr);
+                  const double ab_ir = alphab_face_id * dot(nil, Clr);
+
+                  for (size_t j_cell = 0; j_cell < primal_node_to_cell_matrix[node_id].size(); ++j_cell) {
+                    CellId j_id = primal_node_to_cell_matrix[node_id][j_cell];
+                    S(cell_dof_number[i_id], cell_dof_number[j_id]) -= w_rj(node_id, j_cell) * a_ir;
+                    if (primal_face_is_neumann[face_id]) {
+                      S(face_dof_number[face_id], cell_dof_number[j_id]) += w_rj(node_id, j_cell) * ab_ir;
+                    }
+                  }
+                  if (primal_node_is_on_boundary[node_id]) {
+                    for (size_t l_face = 0; l_face < node_to_face_matrix[node_id].size(); ++l_face) {
+                      FaceId l_id = node_to_face_matrix[node_id][l_face];
+                      if (primal_face_is_on_boundary[l_id]) {
+                        S(cell_dof_number[i_id], face_dof_number[l_id]) -= w_rl(node_id, l_face) * a_ir;
+                        if (primal_face_is_neumann[face_id]) {
+                          S(face_dof_number[face_id], face_dof_number[l_id]) += w_rl(node_id, l_face) * ab_ir;
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          if (primal_face_is_dirichlet[face_id]) {
+            S(face_dof_number[face_id], face_dof_number[face_id]) += 1;
+          }
+        }
+
+        CellValue<const double> fj = f->cellValues();
+
+        CRSMatrix A{S.getCRSMatrix()};
+        Vector<double> b{number_of_dof};
+        b = zero;
+        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+          b[cell_dof_number[cell_id]] = fj[cell_id] * primal_Vj[cell_id];
+        }
+        // Dirichlet on b^L_D
+        {
+          for (const auto& boundary_condition : boundary_condition_list) {
+            std::visit(
+              [&](auto&& bc) {
+                using T = std::decay_t<decltype(bc)>;
+                if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
+                  const auto& face_list  = bc.faceList();
+                  const auto& value_list = bc.valueList();
+                  for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                    const FaceId face_id = face_list[i_face];
+                    b[face_dof_number[face_id]] += value_list[i_face];
+                  }
+                }
+              },
+              boundary_condition);
+          }
+        }
+        // EL b^L
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                            (std::is_same_v<T, FourierBoundaryCondition>)) {
+                const auto& face_list  = bc.faceList();
+                const auto& value_list = bc.valueList();
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  FaceId face_id = face_list[i_face];
+                  b[face_dof_number[face_id]] += mes_l[face_id] * value_list[i_face];
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        Vector<double> T{number_of_dof};
+        T = zero;
+
+        LinearSolver solver;
+        solver.solveLocalSystem(A, T, b);
+
+        m_solution     = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+        auto& solution = *m_solution;
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { solution[cell_id] = T[cell_dof_number[cell_id]]; });
+      }
+    }
+  }
+};
+
+std::shared_ptr<const IDiscreteFunction>
+ScalarDiamondSchemeHandler::solution() const
+{
+  return m_scheme->getSolution();
+}
+
+ScalarDiamondSchemeHandler::ScalarDiamondSchemeHandler(
+  const std::shared_ptr<const IDiscreteFunction>& alpha,
+  const std::shared_ptr<const IDiscreteFunction>& dual_mub,
+  const std::shared_ptr<const IDiscreteFunction>& dual_mu,
+  const std::shared_ptr<const IDiscreteFunction>& f,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+{
+  const std::shared_ptr i_mesh = getCommonMesh({alpha, f});
+  if (not i_mesh) {
+    throw NormalError("primal discrete functions are not defined on the same mesh");
+  }
+  const std::shared_ptr i_dual_mesh = getCommonMesh({dual_mub, dual_mu});
+  if (not i_dual_mesh) {
+    throw NormalError("dual discrete functions are not defined on the same mesh");
+  }
+  checkDiscretizationType({alpha, dual_mub, dual_mu, f}, DiscreteFunctionType::P0);
+
+  switch (i_mesh->dimension()) {
+  case 1: {
+    using MeshType                   = Mesh<Connectivity<1>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, double>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    if (DualMeshManager::instance().getDiamondDualMesh(*mesh) != i_dual_mesh) {
+      throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
+    }
+
+    m_scheme =
+      std::make_unique<ScalarDiamondScheme<1>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(f),
+                                               bc_descriptor_list);
+    break;
+  }
+  case 2: {
+    using MeshType                   = Mesh<Connectivity<2>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, double>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    if (DualMeshManager::instance().getDiamondDualMesh(*mesh) != i_dual_mesh) {
+      throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
+    }
+
+    m_scheme =
+      std::make_unique<ScalarDiamondScheme<2>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(f),
+                                               bc_descriptor_list);
+    break;
+  }
+  case 3: {
+    using MeshType                   = Mesh<Connectivity<3>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, double>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    if (DualMeshManager::instance().getDiamondDualMesh(*mesh) != i_dual_mesh) {
+      throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
+    }
+
+    m_scheme =
+      std::make_unique<ScalarDiamondScheme<3>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(f),
+                                               bc_descriptor_list);
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
+ScalarDiamondSchemeHandler::~ScalarDiamondSchemeHandler() = default;
diff --git a/src/scheme/ScalarDiamondScheme.hpp b/src/scheme/ScalarDiamondScheme.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..47b66177b5628bb8b13a0ea2d92ede5e41a84950
--- /dev/null
+++ b/src/scheme/ScalarDiamondScheme.hpp
@@ -0,0 +1,688 @@
+#ifndef SCALAR_DIAMOND_SCHEME_HPP
+#define SCALAR_DIAMOND_SCHEME_HPP
+
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LinearSolver.hpp>
+#include <algebra/TinyVector.hpp>
+#include <algebra/Vector.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/FourierBoundaryConditionDescriptor.hpp>
+#include <scheme/IDiscreteFunction.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+
+class ScalarDiamondSchemeHandler
+{
+ private:
+  class IScalarDiamondScheme;
+
+  template <size_t Dimension>
+  class ScalarDiamondScheme;
+
+  template <size_t Dimension>
+  class InterpolationWeightsManager;
+
+ public:
+  std::unique_ptr<IScalarDiamondScheme> m_scheme;
+
+  std::shared_ptr<const IDiscreteFunction> solution() const;
+
+  ScalarDiamondSchemeHandler(
+    const std::shared_ptr<const IDiscreteFunction>& alpha,
+    const std::shared_ptr<const IDiscreteFunction>& mu_dualb,
+    const std::shared_ptr<const IDiscreteFunction>& mu_dual,
+    const std::shared_ptr<const IDiscreteFunction>& f,
+    const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list);
+
+  ~ScalarDiamondSchemeHandler();
+};
+
+template <size_t Dimension>
+class LegacyScalarDiamondScheme
+{
+ private:
+  class DirichletBoundaryCondition
+  {
+   private:
+    const Array<const double> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const double>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    DirichletBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~DirichletBoundaryCondition() = default;
+  };
+
+  class NeumannBoundaryCondition
+  {
+   private:
+    const Array<const double> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const double>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    NeumannBoundaryCondition(const Array<const FaceId>& face_list, const Array<const double>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~NeumannBoundaryCondition() = default;
+  };
+
+  class FourierBoundaryCondition
+  {
+   private:
+    const Array<const double> m_coef_list;
+    const Array<const double> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const double>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    const Array<const double>&
+    coefList() const
+    {
+      return m_coef_list;
+    }
+
+   public:
+    FourierBoundaryCondition(const Array<const FaceId>& face_list,
+                             const Array<const double>& coef_list,
+                             const Array<const double>& value_list)
+      : m_coef_list{coef_list}, m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_coef_list.size() == m_face_list.size());
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~FourierBoundaryCondition() = default;
+  };
+
+  class SymmetryBoundaryCondition
+  {
+   private:
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+   public:
+    SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+    ~SymmetryBoundaryCondition() = default;
+  };
+
+ public:
+  LegacyScalarDiamondScheme(std::shared_ptr<const IMesh> i_mesh,
+                            const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+                            const FunctionSymbolId& kappa_id,
+                            const FunctionSymbolId& f_id,
+                            CellValue<double>& Temperature,
+                            FaceValue<double>& Temperature_face,
+                            const double& Tf,
+                            const double& dt,
+                            const CellValuePerNode<double>& w_rj,
+                            const FaceValuePerNode<double>& w_rl)
+  {
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+    using MeshDataType     = MeshData<Dimension>;
+
+    using BoundaryCondition = std::variant<DirichletBoundaryCondition, FourierBoundaryCondition,
+                                           NeumannBoundaryCondition, SymmetryBoundaryCondition>;
+
+    using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+    BoundaryConditionList boundary_condition_list;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        throw NotImplementedError("NIY");
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::dirichlet: {
+        const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+          dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+        if constexpr (Dimension > 1) {
+          MeshFaceBoundary<Dimension> mesh_face_boundary =
+            getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+          const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+          MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
+
+          Array<const double> value_list =
+            InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                      mesh_data.xl(),
+                                                                                                      mesh_face_boundary
+                                                                                                        .faceList());
+          boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+        }
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::fourier: {
+        throw NotImplementedError("NIY");
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::neumann: {
+        const NeumannBoundaryConditionDescriptor& neumann_bc_descriptor =
+          dynamic_cast<const NeumannBoundaryConditionDescriptor&>(*bc_descriptor);
+
+        if constexpr (Dimension > 1) {
+          MeshFaceBoundary<Dimension> mesh_face_boundary =
+            getMeshFaceBoundary(*mesh, neumann_bc_descriptor.boundaryDescriptor());
+
+          const FunctionSymbolId g_id = neumann_bc_descriptor.rhsSymbolId();
+          MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
+
+          Array<const double> value_list =
+            InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
+                                                                                                      mesh_data.xl(),
+                                                                                                      mesh_face_boundary
+                                                                                                        .faceList());
+
+          boundary_condition_list.push_back(NeumannBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+
+        } else {
+          throw NotImplementedError("Dirichlet BC in 1d");
+        }
+        break;
+      }
+      default: {
+        is_valid_boundary_condition = false;
+      }
+      }
+      if (not is_valid_boundary_condition) {
+        std::ostringstream error_msg;
+        error_msg << *bc_descriptor << " is an invalid boundary condition for heat equation";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    if constexpr (Dimension > 1) {
+      const CellValue<const size_t> cell_dof_number = [&] {
+        CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+        return compute_cell_dof_number;
+      }();
+      size_t number_of_dof = mesh->numberOfCells();
+
+      const FaceValue<const size_t> face_dof_number = [&] {
+        FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+        compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                            (std::is_same_v<T, FourierBoundaryCondition>) or
+                            (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                            (std::is_same_v<T, DirichletBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id = face_list[i_face];
+                  if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                    std::ostringstream os;
+                    os << "The face " << face_id << " is used at least twice for boundary conditions";
+                    throw NormalError(os.str());
+                  } else {
+                    compute_face_dof_number[face_id] = number_of_dof++;
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return compute_face_dof_number;
+      }();
+
+      const FaceValue<const bool> primal_face_is_neumann = [&] {
+        FaceValue<bool> face_is_neumann{mesh->connectivity()};
+        face_is_neumann.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                            (std::is_same_v<T, FourierBoundaryCondition>) or
+                            (std::is_same_v<T, SymmetryBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id     = face_list[i_face];
+                  face_is_neumann[face_id] = true;
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return face_is_neumann;
+      }();
+
+      const auto& primal_face_to_node_matrix = mesh->connectivity().faceToNodeMatrix();
+      const auto& face_to_cell_matrix        = mesh->connectivity().faceToCellMatrix();
+      NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+      }
+
+      primal_node_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+            NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+            primal_node_is_on_boundary[node_id] = true;
+          }
+        }
+      }
+
+      FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+      }
+
+      primal_face_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          primal_face_is_on_boundary[face_id] = true;
+        }
+      }
+
+      FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+      }
+
+      primal_face_is_dirichlet.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] && (!primal_face_is_neumann[face_id]));
+      }
+
+      MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+      const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+      const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+      const auto& node_to_face_matrix                  = mesh->connectivity().nodeToFaceMatrix();
+
+      {
+        std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+        MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+        std::shared_ptr mapper =
+          DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+        CellValue<double> kappaj =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(kappa_id,
+                                                                                                    mesh_data.xj());
+
+        CellValue<double> dual_kappaj =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(kappa_id,
+                                                                                                    diamond_mesh_data
+                                                                                                      .xj());
+
+        const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+        const FaceValue<const double> mes_l = [&] {
+          if constexpr (Dimension == 1) {
+            FaceValue<double> compute_mes_l{mesh->connectivity()};
+            compute_mes_l.fill(1);
+            return compute_mes_l;
+          } else {
+            return mesh_data.ll();
+          }
+        }();
+
+        const CellValue<const double> dual_mes_l_j = [=] {
+          CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+          mapper->toDualCell(mes_l, compute_mes_j);
+
+          return compute_mes_j;
+        }();
+
+        FaceValue<const CellId> face_dual_cell_id = [=]() {
+          FaceValue<CellId> computed_face_dual_cell_id{mesh->connectivity()};
+          CellValue<CellId> dual_cell_id{diamond_mesh->connectivity()};
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { dual_cell_id[cell_id] = cell_id; });
+
+          mapper->fromDualCell(dual_cell_id, computed_face_dual_cell_id);
+
+          return computed_face_dual_cell_id;
+        }();
+
+        NodeValue<const NodeId> dual_node_primal_node_id = [=]() {
+          CellValue<NodeId> cell_ignored_id{mesh->connectivity()};
+          cell_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> node_primal_id{mesh->connectivity()};
+
+          parallel_for(
+            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { node_primal_id[node_id] = node_id; });
+
+          NodeValue<NodeId> computed_dual_node_primal_node_id{diamond_mesh->connectivity()};
+
+          mapper->toDualNode(node_primal_id, cell_ignored_id, computed_dual_node_primal_node_id);
+
+          return computed_dual_node_primal_node_id;
+        }();
+
+        CellValue<NodeId> primal_cell_dual_node_id = [=]() {
+          CellValue<NodeId> cell_id{mesh->connectivity()};
+          NodeValue<NodeId> node_ignored_id{mesh->connectivity()};
+          node_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> dual_node_id{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { dual_node_id[node_id] = node_id; });
+
+          CellValue<NodeId> computed_primal_cell_dual_node_id{mesh->connectivity()};
+
+          mapper->fromDualNode(dual_node_id, node_ignored_id, cell_id);
+
+          return cell_id;
+        }();
+        const auto& dual_Cjr                     = diamond_mesh_data.Cjr();
+        FaceValue<TinyVector<Dimension>> dualClj = [&] {
+          FaceValue<TinyVector<Dimension>> computedClj{mesh->connectivity()};
+          const auto& dual_node_to_cell_matrix = diamond_mesh->connectivity().nodeToCellMatrix();
+          const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+          parallel_for(
+            mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+              const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+              for (size_t i = 0; i < primal_face_to_cell.size(); i++) {
+                CellId cell_id            = primal_face_to_cell[i];
+                const NodeId dual_node_id = primal_cell_dual_node_id[cell_id];
+                for (size_t i_dual_cell = 0; i_dual_cell < dual_node_to_cell_matrix[dual_node_id].size();
+                     i_dual_cell++) {
+                  const CellId dual_cell_id = dual_node_to_cell_matrix[dual_node_id][i_dual_cell];
+                  if (face_dual_cell_id[face_id] == dual_cell_id) {
+                    for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                         i_dual_node++) {
+                      const NodeId final_dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                      if (final_dual_node_id == dual_node_id) {
+                        computedClj[face_id] = dual_Cjr(dual_cell_id, i_dual_node);
+                      }
+                    }
+                  }
+                }
+              }
+            });
+          return computedClj;
+        }();
+
+        FaceValue<TinyVector<Dimension>> nlj = [&] {
+          FaceValue<TinyVector<Dimension>> computedNlj{mesh->connectivity()};
+          parallel_for(
+            mesh->numberOfFaces(),
+            PUGS_LAMBDA(FaceId face_id) { computedNlj[face_id] = 1. / l2Norm(dualClj[face_id]) * dualClj[face_id]; });
+          return computedNlj;
+        }();
+
+        FaceValue<const double> alpha_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_kappaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        double lambda      = (Tf == 0) ? 0 : 1;
+        double time_factor = (lambda == 0) ? 1 : dt;
+
+        const CellValue<const double> primal_Vj = mesh_data.Vj();
+        Array<int> non_zero{number_of_dof};
+        non_zero.fill(Dimension * Dimension);
+        CRSMatrixDescriptor<double> S(number_of_dof, number_of_dof, non_zero);
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+          const double beta_l             = l2Norm(dualClj[face_id]) * alpha_l[face_id] * mes_l[face_id];
+          for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+            const CellId cell_id1 = primal_face_to_cell[i_cell];
+            for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+              const CellId cell_id2 = primal_face_to_cell[j_cell];
+              if (i_cell == j_cell) {
+                S(cell_dof_number[cell_id1], cell_dof_number[cell_id2]) +=
+                  (time_factor * beta_l + lambda * primal_Vj[cell_id1]);
+                if (primal_face_is_neumann[face_id]) {
+                  S(face_dof_number[face_id], cell_dof_number[cell_id2]) -= beta_l;
+                }
+              } else {
+                S(cell_dof_number[cell_id1], cell_dof_number[cell_id2]) -= time_factor * beta_l;
+              }
+            }
+          }
+        }
+
+        const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+
+        const auto& primal_node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          const double alpha_face_id = mes_l[face_id] * alpha_l[face_id];
+
+          for (size_t i_face_cell = 0; i_face_cell < face_to_cell_matrix[face_id].size(); ++i_face_cell) {
+            CellId i_id                            = face_to_cell_matrix[face_id][i_face_cell];
+            const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+            for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+              NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+
+              const TinyVector<Dimension> nil = [&] {
+                if (is_face_reversed_for_cell_i) {
+                  return -nlj[face_id];
+                } else {
+                  return nlj[face_id];
+                }
+              }();
+
+              CellId dual_cell_id = face_dual_cell_id[face_id];
+
+              for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size(); ++i_dual_node) {
+                const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                  const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+
+                  const double a_ir = alpha_face_id * dot(nil, Clr);
+
+                  for (size_t j_cell = 0; j_cell < primal_node_to_cell_matrix[node_id].size(); ++j_cell) {
+                    CellId j_id = primal_node_to_cell_matrix[node_id][j_cell];
+                    S(cell_dof_number[i_id], cell_dof_number[j_id]) -= time_factor * w_rj(node_id, j_cell) * a_ir;
+                    if (primal_face_is_neumann[face_id]) {
+                      S(face_dof_number[face_id], cell_dof_number[j_id]) += w_rj(node_id, j_cell) * a_ir;
+                    }
+                  }
+                  if (primal_node_is_on_boundary[node_id]) {
+                    for (size_t l_face = 0; l_face < node_to_face_matrix[node_id].size(); ++l_face) {
+                      FaceId l_id = node_to_face_matrix[node_id][l_face];
+                      if (primal_face_is_on_boundary[l_id]) {
+                        S(cell_dof_number[i_id], face_dof_number[l_id]) -= time_factor * w_rl(node_id, l_face) * a_ir;
+                        if (primal_face_is_neumann[face_id]) {
+                          S(face_dof_number[face_id], face_dof_number[l_id]) += w_rl(node_id, l_face) * a_ir;
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          if (primal_face_is_dirichlet[face_id]) {
+            S(face_dof_number[face_id], face_dof_number[face_id]) += 1;
+          }
+        }
+
+        CellValue<double> fj =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(f_id,
+                                                                                                    mesh_data.xj());
+
+        CRSMatrix A{S.getCRSMatrix()};
+        Vector<double> b{number_of_dof};
+        b = zero;
+
+        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+          b[cell_dof_number[cell_id]] =
+            (time_factor * fj[cell_id] + lambda * Temperature[cell_id]) * primal_Vj[cell_id];
+        }
+        // Dirichlet on b^L_D
+        {
+          for (const auto& boundary_condition : boundary_condition_list) {
+            std::visit(
+              [&](auto&& bc) {
+                using T = std::decay_t<decltype(bc)>;
+                if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
+                  const auto& face_list  = bc.faceList();
+                  const auto& value_list = bc.valueList();
+                  for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                    const FaceId face_id = face_list[i_face];
+                    b[face_dof_number[face_id]] += value_list[i_face];
+                  }
+                }
+              },
+              boundary_condition);
+          }
+        }
+        // EL b^L
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NeumannBoundaryCondition>) or
+                            (std::is_same_v<T, FourierBoundaryCondition>)) {
+                const auto& face_list  = bc.faceList();
+                const auto& value_list = bc.valueList();
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  FaceId face_id = face_list[i_face];
+                  b[face_dof_number[face_id]] += mes_l[face_id] * value_list[i_face];
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        Vector<double> T{number_of_dof};
+        T = zero;
+
+        LinearSolver solver;
+        solver.solveLocalSystem(A, T, b);
+
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { Temperature[cell_id] = T[cell_dof_number[cell_id]]; });
+        parallel_for(
+          mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+            if (primal_face_is_neumann[face_id]) {
+              Temperature_face[face_id] = T[face_dof_number[face_id]];
+            }
+          });
+      }
+    }
+  }
+};
+
+template LegacyScalarDiamondScheme<1>::LegacyScalarDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  CellValue<double>&,
+  FaceValue<double>&,
+  const double&,
+  const double&,
+  const CellValuePerNode<double>& w_rj,
+  const FaceValuePerNode<double>& w_rl);
+
+template LegacyScalarDiamondScheme<2>::LegacyScalarDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  CellValue<double>&,
+  FaceValue<double>&,
+  const double&,
+  const double&,
+  const CellValuePerNode<double>& w_rj,
+  const FaceValuePerNode<double>& w_rl);
+
+template LegacyScalarDiamondScheme<3>::LegacyScalarDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  CellValue<double>&,
+  FaceValue<double>&,
+  const double&,
+  const double&,
+  const CellValuePerNode<double>& w_rj,
+  const FaceValuePerNode<double>& w_rl);
+
+#endif   // SCALAR_DIAMOND_SCHEME_HPP
diff --git a/src/scheme/SymmetryBoundaryConditionDescriptor.hpp b/src/scheme/SymmetryBoundaryConditionDescriptor.hpp
index 9364090dde18490a4803662c7eb770d9f6354b1c..68ef6a55853e8a6665e582277c76a1c6c7d57f63 100644
--- a/src/scheme/SymmetryBoundaryConditionDescriptor.hpp
+++ b/src/scheme/SymmetryBoundaryConditionDescriptor.hpp
@@ -12,13 +12,20 @@ class SymmetryBoundaryConditionDescriptor : public IBoundaryConditionDescriptor
   std::ostream&
   _write(std::ostream& os) const final
   {
-    os << "symmetry(" << *m_boundary_descriptor << ")";
+    os << "symmetry(" << m_name << ',' << *m_boundary_descriptor << ")";
     return os;
   }
 
+  const std::string_view m_name;
+
   std::shared_ptr<const IBoundaryDescriptor> m_boundary_descriptor;
 
  public:
+  std::string_view
+  name() const
+  {
+    return m_name;
+  }
   const IBoundaryDescriptor&
   boundaryDescriptor() const final
   {
diff --git a/src/scheme/VectorDiamondScheme.cpp b/src/scheme/VectorDiamondScheme.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..368713589c323baa59201ea312348837293eba5d
--- /dev/null
+++ b/src/scheme/VectorDiamondScheme.cpp
@@ -0,0 +1,2035 @@
+#include <scheme/VectorDiamondScheme.hpp>
+
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+
+template <size_t Dimension>
+class VectorDiamondSchemeHandler::InterpolationWeightsManager
+{
+ private:
+  std::shared_ptr<const Mesh<Connectivity<Dimension>>> m_mesh;
+  FaceValue<const bool> m_primal_face_is_on_boundary;
+  NodeValue<const bool> m_primal_node_is_on_boundary;
+  FaceValue<const bool> m_primal_face_is_symmetry;
+  CellValuePerNode<double> m_w_rj;
+  FaceValuePerNode<double> m_w_rl;
+
+ public:
+  InterpolationWeightsManager(std::shared_ptr<const Mesh<Connectivity<Dimension>>> mesh,
+                              FaceValue<const bool> primal_face_is_on_boundary,
+                              NodeValue<const bool> primal_node_is_on_boundary,
+                              FaceValue<const bool> primal_face_is_symmetry)
+    : m_mesh(mesh),
+      m_primal_face_is_on_boundary(primal_face_is_on_boundary),
+      m_primal_node_is_on_boundary(primal_node_is_on_boundary),
+      m_primal_face_is_symmetry(primal_face_is_symmetry)
+  {}
+  ~InterpolationWeightsManager() = default;
+  CellValuePerNode<double>&
+  wrj()
+  {
+    return m_w_rj;
+  }
+  FaceValuePerNode<double>&
+  wrl()
+  {
+    return m_w_rl;
+  }
+  void
+  compute()
+  {
+    using MeshDataType      = MeshData<Dimension>;
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*m_mesh);
+
+    const NodeValue<const TinyVector<Dimension>>& xr = m_mesh->xr();
+
+    const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = m_mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix                  = m_mesh->connectivity().nodeToFaceMatrix();
+    const auto& face_to_cell_matrix                  = m_mesh->connectivity().faceToCellMatrix();
+
+    CellValuePerNode<double> w_rj{m_mesh->connectivity()};
+    FaceValuePerNode<double> w_rl{m_mesh->connectivity()};
+
+    const NodeValuePerFace<const TinyVector<Dimension>> primal_nlr = mesh_data.nlr();
+    auto project_to_face = [&](const TinyVector<Dimension>& x, const FaceId face_id) -> const TinyVector<Dimension> {
+      TinyVector<Dimension> proj;
+      const TinyVector<Dimension> nil = primal_nlr(face_id, 0);
+      proj                            = x - dot((x - xl[face_id]), nil) * nil;
+      return proj;
+    };
+
+    for (size_t i = 0; i < w_rl.numberOfValues(); ++i) {
+      w_rl[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+
+    for (NodeId i_node = 0; i_node < m_mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      if (not m_primal_node_is_on_boundary[i_node]) {
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size()};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+      } else {
+        int nb_face_used = 0;
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            nb_face_used++;
+          }
+        }
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size() + nb_face_used};
+        for (size_t j = 0; j < node_to_cell.size() + nb_face_used; j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          int cpt_face = 0;
+          for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+            FaceId face_id = node_to_face_matrix[i_node][i_face];
+            if (m_primal_face_is_on_boundary[face_id]) {
+              if (m_primal_face_is_symmetry[face_id]) {
+                for (size_t j = 0; j < face_to_cell_matrix[face_id].size(); ++j) {
+                  const CellId cell_id                 = face_to_cell_matrix[face_id][j];
+                  TinyVector<Dimension> xproj          = project_to_face(xj[cell_id], face_id);
+                  A(i, node_to_cell.size() + cpt_face) = xproj[i - 1];
+                }
+              } else {
+                A(i, node_to_cell.size() + cpt_face) = xl[face_id][i - 1];
+              }
+              cpt_face++;
+            }
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size() + nb_face_used};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+        int cpt_face = node_to_cell.size();
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            w_rl(i_node, i_face) = x[cpt_face++];
+          }
+        }
+      }
+    }
+    m_w_rj = w_rj;
+    m_w_rl = w_rl;
+  }
+};
+
+class VectorDiamondSchemeHandler::IVectorDiamondScheme
+{
+ public:
+  virtual std::shared_ptr<const IDiscreteFunction> getSolution() const     = 0;
+  virtual std::shared_ptr<const IDiscreteFunction> getDualSolution() const = 0;
+  virtual std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> apply()
+    const = 0;
+  // virtual std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+  // computeEnergyUpdate() const = 0;
+
+  IVectorDiamondScheme()          = default;
+  virtual ~IVectorDiamondScheme() = default;
+};
+
+template <size_t Dimension>
+class VectorDiamondSchemeHandler::VectorDiamondScheme : public VectorDiamondSchemeHandler::IVectorDiamondScheme
+{
+ private:
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>> m_solution;
+  std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>> m_dual_solution;
+  //  std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_energy_delta;
+
+  class DirichletBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const TinyVector<Dimension>>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    DirichletBoundaryCondition(const Array<const FaceId>& face_list,
+                               const Array<const TinyVector<Dimension>>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~DirichletBoundaryCondition() = default;
+  };
+
+  class NormalStrainBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const TinyVector<Dimension>>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    NormalStrainBoundaryCondition(const Array<const FaceId>& face_list,
+                                  const Array<const TinyVector<Dimension>>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~NormalStrainBoundaryCondition() = default;
+  };
+
+  class SymmetryBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+   public:
+    SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+    ~SymmetryBoundaryCondition() = default;
+  };
+
+ public:
+  std::shared_ptr<const IDiscreteFunction>
+  getSolution() const final
+  {
+    return m_solution;
+  }
+
+  std::shared_ptr<const IDiscreteFunction>
+  getDualSolution() const final
+  {
+    return m_dual_solution;
+  }
+
+  std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+  apply() const final
+  {
+    return {m_solution, m_dual_solution};
+  }
+
+  VectorDiamondScheme(const std::shared_ptr<const MeshType>& mesh,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& alpha,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_lambdab,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mub,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_lambda,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mu,
+                      const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>& source,
+                      const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+  {
+    Assert(mesh == alpha->mesh());
+    Assert(mesh == source->mesh());
+    Assert(dual_lambda->mesh() == dual_mu->mesh());
+    Assert(dual_lambdab->mesh() == dual_mu->mesh());
+    Assert(dual_mub->mesh() == dual_mu->mesh());
+    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mu->mesh(),
+           "diffusion coefficient is not defined on the dual mesh!");
+
+    using MeshDataType = MeshData<Dimension>;
+
+    using BoundaryCondition =
+      std::variant<DirichletBoundaryCondition, NormalStrainBoundaryCondition, SymmetryBoundaryCondition>;
+
+    using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+    BoundaryConditionList boundary_condition_list;
+
+    NodeValue<bool> is_dirichlet{mesh->connectivity()};
+    is_dirichlet.fill(false);
+    NodeValue<TinyVector<Dimension>> dirichlet_value{mesh->connectivity()};
+    {
+      TinyVector<Dimension> nan_tiny_vector;
+      for (size_t i = 0; i < Dimension; ++i) {
+        nan_tiny_vector[i] = std::numeric_limits<double>::signaling_NaN();
+      }
+      dirichlet_value.fill(nan_tiny_vector);
+    }
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor =
+          dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+
+        if constexpr (Dimension > 1) {
+          MeshFlatFaceBoundary<Dimension> mesh_face_boundary =
+            getMeshFlatFaceBoundary(*mesh, sym_bc_descriptor.boundaryDescriptor());
+          boundary_condition_list.push_back(SymmetryBoundaryCondition{mesh_face_boundary.faceList()});
+        } else {
+          throw NotImplementedError("Symmetry conditions are not supported in 1d");
+        }
+
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::dirichlet: {
+        const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+          dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+        if (dirichlet_bc_descriptor.name() == "dirichlet") {
+          if constexpr (Dimension > 1) {
+            MeshFaceBoundary<Dimension> mesh_face_boundary =
+              getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+            const FunctionSymbolId g_id                   = dirichlet_bc_descriptor.rhsSymbolId();
+            Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+              TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                            mesh_face_boundary.faceList());
+            boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+          } else {
+            throw NotImplementedError("Neumann conditions are not supported in 1d");
+          }
+        } else if (dirichlet_bc_descriptor.name() == "normal_strain") {
+          if constexpr (Dimension > 1) {
+            MeshFaceBoundary<Dimension> mesh_face_boundary =
+              getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+            const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+            Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+              TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                            mesh_face_boundary.faceList());
+            boundary_condition_list.push_back(NormalStrainBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+
+          } else {
+            throw NotImplementedError("Normal strain conditions are not supported in 1d");
+          }
+        } else {
+          is_valid_boundary_condition = false;
+        }
+        break;
+      }
+      default: {
+        is_valid_boundary_condition = false;
+      }
+      }
+      if (not is_valid_boundary_condition) {
+        std::ostringstream error_msg;
+        error_msg << *bc_descriptor << " is an invalid boundary condition for elasticity equation";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    if constexpr (Dimension > 1) {
+      const CellValue<const size_t> cell_dof_number = [&] {
+        CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+        return compute_cell_dof_number;
+      }();
+      size_t number_of_dof = mesh->numberOfCells();
+
+      const FaceValue<const size_t> face_dof_number = [&] {
+        FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+        compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>) or
+                            (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                            (std::is_same_v<T, DirichletBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id = face_list[i_face];
+                  if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                    std::ostringstream os;
+                    os << "The face " << face_id << " is used at least twice for boundary conditions";
+                    throw NormalError(os.str());
+                  } else {
+                    compute_face_dof_number[face_id] = number_of_dof++;
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return compute_face_dof_number;
+      }();
+
+      const auto& primal_face_to_node_matrix             = mesh->connectivity().faceToNodeMatrix();
+      const auto& face_to_cell_matrix                    = mesh->connectivity().faceToCellMatrix();
+      const FaceValue<const bool> primal_face_is_neumann = [&] {
+        FaceValue<bool> face_is_neumann{mesh->connectivity()};
+        face_is_neumann.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id     = face_list[i_face];
+                  face_is_neumann[face_id] = true;
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return face_is_neumann;
+      }();
+
+      const FaceValue<const bool> primal_face_is_symmetry = [&] {
+        FaceValue<bool> face_is_symmetry{mesh->connectivity()};
+        face_is_symmetry.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, SymmetryBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id      = face_list[i_face];
+                  face_is_symmetry[face_id] = true;
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return face_is_symmetry;
+      }();
+
+      NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+      }
+
+      primal_node_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+            NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+            primal_node_is_on_boundary[node_id] = true;
+          }
+        }
+      }
+
+      FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+      }
+
+      primal_face_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          primal_face_is_on_boundary[face_id] = true;
+        }
+      }
+
+      FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+      }
+
+      primal_face_is_dirichlet.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] &&
+                                             (!primal_face_is_neumann[face_id]) && (!primal_face_is_symmetry[face_id]));
+      }
+
+      InterpolationWeightsManager iwm(mesh, primal_face_is_on_boundary, primal_node_is_on_boundary,
+                                      primal_face_is_symmetry);
+      iwm.compute();
+      CellValuePerNode<double> w_rj = iwm.wrj();
+      FaceValuePerNode<double> w_rl = iwm.wrl();
+
+      MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+      const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+      const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+      // const auto& node_to_cell_matrix                                = mesh->connectivity().nodeToCellMatrix();
+      const auto& node_to_face_matrix                                = mesh->connectivity().nodeToFaceMatrix();
+      const NodeValuePerFace<const TinyVector<Dimension>> primal_nlr = mesh_data.nlr();
+
+      {
+        std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+        MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+        std::shared_ptr mapper =
+          DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+        CellValue<const double> dual_muj      = dual_mu->cellValues();
+        CellValue<const double> dual_lambdaj  = dual_lambda->cellValues();
+        CellValue<const double> dual_mubj     = dual_mub->cellValues();
+        CellValue<const double> dual_lambdabj = dual_lambdab->cellValues();
+
+        CellValue<const TinyVector<Dimension>> fj = source->cellValues();
+        // for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+        //   std::cout << xj[cell_id] << "-> fj[" << cell_id << "]=" << fj[cell_id] << '\n';
+        // }
+
+        const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+        const FaceValue<const double> mes_l = [&] {
+          if constexpr (Dimension == 1) {
+            FaceValue<double> compute_mes_l{mesh->connectivity()};
+            compute_mes_l.fill(1);
+            return compute_mes_l;
+          } else {
+            return mesh_data.ll();
+          }
+        }();
+
+        const CellValue<const double> dual_mes_l_j = [=] {
+          CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+          mapper->toDualCell(mes_l, compute_mes_j);
+
+          return compute_mes_j;
+        }();
+
+        const CellValue<const double> primal_Vj = mesh_data.Vj();
+
+        CellValue<const FaceId> dual_cell_face_id = [=]() {
+          CellValue<FaceId> computed_dual_cell_face_id{diamond_mesh->connectivity()};
+          FaceValue<FaceId> primal_face_id{mesh->connectivity()};
+          parallel_for(
+            mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) { primal_face_id[face_id] = face_id; });
+
+          mapper->toDualCell(primal_face_id, computed_dual_cell_face_id);
+
+          return computed_dual_cell_face_id;
+        }();
+
+        FaceValue<const CellId> face_dual_cell_id = [=]() {
+          FaceValue<CellId> computed_face_dual_cell_id{mesh->connectivity()};
+          CellValue<CellId> dual_cell_id{diamond_mesh->connectivity()};
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { dual_cell_id[cell_id] = cell_id; });
+
+          mapper->fromDualCell(dual_cell_id, computed_face_dual_cell_id);
+
+          return computed_face_dual_cell_id;
+        }();
+
+        NodeValue<const NodeId> dual_node_primal_node_id = [=]() {
+          CellValue<NodeId> cell_ignored_id{mesh->connectivity()};
+          cell_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> node_primal_id{mesh->connectivity()};
+
+          parallel_for(
+            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { node_primal_id[node_id] = node_id; });
+
+          NodeValue<NodeId> computed_dual_node_primal_node_id{diamond_mesh->connectivity()};
+
+          mapper->toDualNode(node_primal_id, cell_ignored_id, computed_dual_node_primal_node_id);
+
+          return computed_dual_node_primal_node_id;
+        }();
+
+        CellValue<NodeId> primal_cell_dual_node_id = [=]() {
+          CellValue<NodeId> cell_id{mesh->connectivity()};
+          NodeValue<NodeId> node_ignored_id{mesh->connectivity()};
+          node_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> dual_node_id{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { dual_node_id[node_id] = node_id; });
+
+          CellValue<NodeId> computed_primal_cell_dual_node_id{mesh->connectivity()};
+
+          mapper->fromDualNode(dual_node_id, node_ignored_id, cell_id);
+
+          return cell_id;
+        }();
+        const auto& dual_Cjr                     = diamond_mesh_data.Cjr();
+        FaceValue<TinyVector<Dimension>> dualClj = [&] {
+          FaceValue<TinyVector<Dimension>> computedClj{mesh->connectivity()};
+          const auto& dual_node_to_cell_matrix = diamond_mesh->connectivity().nodeToCellMatrix();
+          const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+          parallel_for(
+            mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+              const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+              for (size_t i = 0; i < primal_face_to_cell.size(); i++) {
+                CellId cell_id            = primal_face_to_cell[i];
+                const NodeId dual_node_id = primal_cell_dual_node_id[cell_id];
+                for (size_t i_dual_cell = 0; i_dual_cell < dual_node_to_cell_matrix[dual_node_id].size();
+                     i_dual_cell++) {
+                  const CellId dual_cell_id = dual_node_to_cell_matrix[dual_node_id][i_dual_cell];
+                  if (face_dual_cell_id[face_id] == dual_cell_id) {
+                    for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                         i_dual_node++) {
+                      const NodeId final_dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                      if (final_dual_node_id == dual_node_id) {
+                        computedClj[face_id] = dual_Cjr(dual_cell_id, i_dual_node);
+                      }
+                    }
+                  }
+                }
+              }
+            });
+          return computedClj;
+        }();
+
+        FaceValue<TinyVector<Dimension>> nlj = [&] {
+          FaceValue<TinyVector<Dimension>> computedNlj{mesh->connectivity()};
+          parallel_for(
+            mesh->numberOfFaces(),
+            PUGS_LAMBDA(FaceId face_id) { computedNlj[face_id] = 1. / l2Norm(dualClj[face_id]) * dualClj[face_id]; });
+          return computedNlj;
+        }();
+
+        FaceValue<const double> alpha_lambda_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_lambdaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        FaceValue<const double> alpha_mu_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_muj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        FaceValue<const double> alpha_lambdab_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_lambdabj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        FaceValue<const double> alpha_mub_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_mubj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        const TinyMatrix<Dimension> I = identity;
+
+        const Array<int> non_zeros{number_of_dof * Dimension};
+        non_zeros.fill(Dimension * Dimension);
+        CRSMatrixDescriptor<double> S(number_of_dof * Dimension, number_of_dof * Dimension, non_zeros);
+
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          const double beta_mu_l          = l2Norm(dualClj[face_id]) * alpha_mu_l[face_id] * mes_l[face_id];
+          const double beta_lambda_l      = l2Norm(dualClj[face_id]) * alpha_lambda_l[face_id] * mes_l[face_id];
+          const double beta_mub_l         = l2Norm(dualClj[face_id]) * alpha_mub_l[face_id] * mes_l[face_id];
+          const double beta_lambdab_l     = l2Norm(dualClj[face_id]) * alpha_lambdab_l[face_id] * mes_l[face_id];
+          const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+          for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+            const CellId i_id                      = primal_face_to_cell[i_cell];
+            const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+            const TinyVector<Dimension> nil = [&] {
+              if (is_face_reversed_for_cell_i) {
+                return -nlj[face_id];
+              } else {
+                return nlj[face_id];
+              }
+            }();
+            TinyMatrix<Dimension> M =
+              beta_mu_l * I + beta_mu_l * tensorProduct(nil, nil) + beta_lambda_l * tensorProduct(nil, nil);
+            TinyMatrix<Dimension> Mb =
+              beta_mub_l * I + beta_mub_l * tensorProduct(nil, nil) + beta_lambdab_l * tensorProduct(nil, nil);
+            TinyMatrix<Dimension> N = 1.e0 * tensorProduct(nil, nil);
+            double coef_adim        = beta_mu_l + beta_lambdab_l;
+            for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+              const CellId j_id = primal_face_to_cell[j_cell];
+              if (i_cell == j_cell) {
+                for (size_t i = 0; i < Dimension; ++i) {
+                  for (size_t j = 0; j < Dimension; ++j) {
+                    S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) += M(i, j);
+                    if (primal_face_is_neumann[face_id]) {
+                      S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) -=
+                        1.e0 * Mb(i, j);
+                      // S(face_dof_number[face_id] * Dimension + i, face_dof_number[face_id] * Dimension + j) +=
+                      //   1.e-10 * Mb(i, j);
+                    }
+                    if (primal_face_is_symmetry[face_id]) {
+                      S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) +=
+                        ((i == j) ? -coef_adim : 0) + coef_adim * N(i, j);
+                      S(face_dof_number[face_id] * Dimension + i, face_dof_number[face_id] * Dimension + j) +=
+                        (i == j) ? coef_adim : 0;
+                    }
+                  }
+                }
+              } else {
+                for (size_t i = 0; i < Dimension; ++i) {
+                  for (size_t j = 0; j < Dimension; ++j) {
+                    S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) -= M(i, j);
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+          for (size_t i = 0; i < Dimension; ++i) {
+            const size_t j = cell_dof_number[cell_id] * Dimension + i;
+            S(j, j) += (*alpha)[cell_id] * primal_Vj[cell_id];
+          }
+        }
+
+        const auto& dual_cell_to_node_matrix   = diamond_mesh->connectivity().cellToNodeMatrix();
+        const auto& primal_node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          const double alpha_mu_face_id      = mes_l[face_id] * alpha_mu_l[face_id];
+          const double alpha_lambda_face_id  = mes_l[face_id] * alpha_lambda_l[face_id];
+          const double alpha_mub_face_id     = mes_l[face_id] * alpha_mub_l[face_id];
+          const double alpha_lambdab_face_id = mes_l[face_id] * alpha_lambdab_l[face_id];
+
+          for (size_t i_face_cell = 0; i_face_cell < face_to_cell_matrix[face_id].size(); ++i_face_cell) {
+            CellId i_id                            = face_to_cell_matrix[face_id][i_face_cell];
+            const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+            for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+              NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+
+              const TinyVector<Dimension> nil = [&] {
+                if (is_face_reversed_for_cell_i) {
+                  return -nlj[face_id];
+                } else {
+                  return nlj[face_id];
+                }
+              }();
+
+              CellId dual_cell_id = face_dual_cell_id[face_id];
+
+              for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size(); ++i_dual_node) {
+                const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                  const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+
+                  TinyMatrix<Dimension> M = alpha_mu_face_id * dot(Clr, nil) * I +
+                                            alpha_mu_face_id * tensorProduct(Clr, nil) +
+                                            alpha_lambda_face_id * tensorProduct(nil, Clr);
+                  TinyMatrix<Dimension> Mb = alpha_mub_face_id * dot(Clr, nil) * I +
+                                             alpha_mub_face_id * tensorProduct(Clr, nil) +
+                                             alpha_lambdab_face_id * tensorProduct(nil, Clr);
+
+                  for (size_t j_cell = 0; j_cell < primal_node_to_cell_matrix[node_id].size(); ++j_cell) {
+                    CellId j_id = primal_node_to_cell_matrix[node_id][j_cell];
+                    for (size_t i = 0; i < Dimension; ++i) {
+                      for (size_t j = 0; j < Dimension; ++j) {
+                        S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) -=
+                          w_rj(node_id, j_cell) * M(i, j);
+                        if (primal_face_is_neumann[face_id]) {
+                          S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) +=
+                            1.e0 * w_rj(node_id, j_cell) * Mb(i, j);
+                        }
+                      }
+                    }
+                  }
+                  if (primal_node_is_on_boundary[node_id]) {
+                    for (size_t l_face = 0; l_face < node_to_face_matrix[node_id].size(); ++l_face) {
+                      FaceId l_id = node_to_face_matrix[node_id][l_face];
+                      if (primal_face_is_on_boundary[l_id]) {
+                        for (size_t i = 0; i < Dimension; ++i) {
+                          for (size_t j = 0; j < Dimension; ++j) {
+                            // Mb?
+                            S(cell_dof_number[i_id] * Dimension + i, face_dof_number[l_id] * Dimension + j) -=
+                              w_rl(node_id, l_face) * M(i, j);
+                          }
+                        }
+                        if (primal_face_is_neumann[face_id]) {
+                          for (size_t i = 0; i < Dimension; ++i) {
+                            for (size_t j = 0; j < Dimension; ++j) {
+                              S(face_dof_number[face_id] * Dimension + i, face_dof_number[l_id] * Dimension + j) +=
+                                1.e0 * w_rl(node_id, l_face) * Mb(i, j);
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+              //            }
+            }
+          }
+        }
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          if (primal_face_is_dirichlet[face_id]) {
+            for (size_t i = 0; i < Dimension; ++i) {
+              S(face_dof_number[face_id] * Dimension + i, face_dof_number[face_id] * Dimension + i) += 1.e0;
+            }
+          }
+        }
+
+        Vector<double> b{number_of_dof * Dimension};
+        b = zero;
+        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+          for (size_t i = 0; i < Dimension; ++i) {
+            b[(cell_dof_number[cell_id] * Dimension) + i] = primal_Vj[cell_id] * fj[cell_id][i];
+          }
+        }
+
+        // Dirichlet
+        NodeValue<bool> node_tag{mesh->connectivity()};
+        node_tag.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
+                const auto& face_list  = bc.faceList();
+                const auto& value_list = bc.valueList();
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id = face_list[i_face];
+
+                  for (size_t i = 0; i < Dimension; ++i) {
+                    b[(face_dof_number[face_id] * Dimension) + i] += 1.e0 * value_list[i_face][i];
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+                const auto& face_list  = bc.faceList();
+                const auto& value_list = bc.valueList();
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  FaceId face_id = face_list[i_face];
+                  for (size_t i = 0; i < Dimension; ++i) {
+                    b[face_dof_number[face_id] * Dimension + i] +=
+                      1.e0 * mes_l[face_id] * value_list[i_face][i];   // sign
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        CRSMatrix A{S.getCRSMatrix()};
+        Vector<double> U{number_of_dof * Dimension};
+        U        = zero;
+        Vector r = A * U - b;
+        std::cout << "initial (real) residu = " << std::sqrt(dot(r, r)) << '\n';
+
+        LinearSolver solver;
+        solver.solveLocalSystem(A, U, b);
+
+        r = A * U - b;
+
+        std::cout << "final (real) residu = " << std::sqrt(dot(r, r)) << '\n';
+
+        m_solution     = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>(mesh);
+        auto& solution = *m_solution;
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+            for (size_t i = 0; i < Dimension; ++i) {
+              solution[cell_id][i] = U[(cell_dof_number[cell_id] * Dimension) + i];
+            }
+          });
+
+        m_dual_solution     = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>(diamond_mesh);
+        auto& dual_solution = *m_dual_solution;
+        dual_solution.fill(zero);
+        const auto& face_to_cell_matrix = mesh->connectivity().faceToCellMatrix();
+        for (CellId cell_id = 0; cell_id < diamond_mesh->numberOfCells(); ++cell_id) {
+          const FaceId face_id = dual_cell_face_id[cell_id];
+          if (primal_face_is_on_boundary[face_id]) {
+            for (size_t i = 0; i < Dimension; ++i) {
+              dual_solution[cell_id][i] = U[(face_dof_number[face_id] * Dimension) + i];
+            }
+          } else {
+            CellId cell_id1 = face_to_cell_matrix[face_id][0];
+            CellId cell_id2 = face_to_cell_matrix[face_id][1];
+            for (size_t i = 0; i < Dimension; ++i) {
+              dual_solution[cell_id][i] =
+                0.5 * (U[(cell_dof_number[cell_id1] * Dimension) + i] + U[(cell_dof_number[cell_id2] * Dimension) + i]);
+            }
+          }
+        }
+      }
+      // provide a source for E?
+      // computeEnergyUpdate(mesh, dual_lambdab, dual_mub, m_solution,
+      //                     m_dual_solution,   // f,
+      //                     bc_descriptor_list);
+      // computeEnergyUpdate(mesh, alpha, dual_lambdab, dual_mub, dual_lambda, dual_mu, m_solution,
+      //                     m_dual_solution,   // f,
+      //                     bc_descriptor_list);
+    } else {
+      throw NotImplementedError("not done in 1d");
+    }
+  }
+};
+// NEW CLASS
+template <size_t Dimension>
+class EnergyComputerHandler::InterpolationWeightsManager
+{
+ private:
+  std::shared_ptr<const Mesh<Connectivity<Dimension>>> m_mesh;
+  FaceValue<const bool> m_primal_face_is_on_boundary;
+  NodeValue<const bool> m_primal_node_is_on_boundary;
+  FaceValue<const bool> m_primal_face_is_symmetry;
+  CellValuePerNode<double> m_w_rj;
+  FaceValuePerNode<double> m_w_rl;
+
+ public:
+  InterpolationWeightsManager(std::shared_ptr<const Mesh<Connectivity<Dimension>>> mesh,
+                              FaceValue<const bool> primal_face_is_on_boundary,
+                              NodeValue<const bool> primal_node_is_on_boundary,
+                              FaceValue<const bool> primal_face_is_symmetry)
+    : m_mesh(mesh),
+      m_primal_face_is_on_boundary(primal_face_is_on_boundary),
+      m_primal_node_is_on_boundary(primal_node_is_on_boundary),
+      m_primal_face_is_symmetry(primal_face_is_symmetry)
+  {}
+  ~InterpolationWeightsManager() = default;
+  CellValuePerNode<double>&
+  wrj()
+  {
+    return m_w_rj;
+  }
+  FaceValuePerNode<double>&
+  wrl()
+  {
+    return m_w_rl;
+  }
+  void
+  compute()
+  {
+    using MeshDataType      = MeshData<Dimension>;
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*m_mesh);
+
+    const NodeValue<const TinyVector<Dimension>>& xr = m_mesh->xr();
+
+    const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+    const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+    const auto& node_to_cell_matrix                  = m_mesh->connectivity().nodeToCellMatrix();
+    const auto& node_to_face_matrix                  = m_mesh->connectivity().nodeToFaceMatrix();
+    const auto& face_to_cell_matrix                  = m_mesh->connectivity().faceToCellMatrix();
+
+    CellValuePerNode<double> w_rj{m_mesh->connectivity()};
+    FaceValuePerNode<double> w_rl{m_mesh->connectivity()};
+
+    const NodeValuePerFace<const TinyVector<Dimension>> primal_nlr = mesh_data.nlr();
+    auto project_to_face = [&](const TinyVector<Dimension>& x, const FaceId face_id) -> const TinyVector<Dimension> {
+      TinyVector<Dimension> proj;
+      const TinyVector<Dimension> nil = primal_nlr(face_id, 0);
+      proj                            = x - dot((x - xl[face_id]), nil) * nil;
+      return proj;
+    };
+
+    for (size_t i = 0; i < w_rl.numberOfValues(); ++i) {
+      w_rl[i] = std::numeric_limits<double>::signaling_NaN();
+    }
+
+    for (NodeId i_node = 0; i_node < m_mesh->numberOfNodes(); ++i_node) {
+      SmallVector<double> b{Dimension + 1};
+      b[0] = 1;
+      for (size_t i = 1; i < Dimension + 1; i++) {
+        b[i] = xr[i_node][i - 1];
+      }
+      const auto& node_to_cell = node_to_cell_matrix[i_node];
+
+      if (not m_primal_node_is_on_boundary[i_node]) {
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size()};
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size()};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+      } else {
+        int nb_face_used = 0;
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            nb_face_used++;
+          }
+        }
+        SmallMatrix<double> A{Dimension + 1, node_to_cell.size() + nb_face_used};
+        for (size_t j = 0; j < node_to_cell.size() + nb_face_used; j++) {
+          A(0, j) = 1;
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          for (size_t j = 0; j < node_to_cell.size(); j++) {
+            const CellId J = node_to_cell[j];
+            A(i, j)        = xj[J][i - 1];
+          }
+        }
+        for (size_t i = 1; i < Dimension + 1; i++) {
+          int cpt_face = 0;
+          for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+            FaceId face_id = node_to_face_matrix[i_node][i_face];
+            if (m_primal_face_is_on_boundary[face_id]) {
+              if (m_primal_face_is_symmetry[face_id]) {
+                for (size_t j = 0; j < face_to_cell_matrix[face_id].size(); ++j) {
+                  const CellId cell_id                 = face_to_cell_matrix[face_id][j];
+                  TinyVector<Dimension> xproj          = project_to_face(xj[cell_id], face_id);
+                  A(i, node_to_cell.size() + cpt_face) = xproj[i - 1];
+                }
+              } else {
+                A(i, node_to_cell.size() + cpt_face) = xl[face_id][i - 1];
+              }
+              cpt_face++;
+            }
+          }
+        }
+
+        SmallVector<double> x{node_to_cell.size() + nb_face_used};
+        x = zero;
+
+        LeastSquareSolver ls_solver;
+        ls_solver.solveLocalSystem(A, x, b);
+
+        for (size_t j = 0; j < node_to_cell.size(); j++) {
+          w_rj(i_node, j) = x[j];
+        }
+        int cpt_face = node_to_cell.size();
+        for (size_t i_face = 0; i_face < node_to_face_matrix[i_node].size(); ++i_face) {
+          FaceId face_id = node_to_face_matrix[i_node][i_face];
+          if (m_primal_face_is_on_boundary[face_id]) {
+            w_rl(i_node, i_face) = x[cpt_face++];
+          }
+        }
+      }
+    }
+    m_w_rj = w_rj;
+    m_w_rl = w_rl;
+  }
+};
+class EnergyComputerHandler::IEnergyComputer
+{
+ public:
+  // virtual std::shared_ptr<const IDiscreteFunction> getSolution() const     = 0;
+  // virtual std::shared_ptr<const IDiscreteFunction> getDualSolution() const = 0;
+  // virtual std::shared_ptr<const IDiscreteFunction> apply() const           = 0;
+  virtual std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> apply()
+    const = 0;
+
+  IEnergyComputer()          = default;
+  virtual ~IEnergyComputer() = default;
+};
+
+template <size_t Dimension>
+class EnergyComputerHandler::EnergyComputer : public EnergyComputerHandler::IEnergyComputer
+{
+ private:
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+  using MeshDataType     = MeshData<Dimension>;
+
+  std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>> m_solution;
+  std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>> m_dual_solution;
+  std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_energy_delta;
+
+  class DirichletBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const TinyVector<Dimension>>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    DirichletBoundaryCondition(const Array<const FaceId>& face_list,
+                               const Array<const TinyVector<Dimension>>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~DirichletBoundaryCondition() = default;
+  };
+
+  class NormalStrainBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const TinyVector<Dimension>>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    NormalStrainBoundaryCondition(const Array<const FaceId>& face_list,
+                                  const Array<const TinyVector<Dimension>>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~NormalStrainBoundaryCondition() = default;
+  };
+
+  class SymmetryBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+   public:
+    SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+    ~SymmetryBoundaryCondition() = default;
+  };
+
+ public:
+  std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+  apply() const final
+  {
+    return {m_energy_delta, m_dual_solution};
+  }
+
+  // std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+  // computeEnergyUpdate() const final
+  // {
+  //   m_scheme->computeEnergyUpdate(mesh, dual_lambdab, dual_mub, m_solution,
+  //                                 m_dual_solution,   // f,
+  //                                 bc_descriptor_list);
+
+  //   return {m_dual_solution, m_energy_delta};
+  // }
+
+  // compute the fluxes
+  // std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>
+  EnergyComputer(const std::shared_ptr<const MeshType>& mesh,
+                 // const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& alpha,
+                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_lambdab,
+                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_mub,
+                 // const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& dual_lambda,
+                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>& U,
+                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>& dual_U,
+                 const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>& source,
+                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+  {
+    // Assert(mesh == alpha->mesh());
+    Assert(mesh == U->mesh());
+    // Assert(dual_lambda->mesh() == dual_mu->mesh());
+    Assert(dual_lambdab->mesh() == dual_U->mesh());
+    Assert(U->mesh() == source->mesh());
+    Assert(dual_lambdab->mesh() == dual_mub->mesh());
+    Assert(DualMeshManager::instance().getDiamondDualMesh(*mesh) == dual_mub->mesh(),
+           "diffusion coefficient is not defined on the dual mesh!");
+
+    using MeshDataType = MeshData<Dimension>;
+
+    using BoundaryCondition =
+      std::variant<DirichletBoundaryCondition, NormalStrainBoundaryCondition, SymmetryBoundaryCondition>;
+
+    using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+    BoundaryConditionList boundary_condition_list;
+
+    NodeValue<bool> is_dirichlet{mesh->connectivity()};
+    is_dirichlet.fill(false);
+    NodeValue<TinyVector<Dimension>> dirichlet_value{mesh->connectivity()};
+    {
+      TinyVector<Dimension> nan_tiny_vector;
+      for (size_t i = 0; i < Dimension; ++i) {
+        nan_tiny_vector[i] = std::numeric_limits<double>::signaling_NaN();
+      }
+      dirichlet_value.fill(nan_tiny_vector);
+    }
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor =
+          dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+
+        if constexpr (Dimension > 1) {
+          MeshFlatFaceBoundary<Dimension> mesh_face_boundary =
+            getMeshFlatFaceBoundary(*mesh, sym_bc_descriptor.boundaryDescriptor());
+          boundary_condition_list.push_back(SymmetryBoundaryCondition{mesh_face_boundary.faceList()});
+        } else {
+          throw NotImplementedError("Symmetry conditions are not supported in 1d");
+        }
+
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::dirichlet: {
+        const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+          dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+        if (dirichlet_bc_descriptor.name() == "dirichlet") {
+          if constexpr (Dimension > 1) {
+            MeshFaceBoundary<Dimension> mesh_face_boundary =
+              getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+            const FunctionSymbolId g_id                   = dirichlet_bc_descriptor.rhsSymbolId();
+            Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+              TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                            mesh_face_boundary.faceList());
+            boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+          } else {
+            throw NotImplementedError("Neumann conditions are not supported in 1d");
+          }
+        } else if (dirichlet_bc_descriptor.name() == "normal_strain") {
+          if constexpr (Dimension > 1) {
+            MeshFaceBoundary<Dimension> mesh_face_boundary =
+              getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+            const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+            Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+              TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                            mesh_face_boundary.faceList());
+            boundary_condition_list.push_back(NormalStrainBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+
+          } else {
+            throw NotImplementedError("Normal strain conditions are not supported in 1d");
+          }
+        } else {
+          is_valid_boundary_condition = false;
+        }
+        break;
+      }
+      default: {
+        is_valid_boundary_condition = false;
+      }
+      }
+      if (not is_valid_boundary_condition) {
+        std::ostringstream error_msg;
+        error_msg << *bc_descriptor << " is an invalid boundary condition for elasticity equation";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    if constexpr (Dimension > 1) {
+      const CellValue<const size_t> cell_dof_number = [&] {
+        CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+        return compute_cell_dof_number;
+      }();
+      size_t number_of_dof = mesh->numberOfCells();
+
+      const FaceValue<const size_t> face_dof_number = [&] {
+        FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+        compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>) or
+                            (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                            (std::is_same_v<T, DirichletBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id = face_list[i_face];
+                  if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                    std::ostringstream os;
+                    os << "The face " << face_id << " is used at least twice for boundary conditions";
+                    throw NormalError(os.str());
+                  } else {
+                    compute_face_dof_number[face_id] = number_of_dof++;
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return compute_face_dof_number;
+      }();
+
+      const auto& primal_face_to_node_matrix             = mesh->connectivity().faceToNodeMatrix();
+      const auto& face_to_cell_matrix                    = mesh->connectivity().faceToCellMatrix();
+      const FaceValue<const bool> primal_face_is_neumann = [&] {
+        FaceValue<bool> face_is_neumann{mesh->connectivity()};
+        face_is_neumann.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id     = face_list[i_face];
+                  face_is_neumann[face_id] = true;
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return face_is_neumann;
+      }();
+
+      const FaceValue<const bool> primal_face_is_symmetry = [&] {
+        FaceValue<bool> face_is_symmetry{mesh->connectivity()};
+        face_is_symmetry.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, SymmetryBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id      = face_list[i_face];
+                  face_is_symmetry[face_id] = true;
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return face_is_symmetry;
+      }();
+
+      NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+      }
+
+      primal_node_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+            NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+            primal_node_is_on_boundary[node_id] = true;
+          }
+        }
+      }
+
+      FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+      }
+
+      primal_face_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          primal_face_is_on_boundary[face_id] = true;
+        }
+      }
+
+      FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+      }
+
+      primal_face_is_dirichlet.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] &&
+                                             (!primal_face_is_neumann[face_id]) && (!primal_face_is_symmetry[face_id]));
+      }
+
+      InterpolationWeightsManager iwm(mesh, primal_face_is_on_boundary, primal_node_is_on_boundary,
+                                      primal_face_is_symmetry);
+      iwm.compute();
+      CellValuePerNode<double> w_rj = iwm.wrj();
+      FaceValuePerNode<double> w_rl = iwm.wrl();
+
+      MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+      const FaceValue<const TinyVector<Dimension>>& xl = mesh_data.xl();
+      const CellValue<const TinyVector<Dimension>>& xj = mesh_data.xj();
+      // const auto& node_to_cell_matrix                                = mesh->connectivity().nodeToCellMatrix();
+      const auto& node_to_face_matrix                                = mesh->connectivity().nodeToFaceMatrix();
+      const NodeValuePerFace<const TinyVector<Dimension>> primal_nlr = mesh_data.nlr();
+
+      {
+        std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+        MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+        std::shared_ptr mapper =
+          DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+        CellValue<const double> dual_mubj     = dual_mub->cellValues();
+        CellValue<const double> dual_lambdabj = dual_lambdab->cellValues();
+        // attention, fj not in this context
+        CellValue<const TinyVector<Dimension>> velocity = U->cellValues();
+        // CellValue<const TinyVector<Dimension>> dual_velocity = dual_U->cellValues();
+
+        const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+        const FaceValue<const double> mes_l = [&] {
+          if constexpr (Dimension == 1) {
+            FaceValue<double> compute_mes_l{mesh->connectivity()};
+            compute_mes_l.fill(1);
+            return compute_mes_l;
+          } else {
+            return mesh_data.ll();
+          }
+        }();
+
+        const CellValue<const double> dual_mes_l_j = [=] {
+          CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+          mapper->toDualCell(mes_l, compute_mes_j);
+
+          return compute_mes_j;
+        }();
+
+        const CellValue<const double> primal_Vj   = mesh_data.Vj();
+        FaceValue<const CellId> face_dual_cell_id = [=]() {
+          FaceValue<CellId> computed_face_dual_cell_id{mesh->connectivity()};
+          CellValue<CellId> dual_cell_id{diamond_mesh->connectivity()};
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { dual_cell_id[cell_id] = cell_id; });
+
+          mapper->fromDualCell(dual_cell_id, computed_face_dual_cell_id);
+
+          return computed_face_dual_cell_id;
+        }();
+
+        NodeValue<const NodeId> dual_node_primal_node_id = [=]() {
+          CellValue<NodeId> cell_ignored_id{mesh->connectivity()};
+          cell_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> node_primal_id{mesh->connectivity()};
+
+          parallel_for(
+            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { node_primal_id[node_id] = node_id; });
+
+          NodeValue<NodeId> computed_dual_node_primal_node_id{diamond_mesh->connectivity()};
+
+          mapper->toDualNode(node_primal_id, cell_ignored_id, computed_dual_node_primal_node_id);
+
+          return computed_dual_node_primal_node_id;
+        }();
+
+        CellValue<NodeId> primal_cell_dual_node_id = [=]() {
+          CellValue<NodeId> cell_id{mesh->connectivity()};
+          NodeValue<NodeId> node_ignored_id{mesh->connectivity()};
+          node_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> dual_node_id{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { dual_node_id[node_id] = node_id; });
+
+          CellValue<NodeId> computed_primal_cell_dual_node_id{mesh->connectivity()};
+
+          mapper->fromDualNode(dual_node_id, node_ignored_id, cell_id);
+
+          return cell_id;
+        }();
+        const auto& dual_Cjr                     = diamond_mesh_data.Cjr();
+        FaceValue<TinyVector<Dimension>> dualClj = [&] {
+          FaceValue<TinyVector<Dimension>> computedClj{mesh->connectivity()};
+          const auto& dual_node_to_cell_matrix = diamond_mesh->connectivity().nodeToCellMatrix();
+          const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+          parallel_for(
+            mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+              const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+              for (size_t i = 0; i < primal_face_to_cell.size(); i++) {
+                CellId cell_id            = primal_face_to_cell[i];
+                const NodeId dual_node_id = primal_cell_dual_node_id[cell_id];
+                for (size_t i_dual_cell = 0; i_dual_cell < dual_node_to_cell_matrix[dual_node_id].size();
+                     i_dual_cell++) {
+                  const CellId dual_cell_id = dual_node_to_cell_matrix[dual_node_id][i_dual_cell];
+                  if (face_dual_cell_id[face_id] == dual_cell_id) {
+                    for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                         i_dual_node++) {
+                      const NodeId final_dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                      if (final_dual_node_id == dual_node_id) {
+                        computedClj[face_id] = dual_Cjr(dual_cell_id, i_dual_node);
+                      }
+                    }
+                  }
+                }
+              }
+            });
+          return computedClj;
+        }();
+
+        FaceValue<TinyVector<Dimension>> nlj = [&] {
+          FaceValue<TinyVector<Dimension>> computedNlj{mesh->connectivity()};
+          parallel_for(
+            mesh->numberOfFaces(),
+            PUGS_LAMBDA(FaceId face_id) { computedNlj[face_id] = 1. / l2Norm(dualClj[face_id]) * dualClj[face_id]; });
+          return computedNlj;
+        }();
+
+        // FaceValue<const double> alpha_lambda_l = [&] {
+        //   CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+        //   parallel_for(
+        //     diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+        //       alpha_j[diamond_cell_id] = dual_lambdaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+        //     });
+
+        //   FaceValue<double> computed_alpha_l{mesh->connectivity()};
+        //   mapper->fromDualCell(alpha_j, computed_alpha_l);
+        //   return computed_alpha_l;
+        // }();
+
+        // FaceValue<const double> alpha_mu_l = [&] {
+        //   CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+        //   parallel_for(
+        //     diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+        //       alpha_j[diamond_cell_id] = dual_muj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+        //     });
+
+        //   FaceValue<double> computed_alpha_l{mesh->connectivity()};
+        //   mapper->fromDualCell(alpha_j, computed_alpha_l);
+        //   return computed_alpha_l;
+        // }();
+
+        FaceValue<const double> alpha_lambdab_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_lambdabj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        FaceValue<const double> alpha_mub_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_mubj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        const TinyMatrix<Dimension> I             = identity;
+        CellValue<const FaceId> dual_cell_face_id = [=]() {
+          CellValue<FaceId> computed_dual_cell_face_id{diamond_mesh->connectivity()};
+          FaceValue<FaceId> primal_face_id{mesh->connectivity()};
+          parallel_for(
+            mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) { primal_face_id[face_id] = face_id; });
+
+          mapper->toDualCell(primal_face_id, computed_dual_cell_face_id);
+
+          return computed_dual_cell_face_id;
+        }();
+
+        m_dual_solution = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>(diamond_mesh);
+        // m_solution           = std::make_shared<DiscreteFunctionP0<Dimension, TinyVector<Dimension>>>(mesh);
+        // const auto& solution = *U;
+        auto& dual_solution = *dual_U;
+        //  dual_solution.fill(zero);
+        // const auto& face_to_cell_matrix = mesh->connectivity().faceToCellMatrix();
+        // for (CellId cell_id = 0; cell_id < diamond_mesh->numberOfCells(); ++cell_id) {
+        //   const FaceId face_id = dual_cell_face_id[cell_id];
+        //   CellId cell_id1      = face_to_cell_matrix[face_id][0];
+        //   if (primal_face_is_on_boundary[face_id]) {
+        //     for (size_t i = 0; i < Dimension; ++i) {
+        //       // A revoir!!
+        //       dual_solution[cell_id][i] = solution[cell_id1][i];
+        //     }
+        //   } else {
+        //     CellId cell_id1 = face_to_cell_matrix[face_id][0];
+        //     CellId cell_id2 = face_to_cell_matrix[face_id][1];
+        //     for (size_t i = 0; i < Dimension; ++i) {
+        //       dual_solution[cell_id][i] = 0.5 * (solution[cell_id1][i] + solution[cell_id2][i]);
+        //     }
+        //   }
+        // }
+
+        // const Array<int> non_zeros{number_of_dof * Dimension};
+        // non_zeros.fill(Dimension * Dimension);
+        // CRSMatrixDescriptor<double> S(number_of_dof * Dimension, number_of_dof * Dimension, non_zeros);
+        // Begining of main
+        CellValuePerFace<double> flux{mesh->connectivity()};
+        parallel_for(
+          flux.numberOfValues(), PUGS_LAMBDA(size_t jl) { flux[jl] = 0; });
+
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          // const double beta_mu_l     = l2Norm(dualClj[face_id]) * alpha_mu_l[face_id] * mes_l[face_id];
+          // const double beta_lambda_l = l2Norm(dualClj[face_id]) * alpha_lambda_l[face_id] * mes_l[face_id];
+          const double beta_mub_l         = l2Norm(dualClj[face_id]) * alpha_mub_l[face_id] * mes_l[face_id];
+          const double beta_lambdab_l     = l2Norm(dualClj[face_id]) * alpha_lambdab_l[face_id] * mes_l[face_id];
+          const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+          for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+            const CellId i_id                      = primal_face_to_cell[i_cell];
+            const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+            const TinyVector<Dimension> nil = [&] {
+              if (is_face_reversed_for_cell_i) {
+                return -nlj[face_id];
+              } else {
+                return nlj[face_id];
+              }
+            }();
+            TinyMatrix<Dimension> M =
+              beta_mub_l * I + beta_mub_l * tensorProduct(nil, nil) + beta_lambdab_l * tensorProduct(nil, nil);
+            //            TinyMatrix<Dimension, double> Mb =
+            //  beta_mub_l * I + beta_mub_l * tensorProduct(nil, nil) + beta_lambdab_l * tensorProduct(nil, nil);
+            // TinyMatrix<Dimension, double> N = 1.e0 * tensorProduct(nil, nil);
+
+            for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+              const CellId j_id = primal_face_to_cell[j_cell];
+              if (i_cell == j_cell) {
+                flux(face_id, i_cell) += dot(M * velocity[i_id], dual_solution[face_dual_cell_id[face_id]]);
+              } else {
+                flux(face_id, i_cell) -= dot(M * velocity[j_id], dual_solution[face_dual_cell_id[face_id]]);
+              }
+            }
+          }
+        }
+
+        const auto& dual_cell_to_node_matrix   = diamond_mesh->connectivity().cellToNodeMatrix();
+        const auto& primal_node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          // const double alpha_mu_face_id     = mes_l[face_id] * alpha_mu_l[face_id];
+          // const double alpha_lambda_face_id = mes_l[face_id] * alpha_lambda_l[face_id];
+          const double alpha_mub_face_id     = mes_l[face_id] * alpha_mub_l[face_id];
+          const double alpha_lambdab_face_id = mes_l[face_id] * alpha_lambdab_l[face_id];
+
+          for (size_t i_face_cell = 0; i_face_cell < face_to_cell_matrix[face_id].size(); ++i_face_cell) {
+            CellId i_id                            = face_to_cell_matrix[face_id][i_face_cell];
+            const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+            for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+              NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+
+              const TinyVector<Dimension> nil = [&] {
+                if (is_face_reversed_for_cell_i) {
+                  return -nlj[face_id];
+                } else {
+                  return nlj[face_id];
+                }
+              }();
+
+              CellId dual_cell_id = face_dual_cell_id[face_id];
+
+              for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size(); ++i_dual_node) {
+                const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                  const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+
+                  TinyMatrix<Dimension> M = alpha_mub_face_id * dot(Clr, nil) * I +
+                                            alpha_mub_face_id * tensorProduct(Clr, nil) +
+                                            alpha_lambdab_face_id * tensorProduct(nil, Clr);
+                  // TinyMatrix<Dimension, double> Mb = alpha_mub_face_id * dot(Clr, nil) * I +
+                  //                                    alpha_mub_face_id * tensorProduct(Clr, nil) +
+                  //                                    alpha_lambdab_face_id * tensorProduct(nil, Clr);
+
+                  for (size_t j_cell = 0; j_cell < primal_node_to_cell_matrix[node_id].size(); ++j_cell) {
+                    CellId j_id = primal_node_to_cell_matrix[node_id][j_cell];
+                    flux(face_id, i_face_cell) -=
+                      w_rj(node_id, j_cell) * dot(M * velocity[j_id], dual_solution[dual_cell_id]);
+                  }
+                  if (primal_node_is_on_boundary[node_id]) {
+                    for (size_t l_face = 0; l_face < node_to_face_matrix[node_id].size(); ++l_face) {
+                      FaceId l_id = node_to_face_matrix[node_id][l_face];
+                      if (primal_face_is_on_boundary[l_id]) {
+                        flux(face_id, i_face_cell) -=
+                          w_rl(node_id, l_face) *
+                          dot(M * dual_solution[face_dual_cell_id[l_id]], dual_solution[dual_cell_id]);
+                      }
+                    }
+                  }
+                }
+              }
+              //            }
+            }
+          }
+        }
+        // for (const auto& boundary_condition : boundary_condition_list) {
+        //   std::visit(
+        //     [&](auto&& bc) {
+        //       using T = std::decay_t<decltype(bc)>;
+        //       if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+        //         const auto& face_list  = bc.faceList();
+        //         const auto& value_list = bc.valueList();
+        //         for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+        //           FaceId face_id      = face_list[i_face];
+        //           CellId dual_cell_id = face_dual_cell_id[face_id];
+        //           flux(face_id, 0)    = mes_l[face_id] * dot(value_list[i_face], dual_solution[dual_cell_id]);   //
+        //           sign
+        //         }
+        //       }
+        //     },
+        //     boundary_condition);
+        // }
+        // for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        //   if (face_to_cell_matrix[face_id].size() == 2) {
+        //     CellId i_id = face_to_cell_matrix[face_id][0];
+        //     CellId j_id = face_to_cell_matrix[face_id][1];
+        //     if (flux(face_id, 0) != -flux(face_id, 1)) {
+        //       std::cout << "flux(" << i_id << "," << face_id << ")=" << flux(face_id, 0) << " not equal to -flux("
+        //                 << j_id << "," << face_id << ")=" << -flux(face_id, 1) << "\n";
+        //     }
+        //   }
+        //   // exit(0);
+        // }
+        // Assemble
+        m_energy_delta     = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
+        auto& energy_delta = *m_energy_delta;
+        // CellValue<const TinyVector<Dimension>> fj = source->cellValues();
+
+        double sum_deltae = 0.;
+        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+          energy_delta[cell_id] = 0.;   // dot(fj[cell_id], velocity[cell_id]);
+          sum_deltae += energy_delta[cell_id];
+        }
+        // CellValue<double>& deltae = m_energy_delta->cellValues();
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          for (size_t j = 0; j < face_to_cell_matrix[face_id].size(); j++) {
+            CellId i_id = face_to_cell_matrix[face_id][j];
+            energy_delta[i_id] -= flux(face_id, j) / primal_Vj[i_id];
+            sum_deltae -= flux(face_id, j);
+          }
+          // exit(0);
+        }
+
+        std::cout << "sum deltaej " << sum_deltae << "\n";
+      }
+    } else {
+      throw NotImplementedError("not done in 1d");
+    }
+    //    return m_energy_delta;
+  }
+};
+
+std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+VectorDiamondSchemeHandler::apply() const
+{
+  return m_scheme->apply();
+}
+
+std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
+EnergyComputerHandler::computeEnergyUpdate() const
+{
+  return m_energy_computer->apply();
+}
+
+std::shared_ptr<const IDiscreteFunction>
+VectorDiamondSchemeHandler::solution() const
+{
+  return m_scheme->getSolution();
+}
+
+std::shared_ptr<const IDiscreteFunction>
+VectorDiamondSchemeHandler::dual_solution() const
+{
+  return m_scheme->getDualSolution();
+}
+
+VectorDiamondSchemeHandler::VectorDiamondSchemeHandler(
+  const std::shared_ptr<const IDiscreteFunction>& alpha,
+  const std::shared_ptr<const IDiscreteFunction>& dual_lambdab,
+  const std::shared_ptr<const IDiscreteFunction>& dual_mub,
+  const std::shared_ptr<const IDiscreteFunction>& dual_lambda,
+  const std::shared_ptr<const IDiscreteFunction>& dual_mu,
+  const std::shared_ptr<const IDiscreteFunction>& f,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+{
+  const std::shared_ptr i_mesh = getCommonMesh({alpha, f});
+  if (not i_mesh) {
+    throw NormalError("primal discrete functions are not defined on the same mesh");
+  }
+  const std::shared_ptr i_dual_mesh = getCommonMesh({dual_lambda, dual_lambdab, dual_mu, dual_mub});
+  if (not i_dual_mesh) {
+    throw NormalError("dual discrete functions are not defined on the same mesh");
+  }
+  checkDiscretizationType({alpha, dual_lambdab, dual_mub, dual_lambda, dual_mu, f}, DiscreteFunctionType::P0);
+
+  switch (i_mesh->dimension()) {
+  case 1: {
+    using MeshType                   = Mesh<Connectivity<1>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<1, TinyVector<1>>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    m_scheme =
+      std::make_unique<VectorDiamondScheme<1>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(
+                                                 dual_lambdab),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambda),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
+                                               std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(f),
+                                               bc_descriptor_list);
+    break;
+  }
+  case 2: {
+    using MeshType                   = Mesh<Connectivity<2>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<2, TinyVector<2>>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    if (DualMeshManager::instance().getDiamondDualMesh(*mesh) != i_dual_mesh) {
+      throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
+    }
+
+    m_scheme =
+      std::make_unique<VectorDiamondScheme<2>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(
+                                                 dual_lambdab),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambda),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
+                                               std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(f),
+                                               bc_descriptor_list);
+    break;
+  }
+  case 3: {
+    using MeshType                   = Mesh<Connectivity<3>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<3, TinyVector<3>>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    if (DualMeshManager::instance().getDiamondDualMesh(*mesh) != i_dual_mesh) {
+      throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
+    }
+
+    m_scheme =
+      std::make_unique<VectorDiamondScheme<3>>(mesh, std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(alpha),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(
+                                                 dual_lambdab),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambda),
+                                               std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mu),
+                                               std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(f),
+                                               bc_descriptor_list);
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
+VectorDiamondSchemeHandler::~VectorDiamondSchemeHandler() = default;
+
+EnergyComputerHandler::EnergyComputerHandler(
+  const std::shared_ptr<const IDiscreteFunction>& dual_lambdab,
+  const std::shared_ptr<const IDiscreteFunction>& dual_mub,
+  const std::shared_ptr<const IDiscreteFunction>& U,
+  const std::shared_ptr<const IDiscreteFunction>& dual_U,
+  const std::shared_ptr<const IDiscreteFunction>& source,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list)
+{
+  const std::shared_ptr i_mesh = getCommonMesh({U, source});
+  if (not i_mesh) {
+    throw NormalError("primal discrete functions are not defined on the same mesh");
+  }
+  const std::shared_ptr i_dual_mesh = getCommonMesh({dual_lambdab, dual_mub, dual_U});
+  if (not i_dual_mesh) {
+    throw NormalError("dual discrete functions are not defined on the same mesh");
+  }
+  checkDiscretizationType({dual_lambdab, dual_mub, dual_U, U, source}, DiscreteFunctionType::P0);
+
+  switch (i_mesh->dimension()) {
+  case 1: {
+    using MeshType                   = Mesh<Connectivity<1>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<1, double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<1, TinyVector<1>>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    if (DualMeshManager::instance().getDiamondDualMesh(*mesh) != i_dual_mesh) {
+      throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
+    }
+
+    m_energy_computer =
+      std::make_unique<EnergyComputer<1>>(mesh,
+                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambdab),
+                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(U),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(dual_U),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(source),
+                                          bc_descriptor_list);
+    break;
+  }
+  case 2: {
+    using MeshType                   = Mesh<Connectivity<2>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<2, double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<2, TinyVector<2>>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    if (DualMeshManager::instance().getDiamondDualMesh(*mesh) != i_dual_mesh) {
+      throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
+    }
+
+    m_energy_computer =
+      std::make_unique<EnergyComputer<2>>(mesh,
+                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambdab),
+                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(U),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(dual_U),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(source),
+                                          bc_descriptor_list);
+    break;
+  }
+  case 3: {
+    using MeshType                   = Mesh<Connectivity<3>>;
+    using DiscreteScalarFunctionType = DiscreteFunctionP0<3, double>;
+    using DiscreteVectorFunctionType = DiscreteFunctionP0<3, TinyVector<3>>;
+
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    if (DualMeshManager::instance().getDiamondDualMesh(*mesh) != i_dual_mesh) {
+      throw NormalError("dual variables are is not defined on the diamond dual of the primal mesh");
+    }
+
+    m_energy_computer =
+      std::make_unique<EnergyComputer<3>>(mesh,
+                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_lambdab),
+                                          std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(dual_mub),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(U),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(dual_U),
+                                          std::dynamic_pointer_cast<const DiscreteVectorFunctionType>(source),
+                                          bc_descriptor_list);
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid mesh dimension");
+  }
+  }
+}
+
+EnergyComputerHandler::~EnergyComputerHandler() = default;
diff --git a/src/scheme/VectorDiamondScheme.hpp b/src/scheme/VectorDiamondScheme.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..20bbadfa3934cabf213d201e47397c97d56047e0
--- /dev/null
+++ b/src/scheme/VectorDiamondScheme.hpp
@@ -0,0 +1,827 @@
+#ifndef VECTOR_DIAMOND_SCHEME_HPP
+#define VECTOR_DIAMOND_SCHEME_HPP
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LinearSolver.hpp>
+#include <algebra/TinyVector.hpp>
+#include <algebra/Vector.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshFlatFaceBoundary.hpp>
+#include <mesh/PrimalToDiamondDualConnectivityDataMapper.hpp>
+#include <output/VTKWriter.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+
+class VectorDiamondSchemeHandler
+{
+ private:
+  class IVectorDiamondScheme;
+  template <size_t Dimension>
+  class VectorDiamondScheme;
+
+  template <size_t Dimension>
+  class InterpolationWeightsManager;
+
+ public:
+  std::unique_ptr<IVectorDiamondScheme> m_scheme;
+
+  std::shared_ptr<const IDiscreteFunction> solution() const;
+
+  std::shared_ptr<const IDiscreteFunction> dual_solution() const;
+
+  std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> apply() const;
+
+  VectorDiamondSchemeHandler(
+    const std::shared_ptr<const IDiscreteFunction>& alphab,
+    const std::shared_ptr<const IDiscreteFunction>& lambdab,
+    const std::shared_ptr<const IDiscreteFunction>& alpha,
+    const std::shared_ptr<const IDiscreteFunction>& lambda,
+    const std::shared_ptr<const IDiscreteFunction>& mu,
+    const std::shared_ptr<const IDiscreteFunction>& f,
+    const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list);
+
+  ~VectorDiamondSchemeHandler();
+};
+
+class EnergyComputerHandler
+{
+ private:
+  class IEnergyComputer;
+  template <size_t Dimension>
+  class EnergyComputer;
+
+  template <size_t Dimension>
+  class InterpolationWeightsManager;
+
+ public:
+  std::unique_ptr<IEnergyComputer> m_energy_computer;
+  std::shared_ptr<const IDiscreteFunction> dual_solution() const;
+
+  std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> computeEnergyUpdate()
+    const;
+
+  EnergyComputerHandler(const std::shared_ptr<const IDiscreteFunction>& lambdab,
+                        const std::shared_ptr<const IDiscreteFunction>& mub,
+                        const std::shared_ptr<const IDiscreteFunction>& U,
+                        const std::shared_ptr<const IDiscreteFunction>& dual_U,
+                        const std::shared_ptr<const IDiscreteFunction>& source,
+                        const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list);
+
+  ~EnergyComputerHandler();
+};
+
+template <size_t Dimension>
+class LegacyVectorDiamondScheme
+{
+ private:
+  class DirichletBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const TinyVector<Dimension>>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    DirichletBoundaryCondition(const Array<const FaceId>& face_list,
+                               const Array<const TinyVector<Dimension>>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~DirichletBoundaryCondition() = default;
+  };
+
+  class NormalStrainBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+    const Array<const TinyVector<Dimension>>&
+    valueList() const
+    {
+      return m_value_list;
+    }
+
+    NormalStrainBoundaryCondition(const Array<const FaceId>& face_list,
+                                  const Array<const TinyVector<Dimension>>& value_list)
+      : m_value_list{value_list}, m_face_list{face_list}
+    {
+      Assert(m_value_list.size() == m_face_list.size());
+    }
+
+    ~NormalStrainBoundaryCondition() = default;
+  };
+
+  class SymmetryBoundaryCondition
+  {
+   private:
+    const Array<const TinyVector<Dimension>> m_value_list;
+    const Array<const FaceId> m_face_list;
+
+   public:
+    const Array<const FaceId>&
+    faceList() const
+    {
+      return m_face_list;
+    }
+
+   public:
+    SymmetryBoundaryCondition(const Array<const FaceId>& face_list) : m_face_list{face_list} {}
+
+    ~SymmetryBoundaryCondition() = default;
+  };
+
+ public:
+  LegacyVectorDiamondScheme(std::shared_ptr<const IMesh> i_mesh,
+                            const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
+                            const FunctionSymbolId& lambda_id,
+                            const FunctionSymbolId& mu_id,
+                            const FunctionSymbolId& f_id,
+                            CellValue<TinyVector<Dimension>>& Uj,
+                            FaceValue<TinyVector<Dimension>>& Ul,
+                            const double& Tf,
+                            const double& dt,
+                            CellValuePerNode<double>& w_rj,
+                            FaceValuePerNode<double>& w_rl)
+  {
+    using ConnectivityType = Connectivity<Dimension>;
+    using MeshType         = Mesh<ConnectivityType>;
+    using MeshDataType     = MeshData<Dimension>;
+
+    using BoundaryCondition =
+      std::variant<DirichletBoundaryCondition, NormalStrainBoundaryCondition, SymmetryBoundaryCondition>;
+
+    using BoundaryConditionList = std::vector<BoundaryCondition>;
+
+    BoundaryConditionList boundary_condition_list;
+
+    std::cout << "number of bc descr = " << bc_descriptor_list.size() << '\n';
+    std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+
+    NodeValue<bool> is_dirichlet{mesh->connectivity()};
+    is_dirichlet.fill(false);
+    NodeValue<TinyVector<Dimension>> dirichlet_value{mesh->connectivity()};
+    {
+      TinyVector<Dimension> nan_tiny_vector;
+      for (size_t i = 0; i < Dimension; ++i) {
+        nan_tiny_vector[i] = std::numeric_limits<double>::signaling_NaN();
+      }
+      dirichlet_value.fill(nan_tiny_vector);
+    }
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor =
+          dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+
+        if constexpr (Dimension > 1) {
+          MeshFlatFaceBoundary<Dimension> mesh_face_boundary =
+            getMeshFlatFaceBoundary(*mesh, sym_bc_descriptor.boundaryDescriptor());
+          boundary_condition_list.push_back(SymmetryBoundaryCondition{mesh_face_boundary.faceList()});
+        } else {
+          throw NotImplementedError("Symmetry conditions are not supported in 1d");
+        }
+
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::dirichlet: {
+        const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+          dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+        if (dirichlet_bc_descriptor.name() == "dirichlet") {
+          if constexpr (Dimension > 1) {
+            MeshFaceBoundary<Dimension> mesh_face_boundary =
+              getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+            const FunctionSymbolId g_id                   = dirichlet_bc_descriptor.rhsSymbolId();
+            Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+              TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                            mesh_face_boundary.faceList());
+            boundary_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+          } else {
+            throw NotImplementedError("Neumann conditions are not supported in 1d");
+          }
+
+        } else if (dirichlet_bc_descriptor.name() == "normal_strain") {
+          if constexpr (Dimension > 1) {
+            MeshFaceBoundary<Dimension> mesh_face_boundary =
+              getMeshFaceBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+            const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+            Array<const TinyVector<Dimension>> value_list = InterpolateItemValue<TinyVector<Dimension>(
+              TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id, mesh_data.xl(),
+                                                                            mesh_face_boundary.faceList());
+            boundary_condition_list.push_back(NormalStrainBoundaryCondition{mesh_face_boundary.faceList(), value_list});
+
+          } else {
+            throw NotImplementedError("Normal strain conditions are not supported in 1d");
+          }
+
+        } else {
+          is_valid_boundary_condition = false;
+        }
+        break;
+      }
+      default: {
+        is_valid_boundary_condition = false;
+      }
+      }
+      if (not is_valid_boundary_condition) {
+        std::ostringstream error_msg;
+        error_msg << *bc_descriptor << " is an invalid boundary condition for elasticity equation";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    if constexpr (Dimension > 1) {
+      const CellValue<const size_t> cell_dof_number = [&] {
+        CellValue<size_t> compute_cell_dof_number{mesh->connectivity()};
+        parallel_for(
+          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { compute_cell_dof_number[cell_id] = cell_id; });
+        return compute_cell_dof_number;
+      }();
+      size_t number_of_dof = mesh->numberOfCells();
+
+      const FaceValue<const size_t> face_dof_number = [&] {
+        FaceValue<size_t> compute_face_dof_number{mesh->connectivity()};
+        compute_face_dof_number.fill(std::numeric_limits<size_t>::max());
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>) or
+                            (std::is_same_v<T, SymmetryBoundaryCondition>) or
+                            (std::is_same_v<T, DirichletBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id = face_list[i_face];
+                  if (compute_face_dof_number[face_id] != std::numeric_limits<size_t>::max()) {
+                    std::ostringstream os;
+                    os << "The face " << face_id << " is used at least twice for boundary conditions";
+                    throw NormalError(os.str());
+                  } else {
+                    compute_face_dof_number[face_id] = number_of_dof++;
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return compute_face_dof_number;
+      }();
+
+      const auto& primal_face_to_node_matrix             = mesh->connectivity().faceToNodeMatrix();
+      const auto& face_to_cell_matrix                    = mesh->connectivity().faceToCellMatrix();
+      const FaceValue<const bool> primal_face_is_neumann = [&] {
+        FaceValue<bool> face_is_neumann{mesh->connectivity()};
+        face_is_neumann.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id     = face_list[i_face];
+                  face_is_neumann[face_id] = true;
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return face_is_neumann;
+      }();
+
+      const FaceValue<const bool> primal_face_is_symmetry = [&] {
+        FaceValue<bool> face_is_symmetry{mesh->connectivity()};
+        face_is_symmetry.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, SymmetryBoundaryCondition>)) {
+                const auto& face_list = bc.faceList();
+
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id      = face_list[i_face];
+                  face_is_symmetry[face_id] = true;
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        return face_is_symmetry;
+      }();
+
+      NodeValue<bool> primal_node_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of node_is_on_boundary is incorrect");
+      }
+
+      primal_node_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+            NodeId node_id                      = primal_face_to_node_matrix[face_id][i_node];
+            primal_node_is_on_boundary[node_id] = true;
+          }
+        }
+      }
+
+      FaceValue<bool> primal_face_is_on_boundary(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_on_boundary is incorrect");
+      }
+
+      primal_face_is_on_boundary.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        if (face_to_cell_matrix[face_id].size() == 1) {
+          primal_face_is_on_boundary[face_id] = true;
+        }
+      }
+
+      FaceValue<bool> primal_face_is_dirichlet(mesh->connectivity());
+      if (parallel::size() > 1) {
+        throw NotImplementedError("Calculation of face_is_neumann is incorrect");
+      }
+
+      primal_face_is_dirichlet.fill(false);
+      for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+        primal_face_is_dirichlet[face_id] = (primal_face_is_on_boundary[face_id] &&
+                                             (!primal_face_is_neumann[face_id]) && (!primal_face_is_symmetry[face_id]));
+      }
+      MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
+
+      const FaceValue<const TinyVector<Dimension>>& xl               = mesh_data.xl();
+      const CellValue<const TinyVector<Dimension>>& xj               = mesh_data.xj();
+      const auto& node_to_cell_matrix                                = mesh->connectivity().nodeToCellMatrix();
+      const auto& node_to_face_matrix                                = mesh->connectivity().nodeToFaceMatrix();
+      const NodeValuePerFace<const TinyVector<Dimension>> primal_nlr = mesh_data.nlr();
+
+      {
+        std::shared_ptr diamond_mesh = DualMeshManager::instance().getDiamondDualMesh(*mesh);
+
+        MeshDataType& diamond_mesh_data = MeshDataManager::instance().getMeshData(*diamond_mesh);
+
+        std::shared_ptr mapper =
+          DualConnectivityManager::instance().getPrimalToDiamondDualConnectivityDataMapper(mesh->connectivity());
+
+        CellValue<double> dual_muj =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(mu_id,
+                                                                                                    diamond_mesh_data
+                                                                                                      .xj());
+
+        CellValue<double> dual_lambdaj =
+          InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(lambda_id,
+                                                                                                    diamond_mesh_data
+                                                                                                      .xj());
+
+        CellValue<TinyVector<Dimension>> fj = InterpolateItemValue<TinyVector<Dimension>(
+          TinyVector<Dimension>)>::template interpolate<ItemType::cell>(f_id, mesh_data.xj());
+
+        const CellValue<const double> dual_Vj = diamond_mesh_data.Vj();
+
+        const FaceValue<const double> mes_l = [&] {
+          if constexpr (Dimension == 1) {
+            FaceValue<double> compute_mes_l{mesh->connectivity()};
+            compute_mes_l.fill(1);
+            return compute_mes_l;
+          } else {
+            return mesh_data.ll();
+          }
+        }();
+
+        const CellValue<const double> dual_mes_l_j = [=] {
+          CellValue<double> compute_mes_j{diamond_mesh->connectivity()};
+          mapper->toDualCell(mes_l, compute_mes_j);
+
+          return compute_mes_j;
+        }();
+
+        const CellValue<const double> primal_Vj   = mesh_data.Vj();
+        FaceValue<const CellId> face_dual_cell_id = [=]() {
+          FaceValue<CellId> computed_face_dual_cell_id{mesh->connectivity()};
+          CellValue<CellId> dual_cell_id{diamond_mesh->connectivity()};
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { dual_cell_id[cell_id] = cell_id; });
+
+          mapper->fromDualCell(dual_cell_id, computed_face_dual_cell_id);
+
+          return computed_face_dual_cell_id;
+        }();
+
+        NodeValue<const NodeId> dual_node_primal_node_id = [=]() {
+          CellValue<NodeId> cell_ignored_id{mesh->connectivity()};
+          cell_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> node_primal_id{mesh->connectivity()};
+
+          parallel_for(
+            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { node_primal_id[node_id] = node_id; });
+
+          NodeValue<NodeId> computed_dual_node_primal_node_id{diamond_mesh->connectivity()};
+
+          mapper->toDualNode(node_primal_id, cell_ignored_id, computed_dual_node_primal_node_id);
+
+          return computed_dual_node_primal_node_id;
+        }();
+
+        CellValue<NodeId> primal_cell_dual_node_id = [=]() {
+          CellValue<NodeId> cell_id{mesh->connectivity()};
+          NodeValue<NodeId> node_ignored_id{mesh->connectivity()};
+          node_ignored_id.fill(NodeId{std::numeric_limits<unsigned int>::max()});
+
+          NodeValue<NodeId> dual_node_id{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) { dual_node_id[node_id] = node_id; });
+
+          CellValue<NodeId> computed_primal_cell_dual_node_id{mesh->connectivity()};
+
+          mapper->fromDualNode(dual_node_id, node_ignored_id, cell_id);
+
+          return cell_id;
+        }();
+        const auto& dual_Cjr                     = diamond_mesh_data.Cjr();
+        FaceValue<TinyVector<Dimension>> dualClj = [&] {
+          FaceValue<TinyVector<Dimension>> computedClj{mesh->connectivity()};
+          const auto& dual_node_to_cell_matrix = diamond_mesh->connectivity().nodeToCellMatrix();
+          const auto& dual_cell_to_node_matrix = diamond_mesh->connectivity().cellToNodeMatrix();
+          parallel_for(
+            mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+              const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+              for (size_t i = 0; i < primal_face_to_cell.size(); i++) {
+                CellId cell_id            = primal_face_to_cell[i];
+                const NodeId dual_node_id = primal_cell_dual_node_id[cell_id];
+                for (size_t i_dual_cell = 0; i_dual_cell < dual_node_to_cell_matrix[dual_node_id].size();
+                     i_dual_cell++) {
+                  const CellId dual_cell_id = dual_node_to_cell_matrix[dual_node_id][i_dual_cell];
+                  if (face_dual_cell_id[face_id] == dual_cell_id) {
+                    for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size();
+                         i_dual_node++) {
+                      const NodeId final_dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                      if (final_dual_node_id == dual_node_id) {
+                        computedClj[face_id] = dual_Cjr(dual_cell_id, i_dual_node);
+                      }
+                    }
+                  }
+                }
+              }
+            });
+          return computedClj;
+        }();
+
+        FaceValue<TinyVector<Dimension>> nlj = [&] {
+          FaceValue<TinyVector<Dimension>> computedNlj{mesh->connectivity()};
+          parallel_for(
+            mesh->numberOfFaces(),
+            PUGS_LAMBDA(FaceId face_id) { computedNlj[face_id] = 1. / l2Norm(dualClj[face_id]) * dualClj[face_id]; });
+          return computedNlj;
+        }();
+
+        FaceValue<const double> alpha_lambda_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_lambdaj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        FaceValue<const double> alpha_mu_l = [&] {
+          CellValue<double> alpha_j{diamond_mesh->connectivity()};
+
+          parallel_for(
+            diamond_mesh->numberOfCells(), PUGS_LAMBDA(CellId diamond_cell_id) {
+              alpha_j[diamond_cell_id] = dual_muj[diamond_cell_id] / dual_Vj[diamond_cell_id];
+            });
+
+          FaceValue<double> computed_alpha_l{mesh->connectivity()};
+          mapper->fromDualCell(alpha_j, computed_alpha_l);
+          return computed_alpha_l;
+        }();
+
+        double unsteady    = (Tf == 0) ? 0 : 1;
+        double time_factor = (unsteady == 0) ? 1 : dt;
+
+        TinyMatrix<Dimension> I = identity;
+
+        const Array<int> non_zeros{number_of_dof * Dimension};
+        non_zeros.fill(Dimension * Dimension);
+        CRSMatrixDescriptor<double> S(number_of_dof * Dimension, number_of_dof * Dimension, non_zeros);
+
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          const double beta_mu_l          = l2Norm(dualClj[face_id]) * alpha_mu_l[face_id] * mes_l[face_id];
+          const double beta_lambda_l      = l2Norm(dualClj[face_id]) * alpha_lambda_l[face_id] * mes_l[face_id];
+          const auto& primal_face_to_cell = face_to_cell_matrix[face_id];
+          for (size_t i_cell = 0; i_cell < primal_face_to_cell.size(); ++i_cell) {
+            const CellId i_id                      = primal_face_to_cell[i_cell];
+            const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+            const TinyVector<Dimension> nil = [&] {
+              if (is_face_reversed_for_cell_i) {
+                return -nlj[face_id];
+              } else {
+                return nlj[face_id];
+              }
+            }();
+            for (size_t j_cell = 0; j_cell < primal_face_to_cell.size(); ++j_cell) {
+              const CellId j_id = primal_face_to_cell[j_cell];
+              TinyMatrix<Dimension> M =
+                beta_mu_l * I + beta_mu_l * tensorProduct(nil, nil) + beta_lambda_l * tensorProduct(nil, nil);
+              TinyMatrix<Dimension> N = tensorProduct(nil, nil);
+
+              if (i_cell == j_cell) {
+                for (size_t i = 0; i < Dimension; ++i) {
+                  for (size_t j = 0; j < Dimension; ++j) {
+                    S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) +=
+                      (time_factor * M(i, j) + unsteady * primal_Vj[i_id]);
+                    if (primal_face_is_neumann[face_id]) {
+                      S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) -= M(i, j);
+                    }
+                    if (primal_face_is_symmetry[face_id]) {
+                      S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) +=
+                        -((i == j) ? 1 : 0) + N(i, j);
+                      S(face_dof_number[face_id] * Dimension + i, face_dof_number[face_id] * Dimension + j) +=
+                        (i == j) ? 1 : 0;
+                    }
+                  }
+                }
+              } else {
+                for (size_t i = 0; i < Dimension; ++i) {
+                  for (size_t j = 0; j < Dimension; ++j) {
+                    S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) -=
+                      time_factor * M(i, j);
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        const auto& dual_cell_to_node_matrix   = diamond_mesh->connectivity().cellToNodeMatrix();
+        const auto& primal_node_to_cell_matrix = mesh->connectivity().nodeToCellMatrix();
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          const double alpha_mu_face_id     = mes_l[face_id] * alpha_mu_l[face_id];
+          const double alpha_lambda_face_id = mes_l[face_id] * alpha_lambda_l[face_id];
+
+          for (size_t i_face_cell = 0; i_face_cell < face_to_cell_matrix[face_id].size(); ++i_face_cell) {
+            CellId i_id                            = face_to_cell_matrix[face_id][i_face_cell];
+            const bool is_face_reversed_for_cell_i = (dot(dualClj[face_id], xl[face_id] - xj[i_id]) < 0);
+
+            for (size_t i_node = 0; i_node < primal_face_to_node_matrix[face_id].size(); ++i_node) {
+              NodeId node_id = primal_face_to_node_matrix[face_id][i_node];
+
+              const TinyVector<Dimension> nil = [&] {
+                if (is_face_reversed_for_cell_i) {
+                  return -nlj[face_id];
+                } else {
+                  return nlj[face_id];
+                }
+              }();
+
+              CellId dual_cell_id = face_dual_cell_id[face_id];
+
+              for (size_t i_dual_node = 0; i_dual_node < dual_cell_to_node_matrix[dual_cell_id].size(); ++i_dual_node) {
+                const NodeId dual_node_id = dual_cell_to_node_matrix[dual_cell_id][i_dual_node];
+                if (dual_node_primal_node_id[dual_node_id] == node_id) {
+                  const TinyVector<Dimension> Clr = dual_Cjr(dual_cell_id, i_dual_node);
+
+                  TinyMatrix<Dimension> M = alpha_mu_face_id * dot(Clr, nil) * I +
+                                            alpha_mu_face_id * tensorProduct(Clr, nil) +
+                                            alpha_lambda_face_id * tensorProduct(nil, Clr);
+
+                  for (size_t j_cell = 0; j_cell < primal_node_to_cell_matrix[node_id].size(); ++j_cell) {
+                    CellId j_id = primal_node_to_cell_matrix[node_id][j_cell];
+                    for (size_t i = 0; i < Dimension; ++i) {
+                      for (size_t j = 0; j < Dimension; ++j) {
+                        S((cell_dof_number[i_id] * Dimension) + i, (cell_dof_number[j_id] * Dimension) + j) -=
+                          time_factor * w_rj(node_id, j_cell) * M(i, j);
+                        if (primal_face_is_neumann[face_id]) {
+                          S(face_dof_number[face_id] * Dimension + i, cell_dof_number[j_id] * Dimension + j) +=
+                            w_rj(node_id, j_cell) * M(i, j);
+                        }
+                      }
+                    }
+                  }
+                  if (primal_node_is_on_boundary[node_id]) {
+                    for (size_t l_face = 0; l_face < node_to_face_matrix[node_id].size(); ++l_face) {
+                      FaceId l_id = node_to_face_matrix[node_id][l_face];
+                      if (primal_face_is_on_boundary[l_id]) {
+                        for (size_t i = 0; i < Dimension; ++i) {
+                          for (size_t j = 0; j < Dimension; ++j) {
+                            S(cell_dof_number[i_id] * Dimension + i, face_dof_number[l_id] * Dimension + j) -=
+                              time_factor * w_rl(node_id, l_face) * M(i, j);
+                          }
+                        }
+                        if (primal_face_is_neumann[face_id]) {
+                          for (size_t i = 0; i < Dimension; ++i) {
+                            for (size_t j = 0; j < Dimension; ++j) {
+                              S(face_dof_number[face_id] * Dimension + i, face_dof_number[l_id] * Dimension + j) +=
+                                w_rl(node_id, l_face) * M(i, j);
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+              //            }
+            }
+          }
+        }
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          if (primal_face_is_dirichlet[face_id]) {
+            for (size_t i = 0; i < Dimension; ++i) {
+              S(face_dof_number[face_id] * Dimension + i, face_dof_number[face_id] * Dimension + i) += 1;
+            }
+          }
+        }
+
+        Vector<double> b{number_of_dof * Dimension};
+        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+          for (size_t i = 0; i < Dimension; ++i) {
+            b[(cell_dof_number[cell_id] * Dimension) + i] =
+              primal_Vj[cell_id] * (time_factor * fj[cell_id][i] + unsteady * Uj[cell_id][i]);
+          }
+        }
+
+        // Dirichlet
+        NodeValue<bool> node_tag{mesh->connectivity()};
+        node_tag.fill(false);
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
+                const auto& face_list  = bc.faceList();
+                const auto& value_list = bc.valueList();
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  const FaceId face_id = face_list[i_face];
+
+                  for (size_t i = 0; i < Dimension; ++i) {
+                    b[(face_dof_number[face_id] * Dimension) + i] += value_list[i_face][i];
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        for (const auto& boundary_condition : boundary_condition_list) {
+          std::visit(
+            [&](auto&& bc) {
+              using T = std::decay_t<decltype(bc)>;
+              if constexpr ((std::is_same_v<T, NormalStrainBoundaryCondition>)) {
+                const auto& face_list  = bc.faceList();
+                const auto& value_list = bc.valueList();
+                for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+                  FaceId face_id = face_list[i_face];
+                  for (size_t i = 0; i < Dimension; ++i) {
+                    b[face_dof_number[face_id] * Dimension + i] += mes_l[face_id] * value_list[i_face][i];   // sign
+                  }
+                }
+              }
+            },
+            boundary_condition);
+        }
+
+        CRSMatrix A{S.getCRSMatrix()};
+        Vector<double> U{number_of_dof * Dimension};
+        U        = zero;
+        Vector r = A * U - b;
+        std::cout << "initial (real) residu = " << std::sqrt(dot(r, r)) << '\n';
+
+        LinearSolver solver;
+        solver.solveLocalSystem(A, U, b);
+
+        r = A * U - b;
+
+        std::cout << "final (real) residu = " << std::sqrt(dot(r, r)) << '\n';
+
+        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
+          for (size_t i = 0; i < Dimension; ++i) {
+            Uj[cell_id][i] = U[(cell_dof_number[cell_id] * Dimension) + i];
+          }
+        }
+        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
+          for (size_t i = 0; i < Dimension; ++i) {
+            if (primal_face_is_on_boundary[face_id]) {
+              Ul[face_id][i] = U[(face_dof_number[face_id] * Dimension) + i];
+            }
+          }
+        }
+        NodeValue<TinyVector<3>> ur3d{mesh->connectivity()};
+        ur3d.fill(zero);
+
+        parallel_for(
+          mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
+            TinyVector<Dimension> x = zero;
+            const auto node_cells   = node_to_cell_matrix[node_id];
+            for (size_t i_cell = 0; i_cell < node_cells.size(); ++i_cell) {
+              CellId cell_id = node_cells[i_cell];
+              x += w_rj(node_id, i_cell) * Uj[cell_id];
+            }
+            const auto node_faces = node_to_face_matrix[node_id];
+            for (size_t i_face = 0; i_face < node_faces.size(); ++i_face) {
+              FaceId face_id = node_faces[i_face];
+              if (primal_face_is_on_boundary[face_id]) {
+                x += w_rl(node_id, i_face) * Ul[face_id];
+              }
+            }
+            for (size_t i = 0; i < Dimension; ++i) {
+              ur3d[node_id][i] = x[i];
+            }
+          });
+      }
+    } else {
+      throw NotImplementedError("not done in 1d");
+    }
+  }
+};
+template LegacyVectorDiamondScheme<1>::LegacyVectorDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  CellValue<TinyVector<1>>&,
+  FaceValue<TinyVector<1>>&,
+  const double&,
+  const double&,
+  CellValuePerNode<double>&,
+  FaceValuePerNode<double>&);
+
+template LegacyVectorDiamondScheme<2>::LegacyVectorDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  CellValue<TinyVector<2>>&,
+  FaceValue<TinyVector<2>>&,
+  const double&,
+  const double&,
+  CellValuePerNode<double>&,
+  FaceValuePerNode<double>&);
+
+template LegacyVectorDiamondScheme<3>::LegacyVectorDiamondScheme(
+  std::shared_ptr<const IMesh>,
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  const FunctionSymbolId&,
+  CellValue<TinyVector<3>>&,
+  FaceValue<TinyVector<3>>&,
+  const double&,
+  const double&,
+  CellValuePerNode<double>&,
+  FaceValuePerNode<double>&);
+
+#endif   // VECTOR_DIAMOND_SCHEME_HPP
diff --git a/tests/test_CG.cpp b/tests/test_CG.cpp
index f0c14f24f9c416048aa05905123621deb011219f..5afd80e7c39250ac76080cc611f6fe6a19afc595 100644
--- a/tests/test_CG.cpp
+++ b/tests/test_CG.cpp
@@ -4,6 +4,7 @@
 #include <algebra/CG.hpp>
 #include <algebra/CRSMatrix.hpp>
 #include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/Vector.hpp>
 
 // clazy:excludeall=non-pod-global-static
 
diff --git a/tests/test_LeastSquareSolver.cpp b/tests/test_LeastSquareSolver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6022bbe151ff2c261dc21b015692b9045edcc9ca
--- /dev/null
+++ b/tests/test_LeastSquareSolver.cpp
@@ -0,0 +1,77 @@
+#include <catch2/catch.hpp>
+
+#include <algebra/LeastSquareSolver.hpp>
+#include <algebra/LocalRectangularMatrix.hpp>
+#include <algebra/Vector.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("LeastSquareSolver", "[algebra]")
+{
+  SECTION("Least Squares [under-determined]")
+  {
+    Vector<double> b{3};
+    b[0] = 1;
+    b[1] = 0;
+    b[2] = 0;
+
+    LocalRectangularMatrix<double> A{3, 4};
+    A(0, 0) = 1;
+    A(0, 1) = 1;
+    A(0, 2) = 1;
+    A(0, 3) = 1;
+    A(1, 0) = 1;
+    A(1, 1) = -1;
+    A(1, 2) = 0;
+    A(1, 3) = 0;
+    A(2, 0) = 0;
+    A(2, 1) = 0;
+    A(2, 2) = 1;
+    A(2, 3) = -1;
+
+    Vector<double> x_exact{4};
+
+    x_exact[0] = 0.25;
+    x_exact[1] = 0.25;
+    x_exact[2] = 0.25;
+    x_exact[3] = 0.25;
+
+    Vector<double> x{4};
+    x = 0;
+
+    LeastSquareSolver ls_solver;
+    ls_solver.solveLocalSystem(A, x, b);
+
+    Vector error = x - x_exact;
+    REQUIRE(std::sqrt((error, error)) < 1E-10 * std::sqrt((x, x)));
+  }
+
+  SECTION("Least Squares [over-determined]")
+  {
+    LocalRectangularMatrix<double> A{3, 2};
+    A(0, 0) = 0;
+    A(0, 1) = 1;
+    A(1, 0) = 1;
+    A(1, 1) = 1;
+    A(2, 0) = 2;
+    A(2, 1) = 1;
+
+    Vector<double> x_exact{2};
+    x_exact[0] = -3;
+    x_exact[1] = 5;
+
+    Vector b{3};
+    b[0] = 6;
+    b[1] = 0;
+    b[2] = 0;
+
+    Vector<double> x{2};
+    x = 0;
+
+    LeastSquareSolver ls_solver;
+    ls_solver.solveLocalSystem(A, x, b);
+
+    Vector error = x - x_exact;
+    REQUIRE(std::sqrt((error, error)) < 1E-10 * std::sqrt((x_exact, x_exact)));
+  }
+}