Skip to content
Snippets Groups Projects
Select Git revision
  • 048c9123d5b9cddcda30d169288b6d968730b374
  • develop default protected
  • feature/advection
  • feature/composite-scheme-other-fluxes
  • origin/stage/bouguettaia
  • save_clemence
  • feature/local-dt-fsi
  • feature/variational-hydro
  • feature/gmsh-reader
  • feature/reconstruction
  • feature/kinetic-schemes
  • feature/composite-scheme-sources
  • feature/serraille
  • feature/composite-scheme
  • hyperplastic
  • feature/polynomials
  • feature/gks
  • feature/implicit-solver-o2
  • feature/coupling_module
  • feature/implicit-solver
  • feature/merge-local-dt-fsi
  • v0.5.0 protected
  • v0.4.1 protected
  • v0.4.0 protected
  • v0.3.0 protected
  • v0.2.0 protected
  • v0.1.0 protected
  • Kidder
  • v0.0.4 protected
  • v0.0.3 protected
  • v0.0.2 protected
  • v0 protected
  • v0.0.1 protected
33 results

PolynomialReconstruction.cpp

Blame
  • ScalarHybridScheme.cpp 70.50 KiB
    #include <scheme/ScalarHybridScheme.hpp>
    
    #include <scheme/DiscreteFunctionP0.hpp>
    #include <scheme/DiscreteFunctionUtils.hpp>
    
    class ScalarHybridSchemeHandler::IScalarHybridScheme
    {
     public:
      virtual std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>> getSolution()
        const = 0;
    
      // virtual std::shared_ptr<const IDiscreteFunction> getSolution() const = 0;
    
      IScalarHybridScheme()          = default;
      virtual ~IScalarHybridScheme() = default;
    };
    
    template <size_t Dimension>
    class ScalarHybridSchemeHandler::ScalarHybridScheme : public ScalarHybridSchemeHandler::IScalarHybridScheme
    {
     private:
      using ConnectivityType = Connectivity<Dimension>;
      using MeshType         = Mesh<ConnectivityType>;
      using MeshDataType     = MeshData<Dimension>;
    
      std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_cell_temperature;
      std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_node_temperature;
      //  std::shared_ptr<const DiscreteFunctionP0<Dimension, double>> m_solution;
    
      class DirichletBoundaryCondition
      {
       private:
        const Array<const double> m_node_value_list;
        const Array<const double> m_face_value_list;
        const Array<const FaceId> m_face_list;
        const Array<const NodeId> m_node_list;
    
       public:
        const Array<const NodeId>&
        nodeList() const
        {
          return m_node_list;
        }
    
        const Array<const FaceId>&
        faceList() const
        {
          return m_face_list;
        }
    
        const Array<const double>&
        nodeValueList() const
        {
          return m_node_value_list;
        }
    
        const Array<const double>&
        faceValueList() const
        {
          return m_face_value_list;
        }
    
        DirichletBoundaryCondition(const Array<const FaceId>& face_list,
                                   const Array<const NodeId>& node_list,
                                   const Array<const double>& node_value_list,
                                   const Array<const double>& face_value_list)
          : m_node_value_list{node_value_list},
            m_face_value_list{face_value_list},
            m_face_list{face_list},
            m_node_list{node_list}
        {
          Assert(m_node_value_list.size() == m_node_list.size());
          Assert(m_face_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;
        const Array<const NodeId> m_node_list;
    
       public:
        const Array<const NodeId>&
        nodeList() const
        {
          return m_node_list;
        }
    
        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 NodeId>& node_list,
                                 const Array<const double>& value_list)
          : m_value_list{value_list}, m_face_list{face_list}, m_node_list{node_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;
      };
    
     public:
      std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
      getSolution() const
      {
        return {m_cell_temperature, m_node_temperature};
      }
      // std::shared_ptr<const IDiscreteFunction>
      // getSolution() const final
      // {
      //   return m_solution;
      // }
    
      ScalarHybridScheme(const std::shared_ptr<const MeshType>& mesh,
                         const std::shared_ptr<const DiscreteFunctionP0<Dimension, TinyMatrix<Dimension>>>& cell_k,
                         const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& f,
                         const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& f_prev,
                         const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
                         const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_prev_descriptor_list,
                         const std::shared_ptr<const DiscreteFunctionP0<Dimension, double>>& P,
                         const double& dt,
                         const double& cn_coeff,
                         const double& lambda)
      {
        using BoundaryCondition =
          std::variant<DirichletBoundaryCondition, FourierBoundaryCondition, NeumannBoundaryCondition>;
    
        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::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());
              MeshNodeBoundary<Dimension> mesh_node_boundary =
                getMeshNodeBoundary(*mesh, dirichlet_bc_descriptor.boundaryDescriptor());
    
              const FunctionSymbolId g_id = dirichlet_bc_descriptor.rhsSymbolId();
              MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
    
              Array<const double> node_value_list =
                InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::node>(g_id, mesh->xr(),
                                                                                                          mesh_node_boundary
                                                                                                            .nodeList());
              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(DirichletBoundaryCondition{mesh_face_boundary.faceList(),
                                                                           mesh_node_boundary.nodeList(), node_value_list,
                                                                           face_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());
              MeshNodeBoundary<Dimension> mesh_node_boundary =
                getMeshNodeBoundary(*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(), mesh_node_boundary.nodeList(), value_list});
    
            } else {
              throw NotImplementedError("Neumann 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());
          }
        }
    
        BoundaryConditionList boundary_prev_condition_list;
    
        for (const auto& bc_prev_descriptor : bc_prev_descriptor_list) {
          bool is_valid_boundary_condition = true;
    
          switch (bc_prev_descriptor->type()) {
          case IBoundaryConditionDescriptor::Type::dirichlet: {
            const DirichletBoundaryConditionDescriptor& dirichlet_bc_prev_descriptor =
              dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_prev_descriptor);
            if constexpr (Dimension > 1) {
              MeshFaceBoundary<Dimension> mesh_face_boundary =
                getMeshFaceBoundary(*mesh, dirichlet_bc_prev_descriptor.boundaryDescriptor());
              MeshNodeBoundary<Dimension> mesh_node_boundary =
                getMeshNodeBoundary(*mesh, dirichlet_bc_prev_descriptor.boundaryDescriptor());
    
              const FunctionSymbolId g_id = dirichlet_bc_prev_descriptor.rhsSymbolId();
              MeshDataType& mesh_data     = MeshDataManager::instance().getMeshData(*mesh);
    
              Array<const double> node_value_list =
                InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::node>(g_id, mesh->xr(),
                                                                                                          mesh_node_boundary
                                                                                                            .nodeList());
              Array<const double> face_value_list =
                InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::face>(g_id,
                                                                                                          mesh_data.xl(),
                                                                                                          mesh_face_boundary
                                                                                                            .faceList());
              boundary_prev_condition_list.push_back(DirichletBoundaryCondition{mesh_face_boundary.faceList(),
                                                                                mesh_node_boundary.nodeList(),
                                                                                node_value_list, face_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_prev_descriptor =
              dynamic_cast<const NeumannBoundaryConditionDescriptor&>(*bc_prev_descriptor);
    
            if constexpr (Dimension > 1) {
              MeshFaceBoundary<Dimension> mesh_face_boundary =
                getMeshFaceBoundary(*mesh, neumann_bc_prev_descriptor.boundaryDescriptor());
              MeshNodeBoundary<Dimension> mesh_node_boundary =
                getMeshNodeBoundary(*mesh, neumann_bc_prev_descriptor.boundaryDescriptor());
    
              const FunctionSymbolId g_id = neumann_bc_prev_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_prev_condition_list.push_back(
                NeumannBoundaryCondition{mesh_face_boundary.faceList(), mesh_node_boundary.nodeList(), value_list});
    
            } else {
              throw NotImplementedError("Neumann BC in 1d");
            }
            break;
          }
          default: {
            is_valid_boundary_condition = false;
          }
          }
          if (not is_valid_boundary_condition) {
            std::ostringstream error_msg;
            error_msg << *bc_prev_descriptor << " is an invalid boundary condition for heat equation";
            throw NormalError(error_msg.str());
          }
        }
    
        MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(*mesh);
    
        std::shared_ptr dual_mesh = DualMeshManager::instance().getMedianDualMesh(*mesh);
    
        std::shared_ptr mapper =
          DualConnectivityManager::instance().getPrimalToMedianDualConnectivityDataMapper(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 NodeValuePerCell<const TinyVector<Dimension>>& Cjr = mesh_data.Cjr();
        const CellValue<const TinyMatrix<Dimension>> cell_kappaj = cell_k->cellValues();
    
        const auto is_boundary_node = mesh->connectivity().isBoundaryNode();
        const auto is_boundary_face = mesh->connectivity().isBoundaryFace();
    
        const auto& face_to_cell_matrix               = mesh->connectivity().faceToCellMatrix();
        const auto& node_to_face_matrix               = mesh->connectivity().nodeToFaceMatrix();
        const auto& face_to_node_matrix               = mesh->connectivity().faceToNodeMatrix();
        const auto& cell_to_node_matrix               = mesh->connectivity().cellToNodeMatrix();
        const auto& cell_to_face_matrix               = mesh->connectivity().cellToFaceMatrix();
        const auto& node_local_numbers_in_their_cells = mesh->connectivity().nodeLocalNumbersInTheirCells();
        const auto& cell_local_numbers_in_their_faces = mesh->connectivity().cellLocalNumbersInTheirFaces();
        const auto& face_local_numbers_in_their_cells = mesh->connectivity().faceLocalNumbersInTheirCells();
        const CellValue<const double> Vj              = mesh_data.Vj();
        const auto& node_to_cell_matrix               = mesh->connectivity().nodeToCellMatrix();
        const auto& nl                                = mesh_data.nl();
    
        const NodeValue<const bool> node_is_neumann = [&] {
          NodeValue<bool> compute_node_is_neumann{mesh->connectivity()};
          compute_node_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>) {
                  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];
                    const auto& face_nodes = face_to_node_matrix[face_id];
                    for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
                      const NodeId node_id             = face_nodes[i_node];
                      compute_node_is_neumann[node_id] = true;
                    }
                  }
                }
              },
              boundary_condition);
          }
          return compute_node_is_neumann;
        }();
    
        const FaceValue<const bool> face_is_neumann = [&] {
          FaceValue<bool> compute_face_is_neumann{mesh->connectivity()};
          compute_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>) {
                  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];
                    compute_face_is_neumann[face_id] = true;
                  }
                }
              },
              boundary_condition);
          }
          return compute_face_is_neumann;
        }();
    
        const NodeValue<const bool> node_is_dirichlet = [&] {
          NodeValue<bool> compute_node_is_dirichlet{mesh->connectivity()};
          compute_node_is_dirichlet.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& node_list = bc.nodeList();
    
                  for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
                    const NodeId node_id = node_list[i_node];
    
                    compute_node_is_dirichlet[node_id] = true;
                  }
                }
              },
              boundary_condition);
          }
          return compute_node_is_dirichlet;
        }();
    
        const FaceValue<const bool> face_is_dirichlet = [&] {
          FaceValue<bool> compute_face_is_dirichlet{mesh->connectivity()};
          compute_face_is_dirichlet.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();
                  for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
                    const FaceId face_id               = face_list[i_face];
                    compute_face_is_dirichlet[face_id] = true;
                  }
                }
              },
              boundary_condition);
          }
          return compute_face_is_dirichlet;
        }();
    
        const NodeValue<const bool> node_is_corner = [&] {
          NodeValue<bool> compute_node_is_corner{mesh->connectivity()};
          compute_node_is_corner.fill(false);
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              if (is_boundary_node[node_id]) {
                const auto& node_to_cell                  = node_to_cell_matrix[node_id];
                const auto& node_local_number_in_its_cell = node_local_numbers_in_their_cells.itemArray(node_id);
                for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
                  const unsigned int i_node = node_local_number_in_its_cell[i_cell];
                  const CellId cell_id      = node_to_cell[i_cell];
                  const auto& cell_nodes    = cell_to_node_matrix[cell_id];
                  NodeId i_node_prev;
                  NodeId i_node_next;
                  if (i_node == 0) {
                    i_node_prev = cell_nodes.size() - 1;
                  } else {
                    i_node_prev = i_node - 1;
                  }
                  if (i_node == cell_nodes.size() - 1) {
                    i_node_next = 0;
                  } else {
                    i_node_next = i_node + 1;
                  }
                  const NodeId prev_node_id = cell_to_node_matrix[cell_id][i_node_prev];
                  const NodeId next_node_id = cell_to_node_matrix[cell_id][i_node_next];
                  if (is_boundary_node[prev_node_id] and is_boundary_node[next_node_id]) {
                    compute_node_is_corner[node_id] = true;
                  }
                }
              }
            });
          return compute_node_is_corner;
        }();
    
        const NodeValue<const bool> node_is_angle = [&] {
          NodeValue<bool> compute_node_is_angle{mesh->connectivity()};
          compute_node_is_angle.fill(false);
          // for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              if (is_boundary_node[node_id]) {
                const auto& node_to_face = node_to_face_matrix[node_id];
    
                TinyVector<Dimension> n1 = zero;
                TinyVector<Dimension> n2 = zero;
    
                for (size_t i_face = 0; i_face < node_to_face.size(); ++i_face) {
                  FaceId face_id = node_to_face[i_face];
                  if (is_boundary_face[face_id]) {
                    if (l2Norm(n1) == 0) {
                      n1 = nl[face_id];
                    } else {
                      n2 = nl[face_id];
                    }
                  }
                }
                if (l2Norm(n1 - n2) > (1.E-15) and l2Norm(n1 + n2) > (1.E-15) and not node_is_corner[node_id]) {
                  compute_node_is_angle[node_id] = true;
                }
                // std::cout << node_id << "  " << n1 << "  " << n2 << "  " << compute_node_is_angle[node_id] << "\n";
              }
            });
          return compute_node_is_angle;
        }();
    
        const NodeValue<const TinyVector<Dimension>> exterior_normal = [&] {
          NodeValue<TinyVector<Dimension>> compute_exterior_normal{mesh->connectivity()};
          compute_exterior_normal.fill(zero);
          for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
            if (is_boundary_node[node_id]) {
              const auto& node_to_cell                  = node_to_cell_matrix[node_id];
              const auto& node_local_number_in_its_cell = node_local_numbers_in_their_cells.itemArray(node_id);
              for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
                const CellId cell_id      = node_to_cell[i_cell];
                const unsigned int i_node = node_local_number_in_its_cell[i_cell];
                compute_exterior_normal[node_id] += Cjr(cell_id, i_node);
              }
              const double norm_exterior_normal = l2Norm(compute_exterior_normal[node_id]);
              compute_exterior_normal[node_id] *= 1. / norm_exterior_normal;
              // std::cout << node_id << "  " << compute_exterior_normal[node_id] << "\n";
            }
          }
          return compute_exterior_normal;
        }();
    
        // for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
        //  std::cout << node_id << "  " << exterior_normal[node_id] << "  " << node_is_angle[node_id] << "\n";
        //};
    
        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 NodeValue<const TinyVector<Dimension>> normal_angle_base = [&] {
          NodeValue<TinyVector<Dimension>> compute_normal_base{mesh->connectivity()};
          compute_normal_base.fill(zero);
          for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
            if (node_is_angle[node_id]) {
              const auto& node_to_face = node_to_face_matrix[node_id];
              TinyMatrix<Dimension> A;
              A(0, 0) = 1000;
              A(0, 1) = 1000;
              A(1, 0) = 1000;
              A(1, 1) = 1000;
              for (size_t i_face = 0; i_face < node_to_face.size(); ++i_face) {
                const FaceId face_id = node_to_face[i_face];
                if (is_boundary_face[face_id]) {
                  if (A(0, 0) == 1000) {
                    if (dot(nl[face_id], exterior_normal[node_id]) >= 0) {
                      A(0, 0) = nl[face_id][0] * mes_l[face_id];
                      A(1, 0) = nl[face_id][1] * mes_l[face_id];
                    } else {
                      A(0, 0) = -nl[face_id][0] * mes_l[face_id];
                      A(1, 0) = -nl[face_id][1] * mes_l[face_id];
                    }
                  } else {
                    if (dot(nl[face_id], exterior_normal[node_id]) >= 0) {
                      A(0, 1) = nl[face_id][0] * mes_l[face_id];
                      A(1, 1) = nl[face_id][1] * mes_l[face_id];
                    } else {
                      A(0, 1) = -nl[face_id][0] * mes_l[face_id];
                      A(1, 1) = -nl[face_id][1] * mes_l[face_id];
                    }
                  }
                }
              }
              compute_normal_base[node_id] = inverse(A) * exterior_normal[node_id];
            }
          }
          return compute_normal_base;
        }();
    
        const NodeValue<const TinyMatrix<Dimension>> node_kappar = [&] {
          NodeValue<TinyMatrix<Dimension>> kappa{mesh->connectivity()};
          kappa.fill(zero);
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              kappa[node_id]           = zero;
              const auto& node_to_cell = node_to_cell_matrix[node_id];
              double weight            = 0;
              for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
                const CellId cell_id = node_to_cell[i_cell];
                double local_weight  = 1. / l2Norm(xr[node_id] - xj[cell_id]);
                kappa[node_id] += local_weight * cell_kappaj[cell_id];
                weight += local_weight;
              }
              kappa[node_id] = 1. / weight * kappa[node_id];
            });
          return kappa;
        }();
    
        const FaceValue<const TinyMatrix<Dimension>> face_kappal = [&] {
          FaceValue<TinyMatrix<Dimension>> kappa{mesh->connectivity()};
          kappa.fill(zero);
          parallel_for(
            mesh->numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
              const auto& face_to_node = face_to_node_matrix[face_id];
              kappa[face_id]           = 0.5 * (node_kappar[face_to_node[0]] + node_kappar[face_to_node[1]]);
            });
          return kappa;
        }();
    
        const FaceValuePerCell<const TinyVector<Dimension>> xj1_xj2 = [&] {
          FaceValuePerCell<TinyVector<Dimension>> compute_xj1_xj2{mesh->connectivity()};
          compute_xj1_xj2.fill(zero);
          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
            const auto& cell_to_face = cell_to_face_matrix[cell_id];
    
            for (size_t i_face = 0; i_face < cell_to_face.size(); ++i_face) {
              const FaceId face_id     = cell_to_face[i_face];
              const auto& face_to_cell = face_to_cell_matrix[face_id];
              TinyVector<Dimension> xj1;
              TinyVector<Dimension> xj2;
    
              if (not is_boundary_face[face_id]) {
                if (face_to_cell[0] == cell_id) {
                  xj1 = xj[face_to_cell[1]];
                  xj2 = xj[cell_id];
                } else {
                  xj1 = xj[face_to_cell[0]];
                  xj2 = xj[cell_id];
                }
              } else {
                if (face_to_cell[0] == cell_id) {
                  xj1 = xl[face_id];
                  xj2 = xj[cell_id];
                } else {
                  xj1 = xl[face_id];
                  xj2 = xj[cell_id];
                }
              }
              compute_xj1_xj2(cell_id, i_face) = xj1 - xj2;
            }
          }
          return compute_xj1_xj2;
        }();
    
        const FaceValuePerCell<const TinyVector<Dimension>> xj1_xj2_orth = [&] {
          FaceValuePerCell<TinyVector<Dimension>> compute_xj1_xj2_orth{mesh->connectivity()};
          compute_xj1_xj2_orth.fill(zero);
          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
            const auto& cell_to_face = cell_to_face_matrix[cell_id];
    
            for (size_t i_face = 0; i_face < cell_to_face.size(); ++i_face) {
              const FaceId face_id = cell_to_face[i_face];
    
              compute_xj1_xj2_orth(cell_id, i_face)[0] = -xj1_xj2(cell_id, i_face)[1];
              compute_xj1_xj2_orth(cell_id, i_face)[1] = xj1_xj2(cell_id, i_face)[0];
            }
          }
          return compute_xj1_xj2_orth;
        }();
    
        const FaceValuePerCell<const TinyVector<Dimension>> normal_face_base = [&] {
          FaceValuePerCell<TinyVector<Dimension>> compute_normal_base{mesh->connectivity()};
          compute_normal_base.fill(zero);
          for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
            const auto& cell_to_face = cell_to_face_matrix[cell_id];
    
            for (size_t i_face = 0; i_face < cell_to_face.size(); ++i_face) {
              const FaceId face_id = cell_to_face[i_face];
              TinyMatrix<Dimension> A;
              TinyVector<Dimension> face_normal;
    
              A(0, 0) = xj1_xj2_orth(cell_id, i_face)[0];
              A(0, 1) = xj1_xj2(cell_id, i_face)[0];
              A(1, 0) = xj1_xj2_orth(cell_id, i_face)[1];
              A(1, 1) = xj1_xj2(cell_id, i_face)[1];
    
              if (dot(nl[face_id], xj1_xj2(cell_id, i_face)) > 0) {
                face_normal = nl[face_id];
              } else {
                face_normal = -nl[face_id];
              }
    
              // compute_normal_base(cell_id, i_face)[0] =
              //   dot(face_kappal[face_id] * face_normal, 1. / l2Norm(xj1_xj2(cell_id, i_face)) * xj1_xj2(cell_id,
              //   i_face));
              // compute_normal_base(cell_id, i_face)[1] =
              //   dot(face_kappal[face_id] * face_normal,
              //       1. / l2Norm(xj1_xj2_orth(cell_id, i_face)) * xj1_xj2_orth(cell_id, i_face));
              compute_normal_base(cell_id, i_face) = inverse(A) * (face_kappal[face_id] * face_normal);
    
              // std::cout << cell_id << " " << face_id << "; alpha_l = " << compute_normal_base(cell_id, i_face)[0]
              //           << "; delta_l = " << compute_normal_base(cell_id, i_face)[1] << "\n";
            }
          }
    
          return compute_normal_base;
        }();
    
        const FaceValue<const double> face_boundary_values = [&] {
          FaceValue<double> compute_face_boundary_values{mesh->connectivity()};
          FaceValue<double> sum_mes_l{mesh->connectivity()};
          compute_face_boundary_values.fill(0);
          sum_mes_l.fill(0);
          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& face_value_list = bc.faceValueList();
    
                  for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
                    const FaceId face_id = face_list[i_face];
    
                    compute_face_boundary_values[face_id] = face_value_list[i_face];
                  }
                } else if constexpr (std::is_same_v<T, NeumannBoundaryCondition>) {
                  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];
    
                    compute_face_boundary_values[face_id] = value_list[i_face];
                  }
                }
              },
              boundary_condition);
          }
          return compute_face_boundary_values;
        }();
    
        const FaceValue<const double> face_boundary_prev_values = [&] {
          FaceValue<double> compute_face_boundary_values{mesh->connectivity()};
          FaceValue<double> sum_mes_l{mesh->connectivity()};
          compute_face_boundary_values.fill(0);
          sum_mes_l.fill(0);
          for (const auto& boundary_condition : boundary_prev_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& face_value_list = bc.faceValueList();
    
                  for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
                    const FaceId face_id = face_list[i_face];
    
                    compute_face_boundary_values[face_id] = face_value_list[i_face];
                  }
                } else if constexpr (std::is_same_v<T, NeumannBoundaryCondition>) {
                  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];
    
                    compute_face_boundary_values[face_id] = value_list[i_face];
                  }
                }
              },
              boundary_condition);
          }
          return compute_face_boundary_values;
        }();
    
        const NodeValue<const double> node_boundary_values = [&] {
          NodeValue<double> compute_node_boundary_values{mesh->connectivity()};
          NodeValue<double> sum_mes_l{mesh->connectivity()};
          compute_node_boundary_values.fill(0);
          sum_mes_l.fill(0);
          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>) {
                  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];
                    const auto& face_nodes = face_to_node_matrix[face_id];
                    for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
                      const NodeId node_id = face_nodes[i_node];
                      if (not node_is_dirichlet[node_id]) {
                        if (node_is_angle[node_id]) {
                          const auto& node_to_face = node_to_face_matrix[node_id];
                          for (size_t i_face_node = 0; i_face_node < node_to_face.size(); ++i_face_node) {
                            const FaceId face_node_id = node_to_face[i_face_node];
                            if (face_id == face_node_id) {
                              compute_node_boundary_values[node_id] +=
                                value_list[i_face] * mes_l[face_id] * normal_angle_base[node_id][i_face_node];
                            }
                          }
                        } else {
                          compute_node_boundary_values[node_id] += value_list[i_face] * mes_l[face_id];
                          sum_mes_l[node_id] += mes_l[face_id];
                        }
                      }
                    }
                  }
    
                } else if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
                  const auto& node_list       = bc.nodeList();
                  const auto& node_value_list = bc.nodeValueList();
    
                  for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
                    const NodeId node_id = node_list[i_node];
    
                    compute_node_boundary_values[node_id] = node_value_list[i_node];
                  }
                }
              },
              boundary_condition);
          }
          for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
            if ((not node_is_dirichlet[node_id]) && (node_is_neumann[node_id]) and not node_is_angle[node_id]) {
              compute_node_boundary_values[node_id] /= sum_mes_l[node_id];
            }
            // std::cout << node_id << "  " << compute_node_boundary_values[node_id] << "\n";
          }
          return compute_node_boundary_values;
        }();
    
        const NodeValue<const double> node_boundary_prev_values = [&] {
          NodeValue<double> compute_node_boundary_values{mesh->connectivity()};
          NodeValue<double> sum_mes_l{mesh->connectivity()};
          compute_node_boundary_values.fill(0);
          sum_mes_l.fill(0);
          for (const auto& boundary_condition : boundary_prev_condition_list) {
            std::visit(
              [&](auto&& bc) {
                using T = std::decay_t<decltype(bc)>;
                if constexpr (std::is_same_v<T, NeumannBoundaryCondition>) {
                  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];
                    const auto& face_nodes = face_to_node_matrix[face_id];
                    for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
                      const NodeId node_id = face_nodes[i_node];
                      if (not node_is_dirichlet[node_id]) {
                        if (node_is_angle[node_id]) {
                          const auto& node_to_face = node_to_face_matrix[node_id];
                          for (size_t i_face_node = 0; i_face_node < node_to_face.size(); ++i_face_node) {
                            const FaceId face_node_id = node_to_face[i_face_node];
                            if (face_id == face_node_id) {
                              compute_node_boundary_values[node_id] +=
                                value_list[i_face] * mes_l[face_id] * normal_angle_base[node_id][i_face_node];
                            }
                          }
                        } else {
                          compute_node_boundary_values[node_id] += value_list[i_face] * mes_l[face_id];
                          sum_mes_l[node_id] += mes_l[face_id];
                        }
                      }
                    }
                  }
                } else if constexpr (std::is_same_v<T, DirichletBoundaryCondition>) {
                  const auto& node_list       = bc.nodeList();
                  const auto& node_value_list = bc.nodeValueList();
    
                  for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
                    const NodeId node_id = node_list[i_node];
    
                    compute_node_boundary_values[node_id] = node_value_list[i_node];
                  }
                }
              },
              boundary_condition);
          }
          for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
            if ((not node_is_dirichlet[node_id]) && (node_is_neumann[node_id]) and not node_is_angle[node_id]) {
              compute_node_boundary_values[node_id] /= sum_mes_l[node_id];
            }
          }
          return compute_node_boundary_values;
        }();
    
        const NodeValue<const TinyMatrix<Dimension>> node_betar = [&] {
          NodeValue<TinyMatrix<Dimension>> beta{mesh->connectivity()};
          beta.fill(zero);
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              const auto& node_local_number_in_its_cell = node_local_numbers_in_their_cells.itemArray(node_id);
              const auto& node_to_cell                  = node_to_cell_matrix[node_id];
              beta[node_id]                             = zero;
              for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
                const CellId cell_id      = node_to_cell[i_cell];
                const unsigned int i_node = node_local_number_in_its_cell[i_cell];
                beta[node_id] += tensorProduct(Cjr(cell_id, i_node), xr[node_id] - xj[cell_id]);
              }
            });
          return beta;
        }();
    
        const NodeValue<const TinyMatrix<Dimension>> kappar_invBetar = [&] {
          NodeValue<TinyMatrix<Dimension>> kappa_invBeta{mesh->connectivity()};
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              if (not node_is_corner[node_id]) {
                kappa_invBeta[node_id] = node_kappar[node_id] * inverse(node_betar[node_id]);
              }
            });
          return kappa_invBeta;
        }();
    
        const NodeValue<const TinyMatrix<Dimension>> corner_betar = [&] {
          NodeValue<TinyMatrix<Dimension>> beta{mesh->connectivity()};
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              if (node_is_corner[node_id]) {
                const auto& node_local_number_in_its_cell = node_local_numbers_in_their_cells.itemArray(node_id);
                const auto& node_to_cell                  = node_to_cell_matrix[node_id];
    
                size_t i_cell             = 0;
                const CellId cell_id      = node_to_cell[i_cell];
                const unsigned int i_node = node_local_number_in_its_cell[i_cell];
    
                const auto& cell_nodes  = cell_to_node_matrix[cell_id];
                const NodeId node_id_p1 = cell_to_node_matrix[cell_id][(i_node + 1) % cell_nodes.size()];
                const NodeId node_id_p2 = cell_to_node_matrix[cell_id][(i_node + 2) % cell_nodes.size()];
                const NodeId node_id_m1 = cell_to_node_matrix[cell_id][(i_node - 1) % cell_nodes.size()];
    
                const TinyVector<Dimension> xj1 = 1. / 3. * (xr[node_id] + xr[node_id_m1] + xr[node_id_p2]);
                const TinyVector<Dimension> xj2 = 1. / 3. * (xr[node_id] + xr[node_id_p2] + xr[node_id_p1]);
    
                const TinyVector<Dimension> xrm1 = xr[node_id_m1];
                const TinyVector<Dimension> xrp1 = xr[node_id_p1];
                const TinyVector<Dimension> xrp2 = xr[node_id_p2];
    
                TinyVector<Dimension> Cjr1;
                TinyVector<Dimension> Cjr2;
    
                if (Dimension == 2) {
                  Cjr1[0] = -0.5 * (xrm1[1] - xrp2[1]);
                  Cjr1[1] = 0.5 * (xrm1[0] - xrp2[0]);
                  Cjr2[0] = -0.5 * (xrp2[1] - xrp1[1]);
                  Cjr2[1] = 0.5 * (xrp2[0] - xrp1[0]);
                } else {
                  throw NotImplementedError("The scheme is not implemented in this dimension.");
                }
    
                beta[node_id] = tensorProduct(Cjr1, (xr[node_id] - xj1)) + tensorProduct(Cjr2, (xr[node_id] - xj2));
              }
            });
          return beta;
        }();
    
        const NodeValue<const TinyMatrix<Dimension>> corner_kappar_invBetar = [&] {
          NodeValue<TinyMatrix<Dimension>> kappa_invBeta{mesh->connectivity()};
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              if (node_is_corner[node_id]) {
                kappa_invBeta[node_id] = node_kappar[node_id] * inverse(corner_betar[node_id]);
              }
            });
          return kappa_invBeta;
        }();
    
        const NodeValue<const TinyVector<Dimension>> sum_Cjr = [&] {
          NodeValue<TinyVector<Dimension>> compute_sum_Cjr{mesh->connectivity()};
          compute_sum_Cjr.fill(zero);
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              const auto& node_to_cell                  = node_to_cell_matrix[node_id];
              const auto& node_local_number_in_its_cell = node_local_numbers_in_their_cells.itemArray(node_id);
              compute_sum_Cjr[node_id]                  = zero;
              for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
                const CellId cell_id = node_to_cell[i_cell];
                const size_t i_node  = node_local_number_in_its_cell[i_cell];
                compute_sum_Cjr[node_id] += Cjr(cell_id, i_node);
              }
            });
          return compute_sum_Cjr;
        }();
    
        const NodeValue<const TinyVector<Dimension>> v = [&] {
          NodeValue<TinyVector<Dimension>> compute_v{mesh->connectivity()};
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              if (is_boundary_node[node_id]) {
                compute_v[node_id] = 1. / l2Norm(node_kappar[node_id] * exterior_normal[node_id]) * node_kappar[node_id] *
                                     exterior_normal[node_id];
              }
            });
          return compute_v;
        }();
    
        const NodeValuePerCell<const double> theta = [&] {
          NodeValuePerCell<double> compute_theta{mesh->connectivity()};
          parallel_for(
            mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
              const auto& cell_nodes = cell_to_node_matrix[cell_id];
              for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) {
                const NodeId node_id = cell_nodes[i_node];
                if (is_boundary_node[node_id] && not node_is_corner[node_id]) {
                  compute_theta(cell_id, i_node) = dot(inverse(node_betar[node_id]) * Cjr(cell_id, i_node), v[node_id]);
                }
              }
            });
          return compute_theta;
        }();
    
        const NodeValue<const double> sum_theta = [&] {
          NodeValue<double> compute_sum_theta{mesh->connectivity()};
          compute_sum_theta.fill(0);
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              if ((is_boundary_node[node_id]) && (not node_is_corner[node_id])) {
                const auto& node_to_cell                  = node_to_cell_matrix[node_id];
                const auto& node_local_number_in_its_cell = node_local_numbers_in_their_cells.itemArray(node_id);
                compute_sum_theta[node_id]                = 0;
                for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
                  const CellId cell_id = node_to_cell[i_cell];
                  const size_t i_node  = node_local_number_in_its_cell[i_cell];
                  compute_sum_theta[node_id] += theta(cell_id, i_node);
                }
              }
            });
          return compute_sum_theta;
        }();
    
        const Array<int> non_zeros{mesh->numberOfCells()};
        non_zeros.fill(4);   // Modif pour optimiser
        CRSMatrixDescriptor<double> S1(mesh->numberOfCells(), mesh->numberOfCells(), non_zeros);
        CRSMatrixDescriptor<double> S2(mesh->numberOfCells(), mesh->numberOfCells(), non_zeros);
    
        for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
          const auto& node_to_cell = node_to_cell_matrix[node_id];
          for (size_t i_cell_j = 0; i_cell_j < node_to_cell.size(); ++i_cell_j) {
            const CellId cell_id_j = node_to_cell[i_cell_j];
            for (size_t i_cell_k = 0; i_cell_k < node_to_cell.size(); ++i_cell_k) {
              const CellId cell_id_k   = node_to_cell[i_cell_k];
              S1(cell_id_j, cell_id_k) = 0;
              S2(cell_id_j, cell_id_k) = 0;
            }
          }
        }
    
        for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
          const auto& node_to_cell                   = node_to_cell_matrix[node_id];
          const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(node_id);
    
          if (not is_boundary_node[node_id]) {
            for (size_t i_cell_j = 0; i_cell_j < node_to_cell.size(); ++i_cell_j) {
              const CellId cell_id_j = node_to_cell[i_cell_j];
              const size_t i_node_j  = node_local_number_in_its_cells[i_cell_j];
    
              for (size_t i_cell_k = 0; i_cell_k < node_to_cell.size(); ++i_cell_k) {
                const CellId cell_id_k = node_to_cell[i_cell_k];
                const size_t i_node_k  = node_local_number_in_its_cells[i_cell_k];
    
                S1(cell_id_j, cell_id_k) +=
                  (1 - lambda) * dt * (1. - cn_coeff / 2.) *
                  dot(kappar_invBetar[node_id] * Cjr(cell_id_k, i_node_k), Cjr(cell_id_j, i_node_j));
                S2(cell_id_j, cell_id_k) -=
                  (1 - lambda) * dt * (cn_coeff / 2.) *
                  dot(kappar_invBetar[node_id] * Cjr(cell_id_k, i_node_k), Cjr(cell_id_j, i_node_j));
    
                const auto& cell_to_face = cell_to_face_matrix[cell_id_j];
    
                for (size_t i_face = 0; i_face < cell_to_face.size(); ++i_face) {
                  const FaceId face_id     = cell_to_face[i_face];
                  const auto& face_to_node = face_to_node_matrix[face_id];
    
                  if (face_to_node[0] == node_id or face_to_node[1] == node_id) {
                    S1(cell_id_j, cell_id_k) +=
                      lambda * dt * (1. - cn_coeff / 2.) * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                      dot(inverse(node_betar[node_id]) * Cjr(cell_id_k, i_node_k), xj1_xj2_orth(cell_id_j, i_face));
                    S2(cell_id_j, cell_id_k) -=
                      lambda * dt * (cn_coeff / 2.) * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                      dot(inverse(node_betar[node_id]) * Cjr(cell_id_k, i_node_k), xj1_xj2_orth(cell_id_j, i_face));
                    // if (normal_face_base(cell_id_j, i_face)[0] * xj1_xj2_orth(cell_id_j, i_face) +
                    //       normal_face_base(cell_id_j, i_face)[1] * xj1_xj2(cell_id_j, i_face) !=
                    //     nl[face_id]) {
                    //   std::cout << "(xj1,xj2) = (" << xj[cell_id_k] << "," << xj[cell_id_j] << ") \n"
                    //             << normal_face_base(cell_id_j, i_face)[0] * xj1_xj2_orth(cell_id_j, i_face) +
                    //                  normal_face_base(cell_id_j, i_face)[1] * xj1_xj2(cell_id_j, i_face)
                    //             << " = " << nl[face_id] << "\n"
                    //             << "\n";
                    // }
                    // std::cout << S1(cell_id_j, cell_id_k) << "\n";
                  }
                }
              }
            }
          } else if ((node_is_neumann[node_id]) && (not node_is_corner[node_id]) && (not node_is_dirichlet[node_id])) {
            const TinyMatrix<Dimension> I   = identity;
            const TinyVector<Dimension> n   = exterior_normal[node_id];
            const TinyMatrix<Dimension> nxn = tensorProduct(n, n);
            const TinyMatrix<Dimension> Q   = I - nxn;
    
            for (size_t i_cell_j = 0; i_cell_j < node_to_cell.size(); ++i_cell_j) {
              const CellId cell_id_j = node_to_cell[i_cell_j];
              const size_t i_node_j  = node_local_number_in_its_cells[i_cell_j];
    
              for (size_t i_cell_k = 0; i_cell_k < node_to_cell.size(); ++i_cell_k) {
                const CellId cell_id_k = node_to_cell[i_cell_k];
                const size_t i_node_k  = node_local_number_in_its_cells[i_cell_k];
    
                S1(cell_id_j, cell_id_k) +=
                  (1 - lambda) * dt * (1. - cn_coeff / 2.) *
                  dot(kappar_invBetar[node_id] *
                        (Cjr(cell_id_k, i_node_k) - theta(cell_id_k, i_node_k) / sum_theta[node_id] * sum_Cjr[node_id]),
                      Q * Cjr(cell_id_j, i_node_j));
                S2(cell_id_j, cell_id_k) -=
                  (1 - lambda) * dt * (cn_coeff / 2.) *
                  dot(kappar_invBetar[node_id] *
                        (Cjr(cell_id_k, i_node_k) - theta(cell_id_k, i_node_k) / sum_theta[node_id] * sum_Cjr[node_id]),
                      Q * Cjr(cell_id_j, i_node_j));
              }
            }
          } else if ((node_is_dirichlet[node_id]) && (not node_is_corner[node_id])) {
            for (size_t i_cell_j = 0; i_cell_j < node_to_cell.size(); ++i_cell_j) {
              const CellId cell_id_j = node_to_cell[i_cell_j];
              const size_t i_node_j  = node_local_number_in_its_cells[i_cell_j];
    
              for (size_t i_cell_k = 0; i_cell_k < node_to_cell.size(); ++i_cell_k) {
                const CellId cell_id_k = node_to_cell[i_cell_k];
                const size_t i_node_k  = node_local_number_in_its_cells[i_cell_k];
    
                S1(cell_id_j, cell_id_k) +=
                  (1 - lambda) * dt * (1. - cn_coeff / 2.) *
                  dot(kappar_invBetar[node_id] * Cjr(cell_id_k, i_node_k), Cjr(cell_id_j, i_node_j));
                S2(cell_id_j, cell_id_k) -=
                  (1 - lambda) * dt * (cn_coeff / 2.) *
                  dot(kappar_invBetar[node_id] * Cjr(cell_id_k, i_node_k), Cjr(cell_id_j, i_node_j));
    
                const auto& cell_to_face = cell_to_face_matrix[cell_id_j];
    
                for (size_t i_face = 0; i_face < cell_to_face.size(); ++i_face) {
                  const FaceId face_id     = cell_to_face[i_face];
                  const auto& face_to_node = face_to_node_matrix[face_id];
    
                  if (face_to_node[0] == node_id or face_to_node[1] == node_id) {
                    S1(cell_id_j, cell_id_k) +=
                      lambda * dt * (1. - cn_coeff / 2.) * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                      dot(inverse(node_betar[node_id]) * Cjr(cell_id_k, i_node_k), xj1_xj2_orth(cell_id_j, i_face));
                    S2(cell_id_j, cell_id_k) -=
                      lambda * dt * (cn_coeff / 2.) * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                      dot(inverse(node_betar[node_id]) * Cjr(cell_id_k, i_node_k), xj1_xj2_orth(cell_id_j, i_face));
                    // if (normal_face_base(cell_id_j, i_face)[0] * xj1_xj2_orth(cell_id_j, i_face) +
                    //       normal_face_base(cell_id_j, i_face)[1] * xj1_xj2(cell_id_j, i_face) !=
                    //     nl[face_id]) {
                    //   std::cout << "(xj1,xj2) = (" << xj[cell_id_k] << "," << xj[cell_id_j] << ") \n"
                    //             << normal_face_base(cell_id_j, i_face)[0] * xj1_xj2_orth(cell_id_j, i_face) +
                    //                  normal_face_base(cell_id_j, i_face)[1] * xj1_xj2(cell_id_j, i_face)
                    //             << " = " << nl[face_id] << "\n"
                    //             << "\n";
                    // }
                  }
                }
              }
            }
          } else if (node_is_dirichlet[node_id] && node_is_corner[node_id]) {
            for (size_t i_cell_j = 0; i_cell_j < node_to_cell.size(); ++i_cell_j) {
              const CellId cell_id_j = node_to_cell[i_cell_j];
              const size_t i_node_j  = node_local_number_in_its_cells[i_cell_j];
    
              for (size_t i_cell_k = 0; i_cell_k < node_to_cell.size(); ++i_cell_k) {
                const CellId cell_id_k = node_to_cell[i_cell_k];
                const size_t i_node_k  = node_local_number_in_its_cells[i_cell_k];
    
                S1(cell_id_j, cell_id_k) +=
                  (1 - lambda) * dt * (1. - cn_coeff / 2.) *
                  dot(corner_kappar_invBetar[node_id] * Cjr(cell_id_k, i_node_k), Cjr(cell_id_j, i_node_j));
                S2(cell_id_j, cell_id_k) -=
                  (1 - lambda) * dt * (cn_coeff / 2.) *
                  dot(corner_kappar_invBetar[node_id] * Cjr(cell_id_k, i_node_k), Cjr(cell_id_j, i_node_j));
    
                const auto& cell_to_face = cell_to_face_matrix[cell_id_j];
    
                for (size_t i_face = 0; i_face < cell_to_face.size(); ++i_face) {
                  const FaceId face_id     = cell_to_face[i_face];
                  const auto& face_to_node = face_to_node_matrix[face_id];
    
                  if (face_to_node[0] == node_id or face_to_node[1] == node_id) {
                    S1(cell_id_j, cell_id_k) +=
                      lambda * dt * (1. - cn_coeff / 2.) * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                      dot(inverse(corner_betar[node_id]) * Cjr(cell_id_k, i_node_k), xj1_xj2_orth(cell_id_j, i_face));
    
                    S2(cell_id_j, cell_id_k) -=
                      lambda * dt * (cn_coeff / 2.) * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                      dot(inverse(corner_betar[node_id]) * Cjr(cell_id_k, i_node_k), xj1_xj2_orth(cell_id_j, i_face));
    
                    // if (normal_face_base(cell_id_j, i_face)[0] * xj1_xj2_orth(cell_id_j, i_face) +
                    //       normal_face_base(cell_id_j, i_face)[1] * xj1_xj2(cell_id_j, i_face) !=
                    //     nl[face_id]) {
                    //   std::cout << "(xj1,xj2) = (" << xj[cell_id_k] << "," << xj[cell_id_j] << ") \n"
                    //             << normal_face_base(cell_id_j, i_face)[0] * xj1_xj2_orth(cell_id_j, i_face) +
                    //                  normal_face_base(cell_id_j, i_face)[1] * xj1_xj2(cell_id_j, i_face)
                    //             << " = " << nl[face_id] << "\n"
                    //             << "\n";
                    // }
                  }
                }
              }
            }
          }
        }
    
        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
          const auto& face_to_cell                   = face_to_cell_matrix[face_id];
          const auto& face_local_number_in_its_cells = face_local_numbers_in_their_cells.itemArray(face_id);
          if (not is_boundary_face[face_id]) {
            for (size_t i_cell_j = 0; i_cell_j < face_to_cell.size(); ++i_cell_j) {
              const CellId cell_id_j = face_to_cell[i_cell_j];
              const size_t i_face    = face_local_number_in_its_cells[i_cell_j];
    
              for (size_t i_cell_k = 0; i_cell_k < face_to_cell.size(); ++i_cell_k) {
                const CellId cell_id_k = face_to_cell[i_cell_k];
    
                if (cell_id_j == cell_id_k) {
                  S1(cell_id_j, cell_id_k) +=
                    lambda * dt * (1. - cn_coeff / 2.) * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[1];
                  S2(cell_id_j, cell_id_k) -=
                    lambda * dt * (cn_coeff / 2.) * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[1];
                } else {
                  S1(cell_id_j, cell_id_k) -=
                    lambda * dt * (1. - cn_coeff / 2.) * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[1];
                  S2(cell_id_j, cell_id_k) +=
                    lambda * dt * (cn_coeff / 2.) * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[1];
                }
              }
            }
          } else if (face_is_dirichlet[face_id]) {
            for (size_t i_cell_j = 0; i_cell_j < face_to_cell.size(); ++i_cell_j) {
              const CellId cell_id_j = face_to_cell[i_cell_j];
              const size_t i_face    = face_local_number_in_its_cells[i_cell_j];
              S1(cell_id_j, cell_id_j) +=
                lambda * dt * (1. - cn_coeff / 2.) * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[1];
              S2(cell_id_j, cell_id_j) -=
                lambda * dt * (cn_coeff / 2.) * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[1];
            }
          }
        };
    
        // std::cout << "S1 = " << S1 << "\n";
    
        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
          S1(cell_id, cell_id) += Vj[cell_id];
          S2(cell_id, cell_id) += Vj[cell_id];
        }
    
        CellValue<const double> fj      = f->cellValues();
        CellValue<const double> fj_prev = f_prev->cellValues();
        CellValue<const double> Pj      = P->cellValues();
    
        Vector<double> Ph{mesh->numberOfCells()};
        Ph = zero;
        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
          Ph[cell_id] = Pj[cell_id];
        };
    
        // std::cout << "P = " << Ph << "\n";
    
        CRSMatrix A1{S1.getCRSMatrix()};
        CRSMatrix A2{S2.getCRSMatrix()};
    
        Vector<double> b{mesh->numberOfCells()};
        b = zero;
        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
          b[cell_id] = Vj[cell_id] * fj[cell_id];
        };
    
        Vector<double> b_prev{mesh->numberOfCells()};
        b_prev = zero;
        for (CellId cell_id = 0; cell_id < mesh->numberOfCells(); ++cell_id) {
          b_prev[cell_id] = Vj[cell_id] * fj_prev[cell_id];
        };
    
        for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
          const auto& node_to_cell                   = node_to_cell_matrix[node_id];
          const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(node_id);
          const TinyMatrix<Dimension> I              = identity;
          const TinyVector<Dimension> n              = exterior_normal[node_id];
          const TinyMatrix<Dimension> nxn            = tensorProduct(n, n);
          const TinyMatrix<Dimension> Q              = I - nxn;
          if ((node_is_neumann[node_id]) && (not node_is_dirichlet[node_id])) {
            if ((is_boundary_node[node_id]) and (not node_is_corner[node_id])) {
              for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
                const CellId cell_id = node_to_cell[i_cell];
                const size_t i_node  = node_local_number_in_its_cells[i_cell];
    
                b[cell_id] += (1 - lambda) * node_boundary_values[node_id] *
                              (1. / (sum_theta[node_id] * l2Norm(node_kappar[node_id] * n)) *
                                 dot(kappar_invBetar[node_id] * sum_Cjr[node_id], Q * Cjr(cell_id, i_node)) +
                               dot(Cjr(cell_id, i_node), n));
                b_prev[cell_id] += (1 - lambda) * node_boundary_prev_values[node_id] *
                                   (1. / (sum_theta[node_id] * l2Norm(node_kappar[node_id] * n)) *
                                      dot(kappar_invBetar[node_id] * sum_Cjr[node_id], Q * Cjr(cell_id, i_node)) +
                                    dot(Cjr(cell_id, i_node), n));
              }
            } else if (node_is_corner[node_id]) {
              const auto& node_to_face = node_to_face_matrix[node_id];
              const CellId cell_id     = node_to_cell[0];
              double sum_mes_l         = 0;
              for (size_t i_face = 0; i_face < node_to_face.size(); ++i_face) {
                FaceId face_id = node_to_face[i_face];
                sum_mes_l += mes_l[face_id];
              }
              b[cell_id] += (1 - lambda) * 0.5 * node_boundary_values[node_id] * sum_mes_l;
              b_prev[cell_id] += (1 - lambda) * 0.5 * node_boundary_prev_values[node_id] * sum_mes_l;
            }
    
          } else if (node_is_dirichlet[node_id]) {
            if (not node_is_corner[node_id]) {
              for (size_t i_cell_j = 0; i_cell_j < node_to_cell.size(); ++i_cell_j) {
                const CellId cell_id_j = node_to_cell[i_cell_j];
                const size_t i_node_j  = node_local_number_in_its_cells[i_cell_j];
    
                for (size_t i_cell_k = 0; i_cell_k < node_to_cell.size(); ++i_cell_k) {
                  const CellId cell_id_k = node_to_cell[i_cell_k];
                  const size_t i_node_k  = node_local_number_in_its_cells[i_cell_k];
    
                  b[cell_id_j] +=
                    (1 - lambda) * dot(node_boundary_values[node_id] * kappar_invBetar[node_id] * Cjr(cell_id_k, i_node_k),
                                       Cjr(cell_id_j, i_node_j));
                  b_prev[cell_id_j] += (1 - lambda) * dot(node_boundary_prev_values[node_id] * kappar_invBetar[node_id] *
                                                            Cjr(cell_id_k, i_node_k),
                                                          Cjr(cell_id_j, i_node_j));
                }
                const auto& cell_to_face = cell_to_face_matrix[cell_id_j];
    
                for (size_t i_face = 0; i_face < cell_to_face.size(); ++i_face) {
                  const FaceId face_id     = cell_to_face[i_face];
                  const auto& face_to_node = face_to_node_matrix[face_id];
                  if (is_boundary_face[face_id]) {
                    if (face_to_node[0] == node_id or face_to_node[1] == node_id) {
                      b[cell_id_j] += lambda * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                                      node_boundary_values[node_id] *
                                      dot(inverse(node_betar[node_id]) * sum_Cjr[node_id], xj1_xj2_orth(cell_id_j, i_face));
                      // std::cout << "xl - xj2 = " << xl[face_id] -
                      b_prev[cell_id_j] +=
                        lambda * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                        node_boundary_prev_values[node_id] *
                        dot(inverse(node_betar[node_id]) * sum_Cjr[node_id], xj1_xj2_orth(cell_id_j, i_face));
                    }
                  }
                }
              }
    
            } else if (node_is_corner[node_id]) {
              for (size_t i_cell_j = 0; i_cell_j < node_to_cell.size(); ++i_cell_j) {
                const CellId cell_id_j = node_to_cell[i_cell_j];
                const size_t i_node_j  = node_local_number_in_its_cells[i_cell_j];
    
                for (size_t i_cell_k = 0; i_cell_k < node_to_cell.size(); ++i_cell_k) {
                  const CellId cell_id_k = node_to_cell[i_cell_k];
                  const size_t i_node_k  = node_local_number_in_its_cells[i_cell_k];
    
                  b[cell_id_j] += (1 - lambda) * dot(node_boundary_values[node_id] * corner_kappar_invBetar[node_id] *
                                                       Cjr(cell_id_k, i_node_k),
                                                     Cjr(cell_id_j, i_node_j));
                  b_prev[cell_id_j] += (1 - lambda) * dot(node_boundary_prev_values[node_id] *
                                                            corner_kappar_invBetar[node_id] * Cjr(cell_id_k, i_node_k),
                                                          Cjr(cell_id_j, i_node_j));
                }
                const auto& cell_to_face = cell_to_face_matrix[cell_id_j];
    
                for (size_t i_face = 0; i_face < cell_to_face.size(); ++i_face) {
                  const FaceId face_id     = cell_to_face[i_face];
                  const auto& face_to_node = face_to_node_matrix[face_id];
                  if (is_boundary_face[face_id]) {
                    if (face_to_node[0] == node_id or face_to_node[1] == node_id) {
                      b[cell_id_j] +=
                        lambda * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                        node_boundary_values[node_id] *
                        dot(inverse(corner_betar[node_id]) * sum_Cjr[node_id], xj1_xj2_orth(cell_id_j, i_face));
                      b_prev[cell_id_j] +=
                        lambda * 0.5 * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[0] *
                        node_boundary_prev_values[node_id] *
                        dot(inverse(corner_betar[node_id]) * sum_Cjr[node_id], xj1_xj2_orth(cell_id_j, i_face));
                    }
                  }
                }
              }
            }
          }
        };
    
        for (FaceId face_id = 0; face_id < mesh->numberOfFaces(); ++face_id) {
          const auto& face_to_cell                   = face_to_cell_matrix[face_id];
          const auto& face_local_number_in_its_cells = face_local_numbers_in_their_cells.itemArray(face_id);
          if (face_is_dirichlet[face_id]) {
            for (size_t i_cell_j = 0; i_cell_j < face_to_cell.size(); ++i_cell_j) {
              const CellId cell_id_j = face_to_cell[i_cell_j];
              const size_t i_face    = face_local_number_in_its_cells[i_cell_j];
    
              b[cell_id_j] +=
                lambda * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[1] * face_boundary_values[face_id];
              b_prev[cell_id_j] +=
                lambda * mes_l[face_id] * normal_face_base(cell_id_j, i_face)[1] * face_boundary_prev_values[face_id];
            }
          } else if (face_is_neumann[face_id]) {
            for (size_t i_cell_j = 0; i_cell_j < face_to_cell.size(); ++i_cell_j) {
              const CellId cell_id_j = face_to_cell[i_cell_j];
    
              b[cell_id_j] += lambda * mes_l[face_id] * face_boundary_values[face_id];
              b_prev[cell_id_j] += lambda * mes_l[face_id] * face_boundary_prev_values[face_id];
            }
          }
        }
    
        Vector<double> T{mesh->numberOfCells()};
        T = zero;
    
        // Array<double> Ph2  = Ph;
        Vector<double> A2P = A2 * Ph;
    
        Vector<double> B{mesh->numberOfCells()};
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
            B[cell_id] = dt * ((1. - cn_coeff / 2.) * b[cell_id] + cn_coeff / 2. * b_prev[cell_id]) + A2P[cell_id];
          });
    
        NodeValue<TinyVector<Dimension>> ur{mesh->connectivity()};
        ur.fill(zero);
        for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); ++node_id) {
          const auto& node_to_cell                  = node_to_cell_matrix[node_id];
          const auto& node_local_number_in_its_cell = node_local_numbers_in_their_cells.itemArray(node_id);
          if (not is_boundary_node[node_id]) {
            for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
              const CellId cell_id = node_to_cell[i_cell];
              const size_t i_node  = node_local_number_in_its_cell[i_cell];
              ur[node_id] += Pj[cell_id] * Cjr(cell_id, i_node);
            }
            ur[node_id] = -inverse(node_betar[node_id]) * ur[node_id];
            // std::cout << node_id << "; ur = " << ur[node_id] << "\n";
          } else if (is_boundary_node[node_id] and node_is_dirichlet[node_id]) {
            for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
              const CellId cell_id = node_to_cell[i_cell];
              const size_t i_node  = node_local_number_in_its_cell[i_cell];
              ur[node_id] += (node_boundary_values[node_id] - Pj[cell_id]) * Cjr(cell_id, i_node);
            }
            if (not node_is_corner[node_id]) {
              ur[node_id] = inverse(node_betar[node_id]) * ur[node_id];
            } else {
              ur[node_id] = inverse(corner_betar[node_id]) * ur[node_id];
            }
            // std::cout << "bord : " << node_id << "; ur = " << ur[node_id] << "\n";
          }
        }
    
        LinearSolver solver;
        solver.solveLocalSystem(A1, T, B);
    
        const NodeValue<const double> primal_node_temperature = [&] {
          NodeValue<double> compute_primal_node_temperature{mesh->connectivity()};
          compute_primal_node_temperature.fill(0);
          parallel_for(
            mesh->numberOfNodes(), PUGS_LAMBDA(NodeId node_id) {
              // for (NodeId node_id = 0; node_id < mesh->numberOfNodes(); node_id++) {
              const auto& node_to_cell = node_to_cell_matrix[node_id];
              double sum_Vj            = 0;
              for (size_t i_cell = 0; i_cell < node_to_cell.size(); ++i_cell) {
                const CellId cell_id = node_to_cell[i_cell];
                compute_primal_node_temperature[node_id] += Vj[cell_id] * T[cell_id];
                sum_Vj += Vj[cell_id];
              }
              compute_primal_node_temperature[node_id] /= sum_Vj;
            });
          return compute_primal_node_temperature;
        }();
    
        const CellValue<const double> dual_node_temperature = [=] {
          CellValue<double> my_dual_temperature{dual_mesh->connectivity()};
          mapper->toDualCell(primal_node_temperature, my_dual_temperature);
          return my_dual_temperature;
        }();
    
        // m_solution     = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
        m_cell_temperature = std::make_shared<DiscreteFunctionP0<Dimension, double>>(mesh);
        m_node_temperature = std::make_shared<DiscreteFunctionP0<Dimension, double>>(dual_mesh);
        // auto& solution = *m_solution;
        auto& cell_temperature = *m_cell_temperature;
        auto& node_temperature = *m_node_temperature;
        parallel_for(
          mesh->numberOfCells(), PUGS_LAMBDA(CellId cell_id) { cell_temperature[cell_id] = T[cell_id]; });
        parallel_for(
          dual_mesh->numberOfCells(),
          PUGS_LAMBDA(CellId cell_id) { node_temperature[cell_id] = dual_node_temperature[cell_id]; });
      }
    };
    
    // std::shared_ptr<const IDiscreteFunction>
    // ScalarHybridSchemeHandler::solution() const
    // {
    //   return m_scheme->getSolution();
    // }
    std::tuple<std::shared_ptr<const IDiscreteFunction>, std::shared_ptr<const IDiscreteFunction>>
    ScalarHybridSchemeHandler::solution() const
    {
      return m_scheme->getSolution();
    }
    
    ScalarHybridSchemeHandler::ScalarHybridSchemeHandler(
      const std::shared_ptr<const IDiscreteFunction>& cell_k,
      const std::shared_ptr<const IDiscreteFunction>& f,
      const std::shared_ptr<const IDiscreteFunction>& f_prev,
      const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list,
      const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_prev_descriptor_list,
      const std::shared_ptr<const IDiscreteFunction>& P,
      const double& dt,
      const double& cn_coeff,
      const double& lambda)
    {
      const std::shared_ptr i_mesh = getCommonMesh({cell_k, f});
      if (not i_mesh) {
        throw NormalError("primal discrete functions are not defined on the same mesh");
      }
    
      checkDiscretizationType({cell_k, f}, DiscreteFunctionType::P0);
    
      switch (i_mesh->dimension()) {
      case 1: {
        throw NormalError("The scheme is not implemented in dimension 1");
        break;
      }
      case 2: {
        using MeshType                   = Mesh<Connectivity<2>>;
        using DiscreteTensorFunctionType = DiscreteFunctionP0<2, TinyMatrix<2>>;
        using DiscreteScalarFunctionType = DiscreteFunctionP0<2, double>;
    
        std::shared_ptr mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
    
        m_scheme =
          std::make_unique<ScalarHybridScheme<2>>(mesh, std::dynamic_pointer_cast<const DiscreteTensorFunctionType>(cell_k),
                                                  std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(f),
                                                  std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(f_prev),
                                                  bc_descriptor_list, bc_prev_descriptor_list,
                                                  std::dynamic_pointer_cast<const DiscreteScalarFunctionType>(P), dt,
                                                  cn_coeff, lambda);
        break;
      }
      case 3: {
        throw NormalError("The scheme is not implemented in dimension 3");
        break;
      }
      default: {
        throw UnexpectedError("invalid mesh dimension");
      }
      }
    }
    
    ScalarHybridSchemeHandler::~ScalarHybridSchemeHandler() = default;