diff --git a/src/algebra/EigenvalueSolver.cpp b/src/algebra/EigenvalueSolver.cpp
index b17e650af775530d3e61f4561b7676dccf1efdd4..fbe0e344bb8a0d011f30eabcd84d9406cdc1bee7 100644
--- a/src/algebra/EigenvalueSolver.cpp
+++ b/src/algebra/EigenvalueSolver.cpp
@@ -1,9 +1,10 @@
 #include <algebra/EigenvalueSolver.hpp>
+#include <cmath>
 #include <utils/pugs_config.hpp>
-
 #ifdef PUGS_HAS_SLEPC
 #include <algebra/PETScUtils.hpp>
 #include <slepc.h>
+#include <slepceps.h>
 #endif   // PUGS_HAS_SLEPC
 
 #ifdef PUGS_HAS_EIGEN3
@@ -233,6 +234,368 @@ EigenvalueSolver::_slepscComputeForSymmetricMatrix(const CRSMatrix<double>& A, S
 {
   Internals::slepscComputeForSymmetricMatrix(A, eigenvalues);
 }
+#endif   // PUGS_HAS_SLEPC
+
+template <typename T>
+PUGS_INLINE TinyMatrix<3, 3, T>
+EigenvalueSolver::swap(TinyMatrix<3, 3, T>& matrix, size_t i, size_t j) const
+{
+  TinyMatrix<3, 3, T> swapMat = matrix;
+  for (size_t k = 0; k < 3; k++) {
+    double valuei = matrix(i, k);
+    swapMat(i, k) = matrix(j, k);
+    swapMat(j, k) = valuei;
+  }
+  return swapMat;
+}
+
+template <typename T>
+PUGS_INLINE constexpr TinyMatrix<3, 3, T>
+EigenvalueSolver::rowReduce(const TinyMatrix<3, 3, T>& matrix, const double epsilon) const
+{
+  TinyMatrix<3, 3> redmat = matrix;
+  for (size_t i = 0; i < 3; ++i) {
+    size_t pivotRow = i;
+    while (pivotRow < 3 && std::fabs(redmat(pivotRow, i)) < epsilon) {
+      ++pivotRow;
+    }
+    if (pivotRow == 3) {
+      continue;
+    }
+    // std::cout << "before: "
+    //           << "i = " << i << " pivotRow = " << pivotRow << " " << redmat << "\n";
+    redmat = swap(redmat, i, pivotRow);
+    // std::cout << "after: " << redmat << "\n";
+
+    double pivotValue = redmat(i, i);
+    for (size_t j = i; j < 3; ++j) {
+      redmat(i, j) /= pivotValue;
+    }
+
+    for (size_t k = i + 1; k < 3; ++k) {
+      double factor = redmat(k, i);
+      for (size_t j = i; j < 3; ++j) {
+        redmat(k, j) -= factor * redmat(i, j);
+      }
+    }
+  }
+  return redmat;
+}
+
+TinyMatrix<3, 3>
+EigenvalueSolver::findEigenvector(const TinyMatrix<3, 3>& A,
+                                  const double eigenvalue,
+                                  const size_t space_size,
+                                  const double epsilon) const
+{
+  TinyMatrix<3, 3> eigenvectors = zero;
+  if (space_size == 3) {
+    // std::cout << "space_size = 3 "
+    //           << "\n";
+    return eigenvectors;
+  }
+  TinyMatrix<3, 3> B = A;
+
+  for (size_t i = 0; i < 3; ++i) {
+    B(i, i) -= eigenvalue;
+  }
+
+  // std::cout << " Avant " << B << "\n"
+  //           << "\n";
+
+  B = rowReduce(B, epsilon);
+
+  // std::cout << " Après " << B << "\n"
+  //           << "\n";
+  for (size_t i = 0; i < 3; ++i) {
+    bool isFreeVariableRow = true;
+    for (size_t j = 0; j < 3; ++j) {
+      if (std::fabs(B(i, j)) > epsilon) {
+        isFreeVariableRow = false;
+        // std::cout << " B(" << i << "," << j << ") = " << B(i, j) << "\n";
+        break;
+      }
+    }
+
+    if (isFreeVariableRow) {
+      TinyVector<3> eigenvector = zero;
+      eigenvector[i]            = 1.0;
+      // std::cout << i << " is free "
+      //           << "\n";
+      // std::cout << std::flush;
+      for (int k = i - 1; k >= 0; --k) {
+        double sum = 0.0;
+        for (size_t j = k + 1; j < 3; ++j) {
+          sum += B(k, j) * eigenvector[j];
+        }
+        eigenvector[k] = -sum;
+      }
+
+      eigenvector = 1. / l2Norm(eigenvector) * eigenvector;
+
+      for (size_t j = 0; j < 3; ++j) {
+        eigenvectors(i, j) = eigenvector[j];
+      }
+    }
+  }
+  // std::cout << " Before orthorgonalization " << B << "\n";
+  // std::cout << " Before orthorgonalization " << eigenvectors << "\n";
+
+  if (space_size == 2) {
+    TinyVector<3> first  = zero;
+    TinyVector<3> second = zero;
+    //      TinyVector<3> new_second =zero;
+    // size_t rank1 = 0;
+    size_t rank2 = 1;
+    for (size_t j = 0; j < 3; ++j) {
+      first[j]  = eigenvectors(0, j);
+      second[j] = eigenvectors(1, j);
+    }
+    if (l2Norm(first) < 1e-3) {
+      for (size_t j = 0; j < 3; ++j) {
+        first[j] = eigenvectors(2, j);
+        // rank1    = 2;
+      }
+    }
+    if (l2Norm(second) < 1e-3) {
+      for (size_t j = 0; j < 3; ++j) {
+        second[j] = eigenvectors(2, j);
+        rank2     = 2;
+      }
+    }
+    double scalprod             = dot(first, second);
+    double norm2                = dot(first, first);
+    TinyVector<3> normal_vector = second - scalprod / norm2 * first;
+    normal_vector               = 1. / l2Norm(normal_vector) * normal_vector;
+    for (size_t j = 0; j < 3; ++j) {
+      eigenvectors(rank2, j) = normal_vector[j];
+    }
+    // std::cout << " rank1 : " << rank1 << " rank2 " << rank2 << "\n";
+  }
+  // std::cout << "In findEigenvectors for eigenvalue " << eigenvalue << " eigenvectors " << eigenvectors << "\n";
+  return eigenvectors;
+}
+
+bool
+EigenvalueSolver::isDiagonal(const TinyMatrix<3, 3>& A, const double epsilon)
+{
+  bool isDiag = true;
+  for (size_t i = 0; i < 2; i++) {
+    for (size_t j = i + 1; j < 3; j++) {
+      isDiag = !(fabs(A(i, j)) > epsilon);
+      return isDiag;
+    }
+  }
+  return isDiag;
+}
+
+TinyMatrix<3, 3>
+EigenvalueSolver::findEigenvectors(const TinyMatrix<3, 3>& A, TinyVector<3> eigenvalues, const double epsilon) const
+{
+  TinyMatrix<3> eigenMatrix     = zero;
+  size_t count                  = 0;
+  TinyVector<3, int> space_size = zero;
+  for (size_t i = 0; i < 3; i++) {
+    space_size[i] = 1;
+    for (size_t j = i + 1; j < 3; j++) {
+      if (fabs(eigenvalues[i] - eigenvalues[j]) < epsilon) {
+        double temp        = eigenvalues[i + 1];
+        eigenvalues[i + 1] = eigenvalues[j];
+        eigenvalues[j]     = temp;
+        space_size[i] += 1;
+        i += 1;
+      }
+    }
+  }
+  // std::cout << "space_size: " << space_size << "\n";
+  // std::cout << "eigenvalues: " << eigenvalues << "\n";
+  size_t it = 0;
+  while (count < 3 && it < 3) {
+    TinyMatrix<3> Try = findEigenvector(A, eigenvalues[count], space_size[count], epsilon);
+    // std::cout << eigenvalues[count] << " Frobenius norm try: " << frobeniusNorm(Try) << "\n";
+    Assert(frobeniusNorm(Try) > 1e-3, " Error : no eigenvector corresponds to eigenvalue ");
+    if (frobeniusNorm(Try) < 1e-3) {
+      std::cout << " Error : no eigenvector corresponds to eigenvalue \n";
+      exit(0);
+    }
+
+    TinyVector<3> eigenvector = zero;
+    int count2                = 0;
+    for (size_t i = 0; i < 3; ++i) {
+      for (size_t j = 0; j < 3; j++) {
+        eigenvector[j] = Try(i, j);
+      }
+      // std::cout << " count, i: " << count << ", " << i << " l2Norm(eigenvector) " << l2Norm(eigenvector) << "\n";
+      if (l2Norm(eigenvector) > 1e-3) {
+        for (size_t j = 0; j < 3; j++) {
+          eigenMatrix(count, j) = eigenvector[j];
+        }
+        count += 1;
+        count2 += 1;
+      }
+    }
+    Assert(count2 == space_size[count - count2], " eigenvector space size is not what was expected ");
+    if (count2 != space_size[count - count2]) {
+      std::cout << " eigenvector space size is not what was expected \n";
+      exit(0);
+    }
+    it++;
+  }
+  // std ::cout << " eigenMatrix " << eigenMatrix << "\n";
+  return eigenMatrix;
+}
+
+std::tuple<TinyVector<3>, TinyMatrix<3>>
+EigenvalueSolver::findEigen(const TinyMatrix<3> A)
+{
+  constexpr TinyVector<3> eigenvalues0  = zero;
+  constexpr TinyMatrix<3> eigenvectors0 = identity;
+  const double epsilon                  = 1.e-6;   // * l2Norm(eigenvalues);
+  if (frobeniusNorm(A) < 1.e-15) {
+    // std::cout << " Frobenius norm 0 " << frobeniusNorm(A) << "\n";
+    return std::make_tuple(eigenvalues0, eigenvectors0);
+  }
+  TinyMatrix<3> C = 1. / frobeniusNorm(A) * A;
+  if (isDiagonal(C, epsilon)) {
+    std::cout << "Matrix C " << C << " is diagonal "
+              << "\n";
+    const TinyVector<3> eigenvalues(A(0, 0), A(1, 1), A(2, 2));
+    TinyMatrix<3> Diag = zero;
+    for (size_t i = 0; i < 3; ++i) {
+      Diag(i, i) = eigenvalues[i];
+    }
+    TinyMatrix<3> B = eigenvectors0 * Diag * transpose(eigenvectors0);
+    // std::cout << "\n"
+    //           << B << ", " << A << " Diff "
+    //           << " A - B " << 1 / frobeniusNorm(A) * (A - B) << "\n";
+    return std::make_tuple(eigenvalues, eigenvectors0);
+  }
+  const TinyVector<3> eigenvalues = _findEigenValues(C, epsilon);
+  // std::cout << std::setprecision(15) << eigenvalues << "\n";
+  TinyMatrix<3> Eigenmatrix = transpose(findEigenvectors(C, eigenvalues, epsilon));
+  TinyMatrix<3> Diag        = zero;
+  for (size_t i = 0; i < 3; ++i) {
+    Diag(i, i) = frobeniusNorm(A) * eigenvalues[i];
+  }
+  TinyMatrix<3> B = Eigenmatrix * Diag * transpose(Eigenmatrix);
+  // std::cout << "\n"
+  //           << B << ", " << A << " Diff "
+  //           << " A - B " << 1 / frobeniusNorm(A) * (A - B) << "\n";
+  return std::make_tuple(frobeniusNorm(A) * eigenvalues, Eigenmatrix);
+}
+TinyVector<3>
+EigenvalueSolver::_findEigenValues(const TinyMatrix<3>& A, const double epsilon) const
+{
+  // std::cout << " Matrix " << A << "\n";
+  TinyVector<3> eigenvalues(0., 0., 0.);
+  double b = -trace(A);
+  double c = 0.5 * (trace(A) * trace(A) - trace(A * A));
+  double d = -det(A);
+  Polynomial<3> P(d, c, b, 1.);
+  // std::cout << P << "\n";
+  Polynomial<2> Q(c, b, 1.);
+  if (fabs(d) > 1e-11) {
+    eigenvalues[0] = _findFirstRoot(P);
+    Polynomial<1> Q1(-eigenvalues[0], 1);
+    Polynomial<3> S;
+    Polynomial<3> R;
+    divide(P, Q1, S, R);
+    // std::cout << "Q1 : " << Q1 << "\n";
+    // std::cout << "R : " << R << "\n";
+    // std::cout << "S : " << S << "\n";
+    Q.coefficients()[0] = S.coefficients()[0];
+    Q.coefficients()[1] = S.coefficients()[1];
+    Q.coefficients()[2] = S.coefficients()[2];
+  }
+  TinyVector<2> last_eigenvalues = _findLastRoots(Q, epsilon);
+  eigenvalues[1]                 = last_eigenvalues[0];
+  eigenvalues[2]                 = last_eigenvalues[1];
+  TinyVector<3, int> space_size  = zero;
+  for (size_t i = 0; i < 3; i++) {
+    space_size[i] = 1;
+    for (size_t j = i + 1; j < 3; j++) {
+      if (eigenvalues[i] == eigenvalues[j]) {
+        double temp        = eigenvalues[i + 1];
+        eigenvalues[i + 1] = eigenvalues[j];
+        eigenvalues[j]     = temp;
+        space_size[i] += 1;
+        i += 1;
+      }
+    }
+  }
+  return eigenvalues;
+}
+
+double
+EigenvalueSolver::_findFirstRoot(Polynomial<3> P) const
+{
+  double guess     = -P.coefficients()[2] / 3.;
+  double old_guess = -P.coefficients()[2] / 3.;
+  // std::cout << " coefs P " << P.coefficients() << "\n";
+  //    double delta = pow(2.*P.coefficients()[2],2) - 4*3.*P.coefficients()[3]*P.coefficients()[1];
+  Polynomial<2> Q = derivative(P);
+  Polynomial<1> R = derivative(Q);
+  // size_t iteration = 0;
+  double residu    = 0.;
+  size_t iteration = 0;
+  do {
+    //      guess -= P(guess) / Q(guess);
+    old_guess = guess;
+    guess -= 2 * P(guess) * Q(guess) / (2 * Q(guess) * Q(guess) - P(guess) * R(guess));
+    // std::cout << "guess = " << guess << " old_guess " << old_guess << "\n";
+    //  std::cout << "g(guess) = " << Q(guess) << "\n";
+    residu = P(guess);
+    // std::cout << "residu = " << residu << " delta " << fabs(old_guess - guess) << "\n";
+    iteration += 1;
+  } while (((fabs(residu) > 1.e-16) or (fabs(old_guess - guess) > 1.e-10)) and (iteration < 10000));
+  if (iteration == 100) {
+    std::cout << " nb Newton iterations reached, residu " << residu << "\n";
+  }
+  return guess;
+}
+
+TinyVector<2>
+EigenvalueSolver::_findLastRoots(Polynomial<2> P, const double epsilon) const
+{
+  TinyVector<2> roots(0., 0.);
+  double delta = P.coefficients()[1] * P.coefficients()[1] - 4 * P.coefficients()[2] * P.coefficients()[0];
+  // if (fabs(delta) > epsilon) {
+  //   std::cout << "Find roots is only for symmetric matrix \n";
+  //   exit(0);
+  // }
+  Assert(delta > -epsilon, "Find roots is only for symmetric matrix");
+  if (fabs(delta) < epsilon) {
+    roots[0] = -P.coefficients()[1] / (2. * P.coefficients()[2]);
+    roots[1] = roots[0];
+  }
+  if (delta >= 0.) {
+    roots[0] = (-P.coefficients()[1] - std::sqrt(delta)) / (2. * P.coefficients()[2]);
+    roots[1] = (-P.coefficients()[1] + std::sqrt(delta)) / (2. * P.coefficients()[2]);
+  }
+  return roots;
+}
+
+TinyMatrix<3>
+EigenvalueSolver::computeExpForTinyMatrix(const TinyMatrix<3>& A)
+{
+  TinyMatrix<3> expA;
+  auto [eigenvalues, eigenvectors] = findEigen(A);
+  TinyMatrix<3> D                  = zero;
+  for (size_t i = 0; i < 3; ++i) {
+    if (std::abs(eigenvalues[i]) > 200) {
+      std::cout << "Warning: large eigenvalue for matrix " << A << " eigenvalues " << eigenvalues << "\n";
+      //      eigenvalues[i] = eigenvalues[i] / eigenvalues[i] * 200.;
+      //      exit(0);
+    }
+  }
+  for (size_t i = 0; i < 3; ++i) {
+    D(i, i) = std::exp(eigenvalues[i]);
+  }
+  expA = eigenvectors * D * transpose(eigenvectors);
+  return expA;
+}
+
+#ifdef PUGS_HAS_SLEPC
 
 void
 EigenvalueSolver::_slepscComputeForSymmetricMatrix(const DenseMatrixWrapper<double>& A, SmallArray<double>& eigenvalues)
diff --git a/src/algebra/EigenvalueSolver.hpp b/src/algebra/EigenvalueSolver.hpp
index 6567adcdc12caabae69baede83e74166696baaf0..98696152215164477c34e4743accb5ad4659e85d 100644
--- a/src/algebra/EigenvalueSolver.hpp
+++ b/src/algebra/EigenvalueSolver.hpp
@@ -8,6 +8,7 @@
 #include <algebra/SmallMatrix.hpp>
 #include <algebra/SmallVector.hpp>
 #include <algebra/TinyMatrix.hpp>
+#include <analysis/Polynomial.hpp>
 #include <utils/Exceptions.hpp>
 #include <utils/SmallArray.hpp>
 
@@ -127,6 +128,41 @@ class EigenvalueSolver
     }
   }
 
+  template <typename MatrixType>
+  void
+  computeExpForSymmetricMatrix([[maybe_unused]] const MatrixType& A, [[maybe_unused]] SmallMatrix<double>& expA)
+  {
+#ifdef PUGS_HAS_SLEPC
+    this->computeExpForSymmetricMatrix(PETScAijMatrixEmbedder{A}, expA);
+#else    // PUGS_HAS_SLEPC
+    throw NotImplementedError("SLEPc is required to solve eigenvalue problems");
+#endif   // PUGS_HAS_SLEPC
+  }
+
+  template <typename T>
+  PUGS_INLINE TinyMatrix<3, 3, T> swap(TinyMatrix<3, 3, T>& matrix, size_t i, size_t j) const;
+
+  template <typename T>
+  PUGS_INLINE constexpr TinyMatrix<3, 3, T> rowReduce(const TinyMatrix<3, 3, T>& matrix, const double epsilon) const;
+
+  TinyMatrix<3, 3> findEigenvector(const TinyMatrix<3, 3>& A,
+                                   const double eigenvalue,
+                                   const size_t space_size,
+                                   const double epsilon) const;
+
+  bool isDiagonal(const TinyMatrix<3, 3>& A, const double epsilon);
+
+  TinyMatrix<3, 3> findEigenvectors(const TinyMatrix<3, 3>& A, TinyVector<3> eigenvalues, const double epsilon) const;
+
+  std::tuple<TinyVector<3>, TinyMatrix<3>> findEigen(const TinyMatrix<3> A);
+  TinyVector<3> _findEigenValues(const TinyMatrix<3>& A, const double epsilon) const;
+
+  double _findFirstRoot(Polynomial<3> P) const;
+
+  TinyVector<2> _findLastRoots(Polynomial<2> P, const double epsilon) const;
+
+  TinyMatrix<3> computeExpForTinyMatrix(const TinyMatrix<3>& A);
+
   EigenvalueSolver(const EigenvalueSolverOptions& options = EigenvalueSolverOptions::default_options);
   ~EigenvalueSolver() = default;
 };
diff --git a/src/analysis/Polynomial.hpp b/src/analysis/Polynomial.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf716a246a4bfa81d2de207c0101f23ea4aa7c09
--- /dev/null
+++ b/src/analysis/Polynomial.hpp
@@ -0,0 +1,494 @@
+#ifndef POLYNOMIAL_HPP
+#define POLYNOMIAL_HPP
+
+#include <algebra/TinyVector.hpp>
+
+template <size_t N>
+class Polynomial
+{
+ private:
+  using Coefficients = TinyVector<N + 1, double>;
+  Coefficients m_coefficients;
+  static_assert(N >= 0, "Polynomial degree must be non-negative");
+
+ public:
+  PUGS_INLINE
+  constexpr size_t
+  degree() const
+  {
+    return N;
+  }
+
+  PUGS_INLINE
+  constexpr size_t
+  realDegree() const
+  {
+    for (size_t j = N; j > 0; j--) {
+      if (std::abs(coefficients()[j]) > 1.e-14) {
+        return j;
+      }
+    }
+    return 0;
+  }
+
+  PUGS_INLINE
+  constexpr const Coefficients&
+  coefficients() const
+  {
+    return m_coefficients;
+  }
+
+  PUGS_INLINE
+  constexpr Coefficients&
+  coefficients()
+  {
+    return m_coefficients;
+  }
+
+  PUGS_INLINE constexpr bool
+  operator==(const Polynomial& q) const
+  {
+    return m_coefficients == q.m_coefficients;
+  }
+
+  PUGS_INLINE
+  constexpr bool
+  operator!=(const Polynomial& q) const
+  {
+    return not(*this == q);
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial<std::max(M, N)>
+  operator+(const Polynomial<M>& Q) const
+  {
+    Polynomial<std::max(M, N)> P;
+    if constexpr (M > N) {
+      P.coefficients() = Q.coefficients();
+      for (size_t i = 0; i <= N; ++i) {
+        P.coefficients()[i] += coefficients()[i];
+      }
+    } else {
+      P.coefficients() = coefficients();
+      for (size_t i = 0; i <= M; ++i) {
+        P.coefficients()[i] += Q.coefficients()[i];
+      }
+    }
+    return P;
+  }
+
+  PUGS_INLINE constexpr Polynomial
+  operator-() const
+  {
+    return Polynomial{-m_coefficients};
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial<std::max(M, N)>
+  operator-(const Polynomial<M>& Q) const
+  {
+    Polynomial<std::max(M, N)> P;
+    if constexpr (M > N) {
+      P.coefficients() = -Q.coefficients();
+      for (size_t i = 0; i <= N; ++i) {
+        P.coefficients()[i] += coefficients()[i];
+      }
+    } else {
+      P.coefficients() = coefficients();
+      for (size_t i = 0; i <= M; ++i) {
+        P.coefficients()[i] -= Q.coefficients()[i];
+      }
+    }
+    return P;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial&
+  operator=(const Polynomial<M>& Q)
+  {
+    coefficients() = zero;
+    for (size_t i = N + 1; i <= M; ++i) {
+      Assert(Q.coefficients()[i] == 0, "degree of polynomial to small in assignation");
+    }
+    //    static_assert(N >= M, "degree of polynomial to small in assignation");
+    for (size_t i = 0; i <= std::min(M, N); ++i) {
+      coefficients()[i] = Q.coefficients()[i];
+    }
+    return *this;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial&
+  operator+=(const Polynomial<M>& Q)
+  {
+    static_assert(N >= M, "Polynomial degree to small in affectation addition");
+    for (size_t i = 0; i <= M; ++i) {
+      coefficients()[i] += Q.coefficients()[i];
+    }
+    return *this;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial&
+  operator-=(const Polynomial<M>& Q)
+  {
+    static_assert(N >= M, "Polynomial degree to small in affectation addition");
+    for (size_t i = 0; i <= M; ++i) {
+      coefficients()[i] -= Q.coefficients()[i];
+    }
+    return *this;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial<M + N>
+  operator*(const Polynomial<M>& Q) const
+  {
+    Polynomial<M + N> P;
+    P.coefficients() = zero;
+    for (size_t i = 0; i <= N; ++i) {
+      for (size_t j = 0; j <= M; ++j) {
+        P.coefficients()[i + j] += coefficients()[i] * Q.coefficients()[j];
+      }
+    }
+    return P;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial&
+  operator*=(const Polynomial<M>& Q)
+  {
+    static_assert(N >= M, "Degree to small in affectation product");
+    for (size_t i = N - M + 1; i <= N; ++i) {
+      Assert(coefficients()[i] == 0, "Degree of affectation product greater than the degree of input polynomial");
+    }
+    Polynomial P(zero);
+    for (size_t i = 0; i <= N - M; ++i) {
+      for (size_t j = 0; j <= M; ++j) {
+        P.coefficients()[i + j] += coefficients()[i] * Q.coefficients()[j];
+      }
+    }
+    coefficients() = P.coefficients();
+    return *this;
+  }
+
+  PUGS_INLINE
+  constexpr Polynomial
+  operator*(const double& lambda) const
+  {
+    return Polynomial(lambda * m_coefficients);
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial<M * N>
+  compose(const Polynomial<M>& Q) const
+  {
+    Polynomial<M * N> P;
+    P.coefficients() = zero;
+    Polynomial<M * N> R;
+    R.coefficients() = zero;
+
+    for (size_t i = 0; i <= N; ++i) {
+      R = Q.template pow<N>(i) * coefficients()[i];
+      P += R;   // R;
+    }
+    return P;
+  }
+
+  template <size_t M, size_t I>
+  PUGS_INLINE constexpr Polynomial<M * N>
+  power(const Polynomial<M>& Q) const
+  {
+    return coefficients()[I] * Q.template pow<N>(I);
+  }
+
+  template <size_t M, size_t... I>
+  PUGS_INLINE constexpr Polynomial<M * N>
+  compose2(const Polynomial<M>& Q, std::index_sequence<I...>) const
+  {
+    Polynomial<M * N> P;
+    P.coefficients() = zero;
+    P                = (power<M, I>(Q) + ...);
+    return P;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial<M * N>
+  compose2(const Polynomial<M>& Q) const
+  {
+    Polynomial<M * N> P;
+    P.coefficients()    = zero;
+    using IndexSequence = std::make_index_sequence<N + 1>;
+    return compose2<M>(Q, IndexSequence{});
+    //    for (size_t i = 0; i <= N; ++i) {
+    //      P += Q.template pow<N>(i) * coefficients()[i];
+    //    }
+    return P;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial<M * N>
+  operator()(const Polynomial<M>& Q) const
+  {
+    Polynomial<M * N> P;
+    P.coefficients() = zero;
+    Polynomial<M * N> R;
+    R.coefficients() = zero;
+
+    for (size_t i = 0; i <= N; ++i) {
+      R = Q.template pow<N>(i) * coefficients()[i];
+      P += R;   // R;
+    }
+    return P;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr Polynomial<M * N>
+  pow(size_t power) const
+  {
+    Assert(power <= M, "You declared a polynomial of degree too small for return of the pow function");
+    Polynomial<M * N> R;
+    R.coefficients() = zero;
+    if (power == 0) {
+      R.coefficients()[0] = 1;
+    } else {
+      R = *this;
+      for (size_t i = 1; i < power; ++i) {
+        R = R * *this;
+      }
+    }
+    return R;
+  }
+
+  PUGS_INLINE
+  constexpr friend Polynomial
+  operator*(const double& lambda, const Polynomial& P)
+  {
+    return P * lambda;
+  }
+
+  PUGS_INLINE
+  constexpr double
+  operator()(double x) const
+  {
+    double p_x = m_coefficients[N];
+    for (size_t i = N; i > 0; --i) {
+      p_x *= x;
+      p_x += m_coefficients[i - 1];
+    }
+    return p_x;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr friend void
+  divide(const Polynomial<N>& P1, const Polynomial<M>& P2, Polynomial<N>& Q, Polynomial<N>& R)
+  {
+    const size_t Nr  = P1.realDegree();
+    const size_t Mr  = P2.realDegree();
+    R.coefficients() = P1.coefficients();
+    Q.coefficients() = zero;
+    for (size_t k = Nr - Mr + 1; k > 0; --k) {
+      Q.coefficients()[k - 1] = R.coefficients()[Mr + k - 1] / P2.coefficients()[Mr];
+      Polynomial<N - M> Q1;
+      Q1.coefficients()        = zero;
+      Q1.coefficients()[k - 1] = Q.coefficients()[k - 1];
+      R -= Q1 * P2;
+    }
+    for (size_t j = Mr + 1; j < Nr + 1; ++j) {
+      R.coefficients()[j] = 0;
+    }
+  }
+  PUGS_INLINE
+  constexpr friend Polynomial<N + 1>
+  primitive(const Polynomial& P)
+  {
+    TinyVector<N + 2> coefs;
+    for (size_t i = 0; i < N + 1; ++i) {
+      coefs[i + 1] = P.coefficients()[i] / double(i + 1);
+    }
+    coefs[0] = 0;
+    return Polynomial<N + 1>{coefs};
+  }
+
+  PUGS_INLINE
+  constexpr friend std::ostream&
+  operator<<(std::ostream& os, const Polynomial& P)
+  {
+    //    os << "P(x) = ";
+    bool all_coef_zero = true;
+    if (N == 0) {
+      os << P.coefficients()[0];
+      return os;
+    }
+    if (N != 1) {
+      if (P.coefficients()[N] != 0.) {
+        if (P.coefficients()[N] < 0.) {
+          os << "- ";
+        }
+        if (P.coefficients()[N] != 1 && P.coefficients()[N] != -1) {
+          os << std::abs(P.coefficients()[N]);
+        }
+        os << "x^" << N;
+        all_coef_zero = false;
+      }
+    }
+    for (size_t i = N - 1; i > 1; --i) {
+      if (P.coefficients()[i] != 0.) {
+        if (P.coefficients()[i] > 0.) {
+          os << " + ";
+        } else if (P.coefficients()[i] < 0.) {
+          os << " - ";
+        }
+        if (P.coefficients()[i] != 1 && P.coefficients()[i] != -1) {
+          os << std::abs(P.coefficients()[i]);
+        }
+        os << "x^" << i;
+        all_coef_zero = false;
+      }
+    }
+    if (P.coefficients()[1] != 0.) {
+      if (P.coefficients()[1] > 0. && N != 1) {
+        os << " + ";
+      } else if (P.coefficients()[1] < 0.) {
+        os << " - ";
+      }
+      if (P.coefficients()[1] != 1 && P.coefficients()[1] != -1) {
+        os << std::abs(P.coefficients()[1]);
+      }
+      os << "x";
+      all_coef_zero = false;
+    }
+    if (P.coefficients()[0] != 0. || all_coef_zero) {
+      if (P.coefficients()[0] > 0.) {
+        os << " + ";
+      } else if (P.coefficients()[0] < 0.) {
+        os << " - ";
+      }
+      os << std::abs(P.coefficients()[0]);
+    }
+    return os;
+  }
+
+  PUGS_INLINE
+  constexpr friend void
+  lagrangeBasis(const TinyVector<N + 1> zeros, TinyVector<N + 1, Polynomial<N>>& basis)
+  {
+    Polynomial<N> lj;
+    for (size_t j = 0; j < N + 1; ++j) {
+      basis[j] = lagrangePolynomial(zeros, j);
+    }
+  }
+
+  PUGS_INLINE
+  constexpr friend Polynomial<N>
+  lagrangeToCanonical(const TinyVector<N + 1> lagrange_coefs, const std::array<Polynomial<N>, N + 1>& basis)
+  {
+    Polynomial<N> P(zero);
+    //    lagrangeBasis({0, 0, 0}, basis);
+    for (size_t j = 0; j < N + 1; ++j) {
+      P += basis[j] * lagrange_coefs[j];
+    }
+    return P;
+  }
+
+  PUGS_INLINE constexpr Polynomial& operator=(const Polynomial&) = default;
+  PUGS_INLINE constexpr Polynomial& operator=(Polynomial&&)      = default;
+
+  PUGS_INLINE
+  constexpr Polynomial(const TinyVector<N + 1>& coefficients) noexcept : m_coefficients(coefficients) {}
+
+  PUGS_INLINE
+  constexpr Polynomial(const Polynomial&) noexcept = default;
+
+  PUGS_INLINE
+  constexpr Polynomial(Polynomial&&) noexcept = default;
+
+  template <typename... T>
+  explicit PUGS_INLINE constexpr Polynomial(T&&... coefficients) noexcept : m_coefficients(coefficients...)
+  {
+    // static_assert(sizeof...(T) == N + 1, "invalid number of parameters");
+    // static_assert((std::is_convertible_v<T, double> and ...), "arguments must be convertible to double");
+  }
+
+  PUGS_INLINE
+  constexpr Polynomial() noexcept = default;
+
+  ~Polynomial() = default;
+};
+
+// template <size_t N>
+// template <>
+// PUGS_INLINE constexpr Polynomial(TinyVector<N + 1>&& coefficients) noexcept : m_coefficients{coefficients}
+// {}
+
+template <size_t N>
+PUGS_INLINE constexpr Polynomial<N> lagrangePolynomial(const TinyVector<N + 1> zeros, const size_t k);
+
+template <size_t N>
+PUGS_INLINE constexpr std::array<Polynomial<N - 1>, N>
+lagrangeBasis(const TinyVector<N>& zeros)
+{
+  static_assert(N >= 1, "invalid degree");
+  std::array<Polynomial<N - 1>, N> basis;
+  for (size_t j = 0; j < N; ++j) {
+    basis[j] = lagrangePolynomial<N - 1>(zeros, j);
+  }
+  return basis;
+}
+
+template <size_t N>
+PUGS_INLINE constexpr double
+integrate(const Polynomial<N>& P, const double& xinf, const double& xsup)
+{
+  Polynomial<N + 1> Q = primitive(P);
+  return (Q(xsup) - Q(xinf));
+}
+
+template <size_t N>
+PUGS_INLINE constexpr double
+symmetricIntegrate(const Polynomial<N>& P, const double& delta)
+{
+  Assert(delta > 0, "A positive delta is needed for symmetricIntegrate");
+  double integral = 0.;
+  for (size_t i = 0; i <= N; ++i) {
+    if (i % 2 == 0)
+      integral += 2. * P.coefficients()[i] * std::pow(delta, i + 1) / (i + 1);
+  }
+  return integral;
+}
+
+template <size_t N>
+PUGS_INLINE constexpr auto
+derivative(const Polynomial<N>& P)
+{
+  if constexpr (N == 0) {
+    return Polynomial<0>(zero);
+  } else {
+    TinyVector<N> coefs;
+    for (size_t i = 0; i < N; ++i) {
+      coefs[i] = double(i + 1) * P.coefficients()[i + 1];
+    }
+    return Polynomial<N - 1>(coefs);
+  }
+}
+
+template <size_t N>
+PUGS_INLINE constexpr Polynomial<N>
+lagrangePolynomial(const TinyVector<N + 1> zeros, const size_t k)
+{
+  for (size_t i = 0; i < N; ++i) {
+    Assert(zeros[i] < zeros[i + 1], "Interpolation values must be strictly increasing in Lagrange polynomials");
+  }
+  Polynomial<N> lk;
+  lk.coefficients()    = zero;
+  lk.coefficients()[0] = 1;
+  for (size_t i = 0; i < N + 1; ++i) {
+    if (k == i)
+      continue;
+    double factor = 1. / (zeros[k] - zeros[i]);
+    Polynomial<1> P(TinyVector<2>{-zeros[i] * factor, factor});
+    lk *= P;
+  }
+  return lk;
+}
+
+#endif   // POLYNOMIAL_HPP
diff --git a/src/analysis/PolynomialBasis.hpp b/src/analysis/PolynomialBasis.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a0b07ecafd27bbdc24dc41b4f210a2861cac46c1
--- /dev/null
+++ b/src/analysis/PolynomialBasis.hpp
@@ -0,0 +1,160 @@
+#ifndef POLYNOMIAL_BASIS_HPP
+#define POLYNOMIAL_BASIS_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <analysis/Polynomial.hpp>
+#include <utils/Messenger.hpp>
+
+enum class BasisType
+{
+  undefined,
+  lagrange,
+  canonical,
+  taylor
+};
+
+template <size_t N>
+class PolynomialBasis
+{
+ private:
+  static_assert((N >= 0), "Number of elements in the basis must be non-negative");
+  std::array<Polynomial<N>, N + 1> m_elements;
+  BasisType m_basis_type;
+  PUGS_INLINE
+  constexpr PolynomialBasis<N>&
+  _buildCanonicalBasis()
+  {
+    for (size_t i = 0; i <= N; i++) {
+      TinyVector<N + 1> coefficients(zero);
+      coefficients[i] = 1;
+      elements()[i]   = Polynomial<N>(coefficients);
+    }
+    return *this;
+  }
+
+  PUGS_INLINE
+  constexpr PolynomialBasis<N>&
+  _buildTaylorBasis(const double& shift)
+  {
+    TinyVector<N + 1> coefficients(zero);
+    elements()[0] = Polynomial<N>(coefficients);
+    elements()[0] += Polynomial<0>(1);
+    Polynomial<1> unit(-shift, 1);
+    for (size_t i = 1; i <= N; i++) {
+      elements()[i] = elements()[i - 1] * unit;
+    }
+    return *this;
+  }
+
+  PUGS_INLINE
+  constexpr PolynomialBasis<N>&
+  _buildLagrangeBasis(const TinyVector<N + 1>& zeros)
+  {
+    if constexpr (N == 0) {
+      elements()[0] = Polynomial<0>(1);
+      return *this;
+    } else {
+      for (size_t i = 0; i < N; ++i) {
+        Assert(zeros[i] < zeros[i + 1], "Interpolation values must be strictly increasing in Lagrange polynomials");
+      }
+      for (size_t i = 0; i <= N; ++i) {
+        TinyVector<N + 1> coefficients(zero);
+        elements()[i]                   = Polynomial<N>(coefficients);
+        elements()[i].coefficients()[0] = 1;
+        for (size_t j = 0; j < N + 1; ++j) {
+          if (i == j)
+            continue;
+          double adim = 1. / (zeros[i] - zeros[j]);
+          elements()[i] *= Polynomial<1>{-zeros[j] * adim, adim};
+        }
+      }
+      return *this;
+    }
+  }
+
+ public:
+  PUGS_INLINE
+  constexpr size_t
+  size() const
+  {
+    return N + 1;
+  }
+
+  PUGS_INLINE
+  constexpr size_t
+  degree() const
+  {
+    return N;
+  }
+
+  PUGS_INLINE
+  constexpr BasisType&
+  type()
+  {
+    return m_basis_type;
+  }
+
+  PUGS_INLINE
+  std::string_view
+  displayType()
+  {
+    switch (m_basis_type) {
+    case BasisType::lagrange:
+      return "lagrange";
+    case BasisType::canonical:
+      return "canonical";
+    case BasisType::taylor:
+      return "taylor";
+    case BasisType::undefined:
+      return "undefined";
+    default:
+      return "unknown basis type";
+    }
+  }
+
+  PUGS_INLINE
+  constexpr const std::array<Polynomial<N>, N + 1>&
+  elements() const
+  {
+    return m_elements;
+  }
+
+  PUGS_INLINE
+  constexpr std::array<Polynomial<N>, N + 1>&
+  elements()
+  {
+    return m_elements;
+  }
+
+  PUGS_INLINE
+  constexpr PolynomialBasis<N>&
+  build(BasisType basis_type, const double& shift = 0, const TinyVector<N + 1>& zeros = zero)
+  {
+    type() = basis_type;
+    switch (basis_type) {
+    case BasisType::lagrange: {
+      return _buildLagrangeBasis(zeros);
+      break;
+    }
+    case BasisType::canonical: {
+      return _buildCanonicalBasis();
+      break;
+    }
+    case BasisType::taylor: {
+      return _buildTaylorBasis(shift);
+      break;
+    }
+      // LCOV_EXCL_START
+    default: {
+      throw UnexpectedError("unknown basis type");
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+
+  PUGS_INLINE
+  constexpr PolynomialBasis() noexcept : m_basis_type{BasisType::undefined} {}
+
+  ~PolynomialBasis() = default;
+};
+#endif   // POLYNOMIAL_BASIS_HPP
diff --git a/src/analysis/PolynomialP.hpp b/src/analysis/PolynomialP.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1665d27e57feca7ffaa7c7bbc1190a6c2de99cab
--- /dev/null
+++ b/src/analysis/PolynomialP.hpp
@@ -0,0 +1,658 @@
+#ifndef POLYNOMIALP_HPP
+#define POLYNOMIALP_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <analysis/CubeGaussQuadrature.hpp>
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/SquareGaussQuadrature.hpp>
+#include <analysis/TriangleGaussQuadrature.hpp>
+#include <geometry/LineTransformation.hpp>
+#include <geometry/SquareTransformation.hpp>
+#include <geometry/TriangleTransformation.hpp>
+#include <utils/Exceptions.hpp>
+
+template <size_t N, size_t Dimension>
+class PolynomialP
+{
+ private:
+  static constexpr size_t size_coef = [] {
+    if constexpr (Dimension == 1) {
+      return N + 1;
+    } else if constexpr (Dimension == 2) {
+      return (N + 1) * (N + 2) / 2;
+    } else {
+      static_assert(Dimension == 3);
+      return (N + 1) * (N + 2) * (N + 3) / 6;
+    }
+  }();
+
+  using Coefficients = TinyVector<size_coef, double>;
+  Coefficients m_coefficients;
+  static_assert((N >= 0), "PolynomialP degree must be non-negative");
+  static_assert((Dimension > 0), "PolynomialP dimension must be positive");
+  static_assert((Dimension <= 3), "PolynomialP dimension must no greater than three");
+
+ public:
+  PUGS_INLINE
+  constexpr size_t
+  degree() const
+  {
+    return N;
+  }
+
+  constexpr size_t
+  dim() const
+  {
+    return Dimension;
+  }
+
+  PUGS_INLINE
+  constexpr const TinyVector<size_coef, double>&
+  coefficients() const
+  {
+    return m_coefficients;
+  }
+
+  PUGS_INLINE
+  constexpr TinyVector<size_coef, double>&
+  coefficients()
+  {
+    return m_coefficients;
+  }
+
+  PUGS_INLINE constexpr bool
+  operator==(const PolynomialP& q) const
+  {
+    return m_coefficients == q.m_coefficients;
+  }
+
+  PUGS_INLINE constexpr PolynomialP(const TinyVector<size_coef, double>& coefficients) noexcept
+    : m_coefficients{coefficients}
+  {}
+
+  PUGS_INLINE
+  constexpr PolynomialP(TinyVector<size_coef, double>&& coefficients) noexcept : m_coefficients{coefficients} {}
+
+  PUGS_INLINE
+  constexpr bool
+  operator!=(const PolynomialP& q) const
+  {
+    return not this->operator==(q);
+  }
+
+  PUGS_INLINE constexpr PolynomialP
+  operator+(const PolynomialP Q) const
+  {
+    PolynomialP<N, Dimension> P(m_coefficients);
+    for (size_t i = 0; i < size_coef; ++i) {
+      P.coefficients()[i] += Q.coefficients()[i];
+    }
+    return P;
+  }
+
+  PUGS_INLINE constexpr PolynomialP
+  operator-() const
+  {
+    PolynomialP<N, Dimension> P;
+    P.coefficients() = -coefficients();
+    return P;
+  }
+
+  PUGS_INLINE constexpr PolynomialP
+  operator-(const PolynomialP Q) const
+  {
+    PolynomialP<N, Dimension> P(m_coefficients);
+    P = P + (-Q);
+    return P;
+  }
+
+  template <size_t M, size_t Dim>
+  PUGS_INLINE constexpr PolynomialP&
+  operator=(const PolynomialP<M, Dim>& Q)
+  {
+    coefficients() = zero;
+    for (size_t i = 0; i < size_coef; ++i) {
+      coefficients()[i] = Q.coefficients()[i];
+    }
+    return *this;
+  }
+
+  PUGS_INLINE constexpr PolynomialP&
+  operator+=(const PolynomialP& Q)
+  {
+    m_coefficients += Q.coefficients();
+    return *this;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr PolynomialP&
+  operator-=(const PolynomialP& Q)
+  {
+    m_coefficients -= Q.coefficients();
+    return *this;
+  }
+
+  PUGS_INLINE
+  constexpr PolynomialP
+  operator*(const double& lambda) const
+  {
+    TinyVector<size_coef> mult_coefs = lambda * m_coefficients;
+    PolynomialP<N, Dimension> M(mult_coefs);
+    return M;
+  }
+
+  PUGS_INLINE
+  constexpr friend PolynomialP<N, Dimension>
+  operator*(const double& lambda, const PolynomialP<N, Dimension> P)
+  {
+    return P * lambda;
+  }
+
+  PUGS_INLINE
+  constexpr double
+  operator[](const TinyVector<Dimension, size_t> relative_pos) const
+  {
+    size_t total_degree_chk = 0;
+    for (size_t i = 0; i < Dimension; ++i) {
+      Assert((relative_pos[i] <= N),
+             "You are looking for a coefficient of order greater than the degree of the polynomial");
+      total_degree_chk += relative_pos[i];
+    }
+    Assert((total_degree_chk <= N), "The sum of the degrees of the coefficient you are looking for is greater than the "
+                                    "degree of the polynomial");
+    TinyVector<size_coef> absolute_coefs = this->coefficients();
+    size_t absolute_position             = 0;
+    if constexpr (Dimension == 1) {
+      absolute_position = relative_pos[0];
+    } else if constexpr (Dimension == 2) {
+      size_t total_degree = relative_pos[0] + relative_pos[1];
+      absolute_position   = total_degree * (total_degree + 1) / 2 + relative_pos[1];
+    } else {
+      // throw NotImplementedError("Not yet Available in 3D");
+      static_assert(Dimension == 3);
+      size_t total_degree     = relative_pos[0] + relative_pos[1] + relative_pos[2];
+      size_t total_sub_degree = relative_pos[1] + relative_pos[2];
+      absolute_position       = total_degree * (total_degree + 1) * (total_degree + 2) / 6 +
+                          total_sub_degree * (total_sub_degree + 1) / 2 + relative_pos[2];
+    }
+
+    return absolute_coefs[absolute_position];
+  }
+
+  PUGS_INLINE
+  constexpr double
+  operator[](const TinyVector<Dimension, size_t> relative_pos)
+  {
+    size_t total_degree_chk = 0;
+    for (size_t i = 0; i < Dimension; ++i) {
+      Assert((relative_pos[i] <= N),
+             "You are looking for a coefficient of order greater than the degree of the polynomial");
+      total_degree_chk += relative_pos[i];
+    }
+    Assert((total_degree_chk <= N), "The sum of the degrees of the coefficient you are looking for is greater than the "
+                                    "degree of the polynomial");
+    TinyVector<size_coef> absolute_coefs = this->coefficients();
+    size_t absolute_position             = 0;
+    if constexpr (Dimension == 1) {
+      absolute_position = relative_pos[0];
+    } else if constexpr (Dimension == 2) {
+      size_t total_degree = relative_pos[0] + relative_pos[1];
+      absolute_position   = total_degree * (total_degree + 1) / 2 + relative_pos[1];
+    } else {
+      // throw NotImplementedError("Not yet Available in 3D");
+      static_assert(Dimension == 3);
+      size_t total_degree     = relative_pos[0] + relative_pos[1] + relative_pos[2];
+      size_t total_sub_degree = relative_pos[1] + relative_pos[2];
+      absolute_position       = total_degree * (total_degree + 1) * (total_degree + 2) / 6 +
+                          total_sub_degree * (total_sub_degree + 1) / 2 + relative_pos[2];
+    }
+
+    return absolute_coefs[absolute_position];
+  }
+
+  PUGS_INLINE
+  constexpr double
+  absolute_position(const TinyVector<Dimension, size_t> relative_pos) const
+  {
+    size_t total_degree = 0;
+    for (size_t i = 0; i < Dimension; ++i) {
+      Assert((relative_pos[i] <= N),
+             "You are looking for a coefficient of order greater than the degree of the polynomial");
+      total_degree += relative_pos[i];
+    }
+    Assert((total_degree <= N), "The sum of the degrees of the coefficient you are looking for is greater than the "
+                                "degree of the polynomial");
+    size_t abs_pos = 0;
+    if constexpr (Dimension == 1) {
+      abs_pos = relative_pos[0];
+    } else if constexpr (Dimension == 2) {
+      abs_pos = total_degree * (total_degree + 1) / 2 + relative_pos[1];
+    } else {
+      static_assert(Dimension == 3);
+      total_degree            = relative_pos[0] + relative_pos[1] + relative_pos[2];
+      size_t total_sub_degree = relative_pos[1] + relative_pos[2];
+      abs_pos                 = total_degree * (total_degree + 1) * (total_degree + 2) / 6 +
+                total_sub_degree * (total_sub_degree + 1) / 2 + relative_pos[2];
+      // throw NotImplementedError("Not yet Available in 3D");
+    }
+
+    return abs_pos;
+  }
+
+  PUGS_INLINE
+  constexpr double
+  operator()(const TinyVector<Dimension> x) const
+  {
+    const auto& P = *this;
+    double value  = 0.;
+    if constexpr (Dimension == 1) {
+      value = m_coefficients[N];
+      for (size_t i = N; i > 0; --i) {
+        value *= x;
+        value += m_coefficients[i - 1];
+      }
+    } else if constexpr (Dimension == 2) {
+      TinyVector<Dimension, size_t> relative_pos(0, N);
+      value = P[relative_pos];
+      for (size_t i = N; i > 0; --i) {
+        value *= x[1];
+        relative_pos  = TinyVector<Dimension, size_t>(N - i + 1, i - 1);
+        double valuex = P[relative_pos];
+        for (size_t j = N - i + 1; j > 0; --j) {
+          valuex *= x[0];
+          relative_pos = TinyVector<Dimension, size_t>(j - 1, i - 1);
+          valuex += P[relative_pos];
+        }
+        value += valuex;
+      }
+    } else {
+      TinyVector<Dimension, size_t> relative_pos(0, 0, N);
+      value = P[relative_pos];
+      for (size_t i = N; i > 0; --i) {
+        value *= x[2];
+        relative_pos  = TinyVector<Dimension, size_t>(0, N - i + 1, i - 1);
+        double valuey = P[relative_pos];
+        for (size_t j = N - i + 1; j > 0; --j) {
+          valuey *= x[1];
+          relative_pos  = TinyVector<Dimension, size_t>(N - i - j + 2, j - 1, i - 1);
+          double valuex = P[relative_pos];
+          for (size_t k = N - i - j + 2; k > 0; --k) {
+            valuex *= x[0];
+            relative_pos = TinyVector<Dimension, size_t>(k - 1, j - 1, i - 1);
+
+            valuex += P[relative_pos];
+          }
+          valuey += valuex;
+        }
+        value += valuey;
+        // throw NotImplementedError("Not yet Available in 3D");
+      }
+    }
+    return value;
+  }
+
+  PUGS_INLINE size_t
+  find_size_coef(const size_t degree)
+  {
+    if constexpr (Dimension == 1) {
+      return degree + 1;
+    } else if constexpr (Dimension == 2) {
+      return (degree + 1) * (degree + 2) / 2;
+    } else {
+      static_assert(Dimension == 3);
+      return (degree + 1) * (degree + 2) * (degree + 3) / 6;
+    }
+  }
+
+  PUGS_INLINE constexpr PolynomialP<N, Dimension>
+  derivative(const size_t var) const
+  {
+    const auto P = *this;
+    TinyVector<size_coef> coefs(zero);
+    PolynomialP<N, Dimension> Q(coefs);
+    if constexpr (N != 0) {
+      Assert(var < Dimension,
+             "You can not derive a polynomial with respect to a variable of rank greater than the dimension");
+      if constexpr (Dimension == 1) {
+        for (size_t i = 0; i < size_coef - 1; ++i) {
+          coefs[i] = double(i + 1) * P.coefficients()[i + 1];
+        }
+      } else if constexpr (Dimension == 2) {
+        if (var == 0) {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              TinyVector<Dimension, size_t> relative_pos(i, j);
+              TinyVector<Dimension, size_t> relative_posp(i + 1, j);
+              size_t absolute_position            = Q.absolute_position(relative_pos);
+              size_t absolute_positionp           = P.absolute_position(relative_posp);
+              Q.coefficients()[absolute_position] = double(i + 1) * m_coefficients[absolute_positionp];
+            }
+          }
+        } else {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              TinyVector<Dimension, size_t> relative_pos(i, j);
+              TinyVector<Dimension, size_t> relative_posp(i, j + 1);
+              size_t absolute_position            = Q.absolute_position(relative_pos);
+              size_t absolute_positionp           = P.absolute_position(relative_posp);
+              Q.coefficients()[absolute_position] = double(j + 1) * m_coefficients[absolute_positionp];
+            }
+          }
+        }
+      } else {
+        static_assert(Dimension == 3);
+        if (var == 0) {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              for (size_t k = 0; k < N - i - j; ++k) {
+                TinyVector<Dimension, size_t> relative_pos(i, j, k);
+                TinyVector<Dimension, size_t> relative_posp(i + 1, j, k);
+                size_t absolute_position            = Q.absolute_position(relative_pos);
+                size_t absolute_positionp           = P.absolute_position(relative_posp);
+                Q.coefficients()[absolute_position] = double(i + 1) * m_coefficients[absolute_positionp];
+              }
+            }
+          }
+        } else if (var == 1) {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              for (size_t k = 0; k < N - i - j; ++k) {
+                TinyVector<Dimension, size_t> relative_pos(i, j, k);
+                TinyVector<Dimension, size_t> relative_posp(i, j + 1, k);
+                size_t absolute_position            = Q.absolute_position(relative_pos);
+                size_t absolute_positionp           = P.absolute_position(relative_posp);
+                Q.coefficients()[absolute_position] = double(j + 1) * m_coefficients[absolute_positionp];
+              }
+            }
+          }
+        } else {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              for (size_t k = 0; k < N - i - j; ++k) {
+                TinyVector<Dimension, size_t> relative_pos(i, j, k);
+                TinyVector<Dimension, size_t> relative_posp(i, j, k + 1);
+                size_t absolute_position            = Q.absolute_position(relative_pos);
+                size_t absolute_positionp           = P.absolute_position(relative_posp);
+                Q.coefficients()[absolute_position] = double(k + 1) * m_coefficients[absolute_positionp];
+              }
+            }
+          }
+        }
+        // throw NotImplementedError("Not yet Available in 3D");
+      }
+    }
+    return Q;
+  }
+
+  PUGS_INLINE
+  constexpr friend std::ostream&
+  operator<<(std::ostream& os, const PolynomialP<N, Dimension>& P)
+  {
+    //    os << "P(x) = ";
+    bool all_coef_zero = true;
+    if (N == 0) {
+      os << P.coefficients()[0];
+      return os;
+    }
+    if constexpr (Dimension == 1) {
+      if (N != 1) {
+        if (P.coefficients()[N] != 0.) {
+          if (P.coefficients()[N] < 0.) {
+            os << "- ";
+          }
+          if (P.coefficients()[N] != 1 && P.coefficients()[N] != -1) {
+            os << std::abs(P.coefficients()[N]);
+          }
+          os << "x^" << N;
+          all_coef_zero = false;
+        }
+      }
+      for (size_t i = N - 1; i > 1; --i) {
+        if (P.coefficients()[i] != 0.) {
+          if (P.coefficients()[i] > 0.) {
+            os << " + ";
+          } else if (P.coefficients()[i] < 0.) {
+            os << " - ";
+          }
+          if (P.coefficients()[i] != 1 && P.coefficients()[i] != -1) {
+            os << std::abs(P.coefficients()[i]);
+          }
+          os << "x^" << i;
+          all_coef_zero = false;
+        }
+      }
+      if (P.coefficients()[1] != 0.) {
+        if (P.coefficients()[1] > 0. && N != 1) {
+          os << " + ";
+        } else if (P.coefficients()[1] < 0.) {
+          os << " - ";
+        }
+        if (P.coefficients()[1] != 1 && P.coefficients()[1] != -1) {
+          os << std::abs(P.coefficients()[1]);
+        }
+        os << "x";
+        all_coef_zero = false;
+      }
+      if (P.coefficients()[0] != 0. || all_coef_zero) {
+        if (P.coefficients()[0] > 0.) {
+          os << " + ";
+        } else if (P.coefficients()[0] < 0.) {
+          os << " - ";
+        }
+        os << std::abs(P.coefficients()[0]);
+      }
+      return os;
+    } else if constexpr (Dimension == 2) {
+      size_t i = 0;
+      size_t j = N;
+      TinyVector<Dimension, size_t> rel_pos(i, j);
+      double coef = P[rel_pos];
+      if (coef != 0.) {
+        if (coef < 0.) {
+          os << " - ";
+        }
+        if (coef != 1 && coef != -1) {
+          os << std::abs(coef);
+        }
+        os << "y^" << N;
+      }
+      size_t degree = N;
+      for (size_t k = size_coef - 1; k > 0; --k) {
+        if (j > 0) {
+          j--;
+          i++;
+        } else {
+          degree--;
+          j = degree;
+          i = 0;
+        }
+        rel_pos = TinyVector<Dimension, size_t>(i, j);
+        coef    = P[rel_pos];
+        if (coef != 0.) {
+          if (coef > 0.) {
+            os << " + ";
+          } else if (coef < 0.) {
+            os << " - ";
+          }
+          if ((coef != 1 && coef != -1) || (i == 0 && j == 0)) {
+            os << std::abs(coef);
+          }
+          if (i == 0 && j == 0)
+            continue;
+          if (i == 0) {
+            if (j != 1) {
+              os << "y^" << j;
+            } else {
+              os << "y";
+            }
+          } else if (j == 0) {
+            if (i == 1) {
+              os << "x";
+            } else {
+              os << "x^" << i;
+            }
+          } else {
+            if (i == 1 && j == 1) {
+              os << "xy";
+            } else if (i == 1) {
+              os << "x"
+                 << "y^" << j;
+            } else if (j == 1) {
+              os << "x^" << i << "y";
+            } else {
+              os << "x^" << i << "y^" << j;
+            }
+          }
+          all_coef_zero = false;
+        }
+      }
+
+      return os;
+    } else {
+      // size_t i = 0;
+      // size_t j = 0;
+      // size_t k = N;
+      // TinyVector<Dimension, size_t> rel_pos(i, j, k);
+      // double coef = P[rel_pos];
+      // if (coef != 0.) {
+      //   if (coef < 0.) {
+      //     os << " - ";
+      //   }
+      //   if (coef != 1 && coef != -1) {
+      //     os << std::abs(coef);
+      //   }
+      //   os << "z^" << N;
+      // }
+      // size_t degree = N;
+      // for (size_t l = size_coef - 1; l > 0; --l) {
+      //   if (k > 0) {
+      //     k--;
+      //     if (j < k) {
+      //       j++;
+      //     } else {
+      //       j--;
+      //       i++;
+      //     }
+      //   } else {
+      //     degree--;
+      //     k = degree;
+      //     i = 0;
+      //     j = 0;
+      //   }
+
+      //   rel_pos     = TinyVector<Dimension, size_t>(i, j, k);
+      //   double coef = P[rel_pos];
+      //   if (coef != 0.) {
+      //     if (coef > 0.) {
+      //       os << " + ";
+      //     } else if (coef < 0.) {
+      //       os << " - ";
+      //     }
+      //     if ((coef != 1 && coef != -1) || (i == 0 && j == 0 && k == 0)) {
+      //       os << std::abs(coef);
+      //     }
+      //     if (i == 0 && j == 0 && k == 0)
+      //       continue;
+      //     if (i == 0 && j == 0) {
+      //       if (k != 1) {
+      //         os << "z^" << j;
+      //       } else {
+      //         os << "z";
+      //       }
+      //     } else if (i == 0 && k == 0) {
+      //       if (j == 1) {
+      //         os << "y";
+      //       } else {
+      //         os << "y^" << i;
+      //       }
+      //     } else if (j == 0 && k == 0) {
+      //       if (i == 1) {
+      //         os << "x";
+      //       } else {
+      //         os << "x^" << i;
+      //       }
+      //     } else {
+      //       if (i == 1 && j == 1 && k == 1) {
+      //         os << "xyz";
+      //       } else if (i == 1) {
+      //         os << "x"
+      //            << "y^" << j << "z^" << k;
+      //       } else if (j == 1) {
+      //         os << "x^" << i << "y"
+      //            << "z^" << k;
+      //       } else if (k == 1) {
+      //         os << "x^" << i << "y^" << j << "z";
+      //       } else {
+      //         os << "x^" << i << "y^" << j << "z^" << k;
+      //       }
+      //     }
+      //     all_coef_zero = false;
+      //   }
+      //
+      for (size_t l = 0; l < size_coef; ++l) {
+        double coef = P.coefficients()[l];
+        os << coef << " ";
+      }
+      return os;
+      //      throw NotImplementedError("Not yet Available in 3D");
+    }
+  }
+
+  PUGS_INLINE constexpr PolynomialP() noexcept = default;
+  ~PolynomialP()                               = default;
+};
+
+template <size_t N, size_t Dimension, size_t P>
+PUGS_INLINE double
+integrate(const PolynomialP<N, Dimension> Q, const std::array<TinyVector<Dimension>, P>& positions)
+{
+  double integral = 0.;
+  static_assert(P > 1, "For the integration, number of positions should be greater or equal to 2");
+  static_assert(N >= 0, "invalid degree");
+  if constexpr (Dimension == 1) {
+    static_assert(P == 2, "In 1D number of positions should be 2");
+    throw NotImplementedError("Not yet Available in 1D");
+  } else if constexpr (Dimension == 2) {
+    static_assert(P <= 4, "In 2D number of positions should be lesser or equal to 4");
+    if constexpr (P == 2) {
+      const QuadratureFormula<1>& lN =
+        QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(N + 1));
+      const LineTransformation<2> t{positions[0], positions[1]};
+      double value = 0.;
+      for (size_t i = 0; i < lN.numberOfPoints(); ++i) {
+        value += lN.weight(i) * t.velocityNorm() * Q(t(lN.point(i)));
+      }
+      integral = value;
+
+    } else if constexpr (P == 3) {
+      const QuadratureFormula<2>& lN = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(N));
+      auto point_list                = lN.pointList();
+      auto weight_list               = lN.weightList();
+      TriangleTransformation<2> t{positions[0], positions[1], positions[2]};
+      auto value = weight_list[0] * Q(t(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * Q(t(point_list[i]));
+      }
+      integral = t.jacobianDeterminant() * value;
+    } else {
+      const QuadratureFormula<2>& lN =
+        QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(N + 1));
+      auto point_list  = lN.pointList();
+      auto weight_list = lN.weightList();
+      SquareTransformation<2> s{positions[0], positions[1], positions[2], positions[3]};
+      auto value = weight_list[0] * s.jacobianDeterminant(point_list[0]) * Q(s(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * s.jacobianDeterminant(point_list[i]) * Q(s(point_list[i]));
+      }
+      integral = value;
+    }
+  } else {
+    static_assert(Dimension == 3, "Dimension should be <=3");
+    throw NotImplementedError("Not yet Available in 3D");
+  }
+  return integral;
+}
+
+#endif   // POLYNOMIALP_HPP
diff --git a/src/analysis/TaylorPolynomial.hpp b/src/analysis/TaylorPolynomial.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc1a607b31769b62ac7c061af1ee69a851868934
--- /dev/null
+++ b/src/analysis/TaylorPolynomial.hpp
@@ -0,0 +1,681 @@
+#ifndef TAYLOR_POLYNOMIAL_HPP
+#define TAULOR_POLYNOMIAL_HPP
+
+#include <algebra/TinyVector.hpp>
+#include <analysis/CubeGaussQuadrature.hpp>
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/SquareGaussQuadrature.hpp>
+#include <analysis/TriangleGaussQuadrature.hpp>
+#include <geometry/LineTransformation.hpp>
+#include <geometry/SquareTransformation.hpp>
+#include <geometry/TriangleTransformation.hpp>
+#include <utils/Exceptions.hpp>
+
+template <size_t N, size_t Dimension>
+class TaylorPolynomial
+{
+ private:
+  static constexpr size_t size_coef = [] {
+    if constexpr (Dimension == 1) {
+      return N + 1;
+    } else if constexpr (Dimension == 2) {
+      return (N + 1) * (N + 2) / 2;
+    } else {
+      static_assert(Dimension == 3);
+      return (N + 1) * (N + 2) * (N + 3) / 6;
+    }
+  }();
+
+  using Coefficients = TinyVector<size_coef, double>;
+  Coefficients m_coefficients;
+  TinyVector<Dimension> m_x0;
+  static_assert((N >= 0), "TaylorPolynomial degree must be non-negative");
+  static_assert((Dimension > 0), "TaylorPolynomial dimension must be positive");
+  static_assert((Dimension <= 3), "TaylorPolynomial dimension must no greater than three");
+
+ public:
+  PUGS_INLINE
+  constexpr size_t
+  degree() const
+  {
+    return N;
+  }
+
+  constexpr size_t
+  dim() const
+  {
+    return Dimension;
+  }
+
+  PUGS_INLINE
+  constexpr const TinyVector<size_coef, double>&
+  coefficients() const
+  {
+    return m_coefficients;
+  }
+
+  PUGS_INLINE
+  constexpr const TinyVector<Dimension, double>&
+  x0() const
+  {
+    return m_x0;
+  }
+
+  PUGS_INLINE
+  constexpr TinyVector<size_coef, double>&
+  coefficients()
+  {
+    return m_coefficients;
+  }
+
+  PUGS_INLINE constexpr bool
+  operator==(const TaylorPolynomial& q) const
+  {
+    return (m_coefficients == q.m_coefficients && m_x0 == q.m_x0);
+  }
+
+  PUGS_INLINE constexpr TaylorPolynomial(const TinyVector<size_coef, double>& coefficients,
+                                         const TinyVector<Dimension, double>& x0) noexcept
+    : m_coefficients{coefficients}, m_x0(x0)
+  {}
+
+  PUGS_INLINE
+  constexpr TaylorPolynomial(TinyVector<size_coef, double>&& coefficients,
+                             const TinyVector<Dimension, double>&& x0) noexcept
+    : m_coefficients{coefficients}, m_x0(x0)
+  {}
+
+  PUGS_INLINE
+  constexpr bool
+  operator!=(const TaylorPolynomial& q) const
+  {
+    return not this->operator==(q);
+  }
+
+  PUGS_INLINE constexpr TaylorPolynomial
+  operator+(const TaylorPolynomial Q) const
+  {
+    Assert(m_x0 == Q.m_x0, "You cannot add Taylor polynomials with different origins");
+    TaylorPolynomial<N, Dimension> P(m_coefficients, m_x0);
+    for (size_t i = 0; i < size_coef; ++i) {
+      P.coefficients()[i] += Q.coefficients()[i];
+    }
+    return P;
+  }
+
+  PUGS_INLINE constexpr TaylorPolynomial
+  operator-() const
+  {
+    TaylorPolynomial<N, Dimension> P;
+    P.coefficients() = -coefficients();
+    P.m_x0           = m_x0;
+    return P;
+  }
+
+  PUGS_INLINE constexpr TaylorPolynomial
+  operator-(const TaylorPolynomial Q) const
+  {
+    Assert(m_x0 == Q.m_x0, "You cannot subtract Taylor polynomials with different origins");
+    TaylorPolynomial<N, Dimension> P(m_coefficients, m_x0);
+    P = P + (-Q);
+    return P;
+  }
+
+  template <size_t M, size_t Dim>
+  PUGS_INLINE constexpr TaylorPolynomial&
+  operator=(const TaylorPolynomial<M, Dim>& Q)
+  {
+    coefficients() = zero;
+    for (size_t i = 0; i < size_coef; ++i) {
+      coefficients()[i] = Q.coefficients()[i];
+    }
+    m_x0 = Q.m_x0;
+    return *this;
+  }
+
+  PUGS_INLINE constexpr TaylorPolynomial&
+  operator+=(const TaylorPolynomial& Q)
+  {
+    Assert(m_x0 == Q.m_x0, "You cannot add Taylor polynomials with different origins");
+    m_coefficients += Q.coefficients();
+    return *this;
+  }
+
+  template <size_t M>
+  PUGS_INLINE constexpr TaylorPolynomial&
+  operator-=(const TaylorPolynomial& Q)
+  {
+    Assert(m_x0 == Q.m_x0, "You cannot subtract Taylor polynomials with different origins");
+    m_coefficients -= Q.coefficients();
+    return *this;
+  }
+
+  PUGS_INLINE
+  constexpr TaylorPolynomial
+  operator*(const double& lambda) const
+  {
+    TinyVector<size_coef> mult_coefs = lambda * m_coefficients;
+    TaylorPolynomial<N, Dimension> M(mult_coefs, m_x0);
+    return M;
+  }
+
+  PUGS_INLINE
+  constexpr friend TaylorPolynomial<N, Dimension>
+  operator*(const double& lambda, const TaylorPolynomial<N, Dimension> P)
+  {
+    return P * lambda;
+  }
+
+  PUGS_INLINE
+  constexpr double
+  operator[](const TinyVector<Dimension, size_t>& relative_pos) const
+  {
+    size_t total_degree_chk = 0;
+    for (size_t i = 0; i < Dimension; ++i) {
+      Assert((relative_pos[i] <= N),
+             "You are looking for a coefficient of order greater than the degree of the polynomial");
+      total_degree_chk += relative_pos[i];
+    }
+    Assert((total_degree_chk <= N), "The sum of the degrees of the coefficient you are looking for is greater than the "
+                                    "degree of the polynomial");
+    size_t absolute_position = 0;
+    if constexpr (Dimension == 1) {
+      absolute_position = relative_pos[0];
+    } else if constexpr (Dimension == 2) {
+      size_t total_degree = relative_pos[0] + relative_pos[1];
+      absolute_position   = total_degree * (total_degree + 1) / 2 + relative_pos[1];
+    } else {
+      // throw NotImplementedError("Not yet Available in 3D");
+      static_assert(Dimension == 3);
+      size_t total_degree     = relative_pos[0] + relative_pos[1] + relative_pos[2];
+      size_t total_sub_degree = relative_pos[1] + relative_pos[2];
+      absolute_position       = total_degree * (total_degree + 1) * (total_degree + 2) / 6 +
+                          total_sub_degree * (total_sub_degree + 1) / 2 + relative_pos[2];
+    }
+
+    return m_coefficients[absolute_position];
+  }
+
+  PUGS_INLINE
+  constexpr double&
+  operator[](const TinyVector<Dimension, size_t>& relative_pos)
+  {
+    size_t total_degree_chk = 0;
+    for (size_t i = 0; i < Dimension; ++i) {
+      Assert((relative_pos[i] <= N),
+             "You are looking for a coefficient of order greater than the degree of the polynomial");
+      total_degree_chk += relative_pos[i];
+    }
+    Assert((total_degree_chk <= N), "The sum of the degrees of the coefficient you are looking for is greater than the "
+                                    "degree of the polynomial");
+    size_t absolute_position = 0;
+    if constexpr (Dimension == 1) {
+      absolute_position = relative_pos[0];
+    } else if constexpr (Dimension == 2) {
+      size_t total_degree = relative_pos[0] + relative_pos[1];
+      absolute_position   = total_degree * (total_degree + 1) / 2 + relative_pos[1];
+    } else {
+      // throw NotImplementedError("Not yet Available in 3D");
+      static_assert(Dimension == 3);
+      size_t total_degree     = relative_pos[0] + relative_pos[1] + relative_pos[2];
+      size_t total_sub_degree = relative_pos[1] + relative_pos[2];
+      absolute_position       = total_degree * (total_degree + 1) * (total_degree + 2) / 6 +
+                          total_sub_degree * (total_sub_degree + 1) / 2 + relative_pos[2];
+    }
+
+    return m_coefficients[absolute_position];
+  }
+
+  PUGS_INLINE
+  constexpr double
+  absolute_position(const TinyVector<Dimension, size_t> relative_pos) const
+  {
+    size_t total_degree = 0;
+    for (size_t i = 0; i < Dimension; ++i) {
+      Assert((relative_pos[i] <= N),
+             "You are looking for a coefficient of order greater than the degree of the polynomial");
+      total_degree += relative_pos[i];
+    }
+    Assert((total_degree <= N), "The sum of the degrees of the coefficient you are looking for is greater than the "
+                                "degree of the polynomial");
+    size_t abs_pos = 0;
+    if constexpr (Dimension == 1) {
+      abs_pos = relative_pos[0];
+    } else if constexpr (Dimension == 2) {
+      abs_pos = total_degree * (total_degree + 1) / 2 + relative_pos[1];
+    } else {
+      static_assert(Dimension == 3);
+      total_degree            = relative_pos[0] + relative_pos[1] + relative_pos[2];
+      size_t total_sub_degree = relative_pos[1] + relative_pos[2];
+      abs_pos                 = total_degree * (total_degree + 1) * (total_degree + 2) / 6 +
+                total_sub_degree * (total_sub_degree + 1) / 2 + relative_pos[2];
+      // throw NotImplementedError("Not yet Available in 3D");
+    }
+
+    return abs_pos;
+  }
+
+  PUGS_INLINE
+  constexpr double
+  operator()(const TinyVector<Dimension> x) const
+  {
+    const auto& P = *this;
+    double value  = 0.;
+    if constexpr (Dimension == 1) {
+      value = m_coefficients[N];
+      for (size_t i = N; i > 0; --i) {
+        value *= x - m_x0;
+        value += m_coefficients[i - 1];
+      }
+    } else if constexpr (Dimension == 2) {
+      TinyVector<Dimension, size_t> relative_pos(0, N);
+      value = P[relative_pos];
+      for (size_t i = N; i > 0; --i) {
+        value *= (x[1] - m_x0[1]);
+        relative_pos  = TinyVector<Dimension, size_t>(N - i + 1, i - 1);
+        double valuex = P[relative_pos];
+        for (size_t j = N - i + 1; j > 0; --j) {
+          valuex *= (x[0] - m_x0[0]);
+          relative_pos = TinyVector<Dimension, size_t>(j - 1, i - 1);
+          valuex += P[relative_pos];
+        }
+        value += valuex;
+      }
+    } else {
+      TinyVector<Dimension, size_t> relative_pos(0, 0, N);
+      value = P[relative_pos];
+      for (size_t i = N; i > 0; --i) {
+        value *= (x[2] - m_x0[2]);
+        relative_pos  = TinyVector<Dimension, size_t>(0, N - i + 1, i - 1);
+        double valuey = P[relative_pos];
+        for (size_t j = N - i + 1; j > 0; --j) {
+          valuey *= (x[1] - m_x0[1]);
+          relative_pos  = TinyVector<Dimension, size_t>(N - i - j + 2, j - 1, i - 1);
+          double valuex = P[relative_pos];
+          for (size_t k = N - i - j + 2; k > 0; --k) {
+            valuex *= (x[0] - m_x0[0]);
+            relative_pos = TinyVector<Dimension, size_t>(k - 1, j - 1, i - 1);
+
+            valuex += P[relative_pos];
+          }
+          valuey += valuex;
+        }
+        value += valuey;
+        // throw NotImplementedError("Not yet Available in 3D");
+      }
+    }
+
+    return value;
+  }
+
+  PUGS_INLINE size_t
+  find_size_coef(const size_t degree)
+  {
+    if constexpr (Dimension == 1) {
+      return degree + 1;
+    } else if constexpr (Dimension == 2) {
+      return (degree + 1) * (degree + 2) / 2;
+    } else {
+      static_assert(Dimension == 3);
+      return (degree + 1) * (degree + 2) * (degree + 3) / 6;
+    }
+  }
+
+  PUGS_INLINE constexpr TaylorPolynomial<N, Dimension>
+  derivative(const size_t var) const
+  {
+    const auto P = *this;
+    TinyVector<size_coef> coefs(zero);
+    TaylorPolynomial<N, Dimension> Q(coefs, m_x0);
+    if constexpr (N != 0) {
+      Assert(var < Dimension,
+             "You can not derive a polynomial with respect to a variable of rank greater than the dimension");
+      if constexpr (Dimension == 1) {
+        for (size_t i = 0; i < size_coef - 1; ++i) {
+          coefs[i] = double(i + 1) * P.coefficients()[i + 1];
+        }
+      } else if constexpr (Dimension == 2) {
+        if (var == 0) {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              TinyVector<Dimension, size_t> relative_pos(i, j);
+              TinyVector<Dimension, size_t> relative_posp(i + 1, j);
+              size_t absolute_position            = Q.absolute_position(relative_pos);
+              size_t absolute_positionp           = P.absolute_position(relative_posp);
+              Q.coefficients()[absolute_position] = double(i + 1) * m_coefficients[absolute_positionp];
+            }
+          }
+        } else {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              TinyVector<Dimension, size_t> relative_pos(i, j);
+              TinyVector<Dimension, size_t> relative_posp(i, j + 1);
+              size_t absolute_position            = Q.absolute_position(relative_pos);
+              size_t absolute_positionp           = P.absolute_position(relative_posp);
+              Q.coefficients()[absolute_position] = double(j + 1) * m_coefficients[absolute_positionp];
+            }
+          }
+        }
+      } else {
+        static_assert(Dimension == 3);
+        if (var == 0) {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              for (size_t k = 0; k < N - i - j; ++k) {
+                TinyVector<Dimension, size_t> relative_pos(i, j, k);
+                TinyVector<Dimension, size_t> relative_posp(i + 1, j, k);
+                size_t absolute_position            = Q.absolute_position(relative_pos);
+                size_t absolute_positionp           = P.absolute_position(relative_posp);
+                Q.coefficients()[absolute_position] = double(i + 1) * m_coefficients[absolute_positionp];
+              }
+            }
+          }
+        } else if (var == 1) {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              for (size_t k = 0; k < N - i - j; ++k) {
+                TinyVector<Dimension, size_t> relative_pos(i, j, k);
+                TinyVector<Dimension, size_t> relative_posp(i, j + 1, k);
+                size_t absolute_position            = Q.absolute_position(relative_pos);
+                size_t absolute_positionp           = P.absolute_position(relative_posp);
+                Q.coefficients()[absolute_position] = double(j + 1) * m_coefficients[absolute_positionp];
+              }
+            }
+          }
+        } else {
+          for (size_t i = 0; i < N; ++i) {
+            for (size_t j = 0; j < N - i; ++j) {
+              for (size_t k = 0; k < N - i - j; ++k) {
+                TinyVector<Dimension, size_t> relative_pos(i, j, k);
+                TinyVector<Dimension, size_t> relative_posp(i, j, k + 1);
+                size_t absolute_position            = Q.absolute_position(relative_pos);
+                size_t absolute_positionp           = P.absolute_position(relative_posp);
+                Q.coefficients()[absolute_position] = double(k + 1) * m_coefficients[absolute_positionp];
+              }
+            }
+          }
+        }
+        // throw NotImplementedError("Not yet Available in 3D");
+      }
+    }
+    return Q;
+  }
+
+  PUGS_INLINE
+  constexpr friend std::ostream&
+  operator<<(std::ostream& os, const TaylorPolynomial<N, Dimension>& P)
+  {
+    //    os << "P(x) = ";
+    bool all_coef_zero = true;
+    if (N == 0) {
+      os << P.coefficients()[0];
+      return os;
+    }
+    if constexpr (Dimension == 1) {
+      if (N != 1) {
+        if (P.coefficients()[N] != 0.) {
+          if (P.coefficients()[N] < 0.) {
+            os << "- ";
+          }
+          if (P.coefficients()[N] != 1 && P.coefficients()[N] != -1) {
+            os << std::abs(P.coefficients()[N]);
+          }
+          os << "(x - " << P.x0()[0] << ")"
+             << "^" << N;
+          all_coef_zero = false;
+        }
+      }
+      for (size_t i = N - 1; i > 1; --i) {
+        if (P.coefficients()[i] != 0.) {
+          if (P.coefficients()[i] > 0.) {
+            os << " + ";
+          } else if (P.coefficients()[i] < 0.) {
+            os << " - ";
+          }
+          if (P.coefficients()[i] != 1 && P.coefficients()[i] != -1) {
+            os << std::abs(P.coefficients()[i]);
+          }
+          os << "(x - " << P.x0()[0] << ")"
+             << "^" << i;
+          all_coef_zero = false;
+        }
+      }
+      if (P.coefficients()[1] != 0.) {
+        if (P.coefficients()[1] > 0. && N != 1) {
+          os << " + ";
+        } else if (P.coefficients()[1] < 0.) {
+          os << " - ";
+        }
+        if (P.coefficients()[1] != 1 && P.coefficients()[1] != -1) {
+          os << std::abs(P.coefficients()[1]);
+        }
+        os << "(x - " << P.x0()[0] << ")";
+        all_coef_zero = false;
+      }
+      if (P.coefficients()[0] != 0. || all_coef_zero) {
+        if (P.coefficients()[0] > 0.) {
+          os << " + ";
+        } else if (P.coefficients()[0] < 0.) {
+          os << " - ";
+        }
+        os << std::abs(P.coefficients()[0]);
+      }
+      return os;
+    } else if constexpr (Dimension == 2) {
+      size_t i = 0;
+      size_t j = N;
+      TinyVector<Dimension, size_t> rel_pos(i, j);
+      double coef = P[rel_pos];
+      if (coef != 0.) {
+        if (coef < 0.) {
+          os << " - ";
+        }
+        if (coef != 1 && coef != -1) {
+          os << std::abs(coef);
+        }
+        os << "(y - " << P.x0()[1] << ")"
+           << "^" << N;
+      }
+      size_t degree = N;
+      for (size_t k = size_coef - 1; k > 0; --k) {
+        if (j > 0) {
+          j--;
+          i++;
+        } else {
+          degree--;
+          j = degree;
+          i = 0;
+        }
+        rel_pos = TinyVector<Dimension, size_t>(i, j);
+        coef    = P[rel_pos];
+        if (coef != 0.) {
+          if (coef > 0.) {
+            os << " + ";
+          } else if (coef < 0.) {
+            os << " - ";
+          }
+          if ((coef != 1 && coef != -1) || (i == 0 && j == 0)) {
+            os << std::abs(coef);
+          }
+          if (i == 0 && j == 0)
+            continue;
+          if (i == 0) {
+            if (j != 1) {
+              os << "(y - " << P.x0()[1] << ")"
+                 << "^" << j;
+            } else {
+              os << "(y - " << P.x0()[1] << ")";
+            }
+          } else if (j == 0) {
+            if (i == 1) {
+              os << "(x - " << P.x0()[0] << ")";
+            } else {
+              os << "(x - " << P.x0()[0] << ")"
+                 << "^" << i;
+            }
+          } else {
+            if (i == 1 && j == 1) {
+              os << "(x - " << P.x0()[0] << ")"
+                 << "(y - " << P.x0()[1] << ")";
+            } else if (i == 1) {
+              os << "(x - " << P.x0()[0] << ")"
+                 << "(y - " << P.x0()[1] << ")^" << j;
+            } else if (j == 1) {
+              os << "(x - " << P.x0()[0] << ")"
+                 << "^" << i << "(y - " << P.x0()[1] << ")";
+            } else {
+              os << "(x - " << P.x0()[0] << ")"
+                 << "^" << i << "(y - " << P.x0()[1] << ")^" << j;
+            }
+          }
+          all_coef_zero = false;
+        }
+      }
+
+      return os;
+    } else {
+      // size_t i = 0;
+      // size_t j = 0;
+      // size_t k = N;
+      // TinyVector<Dimension, size_t> rel_pos(i, j, k);
+      // double coef = P[rel_pos];
+      // if (coef != 0.) {
+      //   if (coef < 0.) {
+      //     os << " - ";
+      //   }
+      //   if (coef != 1 && coef != -1) {
+      //     os << std::abs(coef);
+      //   }
+      //   os << "z^" << N;
+      // }
+      // size_t degree = N;
+      // for (size_t l = size_coef - 1; l > 0; --l) {
+      //   if (k > 0) {
+      //     k--;
+      //     if (j < k) {
+      //       j++;
+      //     } else {
+      //       j--;
+      //       i++;
+      //     }
+      //   } else {
+      //     degree--;
+      //     k = degree;
+      //     i = 0;
+      //     j = 0;
+      //   }
+
+      //   rel_pos     = TinyVector<Dimension, size_t>(i, j, k);
+      //   double coef = P[rel_pos];
+      //   if (coef != 0.) {
+      //     if (coef > 0.) {
+      //       os << " + ";
+      //     } else if (coef < 0.) {
+      //       os << " - ";
+      //     }
+      //     if ((coef != 1 && coef != -1) || (i == 0 && j == 0 && k == 0)) {
+      //       os << std::abs(coef);
+      //     }
+      //     if (i == 0 && j == 0 && k == 0)
+      //       continue;
+      //     if (i == 0 && j == 0) {
+      //       if (k != 1) {
+      //         os << "z^" << j;
+      //       } else {
+      //         os << "z";
+      //       }
+      //     } else if (i == 0 && k == 0) {
+      //       if (j == 1) {
+      //         os << "y";
+      //       } else {
+      //         os << "y^" << i;
+      //       }
+      //     } else if (j == 0 && k == 0) {
+      //       if (i == 1) {
+      //         os << "x";
+      //       } else {
+      //         os << "x^" << i;
+      //       }
+      //     } else {
+      //       if (i == 1 && j == 1 && k == 1) {
+      //         os << "xyz";
+      //       } else if (i == 1) {
+      //         os << "x"
+      //            << "y^" << j << "z^" << k;
+      //       } else if (j == 1) {
+      //         os << "x^" << i << "y"
+      //            << "z^" << k;
+      //       } else if (k == 1) {
+      //         os << "x^" << i << "y^" << j << "z";
+      //       } else {
+      //         os << "x^" << i << "y^" << j << "z^" << k;
+      //       }
+      //     }
+      //     all_coef_zero = false;
+      //   }
+      //
+      for (size_t l = 0; l < size_coef; ++l) {
+        double coef = P.coefficients()[l];
+        os << coef << " ";
+      }
+      return os;
+      //      throw NotImplementedError("Not yet Available in 3D");
+    }
+  }
+
+  PUGS_INLINE constexpr TaylorPolynomial() noexcept = default;
+  ~TaylorPolynomial()                               = default;
+};
+
+template <size_t N, size_t Dimension, size_t P>
+PUGS_INLINE double
+integrate(const TaylorPolynomial<N, Dimension> Q, const std::array<TinyVector<Dimension>, P>& positions)
+{
+  double integral = 0.;
+  static_assert(P > 1, "For the integration, number of positions should be greater or equal to 2");
+  static_assert(N >= 0, "invalid degree");
+  if constexpr (Dimension == 1) {
+    static_assert(P == 2, "In 1D number of positions should be 2");
+    throw NotImplementedError("Not yet Available in 1D");
+  } else if constexpr (Dimension == 2) {
+    static_assert(P <= 4, "In 2D number of positions should be lesser or equal to 4");
+    if constexpr (P == 2) {
+      const QuadratureFormula<1>& lN = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(N));
+      const LineTransformation<2> t{positions[0], positions[1]};
+      double value = 0.;
+      for (size_t i = 0; i < lN.numberOfPoints(); ++i) {
+        value += lN.weight(i) * t.velocityNorm() * Q(t(lN.point(i)));
+      }
+      integral = value;
+    } else if constexpr (P == 3) {
+      const QuadratureFormula<2>& lN = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(N));
+      auto point_list                = lN.pointList();
+      auto weight_list               = lN.weightList();
+      TriangleTransformation<2> t{positions[0], positions[1], positions[2]};
+      auto value = weight_list[0] * Q(t(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * Q(t(point_list[i]));
+      }
+      integral = t.jacobianDeterminant() * value;
+    } else {
+      const QuadratureFormula<2>& lN =
+        QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(N + 1));
+      auto point_list  = lN.pointList();
+      auto weight_list = lN.weightList();
+      SquareTransformation<2> s{positions[0], positions[1], positions[2], positions[3]};
+      auto value = weight_list[0] * s.jacobianDeterminant(point_list[0]) * Q(s(point_list[0]));
+      for (size_t i = 1; i < weight_list.size(); ++i) {
+        value += weight_list[i] * s.jacobianDeterminant(point_list[i]) * Q(s(point_list[i]));
+      }
+      integral = value;
+    }
+  } else {
+    static_assert(Dimension == 3, "Dimension should be <=3");
+    throw NotImplementedError("Not yet Available in 3D");
+  }
+  return integral;
+}
+
+#endif   // POLYNOMIALP_HPP
diff --git a/src/language/algorithms/CMakeLists.txt b/src/language/algorithms/CMakeLists.txt
index 9612143a50b926c5bd96a8e46f8b87d746812ae5..7466aab0d4938c298702cb61dd3e452249a8a5ac 100644
--- a/src/language/algorithms/CMakeLists.txt
+++ b/src/language/algorithms/CMakeLists.txt
@@ -3,3 +3,7 @@
 add_library(PugsLanguageAlgorithms
   INTERFACE # THIS SHOULD BE REMOVED IF FILES ARE FINALY PROVIDED
   )
+
+add_dependencies(PugsLanguageAlgorithms
+  PugsUtils
+  PugsMesh)
diff --git a/src/language/algorithms/ODEDiscontinuousGalerkin1D.cpp b/src/language/algorithms/ODEDiscontinuousGalerkin1D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9ed431f8aef454ed751fc52f6d4d84df97172c87
--- /dev/null
+++ b/src/language/algorithms/ODEDiscontinuousGalerkin1D.cpp
@@ -0,0 +1,98 @@
+#include <language/algorithms/ODEDiscontinuousGalerkin1D.hpp>
+#include <language/utils/InterpolateArray.hpp>
+#include <scheme/ODEGD1D.hpp>
+
+#include <fstream>
+
+template <size_t Dimension, size_t Degree>
+ODEDiscontinuousGalerkin1D<Dimension, Degree>::ODEDiscontinuousGalerkin1D(const BasisType& basis_type,
+                                                                          std::shared_ptr<const IMesh> i_mesh,
+                                                                          const FunctionSymbolId& uex_id)
+{
+  using ConnectivityType = Connectivity<Dimension>;
+  using MeshType         = Mesh<ConnectivityType>;
+
+  std::shared_ptr<const MeshType> mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+  static_assert(Dimension == 1, "Dimension have to be 1 for DiscontinuousGalerkin1D");
+  using R1 = TinyVector<Dimension>;
+  ODEDG1D<Degree> odedg1d(basis_type, mesh);
+  const NodeValue<const R1> xr    = mesh->xr();
+  const auto& cell_to_node_matrix = mesh->connectivity().cellToNodeMatrix();
+  Array<double> my_array(100 * mesh->numberOfCells() + 1);
+
+  for (CellId j = 0; j < mesh->numberOfCells(); ++j) {
+    const auto& cell_nodes          = cell_to_node_matrix[j];
+    const NodeId r1                 = cell_nodes[0];
+    const NodeId r2                 = cell_nodes[1];
+    const TinyVector<Dimension> xr1 = xr[r1];
+    const TinyVector<Dimension> xr2 = xr[r2];
+    const double deltax             = (xr2[0] - xr1[0]) / 100;
+    bool start                      = (j == 0);
+    for (size_t i = (!start); i <= 100; ++i) {
+      const double x        = xr1[0] + deltax * i;
+      my_array[100 * j + i] = x;
+    }
+  }
+
+  Array<double> ej = InterpolateArray<double(double)>::interpolate(uex_id, my_array);
+  // Array<TinyVector<Dimension>> ej = InterpolateArray<TinyVector<Dimension>(double)>::interpolate(uex_id, my_array);
+
+  // InterpolateItemValue<double(TinyVector<Dimension>)>::template interpolate<ItemType::cell>(uex_id, mesh_data.xj());
+
+  CellValue<Polynomial<Degree>> U(mesh->connectivity());
+  odedg1d.globalSolve(U);
+  PolynomialBasis<Degree> B;
+  TinyVector<Degree + 1> zeros;
+  for (size_t i = 0; i <= Degree; i++) {
+    zeros[i] = i;
+  }
+  B.build(basis_type, 0.5, zeros);
+  std::string filename = "result-basis-";
+  filename += B.displayType();
+  filename += "-degree-";
+  filename += std::to_string(Degree);
+  filename += "-dof-";
+  filename += std::to_string(mesh->numberOfCells());
+  std::string filename2 = filename + "-polynomials";
+  std::ofstream sout(filename.c_str());
+  std::ofstream sout2(filename2.c_str());
+  for (CellId j = 0; j < mesh->numberOfCells(); ++j) {
+    const auto& cell_nodes          = cell_to_node_matrix[j];
+    const NodeId r1                 = cell_nodes[0];
+    const NodeId r2                 = cell_nodes[1];
+    const TinyVector<Dimension> xr1 = xr[r1];
+    const TinyVector<Dimension> xr2 = xr[r2];
+    const double deltax             = (xr2[0] - xr1[0]) / 100;
+    bool start                      = (j == 0);
+    double norm1error               = 0.;
+    for (size_t i = (!start); i <= 100; ++i) {
+      const double x = xr1[0] + deltax * i;
+      sout2 << x << " " << U[j](x) << " " << ej[100 * j + i] << " " << std::abs(U[j](x) - ej[100 * j + i]) << '\n';
+      norm1error += std::abs(U[j](x) - ej[100 * j + i]);
+    }
+    double x13 = (2 * xr1[0] + xr2[0]) / 3;
+    double x23 = (xr1[0] + 2 * xr2[0]) / 3;
+
+    double uex = std::exp(xr1[0]) + 3 * (std::exp(x13) + std::exp(x23)) + std::exp(xr2[0]);
+    uex /= 8;
+    double ucalc = integrate(U[j], xr1[0], xr2[0]) / (xr2[0] - xr1[0]);
+    sout << (0.5 * (xr1[0] + xr2[0])) << " " << uex << " " << ucalc << " " << std::abs(ucalc - uex) << " " << norm1error
+         << '\n';
+  }
+}
+
+template ODEDiscontinuousGalerkin1D<1, 0>::ODEDiscontinuousGalerkin1D(const BasisType& basis_type,
+                                                                      std::shared_ptr<const IMesh>,
+                                                                      const FunctionSymbolId&);
+template ODEDiscontinuousGalerkin1D<1, 1>::ODEDiscontinuousGalerkin1D(const BasisType& basis_type,
+                                                                      std::shared_ptr<const IMesh>,
+                                                                      const FunctionSymbolId&);
+template ODEDiscontinuousGalerkin1D<1, 2>::ODEDiscontinuousGalerkin1D(const BasisType& basis_type,
+                                                                      std::shared_ptr<const IMesh>,
+                                                                      const FunctionSymbolId&);
+template ODEDiscontinuousGalerkin1D<1, 3>::ODEDiscontinuousGalerkin1D(const BasisType& basis_type,
+                                                                      std::shared_ptr<const IMesh>,
+                                                                      const FunctionSymbolId&);
+template ODEDiscontinuousGalerkin1D<1, 4>::ODEDiscontinuousGalerkin1D(const BasisType& basis_type,
+                                                                      std::shared_ptr<const IMesh>,
+                                                                      const FunctionSymbolId&);
diff --git a/src/language/algorithms/ODEDiscontinuousGalerkin1D.hpp b/src/language/algorithms/ODEDiscontinuousGalerkin1D.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f7425d8d9586b6db1d8a823706c5356e9c645384
--- /dev/null
+++ b/src/language/algorithms/ODEDiscontinuousGalerkin1D.hpp
@@ -0,0 +1,16 @@
+#ifndef ORDINARY_DIFFERENTIAL_EQUATION_DISCONTINUOUS_GALERKIN_1D_HPP
+#define ORDINARY_DIFFERENTIAL_EQUATION_DISCONTINUOUS_GALERKIN_1D_HPP
+
+#include <analysis/PolynomialBasis.hpp>
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IMesh.hpp>
+
+template <size_t Dimension, size_t Degree>
+struct ODEDiscontinuousGalerkin1D
+{
+  ODEDiscontinuousGalerkin1D(const BasisType& basis_type,
+                             std::shared_ptr<const IMesh> i_mesh,
+                             const FunctionSymbolId& uex_id);
+};
+
+#endif   // ORDINARY_DIFFERENTIAL_EQUATION_DISCONTINUOUS_GALERKIN_1D_HPP
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index b9b789cfa8e3d7bc5328062c6085217dbe142120..c383baffaa7b3df583e09ed0c4965cb783a8ccde 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -42,6 +42,8 @@
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
 #include <scheme/FreeBoundaryConditionDescriptor.hpp>
 #include <scheme/HyperelasticSolver.hpp>
+#include <scheme/HyperplasticSolver.hpp>
+#include <scheme/HyperplasticSolverO2.hpp>
 #include <scheme/IBoundaryConditionDescriptor.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 #include <scheme/InflowBoundaryConditionDescriptor.hpp>
@@ -76,6 +78,8 @@ SchemeModule::SchemeModule()
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryConditionDescriptor>>);
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const VariableBCDescriptor>>);
 
+  //  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const BasisType>>);
+
   this->_addBuiltinFunction("P0", std::function(
 
                                     []() -> std::shared_ptr<const IDiscreteFunctionDescriptor> {
@@ -654,6 +658,753 @@ SchemeModule::SchemeModule()
 
                               ));
 
+  this->_addBuiltinFunction("hyperplastic_eucclhyd_fluxes",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list)
+                                -> std::tuple<std::shared_ptr<const ItemValueVariant>,
+                                              std::shared_ptr<const SubItemValuePerItemVariant>> {
+                                return HyperplasticSolverHandler{getCommonMesh({rho, aL, aT, u, sigma})}
+                                  .solver()
+                                  .compute_fluxes(HyperplasticSolverHandler::SolverType::Eucclhyd, rho, aL, aT, u,
+                                                  sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_eucclhyd_solver_implicit",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverHandler::SolverType::Eucclhyd,
+                                         HyperplasticSolverHandler::RelaxationType::Implicit, dt, rho, u, E, Fe, zeta,
+                                         yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_eucclhyd_solver_exponential",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverHandler::SolverType::Eucclhyd,
+                                         HyperplasticSolverHandler::RelaxationType::Exponential, dt, rho, u, E, Fe,
+                                         zeta, yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_eucclhyd_solver_cauchygreen",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverHandler::SolverType::Eucclhyd,
+                                         HyperplasticSolverHandler::RelaxationType::CauchyGreen, dt, rho, u, E, Fe,
+                                         zeta, yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_plastic_step_implicit",
+                            std::function([](const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& sigman,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& sigmanp1,
+                                             const double& dt) -> std::shared_ptr<const DiscreteFunctionVariant> {
+                              return HyperplasticSolverHandler{getCommonMesh({Fe, zeta, yield, sigman, sigmanp1})}
+                                .solver()
+                                .apply_plastic_relaxation(HyperplasticSolverHandler::RelaxationType::Implicit, dt, Fe,
+                                                          zeta, yield, sigman, sigmanp1);
+                            }
+
+                                          ));
+
+  this->_addBuiltinFunction("hyperplastic_plastic_step_exponential",
+                            std::function([](const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& sigman,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& sigmanp1,
+                                             const double& dt) -> std::shared_ptr<const DiscreteFunctionVariant> {
+                              return HyperplasticSolverHandler{getCommonMesh({Fe, zeta, yield, sigman, sigmanp1})}
+                                .solver()
+                                .apply_plastic_relaxation(HyperplasticSolverHandler::RelaxationType::Exponential, dt,
+                                                          Fe, zeta, yield, sigman, sigmanp1);
+                            }
+
+                                          ));
+
+  this->_addBuiltinFunction("hyperplastic_elastic_step_eucclhyd",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{getCommonMesh({rho, u, E, Fe, aL, aT, sigma})}
+                                  .solver()
+                                  .apply_elastic(HyperplasticSolverHandler::SolverType::Eucclhyd, dt, rho, u, E, Fe, aL,
+                                                 aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_glace_fluxes",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list)
+                                -> std::tuple<std::shared_ptr<const ItemValueVariant>,
+                                              std::shared_ptr<const SubItemValuePerItemVariant>> {
+                                return HyperplasticSolverHandler{getCommonMesh({rho, aL, aT, u, sigma})}
+                                  .solver()
+                                  .compute_fluxes(HyperplasticSolverHandler::SolverType::Glace,   //
+                                                  rho, aL, aT, u, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_elastic_step_glace",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{getCommonMesh({rho, u, E, Fe, aL, aT, sigma})}
+                                  .solver()
+                                  .apply_elastic(HyperplasticSolverHandler::SolverType::Glace, dt, rho, u, E, Fe, aL,
+                                                 aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_glace_solver_implicit",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverHandler::SolverType::Glace,
+                                         HyperplasticSolverHandler::RelaxationType::Implicit, dt, rho, u, E, Fe, zeta,
+                                         yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_glace_solver_exponential",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverHandler::SolverType::Glace,
+                                         HyperplasticSolverHandler::RelaxationType::Exponential, dt, rho, u, E, Fe,
+                                         zeta, yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_glace_solver_cauchygreen",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverHandler::SolverType::Glace,
+                                         HyperplasticSolverHandler::RelaxationType::CauchyGreen, dt, rho, u, E, Fe,
+                                         zeta, yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("apply_hyperplastic_fluxes_implicit",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,      //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,       //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,     //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,    //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,    //
+                                 const std::shared_ptr<const ItemValueVariant>& ur,              //
+                                 const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,   //
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{getCommonMesh({rho, u, E, Fe})}   //
+                                  .solver()
+                                  .apply_fluxes(dt, rho, u, E, Fe, zeta, yield, sigma, ur, Fjr,
+                                                HyperplasticSolverHandler::RelaxationType::Implicit);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("apply_hyperplastic_fluxes_cauchygreen",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,      //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,       //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,     //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,    //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,    //
+                                 const std::shared_ptr<const ItemValueVariant>& ur,              //
+                                 const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,   //
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{getCommonMesh({rho, u, E, Fe})}   //
+                                  .solver()
+                                  .apply_fluxes(dt, rho, u, E, Fe, zeta, yield, sigma, ur, Fjr,
+                                                HyperplasticSolverHandler::RelaxationType::CauchyGreen);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("apply_hyperplastic_fluxes_exponential",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,      //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,       //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,     //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,    //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,    //
+                                 const std::shared_ptr<const ItemValueVariant>& ur,              //
+                                 const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,   //
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverHandler{getCommonMesh({rho, u, E, Fe})}   //
+                                  .solver()
+                                  .apply_fluxes(dt, rho, u, E, Fe, zeta, yield, sigma, ur, Fjr,
+                                                HyperplasticSolverHandler::RelaxationType::Exponential);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_eucclhyd_fluxes_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list)
+                                -> std::tuple<std::shared_ptr<const ItemValueVariant>,
+                                              std::shared_ptr<const SubItemValuePerItemVariant>> {
+                                return HyperplasticSolverO2Handler{getCommonMesh({rho, aL, aT, u, sigma})}
+                                  .solver()
+                                  .compute_fluxes(HyperplasticSolverO2Handler::SolverType::Eucclhyd, rho, aL, aT, u,
+                                                  sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_eucclhyd_solver_implicit_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverO2Handler::SolverType::Eucclhyd,
+                                         HyperplasticSolverO2Handler::RelaxationType::Implicit, dt, rho, u, E, Fe, zeta,
+                                         yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_eucclhyd_solver_exponential_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverO2Handler::SolverType::Eucclhyd,
+                                         HyperplasticSolverO2Handler::RelaxationType::Exponential, dt, rho, u, E, Fe,
+                                         zeta, yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_eucclhyd_solver_cauchygreen_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverO2Handler::SolverType::Eucclhyd,
+                                         HyperplasticSolverO2Handler::RelaxationType::CauchyGreen, dt, rho, u, E, Fe,
+                                         zeta, yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_plastic_step_implicit_o2",
+                            std::function([](const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& sigman,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& sigmanp1,
+                                             const double& dt) -> std::shared_ptr<const DiscreteFunctionVariant> {
+                              return HyperplasticSolverO2Handler{getCommonMesh({Fe, zeta, yield, sigman, sigmanp1})}
+                                .solver()
+                                .apply_plastic_relaxation(HyperplasticSolverO2Handler::RelaxationType::Implicit, dt, Fe,
+                                                          zeta, yield, sigman, sigmanp1);
+                            }
+
+                                          ));
+
+  this->_addBuiltinFunction("hyperplastic_plastic_step_exponential_o2",
+                            std::function([](const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& sigman,
+                                             const std::shared_ptr<const DiscreteFunctionVariant>& sigmanp1,
+                                             const double& dt) -> std::shared_ptr<const DiscreteFunctionVariant> {
+                              return HyperplasticSolverO2Handler{getCommonMesh({Fe, zeta, yield, sigman, sigmanp1})}
+                                .solver()
+                                .apply_plastic_relaxation(HyperplasticSolverO2Handler::RelaxationType::Exponential, dt,
+                                                          Fe, zeta, yield, sigman, sigmanp1);
+                            }
+
+                                          ));
+
+  this->_addBuiltinFunction("hyperplastic_elastic_step_eucclhyd_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{getCommonMesh({rho, u, E, Fe, aL, aT, sigma})}
+                                  .solver()
+                                  .apply_elastic(HyperplasticSolverO2Handler::SolverType::Eucclhyd, dt, rho, u, E, Fe,
+                                                 aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_glace_fluxes_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list)
+                                -> std::tuple<std::shared_ptr<const ItemValueVariant>,
+                                              std::shared_ptr<const SubItemValuePerItemVariant>> {
+                                return HyperplasticSolverO2Handler{getCommonMesh({rho, aL, aT, u, sigma})}
+                                  .solver()
+                                  .compute_fluxes(HyperplasticSolverO2Handler::SolverType::Glace,   //
+                                                  rho, aL, aT, u, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_elastic_step_glace_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{getCommonMesh({rho, u, E, Fe, aL, aT, sigma})}
+                                  .solver()
+                                  .apply_elastic(HyperplasticSolverO2Handler::SolverType::Glace, dt, rho, u, E, Fe, aL,
+                                                 aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_glace_solver_implicit_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverO2Handler::SolverType::Glace,
+                                         HyperplasticSolverO2Handler::RelaxationType::Implicit, dt, rho, u, E, Fe, zeta,
+                                         yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_glace_solver_exponential_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverO2Handler::SolverType::Glace,
+                                         HyperplasticSolverO2Handler::RelaxationType::Exponential, dt, rho, u, E, Fe,
+                                         zeta, yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("hyperplastic_glace_solver_cauchygreen_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{
+                                  getCommonMesh({rho, u, E, Fe, zeta, yield, aL, aT, sigma})}
+                                  .solver()
+                                  .apply(HyperplasticSolverO2Handler::SolverType::Glace,
+                                         HyperplasticSolverO2Handler::RelaxationType::CauchyGreen, dt, rho, u, E, Fe,
+                                         zeta, yield, aL, aT, sigma, bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("apply_hyperplastic_fluxes_implicit_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,      //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,       //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,     //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,    //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,    //
+                                 const std::shared_ptr<const ItemValueVariant>& ur,              //
+                                 const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,   //
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,   //
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{getCommonMesh({rho, u, E, Fe})}   //
+                                  .solver()
+                                  .apply_fluxes(dt, rho, u, E, Fe, zeta, yield, sigma, ur, Fjr,
+                                                HyperplasticSolverO2Handler::RelaxationType::Implicit,
+                                                bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("apply_hyperplastic_fluxes_cauchygreen_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,      //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,       //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,     //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,    //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,    //
+                                 const std::shared_ptr<const ItemValueVariant>& ur,              //
+                                 const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,   //
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,   //
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{getCommonMesh({rho, u, E, Fe})}   //
+                                  .solver()
+                                  .apply_fluxes(dt, rho, u, E, Fe, zeta, yield, sigma, ur, Fjr,
+                                                HyperplasticSolverO2Handler::RelaxationType::CauchyGreen,
+                                                bc_descriptor_list);
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("apply_hyperplastic_fluxes_exponential_o2",
+                            std::function(
+
+                              [](const std::shared_ptr<const DiscreteFunctionVariant>& rho,      //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& u,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& E,        //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,       //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,     //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,    //
+                                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,    //
+                                 const std::shared_ptr<const ItemValueVariant>& ur,              //
+                                 const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,   //
+                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
+                                   bc_descriptor_list,   //
+                                 const double& dt) -> std::tuple<std::shared_ptr<const MeshVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>,
+                                                                 std::shared_ptr<const DiscreteFunctionVariant>> {
+                                return HyperplasticSolverO2Handler{getCommonMesh({rho, u, E, Fe})}   //
+                                  .solver()
+                                  .apply_fluxes(dt, rho, u, E, Fe, zeta, yield, sigma, ur, Fjr,
+                                                HyperplasticSolverO2Handler::RelaxationType::Exponential,
+                                                bc_descriptor_list);
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("lagrangian",
                             std::function(
 
@@ -721,6 +1472,14 @@ SchemeModule::SchemeModule()
 
                                                  ));
 
+  this->_addBuiltinFunction("hyperplastic_dt", std::function(
+
+                                                 [](const std::shared_ptr<const DiscreteFunctionVariant>& c) -> double {
+                                                   return hyperplastic_dt(c);
+                                                 }
+
+                                                 ));
+
   this->_addBuiltinFunction("fluxing_advection", std::function(
 
                                                    [](const std::shared_ptr<const MeshVariant> new_mesh,
diff --git a/src/language/modules/SchemeModule.hpp b/src/language/modules/SchemeModule.hpp
index cf169175c4b46f7364799e16bdb4b84633d1f61c..6cd0b03427463debf965b9f5662fd21b33f502e3 100644
--- a/src/language/modules/SchemeModule.hpp
+++ b/src/language/modules/SchemeModule.hpp
@@ -1,6 +1,7 @@
 #ifndef SCHEME_MODULE_HPP
 #define SCHEME_MODULE_HPP
 
+#include <analysis/PolynomialBasis.hpp>
 #include <language/modules/BuiltinModule.hpp>
 
 class SchemeModule : public BuiltinModule
diff --git a/src/language/utils/InterpolateArray.hpp b/src/language/utils/InterpolateArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..be5c114f51ffe31da3cf26df690924bd7ef04668
--- /dev/null
+++ b/src/language/utils/InterpolateArray.hpp
@@ -0,0 +1,44 @@
+#ifndef INTERPOLATE_ARRAY_HPP
+#define INTERPOLATE_ARRAY_HPP
+
+#include <language/utils/PugsFunctionAdapter.hpp>
+
+template <typename T>
+class InterpolateArray;
+template <typename OutputType, typename InputType>
+class InterpolateArray<OutputType(InputType)> : public PugsFunctionAdapter<OutputType(InputType)>
+{
+  static constexpr size_t Dimension = OutputType::Dimension;
+  using Adapter                     = PugsFunctionAdapter<OutputType(InputType)>;
+
+ public:
+  static inline Array<OutputType>
+  interpolate(const FunctionSymbolId& function_symbol_id, const Array<const InputType>& position)
+  {
+    auto& expression    = Adapter::getFunctionExpression(function_symbol_id);
+    auto convert_result = Adapter::getResultConverter(expression.m_data_type);
+
+    Array<ExecutionPolicy> context_list = Adapter::getContextList(expression);
+
+    using execution_space = typename Kokkos::DefaultExecutionSpace::execution_space;
+    Kokkos::Experimental::UniqueToken<execution_space, Kokkos::Experimental::UniqueTokenScope::Global> tokens;
+
+    Array<OutputType> value(position.size());
+
+    parallel_for(position.size(), [=, &expression, &tokens](size_t i) {
+      const int32_t t = tokens.acquire();
+
+      auto& execution_policy = context_list[t];
+
+      Adapter::convertArgs(execution_policy.currentContext(), position[i]);
+      auto result = expression.execute(execution_policy);
+      value[i]    = convert_result(std::move(result));
+
+      tokens.release(t);
+    });
+
+    return value;
+  }
+};
+
+#endif /* INTERPOLATE_ARRAY_HPP */
diff --git a/src/scheme/CMakeLists.txt b/src/scheme/CMakeLists.txt
index 692a36598aceda4189be3d4701699933e63caee8..6e49cfd695124e7377cd618117ea1dbe9179a5de 100644
--- a/src/scheme/CMakeLists.txt
+++ b/src/scheme/CMakeLists.txt
@@ -3,6 +3,9 @@
 add_library(
   PugsScheme
   AcousticSolver.cpp
+  HyperelasticSolver.cpp
+  HyperplasticSolver.cpp
+  HyperplasticSolverO2.cpp
   DiscreteFunctionIntegrator.cpp
   DiscreteFunctionInterpoler.cpp
   DiscreteFunctionUtils.cpp
diff --git a/src/scheme/DiscontinuousGalerkin1DTools.hpp b/src/scheme/DiscontinuousGalerkin1DTools.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d28a0337053b0804f3a70d63915edc45accc56c3
--- /dev/null
+++ b/src/scheme/DiscontinuousGalerkin1DTools.hpp
@@ -0,0 +1,77 @@
+#ifndef DISCONTINUOUS_GALERKIN_1D_TOOLS
+#define DISCONTINUOUS_GALERKIN_1D_TOOLS
+
+#include <rang.hpp>
+
+#include <utils/ArrayUtils.hpp>
+
+#include <utils/PugsAssert.hpp>
+
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+#include <analysis/Polynomial.hpp>
+#include <analysis/PolynomialBasis.hpp>
+
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+
+#include <utils/Exceptions.hpp>
+#include <utils/Messenger.hpp>
+
+#include <iostream>
+
+template <size_t Degree>
+class DiscontinuousGalerkin1DTools
+{
+  constexpr static size_t Dimension = 1;
+
+  using Rd  = TinyVector<Degree>;
+  using Rdd = TinyMatrix<Degree>;
+
+  using R1 = TinyVector<Dimension>;
+
+ public:
+  int
+  inverse() const
+  {
+    return 0;
+  }
+
+  void
+  elementarySolve(Polynomial<Degree>& U,
+                  const TinyVector<Degree + 1> moments,
+                  const PolynomialBasis<Degree> Basis,
+                  const double& xinf,
+                  const double& xsup) const
+  {
+    TinyMatrix<Degree + 1> M(zero);
+    for (size_t i = 0; i <= Degree; ++i) {
+      for (size_t j = 0; j <= Degree; ++j) {
+        Polynomial<2 * Degree> P = Basis.elements()[i] * Basis.elements()[j];
+        M(i, j)                  = integrate(P, xinf, xsup);
+      }
+    }
+    TinyMatrix inv_M = ::inverse(M);
+    U.coefficients() = inv_M * moments;
+  }
+
+  // void
+  // integrate() const
+  // {}
+
+ public:
+  // TODO add a flux manager to constructor
+  // TODO add a basis to constructor
+  DiscontinuousGalerkin1DTools()
+  {
+    ;
+  }
+};
+
+#endif   // DISCONTINUOUS_GALERKIN_1D_TOOLS
diff --git a/src/scheme/HyperelasticSolver.cpp b/src/scheme/HyperelasticSolver.cpp
index 7bc6cd8c0dc854ecaa8caa2d8809891ed328790d..8354de88b5e8f0f7233a2ea0d150f44bc5b73e75 100644
--- a/src/scheme/HyperelasticSolver.cpp
+++ b/src/scheme/HyperelasticSolver.cpp
@@ -541,6 +541,7 @@ class HyperelasticSolverHandler::HyperelasticSolver final : public HyperelasticS
         const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
   {
     auto [ur, Fjr] = compute_fluxes(solver_type, rho, aL, aT, u, sigma, bc_descriptor_list);
+    // return apply_fluxes(dt, rho, u, E, CG, ur, Fjr);
     return apply_fluxes(dt, rho, u, E, CG, ur, Fjr);
   }
 
diff --git a/src/scheme/HyperplasticSolver.cpp b/src/scheme/HyperplasticSolver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d0c45c2239a34ce4ba8751e4f40cec69b9a62919
--- /dev/null
+++ b/src/scheme/HyperplasticSolver.cpp
@@ -0,0 +1,1534 @@
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/EigenvalueSolver.hpp>
+#include <algebra/SmallMatrix.hpp>
+#include <analysis/Polynomial.hpp>
+#include <scheme/HyperplasticSolver.hpp>
+
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshFlatNodeBoundary.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <mesh/MeshTraits.hpp>
+#include <mesh/SubItemValuePerItemVariant.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <scheme/ExternalBoundaryConditionDescriptor.hpp>
+#include <scheme/FixedBoundaryConditionDescriptor.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <utils/Socket.hpp>
+#include <variant>
+#include <vector>
+
+double
+hyperplastic_dt(const std::shared_ptr<const DiscreteFunctionVariant>& c_v)
+{
+  const auto& c = c_v->get<DiscreteFunctionP0<const double>>();
+
+  return std::visit(
+    [&](auto&& p_mesh) -> double {
+      const auto& mesh = *p_mesh;
+
+      using MeshType = decltype(mesh);
+      if constexpr (is_polygonal_mesh_v<MeshType>) {
+        const auto Vj = MeshDataManager::instance().getMeshData(mesh).Vj();
+        const auto Sj = MeshDataManager::instance().getMeshData(mesh).sumOverRLjr();
+
+        CellValue<double> local_dt{mesh.connectivity()};
+        parallel_for(
+          mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { local_dt[j] = 2 * Vj[j] / (Sj[j] * c[j]); });
+
+        return min(local_dt);
+      } else {
+        throw NormalError("unexpected mesh type");
+      }
+    },
+    c.meshVariant()->variant());
+}
+
+template <MeshConcept MeshType>
+class HyperplasticSolverHandler::HyperplasticSolver final : public HyperplasticSolverHandler::IHyperplasticSolver
+{
+ private:
+  static constexpr size_t Dimension = MeshType::Dimension;
+  using Rdxd                        = TinyMatrix<Dimension>;
+  using R3x3                        = TinyMatrix<3>;
+  using Rd                          = TinyVector<Dimension>;
+  using R3                          = TinyVector<3>;
+
+  using MeshDataType = MeshData<MeshType>;
+
+  using DiscreteScalarFunction   = DiscreteFunctionP0<const double>;
+  using DiscreteVectorFunction   = DiscreteFunctionP0<const Rd>;
+  using DiscreteTensorFunction   = DiscreteFunctionP0<const Rdxd>;
+  using DiscreteTensorFunction3d = DiscreteFunctionP0<const R3x3>;
+
+  class FixedBoundaryCondition;
+  class PressureBoundaryCondition;
+  class NormalStressBoundaryCondition;
+  class SymmetryBoundaryCondition;
+  class VelocityBoundaryCondition;
+
+  using BoundaryCondition = std::variant<FixedBoundaryCondition,
+                                         PressureBoundaryCondition,
+                                         NormalStressBoundaryCondition,
+                                         SymmetryBoundaryCondition,
+                                         VelocityBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+  R3x3
+  to3D(const Rdxd tensor) const
+  {
+    R3x3 tensor3d = zero;
+    for (size_t i = 0; i < Dimension; i++) {
+      for (size_t j = 0; j < Dimension; j++) {
+        tensor3d(i, j) = tensor(i, j);
+      }
+    }
+    return tensor3d;
+  }
+
+  R3
+  to3D(const Rd vector) const
+  {
+    R3 vector3d = zero;
+    for (size_t i = 0; i < Dimension; i++) {
+      vector3d[i] = vector[i];
+    }
+    return vector3d;
+  }
+
+  CellValue<const R3x3>
+  to3D(const MeshType& mesh, const CellValue<Rdxd>& tensor) const
+  {
+    CellValue<R3x3> tensor3d{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { tensor3d[j] = to3D(tensor[j]); });
+    return tensor3d;
+  }
+
+  CellValue<const R3>
+  to3D(const MeshType& mesh, const CellValue<Rd> vector) const
+  {
+    CellValue<R3> vector3d{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { vector3d[j] = to3D(vector[j]); });
+    return vector3d;
+  }
+
+  Rdxd
+  toDimension(const R3x3 tensor3d) const
+  {
+    Rdxd tensor = zero;
+    for (size_t i = 0; i < Dimension; i++) {
+      for (size_t j = 0; j < Dimension; j++) {
+        tensor(i, j) = tensor3d(i, j);
+      }
+    }
+    return tensor;
+  }
+
+  Rd
+  toDimension(const R3 vector3d) const
+  {
+    Rd vector = zero;
+    for (size_t i = 0; i < Dimension; i++) {
+      vector[i] = vector3d[i];
+    }
+    return vector;
+  }
+
+  CellValue<const Rdxd>
+  toDimension(const MeshType& mesh, const CellValue<R3x3>& tensor3d) const
+  {
+    CellValue<Rdxd> tensor{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { tensor[j] = toDimension(tensor3d[j]); });
+    return tensor;
+  }
+
+  CellValue<const Rd>
+  toDimension(const MeshType& mesh, const CellValue<R3>& vector3d) const
+  {
+    CellValue<Rd> vector{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { vector[j] = to3D(vector3d[j]); });
+    return vector;
+  }
+
+  double
+  _compute_chi(const R3x3 S, const double yield) const
+  {
+    const R3x3 Id       = identity;
+    const R3x3 Sd       = S - 1. / 3 * trace(S) * Id;
+    double chi          = 0;
+    const double normS2 = frobeniusNorm(Sd) * frobeniusNorm(Sd);
+    double limit2       = 2. / 3 * yield * yield;
+    const double power  = 0.5;
+    if ((normS2 - limit2) > 0) {
+      chi = std::pow((normS2 - limit2), power);
+    }
+    //    chi                 = std::max(0., std::sqrt(normS2) - std::sqrt(limit2));
+    return chi;
+  }
+
+  double
+  _compute_chi2(const R3x3 S, const double yield) const
+  {
+    const R3x3 Id       = identity;
+    const R3x3 Sd       = S - 1. / 3 * trace(S) * Id;
+    double chi          = 0;
+    const double normS2 = frobeniusNorm(Sd) * frobeniusNorm(Sd);
+    double limit2       = 2. / 3 * yield * yield;
+    double alpha        = (normS2 - limit2);
+    double sign         = alpha / std::abs(alpha);
+    if (sign < 0.) {
+      return chi;
+    }
+    int power    = 10;
+    double ratio = normS2 / limit2;
+    chi          = ratio;
+    for (int i = 0; i < power; i++) {
+      chi *= ratio;
+    }
+    return chi;
+  }
+
+  NodeValuePerCell<const Rdxd>
+  _computeGlaceAjr(const MeshType& mesh, const DiscreteScalarFunction& rhoaL, const DiscreteScalarFunction& rhoaT) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+    const NodeValuePerCell<const Rd> njr = mesh_data.njr();
+
+    NodeValuePerCell<Rdxd> Ajr{mesh.connectivity()};
+    const Rdxd I = identity;
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const size_t& nb_nodes = Ajr.numberOfSubValues(j);
+        const double& rhoaL_j  = rhoaL[j];
+        const double& rhoaT_j  = rhoaT[j];
+        for (size_t r = 0; r < nb_nodes; ++r) {
+          const Rdxd& M = tensorProduct(Cjr(j, r), njr(j, r));
+          Ajr(j, r)     = rhoaL_j * M + rhoaT_j * (l2Norm(Cjr(j, r)) * I - M);
+        }
+      });
+
+    return Ajr;
+  }
+
+  NodeValuePerCell<const Rdxd>
+  _computeEucclhydAjr(const MeshType& mesh,
+                      const DiscreteScalarFunction& rhoaL,
+                      const DiscreteScalarFunction& rhoaT) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+    const NodeValuePerFace<const Rd> nlr = mesh_data.nlr();
+
+    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();
+
+    NodeValuePerCell<Rdxd> Ajr{mesh.connectivity()};
+
+    parallel_for(
+      Ajr.numberOfValues(), PUGS_LAMBDA(size_t jr) { Ajr[jr] = zero; });
+    const Rdxd I = identity;
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        const auto& cell_faces = cell_to_face_matrix[j];
+
+        const double& rho_aL = rhoaL[j];
+        const double& rho_aT = rhoaT[j];
+
+        for (size_t L = 0; L < cell_faces.size(); ++L) {
+          const FaceId& l        = cell_faces[L];
+          const auto& face_nodes = face_to_node_matrix[l];
+
+          auto local_node_number_in_cell = [&](NodeId node_number) {
+            for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) {
+              if (node_number == cell_nodes[i_node]) {
+                return i_node;
+              }
+            }
+            return std::numeric_limits<size_t>::max();
+          };
+
+          for (size_t rl = 0; rl < face_nodes.size(); ++rl) {
+            const size_t R = local_node_number_in_cell(face_nodes[rl]);
+            const Rdxd& M  = tensorProduct(Nlr(l, rl), nlr(l, rl));
+            Ajr(j, R) += rho_aL * M + rho_aT * (l2Norm(Nlr(l, rl)) * I - M);
+          }
+        }
+      });
+
+    return Ajr;
+  }
+
+  NodeValuePerCell<const Rdxd>
+  _computeAjr(const SolverType& solver_type,
+              const MeshType& mesh,
+              const DiscreteScalarFunction& rhoaL,
+              const DiscreteScalarFunction& rhoaT) const
+  {
+    if constexpr (Dimension == 1) {
+      return _computeGlaceAjr(mesh, rhoaL, rhoaT);
+    } else {
+      switch (solver_type) {
+      case SolverType::Glace: {
+        return _computeGlaceAjr(mesh, rhoaL, rhoaT);
+      }
+      case SolverType::Eucclhyd: {
+        return _computeEucclhydAjr(mesh, rhoaL, rhoaT);
+      }
+      default: {
+        throw UnexpectedError("invalid solver type");
+      }
+      }
+    }
+  }
+
+  NodeValue<Rdxd>
+  _computeAr(const MeshType& mesh, const NodeValuePerCell<const Rdxd>& Ajr) const
+  {
+    const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+    const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+    NodeValue<Rdxd> Ar{mesh.connectivity()};
+
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) {
+        Rdxd sum                                   = zero;
+        const auto& node_to_cell                   = node_to_cell_matrix[r];
+        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(r);
+
+        for (size_t j = 0; j < node_to_cell.size(); ++j) {
+          const CellId J       = node_to_cell[j];
+          const unsigned int R = node_local_number_in_its_cells[j];
+          sum += Ajr(J, R);
+        }
+        Ar[r] = sum;
+      });
+
+    return Ar;
+  }
+
+  NodeValue<Rd>
+  _computeBr(const Mesh<Dimension>& mesh,
+             const NodeValuePerCell<const Rdxd>& Ajr,
+             const DiscreteVectorFunction& u,
+             const DiscreteTensorFunction& sigma) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd>& Cjr = mesh_data.Cjr();
+
+    const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+    const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+    NodeValue<Rd> b{mesh.connectivity()};
+
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) {
+        const auto& node_to_cell                   = node_to_cell_matrix[r];
+        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(r);
+
+        Rd br = zero;
+        for (size_t j = 0; j < node_to_cell.size(); ++j) {
+          const CellId J       = node_to_cell[j];
+          const unsigned int R = node_local_number_in_its_cells[j];
+          br += Ajr(J, R) * u[J] - sigma[J] * Cjr(J, R);
+        }
+
+        b[r] = br;
+      });
+
+    return b;
+  }
+
+  BoundaryConditionList
+  _getBCList(const MeshType& mesh,
+             const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    BoundaryConditionList bc_list;
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        bc_list.emplace_back(
+          SymmetryBoundaryCondition(getMeshFlatNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())));
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::fixed: {
+        bc_list.emplace_back(FixedBoundaryCondition(getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())));
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::dirichlet: {
+        const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+          dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+        if (dirichlet_bc_descriptor.name() == "velocity") {
+          MeshNodeBoundary mesh_node_boundary = getMeshNodeBoundary(mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+          Array<const Rd> value_list =
+            InterpolateItemValue<Rd(Rd)>::template interpolate<ItemType::node>(dirichlet_bc_descriptor.rhsSymbolId(),
+                                                                               mesh.xr(),
+                                                                               mesh_node_boundary.nodeList());
+
+          bc_list.emplace_back(VelocityBoundaryCondition{mesh_node_boundary, value_list});
+        } else if (dirichlet_bc_descriptor.name() == "pressure") {
+          const FunctionSymbolId pressure_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          if constexpr (Dimension == 1) {
+            MeshNodeBoundary mesh_node_boundary = getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            Array<const double> node_values =
+              InterpolateItemValue<double(Rd)>::template interpolate<ItemType::node>(pressure_id, mesh.xr(),
+                                                                                     mesh_node_boundary.nodeList());
+
+            bc_list.emplace_back(PressureBoundaryCondition{mesh_node_boundary, node_values});
+          } else {
+            MeshFaceBoundary mesh_face_boundary = getMeshFaceBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+            Array<const double> face_values =
+              InterpolateItemValue<double(Rd)>::template interpolate<ItemType::face>(pressure_id, mesh_data.xl(),
+                                                                                     mesh_face_boundary.faceList());
+            bc_list.emplace_back(PressureBoundaryCondition{mesh_face_boundary, face_values});
+          }
+
+        } else if (dirichlet_bc_descriptor.name() == "normal-stress") {
+          const FunctionSymbolId normal_stress_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          if constexpr (Dimension == 1) {
+            MeshNodeBoundary mesh_node_boundary = getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            Array<const Rdxd> node_values =
+              InterpolateItemValue<Rdxd(Rd)>::template interpolate<ItemType::node>(normal_stress_id, mesh.xr(),
+                                                                                   mesh_node_boundary.nodeList());
+
+            bc_list.emplace_back(NormalStressBoundaryCondition{mesh_node_boundary, node_values});
+          } else {
+            MeshFaceBoundary mesh_face_boundary = getMeshFaceBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+            Array<const Rdxd> face_values =
+              InterpolateItemValue<Rdxd(Rd)>::template interpolate<ItemType::face>(normal_stress_id, mesh_data.xl(),
+                                                                                   mesh_face_boundary.faceList());
+            bc_list.emplace_back(NormalStressBoundaryCondition{mesh_face_boundary, face_values});
+          }
+
+        } 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 hyperplastic solver";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    return bc_list;
+  }
+
+  void _applyPressureBC(const BoundaryConditionList& bc_list, const MeshType& mesh, NodeValue<Rd>& br) const;
+  void _applyNormalStressBC(const BoundaryConditionList& bc_list, const MeshType& mesh, NodeValue<Rd>& br) const;
+  void _applySymmetryBC(const BoundaryConditionList& bc_list, NodeValue<Rdxd>& Ar, NodeValue<Rd>& br) const;
+  void _applyVelocityBC(const BoundaryConditionList& bc_list, NodeValue<Rdxd>& Ar, NodeValue<Rd>& br) const;
+  void
+  _applyBoundaryConditions(const BoundaryConditionList& bc_list,
+                           const MeshType& mesh,
+                           NodeValue<Rdxd>& Ar,
+                           NodeValue<Rd>& br) const
+  {
+    this->_applyPressureBC(bc_list, mesh, br);
+    this->_applyNormalStressBC(bc_list, mesh, br);
+    this->_applySymmetryBC(bc_list, Ar, br);
+    this->_applyVelocityBC(bc_list, Ar, br);
+  }
+
+  NodeValue<const Rd>
+  _computeUr(const MeshType& mesh, const NodeValue<Rdxd>& Ar, const NodeValue<Rd>& br) const
+  {
+    NodeValue<Rd> u{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { u[r] = inverse(Ar[r]) * br[r]; });
+
+    return u;
+  }
+
+  NodeValuePerCell<Rd>
+  _computeFjr(const MeshType& mesh,
+              const NodeValuePerCell<const Rdxd>& Ajr,
+              const NodeValue<const Rd>& ur,
+              const DiscreteVectorFunction& u,
+              const DiscreteTensorFunction& sigma) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    NodeValuePerCell<Rd> F{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        for (size_t r = 0; r < cell_nodes.size(); ++r) {
+          F(j, r) = -Ajr(j, r) * (u[j] - ur[cell_nodes[r]]) + sigma[j] * Cjr(j, r);
+        }
+      });
+
+    return F;
+  }
+
+ public:
+  std::tuple<const std::shared_ptr<const ItemValueVariant>, const std::shared_ptr<const SubItemValuePerItemVariant>>
+  compute_fluxes(const SolverType& solver_type,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& rho_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& aL_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& aT_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& u_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma_v,
+                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    std::shared_ptr mesh_v = getCommonMesh({rho_v, aL_v, aT_v, u_v, sigma_v});
+    if (not mesh_v) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({rho_v, aL_v, u_v, sigma_v}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperplastic solver expects P0 functions");
+    }
+
+    const MeshType& mesh                    = *mesh_v->get<MeshType>();
+    const DiscreteScalarFunction& rho       = rho_v->get<DiscreteScalarFunction>();
+    const DiscreteVectorFunction& u         = u_v->get<DiscreteVectorFunction>();
+    const DiscreteScalarFunction& aL        = aL_v->get<DiscreteScalarFunction>();
+    const DiscreteScalarFunction& aT        = aT_v->get<DiscreteScalarFunction>();
+    const DiscreteTensorFunction3d& sigma3d = sigma_v->get<DiscreteTensorFunction3d>();
+    const R3x3 I                            = identity;
+
+    //    std::shared_ptr<const MeshType> p_mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+    CellValue<R3x3> tensor_values3d{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { tensor_values3d[j] = sigma3d[j]; });
+    CellValue<const Rdxd> tensor_values = toDimension(mesh, tensor_values3d);
+    DiscreteTensorFunction sigma(mesh_v, tensor_values);
+
+    NodeValuePerCell<const Rdxd> Ajr = this->_computeAjr(solver_type, mesh, rho * aL, rho * aT);
+
+    NodeValue<Rdxd> Ar = this->_computeAr(mesh, Ajr);
+    NodeValue<Rd> br   = this->_computeBr(mesh, Ajr, u, sigma);
+
+    const BoundaryConditionList bc_list = this->_getBCList(mesh, bc_descriptor_list);
+    this->_applyBoundaryConditions(bc_list, mesh, Ar, br);
+
+    synchronize(Ar);
+    synchronize(br);
+
+    NodeValue<const Rd> ur         = this->_computeUr(mesh, Ar, br);
+    NodeValuePerCell<const Rd> Fjr = this->_computeFjr(mesh, Ajr, ur, u, sigma);
+
+    return std::make_tuple(std::make_shared<const ItemValueVariant>(ur),
+                           std::make_shared<const SubItemValuePerItemVariant>(Fjr));
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes(const double& dt,
+               const MeshType& mesh,
+               const DiscreteScalarFunction& rho,
+               const DiscreteVectorFunction& u,
+               const DiscreteScalarFunction& E,
+               const DiscreteTensorFunction3d& Fe,
+               const DiscreteScalarFunction& zeta,
+               const DiscreteScalarFunction& yield,
+               const DiscreteTensorFunction3d& sigma,
+               const NodeValue<const Rd>& ur,
+               const NodeValuePerCell<const Rd>& Fjr) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho   = copy(rho.cellValues());
+    CellValue<Rd> new_u         = copy(u.cellValues());
+    CellValue<double> new_E     = copy(E.cellValues());
+    CellValue<R3x3> new_Fe      = copy(Fe.cellValues());
+    CellValue<double> new_zeta  = copy(zeta.cellValues());
+    CellValue<double> new_yield = copy(yield.cellValues());
+    CellValue<R3x3> sigma_s     = copy(sigma.cellValues());
+    CellValue<double> chi{mesh.connectivity()};
+    CellValue<double> rationorm{mesh.connectivity()};
+    chi.fill(0.);
+    rationorm.fill(0.);
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        Rd momentum_fluxes   = zero;
+        double energy_fluxes = 0;
+        Rdxd gradv           = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        R3x3 I                    = identity;
+        R3x3 gradv3d              = to3D(gradv);
+        R3x3 sigmas               = sigma[j] - 1. / 3 * trace(sigma[j]) * I;
+        const R3x3 elastic_fluxes = gradv3d * Fe[j];
+        const double dt_over_Mj   = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj   = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        new_Fe[j] += dt_over_Vj * elastic_fluxes;
+        const double fenorm0 = frobeniusNorm(new_Fe[j]);
+        chi[j]               = _compute_chi(sigma[j], yield[j]);
+        const R3x3 M         = -dt * chi[j] * zeta[j] * sigmas;
+
+        new_Fe[j]            = EigenvalueSolver{}.computeExpForTinyMatrix(M) * new_Fe[j];
+        const double fenorm1 = frobeniusNorm(new_Fe[j]);
+        rationorm[j]         = fenorm1 / fenorm0;
+      });
+    std::cout << "sum_chi " << sum(chi) << " min norm ratio " << min(rationorm) << " max norm ratio " << max(rationorm)
+              << "\n";
+
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    return {std::make_shared<MeshVariant>(new_mesh),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(new_mesh, new_Fe))};
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes2(const double& dt,
+                const MeshType& mesh,
+                const DiscreteScalarFunction& rho,
+                const DiscreteVectorFunction& u,
+                const DiscreteScalarFunction& E,
+                const DiscreteTensorFunction3d& Fe,
+                const DiscreteScalarFunction& zeta,
+                const DiscreteScalarFunction& yield,
+                const DiscreteTensorFunction3d& sigma,
+                const NodeValue<const Rd>& ur,
+                const NodeValuePerCell<const Rd>& Fjr) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho   = copy(rho.cellValues());
+    CellValue<Rd> new_u         = copy(u.cellValues());
+    CellValue<double> new_E     = copy(E.cellValues());
+    CellValue<R3x3> new_Fe      = copy(Fe.cellValues());
+    CellValue<double> new_zeta  = copy(zeta.cellValues());
+    CellValue<double> new_yield = copy(yield.cellValues());
+    CellValue<R3x3> sigma_s     = copy(sigma.cellValues());
+    CellValue<double> chi{mesh.connectivity()};
+    CellValue<double> rationorm{mesh.connectivity()};
+    chi.fill(0.);
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        Rd momentum_fluxes   = zero;
+        double energy_fluxes = 0;
+        Rdxd gradv           = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        R3x3 I                    = identity;
+        R3x3 gradv3d              = to3D(gradv);
+        R3x3 sigmas               = sigma[j] - 1. / 3 * trace(sigma[j]) * I;
+        const R3x3 elastic_fluxes = gradv3d * Fe[j];
+        const double dt_over_Mj   = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj   = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        new_Fe[j] += dt_over_Vj * elastic_fluxes;
+        chi[j]     = _compute_chi(sigma[j], yield[j]);
+        int sub_it = static_cast<int>(std::ceil(dt * chi[j] * zeta[j] * frobeniusNorm(sigmas)));
+        if (sub_it > 1) {
+          std::cout << sub_it << " dt " << dt << " chi[j] " << chi[j] << " zeta[j] " << zeta[j]
+                    << " frobeniusNorm(sigmas) " << frobeniusNorm(sigmas) << "\n";
+        }
+        for (int i = 0; i < sub_it; i++) {
+          const R3x3 M = I + dt / static_cast<double>(sub_it) * chi[j] * zeta[j] * sigmas;
+          new_Fe[j]    = inverse(M) * new_Fe[j];
+        }
+      });
+    std::cout << "sum_chi " << sum(chi) << "\n";
+
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    return {std::make_shared<MeshVariant>(new_mesh),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(new_mesh, new_Fe))};
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes_B(const double& dt,
+                 const MeshType& mesh,
+                 const DiscreteScalarFunction& rho,
+                 const DiscreteVectorFunction& u,
+                 const DiscreteScalarFunction& E,
+                 const DiscreteTensorFunction3d& Be,
+                 const DiscreteScalarFunction& zeta,
+                 const DiscreteScalarFunction& yield,
+                 const DiscreteTensorFunction3d& sigma,
+                 const NodeValue<const Rd>& ur,
+                 const NodeValuePerCell<const Rd>& Fjr) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho   = copy(rho.cellValues());
+    CellValue<Rd> new_u         = copy(u.cellValues());
+    CellValue<double> new_E     = copy(E.cellValues());
+    CellValue<R3x3> new_Be      = copy(Be.cellValues());
+    CellValue<double> new_zeta  = copy(zeta.cellValues());
+    CellValue<double> new_yield = copy(yield.cellValues());
+    CellValue<R3x3> sigma_s     = copy(sigma.cellValues());
+    CellValue<double> chi{mesh.connectivity()};
+    CellValue<double> rationorm{mesh.connectivity()};
+    chi.fill(0.);
+    // rationorm.fill(0.);
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+        Rd momentum_fluxes     = zero;
+        double energy_fluxes   = 0;
+        Rdxd gradv             = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        R3x3 I       = identity;
+        R3x3 gradv3d = to3D(gradv);
+        R3x3 sigmas  = sigma[j] - 1. / 3 * trace(sigma[j]) * I;
+        //        const R3x3 elastic_fluxes = gradv3d + transpose(gradv3d);
+        const double dt_over_Mj = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        // new_Be[j] += dt_over_Vj * elastic_fluxes;
+        // const double fenorm0 = frobeniusNorm(new_Be[j]);
+        chi[j]           = _compute_chi(sigma[j], yield[j]);
+        const R3x3 M     = dt_over_Vj * gradv3d - dt * chi[j] * zeta[j] * sigmas;
+        const R3x3 expM  = EigenvalueSolver{}.computeExpForTinyMatrix(M);
+        const R3x3 expMT = EigenvalueSolver{}.computeExpForTinyMatrix(transpose(M));
+        new_Be[j]        = expM * Be[j] * expMT;
+        // const double fenorm1 = frobeniusNorm(new_Be[j]);
+        // rationorm[j]         = fenorm1 / fenorm0;
+      });
+    std::cout << "sum_chi " << sum(chi) << "\n";
+
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    return {std::make_shared<MeshVariant>(new_mesh),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(new_mesh, new_Be))};
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes(const double& dt,
+               const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+               const std::shared_ptr<const DiscreteFunctionVariant>& u,
+               const std::shared_ptr<const DiscreteFunctionVariant>& E,
+               const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+               const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+               const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+               const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+               const std::shared_ptr<const ItemValueVariant>& ur,
+               const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,
+               const RelaxationType& relaxation_type) const
+  {
+    std::shared_ptr mesh_v = getCommonMesh({rho, u, E});
+    if (not mesh_v) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({rho, u, E}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperplastic solver expects P0 functions");
+    }
+    switch (relaxation_type) {
+    case RelaxationType::Exponential: {
+      return this->apply_fluxes(dt,                                       //
+                                *mesh_v->get<MeshType>(),                 //
+                                rho->get<DiscreteScalarFunction>(),       //
+                                u->get<DiscreteVectorFunction>(),         //
+                                E->get<DiscreteScalarFunction>(),         //
+                                Fe->get<DiscreteTensorFunction3d>(),      //
+                                zeta->get<DiscreteScalarFunction>(),      //
+                                yield->get<DiscreteScalarFunction>(),     //
+                                sigma->get<DiscreteTensorFunction3d>(),   //
+                                ur->get<NodeValue<const Rd>>(),           //
+                                Fjr->get<NodeValuePerCell<const Rd>>());
+    }
+    case RelaxationType::Implicit: {
+      return this->apply_fluxes2(dt,                                       //
+                                 *mesh_v->get<MeshType>(),                 //
+                                 rho->get<DiscreteScalarFunction>(),       //
+                                 u->get<DiscreteVectorFunction>(),         //
+                                 E->get<DiscreteScalarFunction>(),         //
+                                 Fe->get<DiscreteTensorFunction3d>(),      //
+                                 zeta->get<DiscreteScalarFunction>(),      //
+                                 yield->get<DiscreteScalarFunction>(),     //
+                                 sigma->get<DiscreteTensorFunction3d>(),   //
+                                 ur->get<NodeValue<const Rd>>(),           //
+                                 Fjr->get<NodeValuePerCell<const Rd>>());
+    }
+    case RelaxationType::CauchyGreen: {
+      return this->apply_fluxes_B(dt,                                       //
+                                  *mesh_v->get<MeshType>(),                 //
+                                  rho->get<DiscreteScalarFunction>(),       //
+                                  u->get<DiscreteVectorFunction>(),         //
+                                  E->get<DiscreteScalarFunction>(),         //
+                                  Fe->get<DiscreteTensorFunction3d>(),      //
+                                  zeta->get<DiscreteScalarFunction>(),      //
+                                  yield->get<DiscreteScalarFunction>(),     //
+                                  sigma->get<DiscreteTensorFunction3d>(),   //
+                                  ur->get<NodeValue<const Rd>>(),           //
+                                  Fjr->get<NodeValuePerCell<const Rd>>());
+    }
+    default: {
+      throw UnexpectedError("invalid relaxation type");
+    }
+    }
+  }
+
+  std::shared_ptr<const DiscreteFunctionVariant>
+  apply_plastic_relaxation(const RelaxationType& relaxation_type,
+                           const double& dt,
+                           const std::shared_ptr<const MeshType>& mesh,
+                           const DiscreteTensorFunction3d& Fe,
+                           const DiscreteScalarFunction& zeta,
+                           const DiscreteScalarFunction& yield,
+                           const DiscreteTensorFunction3d& sigman,
+                           const DiscreteTensorFunction3d& sigmanp1) const
+  {
+    CellValue<R3x3> new_Fe = copy(Fe.cellValues());
+    CellValue<double> chi{mesh->connectivity()};
+    chi.fill(0.);
+    CellValue<const double> Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
+    R3x3 I                     = identity;
+    switch (relaxation_type) {
+    case RelaxationType::Exponential: {
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId j) {
+          R3x3 sigmasn   = sigman[j] - 1. / 3 * trace(sigman[j]) * I;
+          R3x3 sigmasnp1 = sigmanp1[j] - 1. / 3 * trace(sigmanp1[j]) * I;
+          chi[j]         = _compute_chi(0.5 * (sigmasn + sigmasnp1), yield[j]);
+          const R3x3 M   = -dt * chi[j] * zeta[j] * 0.5 * (sigmasn + sigmasnp1);
+          new_Fe[j]      = EigenvalueSolver{}.computeExpForTinyMatrix(M) * new_Fe[j];
+        });
+      break;
+    }
+    case RelaxationType::Implicit: {
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId j) {
+          R3x3 sigmasn   = sigman[j] - 1. / 3 * trace(sigman[j]) * I;
+          R3x3 sigmasnp1 = sigmanp1[j] - 1. / 3 * trace(sigmanp1[j]) * I;
+          chi[j]         = _compute_chi(0.5 * (sigmasn + sigmasnp1), yield[j]);
+          int sub_it = static_cast<int>(std::ceil(dt * chi[j] * zeta[j] * frobeniusNorm(0.5 * (sigmasn + sigmasnp1))));
+          if (sub_it > 1) {
+            std::cout << sub_it << " dt " << dt << " chi[j] " << chi[j] << " zeta[j] " << zeta[j]
+                      << " frobeniusNorm(sigmas) " << frobeniusNorm(0.5 * (sigmasn + sigmasnp1)) << "\n";
+          }
+          for (int i = 0; i < sub_it; i++) {
+            const R3x3 M = I + 0.5 * dt / static_cast<double>(sub_it) * chi[j] * zeta[j] * (sigmasn + sigmasnp1);
+            new_Fe[j]    = inverse(M) * new_Fe[j];
+          }
+        });
+      break;
+    }
+    default: {
+      throw UnexpectedError("invalid relaxation type");
+    }
+    }
+
+    // for (CellId j = 0; j < mesh->numberOfCells(); j++) {
+    //   if ((std::abs(frobeniusNorm(new_Fe[j]) - std::sqrt(3.)) > 1.e-1) or (j == 6399)) {
+    //     std::cout << j << " new_Fe " << new_Fe[j] << " chi " << chi[j] << "\n";
+    //   }
+    // }
+    std::cout << "sum_chi " << sum(chi) << "\n";
+
+    return {std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(mesh, new_Fe))};
+  }
+
+  std::shared_ptr<const DiscreteFunctionVariant>
+  apply_plastic_relaxation(const RelaxationType& relaxation_type,
+                           const double& dt,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& sigman,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& sigmanp1) const
+  {
+    std::shared_ptr mesh_v = getCommonMesh({sigman, sigmanp1});
+    if (not mesh_v) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({sigman, sigmanp1}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperplastic solver expects P0 functions");
+    }
+
+    return this->apply_plastic_relaxation(relaxation_type, dt,                    //
+                                          mesh_v->get<MeshType>(),                //
+                                          Fe->get<DiscreteTensorFunction3d>(),    //
+                                          zeta->get<DiscreteScalarFunction>(),    //
+                                          yield->get<DiscreteScalarFunction>(),   //
+                                          sigman->get<DiscreteTensorFunction3d>(),
+                                          sigmanp1->get<DiscreteTensorFunction3d>());
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_elastic_fluxes(const double& dt,
+                       const MeshType& mesh,
+                       const DiscreteScalarFunction& rho,
+                       const DiscreteVectorFunction& u,
+                       const DiscreteScalarFunction& E,
+                       const DiscreteTensorFunction3d& Fe,
+                       const NodeValue<const Rd>& ur,
+                       const NodeValuePerCell<const Rd>& Fjr) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho = copy(rho.cellValues());
+    CellValue<Rd> new_u       = copy(u.cellValues());
+    CellValue<double> new_E   = copy(E.cellValues());
+    CellValue<R3x3> new_Fe    = copy(Fe.cellValues());
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        Rd momentum_fluxes   = zero;
+        double energy_fluxes = 0;
+        Rdxd gradv           = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        R3x3 gradv3d              = to3D(gradv);
+        const R3x3 elastic_fluxes = gradv3d * Fe[j];
+        const double dt_over_Mj   = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj   = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        new_Fe[j] += dt_over_Vj * elastic_fluxes;
+      });
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    return {std::make_shared<MeshVariant>(new_mesh),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(new_mesh, new_Fe))};
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_elastic_fluxes(const double& dt,
+                       const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                       const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                       const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                       const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                       const std::shared_ptr<const ItemValueVariant>& ur,
+                       const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr) const
+  {
+    std::shared_ptr mesh_v = getCommonMesh({rho, u, E});
+    if (not mesh_v) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({rho, u, E}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperplastic solver expects P0 functions");
+    }
+
+    return this->apply_elastic_fluxes(dt,                                    //
+                                      *mesh_v->get<MeshType>(),              //
+                                      rho->get<DiscreteScalarFunction>(),    //
+                                      u->get<DiscreteVectorFunction>(),      //
+                                      E->get<DiscreteScalarFunction>(),      //
+                                      Fe->get<DiscreteTensorFunction3d>(),   //
+                                      ur->get<NodeValue<const Rd>>(),        //
+                                      Fjr->get<NodeValuePerCell<const Rd>>());
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply(const SolverType& solver_type,
+        const RelaxationType& relaxation_type,
+        const double& dt,
+        const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+        const std::shared_ptr<const DiscreteFunctionVariant>& u,
+        const std::shared_ptr<const DiscreteFunctionVariant>& E,
+        const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+        const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+        const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+        const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+        const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+        const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+        const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    auto [ur, Fjr] = compute_fluxes(solver_type, rho, aL, aT, u, sigma, bc_descriptor_list);
+    return apply_fluxes(dt, rho, u, E, Fe, zeta, yield, sigma, ur, Fjr, relaxation_type);
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_elastic(const SolverType& solver_type,
+                const double& dt,
+                const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    auto [ur, Fjr] = compute_fluxes(solver_type, rho, aL, aT, u, sigma, bc_descriptor_list);
+    return apply_elastic_fluxes(dt, rho, u, E, Fe, ur, Fjr);
+  }
+
+  HyperplasticSolver()                     = default;
+  HyperplasticSolver(HyperplasticSolver&&) = default;
+  ~HyperplasticSolver()                    = default;
+};
+
+template <MeshConcept MeshType>
+void
+HyperplasticSolverHandler::HyperplasticSolver<MeshType>::_applyPressureBC(const BoundaryConditionList& bc_list,
+                                                                          const MeshType& mesh,
+                                                                          NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<PressureBoundaryCondition, T>) {
+          MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+          if constexpr (Dimension == 1) {
+            const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+            const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+            const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+            const auto& node_list  = bc.nodeList();
+            const auto& value_list = bc.valueList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(size_t i_node) {
+                const NodeId node_id       = node_list[i_node];
+                const auto& node_cell_list = node_to_cell_matrix[node_id];
+                Assert(node_cell_list.size() == 1);
+
+                CellId node_cell_id              = node_cell_list[0];
+                size_t node_local_number_in_cell = node_local_numbers_in_their_cells(node_id, 0);
+
+                br[node_id] -= value_list[i_node] * Cjr(node_cell_id, node_local_number_in_cell);
+              });
+          } else {
+            const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+
+            const auto& face_to_cell_matrix               = mesh.connectivity().faceToCellMatrix();
+            const auto& face_to_node_matrix               = mesh.connectivity().faceToNodeMatrix();
+            const auto& face_local_numbers_in_their_cells = mesh.connectivity().faceLocalNumbersInTheirCells();
+            const auto& face_cell_is_reversed             = mesh.connectivity().cellFaceIsReversed();
+
+            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_cell_list = face_to_cell_matrix[face_id];
+              Assert(face_cell_list.size() == 1);
+
+              CellId face_cell_id              = face_cell_list[0];
+              size_t face_local_number_in_cell = face_local_numbers_in_their_cells(face_id, 0);
+
+              const double sign = face_cell_is_reversed(face_cell_id, face_local_number_in_cell) ? -1 : 1;
+
+              const auto& face_nodes = face_to_node_matrix[face_id];
+
+              for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
+                NodeId node_id = face_nodes[i_node];
+                br[node_id] -= sign * value_list[i_face] * Nlr(face_id, i_node);
+              }
+            }
+          }
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <MeshConcept MeshType>
+void
+HyperplasticSolverHandler::HyperplasticSolver<MeshType>::_applyNormalStressBC(const BoundaryConditionList& bc_list,
+                                                                              const MeshType& mesh,
+                                                                              NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<NormalStressBoundaryCondition, T>) {
+          MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+          if constexpr (Dimension == 1) {
+            const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+            const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+            const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+            const auto& node_list  = bc.nodeList();
+            const auto& value_list = bc.valueList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(size_t i_node) {
+                const NodeId node_id       = node_list[i_node];
+                const auto& node_cell_list = node_to_cell_matrix[node_id];
+                Assert(node_cell_list.size() == 1);
+
+                CellId node_cell_id              = node_cell_list[0];
+                size_t node_local_number_in_cell = node_local_numbers_in_their_cells(node_id, 0);
+
+                br[node_id] += value_list[i_node] * Cjr(node_cell_id, node_local_number_in_cell);
+              });
+          } else {
+            const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+
+            const auto& face_to_cell_matrix               = mesh.connectivity().faceToCellMatrix();
+            const auto& face_to_node_matrix               = mesh.connectivity().faceToNodeMatrix();
+            const auto& face_local_numbers_in_their_cells = mesh.connectivity().faceLocalNumbersInTheirCells();
+            const auto& face_cell_is_reversed             = mesh.connectivity().cellFaceIsReversed();
+
+            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_cell_list = face_to_cell_matrix[face_id];
+              Assert(face_cell_list.size() == 1);
+
+              CellId face_cell_id              = face_cell_list[0];
+              size_t face_local_number_in_cell = face_local_numbers_in_their_cells(face_id, 0);
+
+              const double sign = face_cell_is_reversed(face_cell_id, face_local_number_in_cell) ? -1 : 1;
+
+              const auto& face_nodes = face_to_node_matrix[face_id];
+
+              for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
+                NodeId node_id = face_nodes[i_node];
+                br[node_id] += sign * value_list[i_face] * Nlr(face_id, i_node);
+              }
+            }
+          }
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <MeshConcept MeshType>
+void
+HyperplasticSolverHandler::HyperplasticSolver<MeshType>::_applySymmetryBC(const BoundaryConditionList& bc_list,
+                                                                          NodeValue<Rdxd>& Ar,
+                                                                          NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<SymmetryBoundaryCondition, T>) {
+          const Rd& n = bc.outgoingNormal();
+
+          const Rdxd I   = identity;
+          const Rdxd nxn = tensorProduct(n, n);
+          const Rdxd P   = I - nxn;
+
+          const Array<const NodeId>& node_list = bc.nodeList();
+          parallel_for(
+            bc.numberOfNodes(), PUGS_LAMBDA(int r_number) {
+              const NodeId r = node_list[r_number];
+
+              Ar[r] = P * Ar[r] * P + nxn;
+              br[r] = P * br[r];
+            });
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <MeshConcept MeshType>
+void
+HyperplasticSolverHandler::HyperplasticSolver<MeshType>::_applyVelocityBC(const BoundaryConditionList& bc_list,
+                                                                          NodeValue<Rdxd>& Ar,
+                                                                          NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<VelocityBoundaryCondition, T>) {
+          const auto& node_list  = bc.nodeList();
+          const auto& value_list = bc.valueList();
+
+          parallel_for(
+            node_list.size(), PUGS_LAMBDA(size_t i_node) {
+              NodeId node_id    = node_list[i_node];
+              const auto& value = value_list[i_node];
+
+              Ar[node_id] = identity;
+              br[node_id] = value;
+            });
+        } else if constexpr (std::is_same_v<FixedBoundaryCondition, T>) {
+          const auto& node_list = bc.nodeList();
+          parallel_for(
+            node_list.size(), PUGS_LAMBDA(size_t i_node) {
+              NodeId node_id = node_list[i_node];
+
+              Ar[node_id] = identity;
+              br[node_id] = zero;
+            });
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <MeshConcept MeshType>
+class HyperplasticSolverHandler::HyperplasticSolver<MeshType>::FixedBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary m_mesh_node_boundary;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  FixedBoundaryCondition(const MeshNodeBoundary& mesh_node_boundary) : m_mesh_node_boundary{mesh_node_boundary} {}
+
+  ~FixedBoundaryCondition() = default;
+};
+
+template <MeshConcept MeshType>
+class HyperplasticSolverHandler::HyperplasticSolver<MeshType>::PressureBoundaryCondition
+{
+ private:
+  const MeshFaceBoundary m_mesh_face_boundary;
+  const Array<const double> m_value_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_mesh_face_boundary.faceList();
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  PressureBoundaryCondition(const MeshFaceBoundary& mesh_face_boundary, const Array<const double>& value_list)
+    : m_mesh_face_boundary{mesh_face_boundary}, m_value_list{value_list}
+  {}
+
+  ~PressureBoundaryCondition() = default;
+};
+
+template <>
+class HyperplasticSolverHandler::HyperplasticSolver<Mesh<1>>::PressureBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary m_mesh_node_boundary;
+  const Array<const double> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  PressureBoundaryCondition(const MeshNodeBoundary& mesh_node_boundary, const Array<const double>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~PressureBoundaryCondition() = default;
+};
+
+template <MeshConcept MeshType>
+class HyperplasticSolverHandler::HyperplasticSolver<MeshType>::NormalStressBoundaryCondition
+{
+ private:
+  const MeshFaceBoundary m_mesh_face_boundary;
+  const Array<const Rdxd> m_value_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_mesh_face_boundary.faceList();
+  }
+
+  const Array<const Rdxd>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NormalStressBoundaryCondition(const MeshFaceBoundary& mesh_face_boundary, const Array<const Rdxd>& value_list)
+    : m_mesh_face_boundary{mesh_face_boundary}, m_value_list{value_list}
+  {}
+
+  ~NormalStressBoundaryCondition() = default;
+};
+
+template <>
+class HyperplasticSolverHandler::HyperplasticSolver<Mesh<1>>::NormalStressBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary m_mesh_node_boundary;
+  const Array<const Rdxd> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const Rdxd>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NormalStressBoundaryCondition(const MeshNodeBoundary& mesh_node_boundary, const Array<const Rdxd>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~NormalStressBoundaryCondition() = default;
+};
+
+template <MeshConcept MeshType>
+class HyperplasticSolverHandler::HyperplasticSolver<MeshType>::VelocityBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary m_mesh_node_boundary;
+
+  const Array<const TinyVector<Dimension>> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const TinyVector<Dimension>>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  VelocityBoundaryCondition(const MeshNodeBoundary& mesh_node_boundary,
+                            const Array<const TinyVector<Dimension>>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~VelocityBoundaryCondition() = default;
+};
+
+template <MeshConcept MeshType>
+class HyperplasticSolverHandler::HyperplasticSolver<MeshType>::SymmetryBoundaryCondition
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const MeshFlatNodeBoundary<MeshType> m_mesh_flat_node_boundary;
+
+ public:
+  const Rd&
+  outgoingNormal() const
+  {
+    return m_mesh_flat_node_boundary.outgoingNormal();
+  }
+
+  size_t
+  numberOfNodes() const
+  {
+    return m_mesh_flat_node_boundary.nodeList().size();
+  }
+
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_flat_node_boundary.nodeList();
+  }
+
+  SymmetryBoundaryCondition(const MeshFlatNodeBoundary<MeshType>& mesh_flat_node_boundary)
+    : m_mesh_flat_node_boundary(mesh_flat_node_boundary)
+  {
+    ;
+  }
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+HyperplasticSolverHandler::HyperplasticSolverHandler(const std::shared_ptr<const MeshVariant>& mesh_v)
+{
+  if (not mesh_v) {
+    throw NormalError("discrete functions are not defined on the same mesh");
+  }
+
+  std::visit(
+    [&](auto&& mesh) {
+      using MeshType = mesh_type_t<decltype(mesh)>;
+      if constexpr (is_polygonal_mesh_v<MeshType>) {
+        m_hyperplastic_solver = std::make_unique<HyperplasticSolver<MeshType>>();
+      } else {
+        throw NormalError("unexpected mesh type");
+      }
+    },
+    mesh_v->variant());
+}
diff --git a/src/scheme/HyperplasticSolver.hpp b/src/scheme/HyperplasticSolver.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8a96fffd2cced6de61ce34e232577dcd02508c18
--- /dev/null
+++ b/src/scheme/HyperplasticSolver.hpp
@@ -0,0 +1,131 @@
+#ifndef HYPERPLASTIC_SOLVER_HPP
+#define HYPERPLASTIC_SOLVER_HPP
+
+#include <mesh/MeshTraits.hpp>
+
+#include <memory>
+#include <tuple>
+#include <vector>
+
+class IBoundaryConditionDescriptor;
+class MeshVariant;
+class ItemValueVariant;
+class SubItemValuePerItemVariant;
+class DiscreteFunctionVariant;
+
+double hyperplastic_dt(const std::shared_ptr<const DiscreteFunctionVariant>& c);
+
+class HyperplasticSolverHandler
+{
+ public:
+  enum class SolverType
+  {
+    Glace,
+    Eucclhyd
+  };
+
+  enum class RelaxationType
+  {
+    Implicit,
+    Exponential,
+    CauchyGreen
+  };
+
+ private:
+  struct IHyperplasticSolver
+  {
+    virtual std::tuple<const std::shared_ptr<const ItemValueVariant>,
+                       const std::shared_ptr<const SubItemValuePerItemVariant>>
+    compute_fluxes(
+      const SolverType& solver_type,
+      const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+      const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+      const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+      const std::shared_ptr<const DiscreteFunctionVariant>& u,
+      const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+      const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
+
+    virtual std::tuple<std::shared_ptr<const MeshVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>>
+    apply_fluxes(const double& dt,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                 const std::shared_ptr<const ItemValueVariant>& ur,
+                 const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,
+                 const RelaxationType& relaxation_type) const = 0;
+
+    virtual std::tuple<std::shared_ptr<const MeshVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>>
+    apply(const SolverType& solver_type,
+          const RelaxationType& relaxation_type,
+          const double& dt,
+          const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+          const std::shared_ptr<const DiscreteFunctionVariant>& u,
+          const std::shared_ptr<const DiscreteFunctionVariant>& E,
+          const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+          const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+          const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+          const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+          const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+          const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+          const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
+
+    virtual std::tuple<std::shared_ptr<const MeshVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>>
+    apply_elastic(const SolverType& solver_type,
+                  const double& dt,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
+
+    virtual std::shared_ptr<const DiscreteFunctionVariant> apply_plastic_relaxation(
+      const RelaxationType& relaxation_type,
+      const double& dt,
+      const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+      const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+      const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+      const std::shared_ptr<const DiscreteFunctionVariant>& sigman,
+      const std::shared_ptr<const DiscreteFunctionVariant>& sigmanp1) const = 0;
+
+    IHyperplasticSolver()                                 = default;
+    IHyperplasticSolver(IHyperplasticSolver&&)            = default;
+    IHyperplasticSolver& operator=(IHyperplasticSolver&&) = default;
+
+    virtual ~IHyperplasticSolver() = default;
+  };
+
+  template <MeshConcept MeshType>
+  class HyperplasticSolver;
+
+  std::unique_ptr<IHyperplasticSolver> m_hyperplastic_solver;
+
+ public:
+  const IHyperplasticSolver&
+  solver() const
+  {
+    return *m_hyperplastic_solver;
+  }
+
+  HyperplasticSolverHandler(const std::shared_ptr<const MeshVariant>& mesh);
+};
+
+#endif   // HYPERPLASTIC_SOLVER_HPP
diff --git a/src/scheme/HyperplasticSolverO2.cpp b/src/scheme/HyperplasticSolverO2.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9a9eff713b6bebc6b722b87c71b35bb947db25dd
--- /dev/null
+++ b/src/scheme/HyperplasticSolverO2.cpp
@@ -0,0 +1,1773 @@
+#include <algebra/CRSMatrix.hpp>
+#include <algebra/CRSMatrixDescriptor.hpp>
+#include <algebra/EigenvalueSolver.hpp>
+#include <algebra/SmallMatrix.hpp>
+#include <analysis/Polynomial.hpp>
+#include <language/utils/InterpolateItemValue.hpp>
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshFlatNodeBoundary.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+#include <mesh/MeshTraits.hpp>
+#include <mesh/StencilArray.hpp>
+#include <mesh/StencilManager.hpp>
+#include <mesh/SubItemValuePerItemVariant.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/DiscreteFunctionDPk.hpp>
+#include <scheme/DiscreteFunctionDPkVariant.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionUtils.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <scheme/ExternalBoundaryConditionDescriptor.hpp>
+#include <scheme/FixedBoundaryConditionDescriptor.hpp>
+#include <scheme/HyperplasticSolverO2.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+#include <scheme/PolynomialReconstruction.hpp>
+#include <scheme/PolynomialReconstructionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <utils/Socket.hpp>
+#include <variant>
+#include <vector>
+
+template <MeshConcept MeshType>
+class HyperplasticSolverO2Handler::HyperplasticSolverO2 final
+  : public HyperplasticSolverO2Handler::IHyperplasticSolverO2
+{
+ private:
+  static constexpr size_t Dimension = MeshType::Dimension;
+  using Rdxd                        = TinyMatrix<Dimension>;
+  using R3x3                        = TinyMatrix<3>;
+  using Rd                          = TinyVector<Dimension>;
+  using R3                          = TinyVector<3>;
+
+  using MeshDataType = MeshData<MeshType>;
+
+  using DiscreteScalarFunction   = DiscreteFunctionP0<const double>;
+  using DiscreteVectorFunction   = DiscreteFunctionP0<const Rd>;
+  using DiscreteTensorFunction   = DiscreteFunctionP0<const Rdxd>;
+  using DiscreteTensorFunction3d = DiscreteFunctionP0<const R3x3>;
+
+  class FixedBoundaryCondition;
+  class PressureBoundaryCondition;
+  class NormalStressBoundaryCondition;
+  class SymmetryBoundaryCondition;
+  class VelocityBoundaryCondition;
+
+  using BoundaryCondition = std::variant<FixedBoundaryCondition,
+                                         PressureBoundaryCondition,
+                                         NormalStressBoundaryCondition,
+                                         SymmetryBoundaryCondition,
+                                         VelocityBoundaryCondition>;
+
+  using BoundaryConditionList = std::vector<BoundaryCondition>;
+  R3x3
+  to3D(const Rdxd tensor) const
+  {
+    R3x3 tensor3d = zero;
+    for (size_t i = 0; i < Dimension; i++) {
+      for (size_t j = 0; j < Dimension; j++) {
+        tensor3d(i, j) = tensor(i, j);
+      }
+    }
+    return tensor3d;
+  }
+
+  R3
+  to3D(const Rd vector) const
+  {
+    R3 vector3d = zero;
+    for (size_t i = 0; i < Dimension; i++) {
+      vector3d[i] = vector[i];
+    }
+    return vector3d;
+  }
+
+  CellValue<const R3x3>
+  to3D(const MeshType& mesh, const CellValue<Rdxd>& tensor) const
+  {
+    CellValue<R3x3> tensor3d{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { tensor3d[j] = to3D(tensor[j]); });
+    return tensor3d;
+  }
+
+  CellValue<const R3>
+  to3D(const MeshType& mesh, const CellValue<Rd> vector) const
+  {
+    CellValue<R3> vector3d{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { vector3d[j] = to3D(vector[j]); });
+    return vector3d;
+  }
+
+  Rdxd
+  toDimension(const R3x3 tensor3d) const
+  {
+    Rdxd tensor = zero;
+    for (size_t i = 0; i < Dimension; i++) {
+      for (size_t j = 0; j < Dimension; j++) {
+        tensor(i, j) = tensor3d(i, j);
+      }
+    }
+    return tensor;
+  }
+
+  Rd
+  toDimension(const R3 vector3d) const
+  {
+    Rd vector = zero;
+    for (size_t i = 0; i < Dimension; i++) {
+      vector[i] = vector3d[i];
+    }
+    return vector;
+  }
+
+  CellValue<const Rdxd>
+  toDimension(const MeshType& mesh, const CellValue<R3x3>& tensor3d) const
+  {
+    CellValue<Rdxd> tensor{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { tensor[j] = toDimension(tensor3d[j]); });
+    return tensor;
+  }
+
+  CellValue<const Rd>
+  toDimension(const MeshType& mesh, const CellValue<R3>& vector3d) const
+  {
+    CellValue<Rd> vector{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { vector[j] = to3D(vector3d[j]); });
+    return vector;
+  }
+
+  double
+  _compute_chi(const R3x3 S, const double yield) const
+  {
+    const R3x3 Id       = identity;
+    const R3x3 Sd       = S - 1. / 3 * trace(S) * Id;
+    double chi          = 0;
+    const double normS2 = frobeniusNorm(Sd) * frobeniusNorm(Sd);
+    double limit2       = 2. / 3 * yield * yield;
+    const double power  = 0.5;
+    if ((normS2 - limit2) > 0) {
+      chi = std::pow((normS2 - limit2), power);
+    }
+    return chi;
+  }
+
+  double
+  _compute_chi2(const R3x3 S, const double yield) const
+  {
+    const R3x3 Id       = identity;
+    const R3x3 Sd       = S - 1. / 3 * trace(S) * Id;
+    double chi          = 0;
+    const double normS2 = frobeniusNorm(Sd) * frobeniusNorm(Sd);
+    double limit2       = 2. / 3 * yield * yield;
+    double alpha        = (normS2 - limit2);
+    double sign         = alpha / std::abs(alpha);
+    if (sign < 0.) {
+      return chi;
+    }
+    int power    = 10;
+    double ratio = normS2 / limit2;
+    chi          = ratio;
+    for (int i = 0; i < power; i++) {
+      chi *= ratio;
+    }
+    return chi;
+  }
+
+  DiscreteFunctionDPk<Dimension, Rd>
+  _limit(const MeshType& mesh,
+         const DiscreteFunctionP0<const Rd>& f,
+         const DiscreteFunctionDPk<Dimension, const Rd>& DPk_fh,
+         const double eps) const
+  {
+    MeshData<MeshType>& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+    StencilManager::BoundaryDescriptorList symmetry_boundary_descriptor_list;
+    StencilDescriptor stencil_descriptor{1, StencilDescriptor::ConnectionType::by_nodes};
+    auto stencil = StencilManager::instance().getCellToCellStencilArray(mesh.connectivity(), stencil_descriptor,
+                                                                        symmetry_boundary_descriptor_list);
+    DiscreteFunctionDPk<Dimension, Rd> DPk_fh_lim = copy(DPk_fh);
+    const auto xj                                 = mesh_data.xj();
+    // return DPk_fh_lim;
+    const double eps2 = 1E-14;
+    double cmin       = 1. - eps;
+    double cmax       = 1. + eps;
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+        const Rd fj             = f[cell_id];
+        const auto cell_stencil = stencil[cell_id];
+        Rd coef1;
+        Rd coef2;
+        for (size_t dim = 0; dim < Dimension; ++dim) {
+          coef1[dim] = 1.;
+          coef2[dim] = 1.;
+        }
+        for (size_t dim = 0; dim < Dimension; ++dim) {
+          double f_min = std::min(cmin * fj[dim], cmax * fj[dim]);
+          double f_max = std::max(cmin * fj[dim], cmax * fj[dim]);
+
+          for (size_t i_cell = 0; i_cell < cell_stencil.size(); ++i_cell) {
+            f_min = std::min(f_min, std::min(cmin * f[cell_stencil[i_cell]][dim], cmax * f[cell_stencil[i_cell]][dim]));
+            f_max = std::max(f_max, std::max(cmin * f[cell_stencil[i_cell]][dim], cmax * f[cell_stencil[i_cell]][dim]));
+          }
+          double f_bar_min = fj[dim];
+          double f_bar_max = fj[dim];
+
+          for (size_t i_cell = 0; i_cell < cell_stencil.size(); ++i_cell) {
+            const CellId cell_k_id = cell_stencil[i_cell];
+            const double f_xk      = DPk_fh[cell_id](xj[cell_k_id])[dim];
+
+            f_bar_min = std::min(f_bar_min, f_xk);
+            f_bar_max = std::max(f_bar_max, f_xk);
+          }
+          if (std::abs(f_bar_max - fj[dim]) > eps2) {
+            coef1[dim] = (f_max - fj[dim]) / ((f_bar_max - fj[dim]));
+          }
+
+          if (std::abs(f_bar_min - fj[dim]) > eps2) {
+            coef2[dim] = (f_min - fj[dim]) / ((f_bar_min - fj[dim]));
+          }
+        }
+        double min_coef1 = min(coef1);
+        double min_coef2 = min(coef2);
+
+        const double lambda = std::max(0., std::min(1., std::min(min_coef1, min_coef2)));
+
+        auto coefficients = DPk_fh_lim.coefficients(cell_id);
+
+        coefficients[0] = (1 - lambda) * f[cell_id] + lambda * coefficients[0];
+        for (size_t i = 1; i < coefficients.size(); ++i) {
+          coefficients[i] *= lambda;
+        }
+      });
+    synchronize(DPk_fh_lim.cellArrays());
+    return DPk_fh_lim;
+  }
+
+  double
+  _min(const Rdxd M) const
+  {
+    double min = M(0, 0);
+    for (size_t i = 0; i < Dimension; ++i) {
+      for (size_t j = 0; j < Dimension; ++j) {
+        min = std::min(min, M(i, j));
+      }
+    }
+    return min;
+  }
+
+  double
+  _max(const Rdxd M) const
+  {
+    double max = M(0, 0);
+    for (size_t i = 0; i < Dimension; ++i) {
+      for (size_t j = 0; j < Dimension; ++j) {
+        max = std::max(max, M(i, j));
+      }
+    }
+    return max;
+  }
+
+  DiscreteFunctionDPk<Dimension, Rdxd>
+  _limit(const MeshType& mesh,
+         const DiscreteFunctionP0<const Rdxd>& f,
+         const DiscreteFunctionDPk<Dimension, const Rdxd>& DPk_fh,
+         const double eps) const
+  {
+    MeshData<MeshType>& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+    StencilManager::BoundaryDescriptorList symmetry_boundary_descriptor_list;
+    StencilDescriptor stencil_descriptor{1, StencilDescriptor::ConnectionType::by_nodes};
+    auto stencil = StencilManager::instance().getCellToCellStencilArray(mesh.connectivity(), stencil_descriptor,
+                                                                        symmetry_boundary_descriptor_list);
+    DiscreteFunctionDPk<Dimension, Rdxd> DPk_fh_lim = copy(DPk_fh);
+    const auto xj                                   = mesh_data.xj();
+
+    const double eps2 = 1E-14;
+    double cmin       = 1. - eps;
+    double cmax       = 1. + eps;
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+        const Rdxd fj           = f[cell_id];
+        const auto cell_stencil = stencil[cell_id];
+        Rdxd coef1;
+        Rdxd coef2;
+        for (size_t i = 0; i < Dimension; ++i) {
+          for (size_t j = 0; j < Dimension; ++j) {
+            coef1(i, j) = 1.;
+            coef2(i, j) = 1.;
+          }
+        }
+        for (size_t i = 0; i < Dimension; ++i) {
+          for (size_t j = 0; j < Dimension; ++j) {
+            double f_min = std::min(cmin * fj(i, j), cmax * fj(i, j));
+            double f_max = std::max(cmin * fj(i, j), cmax * fj(i, j));
+
+            for (size_t i_cell = 0; i_cell < cell_stencil.size(); ++i_cell) {
+              f_min =
+                std::min(f_min, std::min(cmin * f[cell_stencil[i_cell]](i, j), cmax * f[cell_stencil[i_cell]](i, j)));
+              f_max =
+                std::max(f_max, std::max(cmin * f[cell_stencil[i_cell]](i, j), cmax * f[cell_stencil[i_cell]](i, j)));
+            }
+            double f_bar_min = fj(i, j);
+            double f_bar_max = fj(i, j);
+
+            for (size_t i_cell = 0; i_cell < cell_stencil.size(); ++i_cell) {
+              const CellId cell_k_id = cell_stencil[i_cell];
+              const double f_xk      = DPk_fh[cell_id](xj[cell_k_id])(i, j);
+
+              f_bar_min = std::min(f_bar_min, f_xk);
+              f_bar_max = std::max(f_bar_max, f_xk);
+            }
+            if (std::abs(f_bar_max - fj(i, j)) > eps2) {
+              coef1(i, j) = (f_max - fj(i, j)) / ((f_bar_max - fj(i, j)));
+            }
+
+            if (std::abs(f_bar_min - fj(i, j)) > eps2) {
+              coef2(i, j) = (f_min - fj(i, j)) / ((f_bar_min - fj(i, j)));
+            }
+          }
+        }
+        double min_coef1 = _min(coef1);
+        double min_coef2 = _min(coef2);
+
+        const double lambda = std::max(0., std::min(1., std::min(min_coef1, min_coef2)));
+
+        auto coefficients = DPk_fh_lim.coefficients(cell_id);
+
+        coefficients[0] = (1 - lambda) * f[cell_id] + lambda * coefficients[0];
+        for (size_t i = 1; i < coefficients.size(); ++i) {
+          coefficients[i] *= lambda;
+        }
+      });
+
+    synchronize(DPk_fh_lim.cellArrays());
+    return DPk_fh_lim;
+  }
+
+  NodeValuePerCell<const Rdxd>
+  _computeGlaceAjr(const MeshType& mesh, const DiscreteScalarFunction& rhoaL, const DiscreteScalarFunction& rhoaT) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+    const NodeValuePerCell<const Rd> njr = mesh_data.njr();
+
+    NodeValuePerCell<Rdxd> Ajr{mesh.connectivity()};
+    const Rdxd I = identity;
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const size_t& nb_nodes = Ajr.numberOfSubValues(j);
+        const double& rhoaL_j  = rhoaL[j];
+        const double& rhoaT_j  = rhoaT[j];
+        for (size_t r = 0; r < nb_nodes; ++r) {
+          const Rdxd& M = tensorProduct(Cjr(j, r), njr(j, r));
+          Ajr(j, r)     = rhoaL_j * M + rhoaT_j * (l2Norm(Cjr(j, r)) * I - M);
+        }
+      });
+
+    return Ajr;
+  }
+
+  NodeValuePerCell<const Rdxd>
+  _computeEucclhydAjr(const MeshType& mesh,
+                      const DiscreteScalarFunction& rhoaL,
+                      const DiscreteScalarFunction& rhoaT) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+    const NodeValuePerFace<const Rd> nlr = mesh_data.nlr();
+
+    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();
+
+    NodeValuePerCell<Rdxd> Ajr{mesh.connectivity()};
+
+    parallel_for(
+      Ajr.numberOfValues(), PUGS_LAMBDA(size_t jr) { Ajr[jr] = zero; });
+    const Rdxd I = identity;
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        const auto& cell_faces = cell_to_face_matrix[j];
+
+        const double& rho_aL = rhoaL[j];
+        const double& rho_aT = rhoaT[j];
+
+        for (size_t L = 0; L < cell_faces.size(); ++L) {
+          const FaceId& l        = cell_faces[L];
+          const auto& face_nodes = face_to_node_matrix[l];
+
+          auto local_node_number_in_cell = [&](NodeId node_number) {
+            for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) {
+              if (node_number == cell_nodes[i_node]) {
+                return i_node;
+              }
+            }
+            return std::numeric_limits<size_t>::max();
+          };
+
+          for (size_t rl = 0; rl < face_nodes.size(); ++rl) {
+            const size_t R = local_node_number_in_cell(face_nodes[rl]);
+            const Rdxd& M  = tensorProduct(Nlr(l, rl), nlr(l, rl));
+            Ajr(j, R) += rho_aL * M + rho_aT * (l2Norm(Nlr(l, rl)) * I - M);
+          }
+        }
+      });
+
+    return Ajr;
+  }
+
+  NodeValuePerCell<const Rdxd>
+  _computeAjr(const SolverType& solver_type,
+              const MeshType& mesh,
+              const DiscreteScalarFunction& rhoaL,
+              const DiscreteScalarFunction& rhoaT) const
+  {
+    if constexpr (Dimension == 1) {
+      return _computeGlaceAjr(mesh, rhoaL, rhoaT);
+    } else {
+      switch (solver_type) {
+      case SolverType::Glace: {
+        return _computeGlaceAjr(mesh, rhoaL, rhoaT);
+      }
+      case SolverType::Eucclhyd: {
+        return _computeEucclhydAjr(mesh, rhoaL, rhoaT);
+      }
+      default: {
+        throw UnexpectedError("invalid solver type");
+      }
+      }
+    }
+  }
+
+  NodeValue<Rdxd>
+  _computeAr(const MeshType& mesh, const NodeValuePerCell<const Rdxd>& Ajr) const
+  {
+    const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+    const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+    NodeValue<Rdxd> Ar{mesh.connectivity()};
+
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) {
+        Rdxd sum                                   = zero;
+        const auto& node_to_cell                   = node_to_cell_matrix[r];
+        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(r);
+
+        for (size_t j = 0; j < node_to_cell.size(); ++j) {
+          const CellId J       = node_to_cell[j];
+          const unsigned int R = node_local_number_in_its_cells[j];
+          sum += Ajr(J, R);
+        }
+        Ar[r] = sum;
+      });
+
+    return Ar;
+  }
+
+  NodeValue<Rd>
+  _computeBr(const Mesh<Dimension>& mesh,
+             const NodeValuePerCell<const Rdxd>& Ajr,
+             const DiscreteFunctionDPk<Dimension, const Rd>& DPk_u,
+             const DiscreteFunctionDPk<Dimension, const Rdxd>& DPk_sigma) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd>& Cjr = mesh_data.Cjr();
+
+    const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+    const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+    NodeValue<Rd> b{mesh.connectivity()};
+    auto xr = mesh.xr();
+
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) {
+        const auto& node_to_cell                   = node_to_cell_matrix[r];
+        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(r);
+
+        Rd br = zero;
+        for (size_t j = 0; j < node_to_cell.size(); ++j) {
+          const CellId J       = node_to_cell[j];
+          const unsigned int R = node_local_number_in_its_cells[j];
+          br += Ajr(J, R) * DPk_u[J](xr[r]) - DPk_sigma[J](xr[r]) * Cjr(J, R);
+        }
+
+        b[r] = br;
+      });
+
+    return b;
+  }
+
+  BoundaryConditionList
+  _getBCList(const MeshType& mesh,
+             const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    BoundaryConditionList bc_list;
+
+    for (const auto& bc_descriptor : bc_descriptor_list) {
+      bool is_valid_boundary_condition = true;
+
+      switch (bc_descriptor->type()) {
+      case IBoundaryConditionDescriptor::Type::symmetry: {
+        bc_list.emplace_back(
+          SymmetryBoundaryCondition(getMeshFlatNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())));
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::fixed: {
+        bc_list.emplace_back(FixedBoundaryCondition(getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor())));
+        break;
+      }
+      case IBoundaryConditionDescriptor::Type::dirichlet: {
+        const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+          dynamic_cast<const DirichletBoundaryConditionDescriptor&>(*bc_descriptor);
+        if (dirichlet_bc_descriptor.name() == "velocity") {
+          MeshNodeBoundary mesh_node_boundary = getMeshNodeBoundary(mesh, dirichlet_bc_descriptor.boundaryDescriptor());
+
+          Array<const Rd> value_list =
+            InterpolateItemValue<Rd(Rd)>::template interpolate<ItemType::node>(dirichlet_bc_descriptor.rhsSymbolId(),
+                                                                               mesh.xr(),
+                                                                               mesh_node_boundary.nodeList());
+
+          bc_list.emplace_back(VelocityBoundaryCondition{mesh_node_boundary, value_list});
+        } else if (dirichlet_bc_descriptor.name() == "pressure") {
+          const FunctionSymbolId pressure_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          if constexpr (Dimension == 1) {
+            MeshNodeBoundary mesh_node_boundary = getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            Array<const double> node_values =
+              InterpolateItemValue<double(Rd)>::template interpolate<ItemType::node>(pressure_id, mesh.xr(),
+                                                                                     mesh_node_boundary.nodeList());
+
+            bc_list.emplace_back(PressureBoundaryCondition{mesh_node_boundary, node_values});
+          } else {
+            MeshFaceBoundary mesh_face_boundary = getMeshFaceBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+            Array<const double> face_values =
+              InterpolateItemValue<double(Rd)>::template interpolate<ItemType::face>(pressure_id, mesh_data.xl(),
+                                                                                     mesh_face_boundary.faceList());
+            bc_list.emplace_back(PressureBoundaryCondition{mesh_face_boundary, face_values});
+          }
+
+        } else if (dirichlet_bc_descriptor.name() == "normal-stress") {
+          const FunctionSymbolId normal_stress_id = dirichlet_bc_descriptor.rhsSymbolId();
+
+          if constexpr (Dimension == 1) {
+            MeshNodeBoundary mesh_node_boundary = getMeshNodeBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            Array<const Rdxd> node_values =
+              InterpolateItemValue<Rdxd(Rd)>::template interpolate<ItemType::node>(normal_stress_id, mesh.xr(),
+                                                                                   mesh_node_boundary.nodeList());
+
+            bc_list.emplace_back(NormalStressBoundaryCondition{mesh_node_boundary, node_values});
+          } else {
+            MeshFaceBoundary mesh_face_boundary = getMeshFaceBoundary(mesh, bc_descriptor->boundaryDescriptor());
+
+            MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+            Array<const Rdxd> face_values =
+              InterpolateItemValue<Rdxd(Rd)>::template interpolate<ItemType::face>(normal_stress_id, mesh_data.xl(),
+                                                                                   mesh_face_boundary.faceList());
+            bc_list.emplace_back(NormalStressBoundaryCondition{mesh_face_boundary, face_values});
+          }
+
+        } 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 hyperplastic solver";
+        throw NormalError(error_msg.str());
+      }
+    }
+
+    return bc_list;
+  }
+
+  void _applyPressureBC(const BoundaryConditionList& bc_list, const MeshType& mesh, NodeValue<Rd>& br) const;
+  void _applyNormalStressBC(const BoundaryConditionList& bc_list, const MeshType& mesh, NodeValue<Rd>& br) const;
+  void _applySymmetryBC(const BoundaryConditionList& bc_list, NodeValue<Rdxd>& Ar, NodeValue<Rd>& br) const;
+  void _applyVelocityBC(const BoundaryConditionList& bc_list, NodeValue<Rdxd>& Ar, NodeValue<Rd>& br) const;
+  void
+  _applyBoundaryConditions(const BoundaryConditionList& bc_list,
+                           const MeshType& mesh,
+                           NodeValue<Rdxd>& Ar,
+                           NodeValue<Rd>& br) const
+  {
+    this->_applyPressureBC(bc_list, mesh, br);
+    this->_applyNormalStressBC(bc_list, mesh, br);
+    this->_applySymmetryBC(bc_list, Ar, br);
+    this->_applyVelocityBC(bc_list, Ar, br);
+  }
+
+  void
+  _projectOnBoundary(const BoundaryConditionList& bc_list, NodeValue<Rd>& xr) const
+  {
+    for (const auto& boundary_condition : bc_list) {
+      std::visit(
+        [&](auto&& bc) {
+          using T = std::decay_t<decltype(bc)>;
+          if constexpr (std::is_same_v<SymmetryBoundaryCondition, T>) {
+            const Rd& n = bc.outgoingNormal();
+            const Rd& o = bc.origin();
+
+            const Array<const NodeId>& node_list = bc.nodeList();
+            parallel_for(
+              bc.numberOfNodes(), PUGS_LAMBDA(int r_number) {
+                const NodeId r = node_list[r_number];
+                Rd& x          = xr[node_list[r]];
+                x -= dot(x - o, n) * n;
+              });
+          }
+        },
+        boundary_condition);
+    }
+  }
+  NodeValue<const Rd>
+  _computeUr(const MeshType& mesh, const NodeValue<Rdxd>& Ar, const NodeValue<Rd>& br) const
+  {
+    NodeValue<Rd> u{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { u[r] = inverse(Ar[r]) * br[r]; });
+
+    return u;
+  }
+
+  NodeValuePerCell<Rd>
+  _computeFjr(const MeshType& mesh,
+              const NodeValuePerCell<const Rdxd>& Ajr,
+              const NodeValue<const Rd>& ur,
+              const DiscreteFunctionDPk<Dimension, const Rd> DPk_uh,
+              const DiscreteFunctionDPk<Dimension, const Rdxd> DPk_sigmah) const
+  {
+    MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+
+    const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+    const NodeValue<const Rd>& xr   = mesh.xr();
+
+    NodeValuePerCell<Rd> F{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        for (size_t r = 0; r < cell_nodes.size(); ++r) {
+          const NodeId R = cell_nodes[r];
+          F(j, r)        = -Ajr(j, r) * (DPk_uh[j](xr[R]) - ur[R]) + DPk_sigmah[j](xr[R]) * Cjr(j, r);
+        }
+      });
+
+    return F;
+  }
+
+ public:
+  std::tuple<const std::shared_ptr<const ItemValueVariant>, const std::shared_ptr<const SubItemValuePerItemVariant>>
+  compute_fluxes(const SolverType& solver_type,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& rho_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& aL_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& aT_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& u_v,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma_v,
+                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    std::shared_ptr mesh_v = getCommonMesh({rho_v, aL_v, aT_v, u_v, sigma_v});
+    if (not mesh_v) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({rho_v, aL_v, u_v, sigma_v}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperplastic solver expects P0 functions");
+    }
+
+    const MeshType& mesh                    = *mesh_v->get<MeshType>();
+    const DiscreteScalarFunction& rho       = rho_v->get<DiscreteScalarFunction>();
+    const DiscreteVectorFunction& u         = u_v->get<DiscreteVectorFunction>();
+    const DiscreteScalarFunction& aL        = aL_v->get<DiscreteScalarFunction>();
+    const DiscreteScalarFunction& aT        = aT_v->get<DiscreteScalarFunction>();
+    const DiscreteTensorFunction3d& sigma3d = sigma_v->get<DiscreteTensorFunction3d>();
+    const R3x3 I                            = identity;
+
+    //    std::shared_ptr<const MeshType> p_mesh = std::dynamic_pointer_cast<const MeshType>(i_mesh);
+    CellValue<R3x3> tensor_values3d{mesh.connectivity()};
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { tensor_values3d[j] = sigma3d[j]; });
+    CellValue<const Rdxd> tensor_values = toDimension(mesh, tensor_values3d);
+    DiscreteTensorFunction sigma(mesh_v, tensor_values);
+
+    std::vector<std::shared_ptr<const IBoundaryDescriptor>> symmetry_boundary_descriptor_list;
+
+    for (auto&& bc_descriptor : bc_descriptor_list) {
+      if (bc_descriptor->type() == IBoundaryConditionDescriptor::Type::symmetry) {
+        symmetry_boundary_descriptor_list.push_back(bc_descriptor->boundaryDescriptor_shared());
+      }
+    }
+
+    PolynomialReconstructionDescriptor reconstruction_descriptor(IntegrationMethodType::cell_center, 1,
+                                                                 symmetry_boundary_descriptor_list);
+
+    auto reconstruction = PolynomialReconstruction{reconstruction_descriptor}.build(u, sigma);
+    auto DPk_uh         = reconstruction[0]->template get<DiscreteFunctionDPk<Dimension, const Rd>>();
+    auto DPk_sigmah     = reconstruction[1]->template get<DiscreteFunctionDPk<Dimension, const Rdxd>>();
+
+    // DiscreteFunctionDPk<Dimension, Rd> u_lim       = copy(DPk_uh);
+    // DiscreteFunctionDPk<Dimension, Rdxd> sigma_lim = copy(DPk_sigmah);
+    double epsilon = 1.e-6;
+    auto u_lim     = _limit(mesh, u, DPk_uh, epsilon);
+    auto sigma_lim = _limit(mesh, sigma, DPk_sigmah, epsilon);
+
+    NodeValuePerCell<const Rdxd> Ajr = this->_computeAjr(solver_type, mesh, rho * aL, rho * aT);
+
+    NodeValue<Rdxd> Ar = this->_computeAr(mesh, Ajr);
+    NodeValue<Rd> br   = this->_computeBr(mesh, Ajr, u_lim, sigma_lim);
+
+    const BoundaryConditionList bc_list = this->_getBCList(mesh, bc_descriptor_list);
+    this->_applyBoundaryConditions(bc_list, mesh, Ar, br);
+
+    synchronize(Ar);
+    synchronize(br);
+
+    NodeValue<const Rd> ur         = this->_computeUr(mesh, Ar, br);
+    NodeValuePerCell<const Rd> Fjr = this->_computeFjr(mesh, Ajr, ur, u_lim, sigma_lim);
+
+    return std::make_tuple(std::make_shared<const ItemValueVariant>(ur),
+                           std::make_shared<const SubItemValuePerItemVariant>(Fjr));
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes(const double& dt,
+               const MeshType& mesh,
+               const DiscreteScalarFunction& rho,
+               const DiscreteVectorFunction& u,
+               const DiscreteScalarFunction& E,
+               const DiscreteTensorFunction3d& Fe,
+               const DiscreteScalarFunction& zeta,
+               const DiscreteScalarFunction& yield,
+               const DiscreteTensorFunction3d& sigma,
+               const NodeValue<const Rd>& ur,
+               const NodeValuePerCell<const Rd>& Fjr,
+               const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+    const BoundaryConditionList bc_list = this->_getBCList(mesh, bc_descriptor_list);
+    this->_projectOnBoundary(bc_list, new_xr);
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho   = copy(rho.cellValues());
+    CellValue<Rd> new_u         = copy(u.cellValues());
+    CellValue<double> new_E     = copy(E.cellValues());
+    CellValue<R3x3> new_Fe      = copy(Fe.cellValues());
+    CellValue<double> new_zeta  = copy(zeta.cellValues());
+    CellValue<double> new_yield = copy(yield.cellValues());
+    CellValue<R3x3> sigma_s     = copy(sigma.cellValues());
+    CellValue<double> chi{mesh.connectivity()};
+    CellValue<double> rationorm{mesh.connectivity()};
+    chi.fill(0.);
+    rationorm.fill(0.);
+    // for (CellId j = 0; j < mesh.numberOfCells(); ++j) {
+    //   const auto& cell_nodes = cell_to_node_matrix[j];
+
+    //   Rd momentum_fluxes   = zero;
+    //   double energy_fluxes = 0;
+    //   Rdxd gradv           = zero;
+    //   for (size_t R = 0; R < cell_nodes.size(); ++R) {
+    //     const NodeId r = cell_nodes[R];
+    //     gradv += tensorProduct(ur[r], Cjr(j, R));
+    //     momentum_fluxes += Fjr(j, R);
+    //     energy_fluxes += dot(Fjr(j, R), ur[r]);
+    //   }
+    //   R3x3 I                    = identity;
+    //   R3x3 gradv3d              = to3D(gradv);
+    //   R3x3 sigmas               = sigma[j] - 1. / 3 * trace(sigma[j]) * I;
+    //   const R3x3 elastic_fluxes = gradv3d * Fe[j];
+    //   const double dt_over_Mj   = dt / (rho[j] * Vj[j]);
+    //   const double dt_over_Vj   = dt / Vj[j];
+    //   new_u[j] += dt_over_Mj * momentum_fluxes;
+    //   new_E[j] += dt_over_Mj * energy_fluxes;
+    //   new_Fe[j] += dt_over_Vj * elastic_fluxes;
+    //   const double fenorm0 = frobeniusNorm(new_Fe[j]);
+    //   chi[j]               = _compute_chi(sigma[j], yield[j]);
+    //   const R3x3 M         = -dt * chi[j] * zeta[j] * sigmas;
+
+    //   new_Fe[j]            = EigenvalueSolver{}.computeExpForTinyMatrix(M) * new_Fe[j];
+    //   const double fenorm1 = frobeniusNorm(new_Fe[j]);
+    //   rationorm[j]         = fenorm1 / fenorm0;
+    // }
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        Rd momentum_fluxes   = zero;
+        double energy_fluxes = 0;
+        Rdxd gradv           = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        R3x3 I                    = identity;
+        R3x3 gradv3d              = to3D(gradv);
+        R3x3 sigmas               = sigma[j] - 1. / 3 * trace(sigma[j]) * I;
+        const R3x3 elastic_fluxes = gradv3d * Fe[j];
+        const double dt_over_Mj   = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj   = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        new_Fe[j] += dt_over_Vj * elastic_fluxes;
+        const double fenorm0 = frobeniusNorm(new_Fe[j]);
+        chi[j]               = _compute_chi(sigma[j], yield[j]);
+        const R3x3 M         = -dt * chi[j] * zeta[j] * sigmas;
+
+        new_Fe[j]            = EigenvalueSolver{}.computeExpForTinyMatrix(M) * new_Fe[j];
+        const double fenorm1 = frobeniusNorm(new_Fe[j]);
+        rationorm[j]         = fenorm1 / fenorm0;
+      });
+    std::cout << "sum_chi " << sum(chi) << " min norm ratio " << min(rationorm) << " max norm ratio " << max(rationorm)
+              << "\n";
+
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    return {std::make_shared<MeshVariant>(new_mesh),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(new_mesh, new_Fe))};
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes2(const double& dt,
+                const MeshType& mesh,
+                const DiscreteScalarFunction& rho,
+                const DiscreteVectorFunction& u,
+                const DiscreteScalarFunction& E,
+                const DiscreteTensorFunction3d& Fe,
+                const DiscreteScalarFunction& zeta,
+                const DiscreteScalarFunction& yield,
+                const DiscreteTensorFunction3d& sigma,
+                const NodeValue<const Rd>& ur,
+                const NodeValuePerCell<const Rd>& Fjr,
+                const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+    const BoundaryConditionList bc_list = this->_getBCList(mesh, bc_descriptor_list);
+    this->_projectOnBoundary(bc_list, new_xr);
+
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho   = copy(rho.cellValues());
+    CellValue<Rd> new_u         = copy(u.cellValues());
+    CellValue<double> new_E     = copy(E.cellValues());
+    CellValue<R3x3> new_Fe      = copy(Fe.cellValues());
+    CellValue<double> new_zeta  = copy(zeta.cellValues());
+    CellValue<double> new_yield = copy(yield.cellValues());
+    CellValue<R3x3> sigma_s     = copy(sigma.cellValues());
+    CellValue<double> chi{mesh.connectivity()};
+    CellValue<double> rationorm{mesh.connectivity()};
+    chi.fill(0.);
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        Rd momentum_fluxes   = zero;
+        double energy_fluxes = 0;
+        Rdxd gradv           = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        R3x3 I                    = identity;
+        R3x3 gradv3d              = to3D(gradv);
+        R3x3 sigmas               = sigma[j] - 1. / 3 * trace(sigma[j]) * I;
+        const R3x3 elastic_fluxes = gradv3d * Fe[j];
+        const double dt_over_Mj   = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj   = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        new_Fe[j] += dt_over_Vj * elastic_fluxes;
+        chi[j]     = _compute_chi(sigma[j], yield[j]);
+        int sub_it = static_cast<int>(std::ceil(dt * chi[j] * zeta[j] * frobeniusNorm(sigmas)));
+        if (sub_it > 1) {
+          std::cout << sub_it << " dt " << dt << " chi[j] " << chi[j] << " zeta[j] " << zeta[j]
+                    << " frobeniusNorm(sigmas) " << frobeniusNorm(sigmas) << "\n";
+        }
+        for (int i = 0; i < sub_it; i++) {
+          const R3x3 M = I + dt / static_cast<double>(sub_it) * chi[j] * zeta[j] * sigmas;
+          new_Fe[j]    = inverse(M) * new_Fe[j];
+        }
+      });
+    std::cout << "sum_chi " << sum(chi) << "\n";
+
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    return {std::make_shared<MeshVariant>(new_mesh),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(new_mesh, new_Fe))};
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes_B(const double& dt,
+                 const MeshType& mesh,
+                 const DiscreteScalarFunction& rho,
+                 const DiscreteVectorFunction& u,
+                 const DiscreteScalarFunction& E,
+                 const DiscreteTensorFunction3d& Be,
+                 const DiscreteScalarFunction& zeta,
+                 const DiscreteScalarFunction& yield,
+                 const DiscreteTensorFunction3d& sigma,
+                 const NodeValue<const Rd>& ur,
+                 const NodeValuePerCell<const Rd>& Fjr,
+                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+    const BoundaryConditionList bc_list = this->_getBCList(mesh, bc_descriptor_list);
+    this->_projectOnBoundary(bc_list, new_xr);
+
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho   = copy(rho.cellValues());
+    CellValue<Rd> new_u         = copy(u.cellValues());
+    CellValue<double> new_E     = copy(E.cellValues());
+    CellValue<R3x3> new_Be      = copy(Be.cellValues());
+    CellValue<double> new_zeta  = copy(zeta.cellValues());
+    CellValue<double> new_yield = copy(yield.cellValues());
+    CellValue<R3x3> sigma_s     = copy(sigma.cellValues());
+    CellValue<double> chi{mesh.connectivity()};
+    CellValue<double> rationorm{mesh.connectivity()};
+    chi.fill(0.);
+    // rationorm.fill(0.);
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+        Rd momentum_fluxes     = zero;
+        double energy_fluxes   = 0;
+        Rdxd gradv             = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        R3x3 I       = identity;
+        R3x3 gradv3d = to3D(gradv);
+        R3x3 sigmas  = sigma[j] - 1. / 3 * trace(sigma[j]) * I;
+        //        const R3x3 elastic_fluxes = gradv3d + transpose(gradv3d);
+        const double dt_over_Mj = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        // new_Be[j] += dt_over_Vj * elastic_fluxes;
+        // const double fenorm0 = frobeniusNorm(new_Be[j]);
+        chi[j]           = _compute_chi(sigma[j], yield[j]);
+        const R3x3 M     = dt_over_Vj * gradv3d - dt * chi[j] * zeta[j] * sigmas;
+        const R3x3 expM  = EigenvalueSolver{}.computeExpForTinyMatrix(M);
+        const R3x3 expMT = EigenvalueSolver{}.computeExpForTinyMatrix(transpose(M));
+        new_Be[j]        = expM * Be[j] * expMT;
+        // const double fenorm1 = frobeniusNorm(new_Be[j]);
+        // rationorm[j]         = fenorm1 / fenorm0;
+      });
+    std::cout << "sum_chi " << sum(chi) << "\n";
+
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    return {std::make_shared<MeshVariant>(new_mesh),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(new_mesh, new_Be))};
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_fluxes(const double& dt,
+               const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+               const std::shared_ptr<const DiscreteFunctionVariant>& u,
+               const std::shared_ptr<const DiscreteFunctionVariant>& E,
+               const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+               const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+               const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+               const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+               const std::shared_ptr<const ItemValueVariant>& ur,
+               const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,
+               const RelaxationType& relaxation_type,
+               const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    std::shared_ptr mesh_v = getCommonMesh({rho, u, E});
+    if (not mesh_v) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({rho, u, E}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperplastic solver expects P0 functions");
+    }
+    switch (relaxation_type) {
+    case RelaxationType::Exponential: {
+      return this->apply_fluxes(dt,                                       //
+                                *mesh_v->get<MeshType>(),                 //
+                                rho->get<DiscreteScalarFunction>(),       //
+                                u->get<DiscreteVectorFunction>(),         //
+                                E->get<DiscreteScalarFunction>(),         //
+                                Fe->get<DiscreteTensorFunction3d>(),      //
+                                zeta->get<DiscreteScalarFunction>(),      //
+                                yield->get<DiscreteScalarFunction>(),     //
+                                sigma->get<DiscreteTensorFunction3d>(),   //
+                                ur->get<NodeValue<const Rd>>(),           //
+                                Fjr->get<NodeValuePerCell<const Rd>>(), bc_descriptor_list);
+    }
+    case RelaxationType::Implicit: {
+      return this->apply_fluxes2(dt,                                       //
+                                 *mesh_v->get<MeshType>(),                 //
+                                 rho->get<DiscreteScalarFunction>(),       //
+                                 u->get<DiscreteVectorFunction>(),         //
+                                 E->get<DiscreteScalarFunction>(),         //
+                                 Fe->get<DiscreteTensorFunction3d>(),      //
+                                 zeta->get<DiscreteScalarFunction>(),      //
+                                 yield->get<DiscreteScalarFunction>(),     //
+                                 sigma->get<DiscreteTensorFunction3d>(),   //
+                                 ur->get<NodeValue<const Rd>>(),           //
+                                 Fjr->get<NodeValuePerCell<const Rd>>(), bc_descriptor_list);
+    }
+    case RelaxationType::CauchyGreen: {
+      return this->apply_fluxes_B(dt,                                       //
+                                  *mesh_v->get<MeshType>(),                 //
+                                  rho->get<DiscreteScalarFunction>(),       //
+                                  u->get<DiscreteVectorFunction>(),         //
+                                  E->get<DiscreteScalarFunction>(),         //
+                                  Fe->get<DiscreteTensorFunction3d>(),      //
+                                  zeta->get<DiscreteScalarFunction>(),      //
+                                  yield->get<DiscreteScalarFunction>(),     //
+                                  sigma->get<DiscreteTensorFunction3d>(),   //
+                                  ur->get<NodeValue<const Rd>>(),           //
+                                  Fjr->get<NodeValuePerCell<const Rd>>(), bc_descriptor_list);
+    }
+    default: {
+      throw UnexpectedError("invalid relaxation type");
+    }
+    }
+  }
+
+  std::shared_ptr<const DiscreteFunctionVariant>
+  apply_plastic_relaxation(const RelaxationType& relaxation_type,
+                           const double& dt,
+                           const std::shared_ptr<const MeshType>& mesh,
+                           const DiscreteTensorFunction3d& Fe,
+                           const DiscreteScalarFunction& zeta,
+                           const DiscreteScalarFunction& yield,
+                           const DiscreteTensorFunction3d& sigman,
+                           const DiscreteTensorFunction3d& sigmanp1) const
+  {
+    CellValue<R3x3> new_Fe = copy(Fe.cellValues());
+    CellValue<double> chi{mesh->connectivity()};
+    chi.fill(0.);
+    CellValue<const double> Vj = MeshDataManager::instance().getMeshData(*mesh).Vj();
+    R3x3 I                     = identity;
+    switch (relaxation_type) {
+    case RelaxationType::Exponential: {
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId j) {
+          R3x3 sigmasn   = sigman[j] - 1. / 3 * trace(sigman[j]) * I;
+          R3x3 sigmasnp1 = sigmanp1[j] - 1. / 3 * trace(sigmanp1[j]) * I;
+          chi[j]         = _compute_chi(0.5 * (sigmasn + sigmasnp1), yield[j]);
+          const R3x3 M   = -dt * chi[j] * zeta[j] * 0.5 * (sigmasn + sigmasnp1);
+          new_Fe[j]      = EigenvalueSolver{}.computeExpForTinyMatrix(M) * new_Fe[j];
+        });
+      break;
+    }
+    case RelaxationType::Implicit: {
+      parallel_for(
+        mesh->numberOfCells(), PUGS_LAMBDA(CellId j) {
+          R3x3 sigmasn   = sigman[j] - 1. / 3 * trace(sigman[j]) * I;
+          R3x3 sigmasnp1 = sigmanp1[j] - 1. / 3 * trace(sigmanp1[j]) * I;
+          chi[j]         = _compute_chi(0.5 * (sigmasn + sigmasnp1), yield[j]);
+          int sub_it = static_cast<int>(std::ceil(dt * chi[j] * zeta[j] * frobeniusNorm(0.5 * (sigmasn + sigmasnp1))));
+          if (sub_it > 1) {
+            std::cout << sub_it << " dt " << dt << " chi[j] " << chi[j] << " zeta[j] " << zeta[j]
+                      << " frobeniusNorm(sigmas) " << frobeniusNorm(0.5 * (sigmasn + sigmasnp1)) << "\n";
+          }
+          for (int i = 0; i < sub_it; i++) {
+            const R3x3 M = I + 0.5 * dt / static_cast<double>(sub_it) * chi[j] * zeta[j] * (sigmasn + sigmasnp1);
+            new_Fe[j]    = inverse(M) * new_Fe[j];
+          }
+        });
+      break;
+    }
+    default: {
+      throw UnexpectedError("invalid relaxation type");
+    }
+    }
+
+    // for (CellId j = 0; j < mesh->numberOfCells(); j++) {
+    //   if ((std::abs(frobeniusNorm(new_Fe[j]) - std::sqrt(3.)) > 1.e-1) or (j == 6399)) {
+    //     std::cout << j << " new_Fe " << new_Fe[j] << " chi " << chi[j] << "\n";
+    //   }
+    // }
+    std::cout << "sum_chi " << sum(chi) << "\n";
+
+    return {std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(mesh, new_Fe))};
+  }
+
+  std::shared_ptr<const DiscreteFunctionVariant>
+  apply_plastic_relaxation(const RelaxationType& relaxation_type,
+                           const double& dt,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& sigman,
+                           const std::shared_ptr<const DiscreteFunctionVariant>& sigmanp1) const
+  {
+    std::shared_ptr mesh_v = getCommonMesh({sigman, sigmanp1});
+    if (not mesh_v) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({sigman, sigmanp1}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperplastic solver expects P0 functions");
+    }
+
+    return this->apply_plastic_relaxation(relaxation_type, dt,                    //
+                                          mesh_v->get<MeshType>(),                //
+                                          Fe->get<DiscreteTensorFunction3d>(),    //
+                                          zeta->get<DiscreteScalarFunction>(),    //
+                                          yield->get<DiscreteScalarFunction>(),   //
+                                          sigman->get<DiscreteTensorFunction3d>(),
+                                          sigmanp1->get<DiscreteTensorFunction3d>());
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_elastic_fluxes(const double& dt,
+                       const MeshType& mesh,
+                       const DiscreteScalarFunction& rho,
+                       const DiscreteVectorFunction& u,
+                       const DiscreteScalarFunction& E,
+                       const DiscreteTensorFunction3d& Fe,
+                       const NodeValue<const Rd>& ur,
+                       const NodeValuePerCell<const Rd>& Fjr) const
+  {
+    const auto& cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
+
+    if ((mesh.shared_connectivity() != ur.connectivity_ptr()) or
+        (mesh.shared_connectivity() != Fjr.connectivity_ptr())) {
+      throw NormalError("fluxes are not defined on the same connectivity than the mesh");
+    }
+
+    NodeValue<Rd> new_xr = copy(mesh.xr());
+    parallel_for(
+      mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) { new_xr[r] += dt * ur[r]; });
+
+    std::shared_ptr<const MeshType> new_mesh = std::make_shared<MeshType>(mesh.shared_connectivity(), new_xr);
+
+    CellValue<const double> Vj           = MeshDataManager::instance().getMeshData(mesh).Vj();
+    const NodeValuePerCell<const Rd> Cjr = MeshDataManager::instance().getMeshData(mesh).Cjr();
+
+    CellValue<double> new_rho = copy(rho.cellValues());
+    CellValue<Rd> new_u       = copy(u.cellValues());
+    CellValue<double> new_E   = copy(E.cellValues());
+    CellValue<R3x3> new_Fe    = copy(Fe.cellValues());
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) {
+        const auto& cell_nodes = cell_to_node_matrix[j];
+
+        Rd momentum_fluxes   = zero;
+        double energy_fluxes = 0;
+        Rdxd gradv           = zero;
+        for (size_t R = 0; R < cell_nodes.size(); ++R) {
+          const NodeId r = cell_nodes[R];
+          gradv += tensorProduct(ur[r], Cjr(j, R));
+          momentum_fluxes += Fjr(j, R);
+          energy_fluxes += dot(Fjr(j, R), ur[r]);
+        }
+        R3x3 gradv3d              = to3D(gradv);
+        const R3x3 elastic_fluxes = gradv3d * Fe[j];
+        const double dt_over_Mj   = dt / (rho[j] * Vj[j]);
+        const double dt_over_Vj   = dt / Vj[j];
+        new_u[j] += dt_over_Mj * momentum_fluxes;
+        new_E[j] += dt_over_Mj * energy_fluxes;
+        new_Fe[j] += dt_over_Vj * elastic_fluxes;
+      });
+    CellValue<const double> new_Vj = MeshDataManager::instance().getMeshData(*new_mesh).Vj();
+
+    parallel_for(
+      mesh.numberOfCells(), PUGS_LAMBDA(CellId j) { new_rho[j] *= Vj[j] / new_Vj[j]; });
+
+    return {std::make_shared<MeshVariant>(new_mesh),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_rho)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteVectorFunction(new_mesh, new_u)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteScalarFunction(new_mesh, new_E)),
+            std::make_shared<DiscreteFunctionVariant>(DiscreteTensorFunction3d(new_mesh, new_Fe))};
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_elastic_fluxes(const double& dt,
+                       const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                       const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                       const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                       const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                       const std::shared_ptr<const ItemValueVariant>& ur,
+                       const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr) const
+  {
+    std::shared_ptr mesh_v = getCommonMesh({rho, u, E});
+    if (not mesh_v) {
+      throw NormalError("discrete functions are not defined on the same mesh");
+    }
+
+    if (not checkDiscretizationType({rho, u, E}, DiscreteFunctionType::P0)) {
+      throw NormalError("hyperplastic solver expects P0 functions");
+    }
+
+    return this->apply_elastic_fluxes(dt,                                    //
+                                      *mesh_v->get<MeshType>(),              //
+                                      rho->get<DiscreteScalarFunction>(),    //
+                                      u->get<DiscreteVectorFunction>(),      //
+                                      E->get<DiscreteScalarFunction>(),      //
+                                      Fe->get<DiscreteTensorFunction3d>(),   //
+                                      ur->get<NodeValue<const Rd>>(),        //
+                                      Fjr->get<NodeValuePerCell<const Rd>>());
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply(const SolverType& solver_type,
+        const RelaxationType& relaxation_type,
+        const double& dt,
+        const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+        const std::shared_ptr<const DiscreteFunctionVariant>& u,
+        const std::shared_ptr<const DiscreteFunctionVariant>& E,
+        const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+        const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+        const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+        const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+        const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+        const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+        const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    auto [ur, Fjr] = compute_fluxes(solver_type, rho, aL, aT, u, sigma, bc_descriptor_list);
+    return apply_fluxes(dt, rho, u, E, Fe, zeta, yield, sigma, ur, Fjr, relaxation_type, bc_descriptor_list);
+  }
+
+  std::tuple<std::shared_ptr<const MeshVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>,
+             std::shared_ptr<const DiscreteFunctionVariant>>
+  apply_elastic(const SolverType& solver_type,
+                const double& dt,
+                const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const
+  {
+    auto [ur, Fjr] = compute_fluxes(solver_type, rho, aL, aT, u, sigma, bc_descriptor_list);
+    return apply_elastic_fluxes(dt, rho, u, E, Fe, ur, Fjr);
+  }
+
+  HyperplasticSolverO2()                       = default;
+  HyperplasticSolverO2(HyperplasticSolverO2&&) = default;
+  ~HyperplasticSolverO2()                      = default;
+};
+
+template <MeshConcept MeshType>
+void
+HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::_applyPressureBC(const BoundaryConditionList& bc_list,
+                                                                              const MeshType& mesh,
+                                                                              NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<PressureBoundaryCondition, T>) {
+          MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+          if constexpr (Dimension == 1) {
+            const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+            const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+            const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+            const auto& node_list  = bc.nodeList();
+            const auto& value_list = bc.valueList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(size_t i_node) {
+                const NodeId node_id       = node_list[i_node];
+                const auto& node_cell_list = node_to_cell_matrix[node_id];
+                Assert(node_cell_list.size() == 1);
+
+                CellId node_cell_id              = node_cell_list[0];
+                size_t node_local_number_in_cell = node_local_numbers_in_their_cells(node_id, 0);
+
+                br[node_id] -= value_list[i_node] * Cjr(node_cell_id, node_local_number_in_cell);
+              });
+          } else {
+            const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+
+            const auto& face_to_cell_matrix               = mesh.connectivity().faceToCellMatrix();
+            const auto& face_to_node_matrix               = mesh.connectivity().faceToNodeMatrix();
+            const auto& face_local_numbers_in_their_cells = mesh.connectivity().faceLocalNumbersInTheirCells();
+            const auto& face_cell_is_reversed             = mesh.connectivity().cellFaceIsReversed();
+
+            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_cell_list = face_to_cell_matrix[face_id];
+              Assert(face_cell_list.size() == 1);
+
+              CellId face_cell_id              = face_cell_list[0];
+              size_t face_local_number_in_cell = face_local_numbers_in_their_cells(face_id, 0);
+
+              const double sign = face_cell_is_reversed(face_cell_id, face_local_number_in_cell) ? -1 : 1;
+
+              const auto& face_nodes = face_to_node_matrix[face_id];
+
+              for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
+                NodeId node_id = face_nodes[i_node];
+                br[node_id] -= sign * value_list[i_face] * Nlr(face_id, i_node);
+              }
+            }
+          }
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <MeshConcept MeshType>
+void
+HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::_applyNormalStressBC(const BoundaryConditionList& bc_list,
+                                                                                  const MeshType& mesh,
+                                                                                  NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<NormalStressBoundaryCondition, T>) {
+          MeshDataType& mesh_data = MeshDataManager::instance().getMeshData(mesh);
+          if constexpr (Dimension == 1) {
+            const NodeValuePerCell<const Rd> Cjr = mesh_data.Cjr();
+
+            const auto& node_to_cell_matrix               = mesh.connectivity().nodeToCellMatrix();
+            const auto& node_local_numbers_in_their_cells = mesh.connectivity().nodeLocalNumbersInTheirCells();
+
+            const auto& node_list  = bc.nodeList();
+            const auto& value_list = bc.valueList();
+            parallel_for(
+              node_list.size(), PUGS_LAMBDA(size_t i_node) {
+                const NodeId node_id       = node_list[i_node];
+                const auto& node_cell_list = node_to_cell_matrix[node_id];
+                Assert(node_cell_list.size() == 1);
+
+                CellId node_cell_id              = node_cell_list[0];
+                size_t node_local_number_in_cell = node_local_numbers_in_their_cells(node_id, 0);
+
+                br[node_id] += value_list[i_node] * Cjr(node_cell_id, node_local_number_in_cell);
+              });
+          } else {
+            const NodeValuePerFace<const Rd> Nlr = mesh_data.Nlr();
+
+            const auto& face_to_cell_matrix               = mesh.connectivity().faceToCellMatrix();
+            const auto& face_to_node_matrix               = mesh.connectivity().faceToNodeMatrix();
+            const auto& face_local_numbers_in_their_cells = mesh.connectivity().faceLocalNumbersInTheirCells();
+            const auto& face_cell_is_reversed             = mesh.connectivity().cellFaceIsReversed();
+
+            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_cell_list = face_to_cell_matrix[face_id];
+              Assert(face_cell_list.size() == 1);
+
+              CellId face_cell_id              = face_cell_list[0];
+              size_t face_local_number_in_cell = face_local_numbers_in_their_cells(face_id, 0);
+
+              const double sign = face_cell_is_reversed(face_cell_id, face_local_number_in_cell) ? -1 : 1;
+
+              const auto& face_nodes = face_to_node_matrix[face_id];
+
+              for (size_t i_node = 0; i_node < face_nodes.size(); ++i_node) {
+                NodeId node_id = face_nodes[i_node];
+                br[node_id] += sign * value_list[i_face] * Nlr(face_id, i_node);
+              }
+            }
+          }
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <MeshConcept MeshType>
+void
+HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::_applySymmetryBC(const BoundaryConditionList& bc_list,
+                                                                              NodeValue<Rdxd>& Ar,
+                                                                              NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<SymmetryBoundaryCondition, T>) {
+          const Rd& n = bc.outgoingNormal();
+
+          const Rdxd I   = identity;
+          const Rdxd nxn = tensorProduct(n, n);
+          const Rdxd P   = I - nxn;
+
+          const Array<const NodeId>& node_list = bc.nodeList();
+          parallel_for(
+            bc.numberOfNodes(), PUGS_LAMBDA(int r_number) {
+              const NodeId r = node_list[r_number];
+
+              Ar[r] = P * Ar[r] * P + nxn;
+              br[r] = P * br[r];
+            });
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <MeshConcept MeshType>
+void
+HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::_applyVelocityBC(const BoundaryConditionList& bc_list,
+                                                                              NodeValue<Rdxd>& Ar,
+                                                                              NodeValue<Rd>& br) const
+{
+  for (const auto& boundary_condition : bc_list) {
+    std::visit(
+      [&](auto&& bc) {
+        using T = std::decay_t<decltype(bc)>;
+        if constexpr (std::is_same_v<VelocityBoundaryCondition, T>) {
+          const auto& node_list  = bc.nodeList();
+          const auto& value_list = bc.valueList();
+
+          parallel_for(
+            node_list.size(), PUGS_LAMBDA(size_t i_node) {
+              NodeId node_id    = node_list[i_node];
+              const auto& value = value_list[i_node];
+
+              Ar[node_id] = identity;
+              br[node_id] = value;
+            });
+        } else if constexpr (std::is_same_v<FixedBoundaryCondition, T>) {
+          const auto& node_list = bc.nodeList();
+          parallel_for(
+            node_list.size(), PUGS_LAMBDA(size_t i_node) {
+              NodeId node_id = node_list[i_node];
+
+              Ar[node_id] = identity;
+              br[node_id] = zero;
+            });
+        }
+      },
+      boundary_condition);
+  }
+}
+
+template <MeshConcept MeshType>
+class HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::FixedBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary m_mesh_node_boundary;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  FixedBoundaryCondition(const MeshNodeBoundary& mesh_node_boundary) : m_mesh_node_boundary{mesh_node_boundary} {}
+
+  ~FixedBoundaryCondition() = default;
+};
+
+template <MeshConcept MeshType>
+class HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::PressureBoundaryCondition
+{
+ private:
+  const MeshFaceBoundary m_mesh_face_boundary;
+  const Array<const double> m_value_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_mesh_face_boundary.faceList();
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  PressureBoundaryCondition(const MeshFaceBoundary& mesh_face_boundary, const Array<const double>& value_list)
+    : m_mesh_face_boundary{mesh_face_boundary}, m_value_list{value_list}
+  {}
+
+  ~PressureBoundaryCondition() = default;
+};
+
+template <>
+class HyperplasticSolverO2Handler::HyperplasticSolverO2<Mesh<1>>::PressureBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary m_mesh_node_boundary;
+  const Array<const double> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const double>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  PressureBoundaryCondition(const MeshNodeBoundary& mesh_node_boundary, const Array<const double>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~PressureBoundaryCondition() = default;
+};
+
+template <MeshConcept MeshType>
+class HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::NormalStressBoundaryCondition
+{
+ private:
+  const MeshFaceBoundary m_mesh_face_boundary;
+  const Array<const Rdxd> m_value_list;
+
+ public:
+  const Array<const FaceId>&
+  faceList() const
+  {
+    return m_mesh_face_boundary.faceList();
+  }
+
+  const Array<const Rdxd>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NormalStressBoundaryCondition(const MeshFaceBoundary& mesh_face_boundary, const Array<const Rdxd>& value_list)
+    : m_mesh_face_boundary{mesh_face_boundary}, m_value_list{value_list}
+  {}
+
+  ~NormalStressBoundaryCondition() = default;
+};
+
+template <>
+class HyperplasticSolverO2Handler::HyperplasticSolverO2<Mesh<1>>::NormalStressBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary m_mesh_node_boundary;
+  const Array<const Rdxd> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const Rdxd>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  NormalStressBoundaryCondition(const MeshNodeBoundary& mesh_node_boundary, const Array<const Rdxd>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~NormalStressBoundaryCondition() = default;
+};
+
+template <MeshConcept MeshType>
+class HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::VelocityBoundaryCondition
+{
+ private:
+  const MeshNodeBoundary m_mesh_node_boundary;
+
+  const Array<const TinyVector<Dimension>> m_value_list;
+
+ public:
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_node_boundary.nodeList();
+  }
+
+  const Array<const TinyVector<Dimension>>&
+  valueList() const
+  {
+    return m_value_list;
+  }
+
+  VelocityBoundaryCondition(const MeshNodeBoundary& mesh_node_boundary,
+                            const Array<const TinyVector<Dimension>>& value_list)
+    : m_mesh_node_boundary{mesh_node_boundary}, m_value_list{value_list}
+  {}
+
+  ~VelocityBoundaryCondition() = default;
+};
+
+template <MeshConcept MeshType>
+class HyperplasticSolverO2Handler::HyperplasticSolverO2<MeshType>::SymmetryBoundaryCondition
+{
+ public:
+  using Rd = TinyVector<Dimension, double>;
+
+ private:
+  const MeshFlatNodeBoundary<MeshType> m_mesh_flat_node_boundary;
+
+ public:
+  const Rd&
+  outgoingNormal() const
+  {
+    return m_mesh_flat_node_boundary.outgoingNormal();
+  }
+
+  const Rd&
+  origin() const
+  {
+    return m_mesh_flat_node_boundary.origin();
+  }
+
+  size_t
+  numberOfNodes() const
+  {
+    return m_mesh_flat_node_boundary.nodeList().size();
+  }
+
+  const Array<const NodeId>&
+  nodeList() const
+  {
+    return m_mesh_flat_node_boundary.nodeList();
+  }
+
+  SymmetryBoundaryCondition(const MeshFlatNodeBoundary<MeshType>& mesh_flat_node_boundary)
+    : m_mesh_flat_node_boundary(mesh_flat_node_boundary)
+  {
+    ;
+  }
+
+  ~SymmetryBoundaryCondition() = default;
+};
+
+HyperplasticSolverO2Handler::HyperplasticSolverO2Handler(const std::shared_ptr<const MeshVariant>& mesh_v)
+{
+  if (not mesh_v) {
+    throw NormalError("discrete functions are not defined on the same mesh");
+  }
+
+  std::visit(
+    [&](auto&& mesh) {
+      using MeshType = mesh_type_t<decltype(mesh)>;
+      if constexpr (is_polygonal_mesh_v<MeshType>) {
+        m_hyperplastic_solver = std::make_unique<HyperplasticSolverO2<MeshType>>();
+      } else {
+        throw NormalError("unexpected mesh type");
+      }
+    },
+    mesh_v->variant());
+}
diff --git a/src/scheme/HyperplasticSolverO2.hpp b/src/scheme/HyperplasticSolverO2.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..063dcc7d66226d12847b250a57da530590090902
--- /dev/null
+++ b/src/scheme/HyperplasticSolverO2.hpp
@@ -0,0 +1,132 @@
+#ifndef HYPERPLASTIC_SOLVER_O2_HPP
+#define HYPERPLASTIC_SOLVER_O2_HPP
+
+#include <mesh/MeshTraits.hpp>
+
+#include <memory>
+#include <tuple>
+#include <vector>
+
+class IBoundaryConditionDescriptor;
+class MeshVariant;
+class ItemValueVariant;
+class SubItemValuePerItemVariant;
+class DiscreteFunctionVariant;
+
+double hyperplastic_dt(const std::shared_ptr<const DiscreteFunctionVariant>& c);
+
+class HyperplasticSolverO2Handler
+{
+ public:
+  enum class SolverType
+  {
+    Glace,
+    Eucclhyd
+  };
+
+  enum class RelaxationType
+  {
+    Implicit,
+    Exponential,
+    CauchyGreen
+  };
+
+ private:
+  struct IHyperplasticSolverO2
+  {
+    virtual std::tuple<const std::shared_ptr<const ItemValueVariant>,
+                       const std::shared_ptr<const SubItemValuePerItemVariant>>
+    compute_fluxes(
+      const SolverType& solver_type,
+      const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+      const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+      const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+      const std::shared_ptr<const DiscreteFunctionVariant>& u,
+      const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+      const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
+
+    virtual std::tuple<std::shared_ptr<const MeshVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>>
+    apply_fluxes(const double& dt,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+                 const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                 const std::shared_ptr<const ItemValueVariant>& ur,
+                 const std::shared_ptr<const SubItemValuePerItemVariant>& Fjr,
+                 const RelaxationType& relaxation_type,
+                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
+
+    virtual std::tuple<std::shared_ptr<const MeshVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>>
+    apply(const SolverType& solver_type,
+          const RelaxationType& relaxation_type,
+          const double& dt,
+          const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+          const std::shared_ptr<const DiscreteFunctionVariant>& u,
+          const std::shared_ptr<const DiscreteFunctionVariant>& E,
+          const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+          const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+          const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+          const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+          const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+          const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+          const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
+
+    virtual std::tuple<std::shared_ptr<const MeshVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>,
+                       std::shared_ptr<const DiscreteFunctionVariant>>
+    apply_elastic(const SolverType& solver_type,
+                  const double& dt,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& rho,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& u,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& E,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& aL,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& aT,
+                  const std::shared_ptr<const DiscreteFunctionVariant>& sigma,
+                  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& bc_descriptor_list) const = 0;
+
+    virtual std::shared_ptr<const DiscreteFunctionVariant> apply_plastic_relaxation(
+      const RelaxationType& relaxation_type,
+      const double& dt,
+      const std::shared_ptr<const DiscreteFunctionVariant>& Fe,
+      const std::shared_ptr<const DiscreteFunctionVariant>& zeta,
+      const std::shared_ptr<const DiscreteFunctionVariant>& yield,
+      const std::shared_ptr<const DiscreteFunctionVariant>& sigman,
+      const std::shared_ptr<const DiscreteFunctionVariant>& sigmanp1) const = 0;
+
+    IHyperplasticSolverO2()                                   = default;
+    IHyperplasticSolverO2(IHyperplasticSolverO2&&)            = default;
+    IHyperplasticSolverO2& operator=(IHyperplasticSolverO2&&) = default;
+
+    virtual ~IHyperplasticSolverO2() = default;
+  };
+
+  template <MeshConcept MeshType>
+  class HyperplasticSolverO2;
+
+  std::unique_ptr<IHyperplasticSolverO2> m_hyperplastic_solver;
+
+ public:
+  const IHyperplasticSolverO2&
+  solver() const
+  {
+    return *m_hyperplastic_solver;
+  }
+
+  HyperplasticSolverO2Handler(const std::shared_ptr<const MeshVariant>& mesh);
+};
+
+#endif   // HYPERPLASTIC_SOLVER_O2_HPP
diff --git a/src/scheme/ODEGD1D.hpp b/src/scheme/ODEGD1D.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..19198305f6d80f183c764503cdc7717e54ae9b0e
--- /dev/null
+++ b/src/scheme/ODEGD1D.hpp
@@ -0,0 +1,218 @@
+#ifndef ODE_DG_1D
+#define ODE_DG_1D
+
+#include <rang.hpp>
+
+#include <utils/ArrayUtils.hpp>
+
+#include <utils/PugsAssert.hpp>
+
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshData.hpp>
+#include <mesh/MeshDataManager.hpp>
+#include <mesh/MeshNodeBoundary.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+#include <analysis/Polynomial.hpp>
+#include <analysis/PolynomialBasis.hpp>
+
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+
+#include <scheme/DiscontinuousGalerkin1DTools.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/Messenger.hpp>
+
+#include <iostream>
+enum class FluxType
+{
+  centered,
+  stable
+};
+
+template <size_t Degree>
+class ODEDG1D
+{
+  using MeshType                    = Mesh<Connectivity1D>;
+  constexpr static size_t Dimension = MeshType::Dimension;
+  static_assert(Dimension == 1, "Galerkin 1D only works in dimension 1");
+  using MeshDataType = MeshData<Dimension>;
+  //  using UnknownsType = FiniteVolumesEulerUnknowns<MeshType>;
+
+  const BasisType& m_basis_type;
+  std::shared_ptr<const MeshType> m_mesh;
+  const typename MeshType::Connectivity& m_connectivity;
+
+  // class DirichletBoundaryCondition;
+
+  // using BoundaryCondition = std::variant<DirichletBoundaryCondition>;
+
+  // using BoundaryConditionList = std::vector<BoundaryCondition>;
+  // DiscontinuousGalerkin1DTools<Degree>& m_dgtools;
+  using Rd  = TinyVector<Degree>;
+  using Rdd = TinyMatrix<Degree>;
+
+  PolynomialBasis<Degree> m_basis;
+  using R1 = TinyVector<Dimension>;
+  // const BoundaryConditionList m_boundary_condition_list;
+  // NodeValue<R1> m_flux;
+  TinyVector<Degree + 1>
+  _buildZeros(double xmin, double xmax)
+  {
+    TinyVector<Degree + 1> zeros(zero);
+    if constexpr (Degree == 0) {
+      zeros[0] = 0.5 * (xmax - xmin);
+    } else {
+      zeros[0]  = xmin;
+      double dx = (xmax - xmin) / Degree;
+      for (size_t i = 1; i <= Degree; ++i) {
+        zeros[i] = xmin + i * dx;
+      }
+    }
+    return zeros;
+  }
+
+ public:
+  // void
+  // _applyBC()
+  // {
+  //   for (const auto& boundary_condition : m_boundary_condition_list) {
+  //     std::visit(
+  //       [&](auto&& bc) {
+  //         using T = std::decay_t<decltype(bc)>;
+  //         if constexpr (std::is_same_v<DirichletBoundaryCondition, T>) {
+  //           const auto& node_list  = bc.nodeList();
+  //           const auto& value_list = bc.valueList();
+
+  //           parallel_for(
+  //             node_list.size(), PUGS_LAMBDA(size_t i_node) {
+  //               NodeId node_id    = node_list[i_node];
+  //               const auto& value = value_list[i_node];
+
+  //               m_flux[node_id] = value;
+  //             });
+  //         }
+  //       },
+  //       boundary_condition);
+  //   }
+  // }
+
+  // NodeValue<R1>
+  // computeFluxes(FluxType flux_type, const PolynomialBasis<Degree> Basis, Polynomial<Degree>& U)
+  // {
+  //   switch (flux_type) {
+  //   // case FluxType::centered: {
+  //   //   return computeCenteredFlux(Basis, U);
+  //   //   break;
+  //   // }
+  //   case FluxType::stable: {
+  //     return computeStableFlux(Basis, U);
+  //     break;
+  //   }
+  //   default:
+  //     throw UnexpectedError("unknown flux type");
+  //   }
+  // }
+
+  double
+  computeStableFluxes(const CellValue<const Polynomial<Degree>>& U, NodeId r)
+  {
+    double flux;
+    const NodeValue<const R1> xr    = m_mesh->xr();
+    const auto& node_to_cell_matrix = m_mesh->connectivity().nodeToCellMatrix();
+
+    const auto& node_cells = node_to_cell_matrix[r];
+    if (node_cells.size() == 1) {
+      flux = 1.;
+    } else {
+      const CellId j1  = node_cells[0];
+      const double xr0 = xr[r][0];
+      flux             = U[j1](xr0);
+      std::cout << "U[" << j1 << "]=" << U[j1] << "\n";
+    }
+    return flux;
+  }
+
+  void
+  globalSolve(CellValue<Polynomial<Degree>>& U)
+  {
+    const NodeValue<const R1> xr    = m_mesh->xr();
+    const auto& cell_to_node_matrix = m_mesh->connectivity().cellToNodeMatrix();
+    CellValue<TinyVector<Degree + 1>> moments(m_connectivity);
+    for (CellId j = 0; j < m_mesh->numberOfCells(); j++) {
+      const auto& cell_nodes             = cell_to_node_matrix[j];
+      const NodeId r1                    = cell_nodes[0];
+      const NodeId r2                    = cell_nodes[1];
+      const TinyVector<Dimension> xr1    = xr[r1];
+      const TinyVector<Dimension> xr2    = xr[r2];
+      const TinyVector<Degree + 1> zeros = _buildZeros(xr1[0], xr2[0]);
+      std::cout << "x1 " << xr1[0] << " x2 " << xr2[0] << " zeros " << zeros << "\n";
+      m_basis.build(m_basis_type, 0.5 * (xr1[0] + xr2[0]), zeros);
+      std::cout << " B[" << j << "]=" << m_basis.elements()[0] << "\n";
+      const double fluxg = computeStableFluxes(U, r1);
+      std::cout << j << " flux " << fluxg << "\n";
+      moments[j] = buildMoments(m_basis, fluxg, xr1[0]);
+      std::cout << " moments[" << j << "]=" << moments[j] << "\n";
+
+      U[j] = elementarySolve(moments[j], m_basis, xr1[0], xr2[0]);
+      std::cout << " U[" << j << "]=" << U[j] << "\n";
+    }
+  }
+
+  // void
+  // integrate() const
+  // {}
+  PUGS_INLINE constexpr TinyVector<Degree + 1>
+  buildMoments(PolynomialBasis<Degree> basis, double fluxg, double xr1)
+  {
+    TinyVector<Degree + 1> moments(zero);
+    for (size_t i = 0; i <= Degree; i++) {
+      // moments[i] = (basis.elements()[i](xr2) - basis.elements()[i](xr1)) * fluxg;
+      moments[i] = -basis.elements()[i](xr1) * fluxg;
+    }
+    return moments;
+  }
+
+  PUGS_INLINE constexpr Polynomial<Degree>
+  elementarySolve(const TinyVector<Degree + 1> moments,
+                  const PolynomialBasis<Degree> Basis,
+                  const double& xinf,
+                  const double& xsup) const
+  {
+    Polynomial<Degree> U;
+    U.coefficients() = zero;
+    TinyMatrix<Degree + 1> M(zero);
+    for (size_t i = 0; i <= Degree; ++i) {
+      for (size_t j = 0; j <= Degree; ++j) {
+        Polynomial<2 * Degree> P = Basis.elements()[j] * Basis.elements()[i];
+        P += derivative(Basis.elements()[i]) * Basis.elements()[j];
+        double fluxd = Basis.elements()[i](xsup) * Basis.elements()[j](xsup);
+        M(i, j)      = integrate(P, xinf, xsup) - fluxd;
+      }
+    }
+    std::cout << " M " << M << "\n";
+    TinyMatrix inv_M                    = ::inverse(M);
+    TinyVector<Degree + 1> coefficients = inv_M * moments;
+    for (size_t i = 0; i <= Degree; ++i) {
+      U += coefficients[i] * Basis.elements()[i];
+    }
+    std::cout << "U(" << xsup << ")=" << U(xsup) << "\n";
+    return U;
+  }
+
+ public:
+  // TODO add a flux manager to constructor
+  // TODO add a basis to constructor
+  ODEDG1D(const BasisType& basis_type, std::shared_ptr<const IMesh> i_mesh)   //, const BoundaryConditionList& bc_list)
+  : m_basis_type(basis_type),
+    m_mesh(std::dynamic_pointer_cast<const MeshType>(i_mesh)),
+    m_connectivity(m_mesh->connectivity())   //,
+  // m_boundary_condition_list(bc_list)
+  {
+    ;
+  }
+};
+
+#endif   // ODE_DG_1D
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e3739abe7afd3a625b6510d9be792bdb3b3a76dd..52d3a91ecb06c63b44bb6b391eb1c51bde95ed83 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -146,6 +146,10 @@ add_executable (unit_tests
   test_ParseError.cpp
   test_PartitionerOptions.cpp
   test_PETScUtils.cpp
+  test_Polynomial.cpp
+  test_PolynomialP.cpp
+  test_TaylorPolynomial.cpp
+  test_PolynomialBasis.cpp
   test_PrimalToDiamondDualConnectivityDataMapper.cpp
   test_PrimalToDual1DConnectivityDataMapper.cpp
   test_PrimalToMedianDualConnectivityDataMapper.cpp
diff --git a/tests/test_DiscontinuousGalerkin1D.cpp b/tests/test_DiscontinuousGalerkin1D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3b1e02dbb0fc5b692629dcc2a9a0ee0faf0546dc
--- /dev/null
+++ b/tests/test_DiscontinuousGalerkin1D.cpp
@@ -0,0 +1,57 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <Kokkos_Core.hpp>
+
+#include <utils/PugsAssert.hpp>
+#include <utils/Types.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+#include <analysis/Polynomial.hpp>
+#include <analysis/PolynomialBasis.hpp>
+#include <mesh/CartesianMeshBuilder.hpp>
+#include <mesh/DiamondDualConnectivityManager.hpp>
+#include <mesh/DiamondDualMeshManager.hpp>
+#include <scheme/DiscontinuousGalerkin1DTools.hpp>
+#include <scheme/ODEGD1D.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class Polynomial<0>;
+template class Polynomial<1>;
+template class Polynomial<2>;
+template class Polynomial<3>;
+template class Polynomial<4>;
+template class Polynomial<5>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("Discontinuous-Galerkin-1D", "[discontinuous-galerkin-1d]")
+{
+  SECTION("Elementary-tests")
+  {
+    TinyVector<1> a{0};
+    TinyVector<1> b{1};
+    TinyVector<1, uint64_t> nbcells{10};
+    // CartesianMeshBuilder mesh_builder = CartesianMeshBuilder(a, b, nbcells);
+    // auto mesh                         = mesh_builder.mesh();
+    // DiscontinuousGalerkin1D<2>{mesh};
+    REQUIRE_NOTHROW(DiscontinuousGalerkin1DTools<2>());
+    DiscontinuousGalerkin1DTools dg = DiscontinuousGalerkin1DTools<1>();
+    Polynomial<1> U;
+    TinyVector<2> F({3, 2});
+    PolynomialBasis<1> Basis;
+    Basis.build(BasisType::canonical);
+    dg.elementarySolve(U, F, Basis, -1, 1);
+    REQUIRE(U == Polynomial<1>{{3. / 2, 3}});
+    Polynomial<2> U2;
+    TinyVector<3> F2({1, 1, 1});
+    PolynomialBasis<2> Basis2;
+    Basis2.build(BasisType::canonical);
+    DiscontinuousGalerkin1DTools dg2 = DiscontinuousGalerkin1DTools<2>();
+    dg2.elementarySolve(U2, F2, Basis2, -1, 1);
+    REQUIRE(integrate(U2, -1, 1) == Catch::Approx(1).epsilon(1E-14));
+    REQUIRE(integrate(U2 * Polynomial<2>{{0, 0, 1}}, -1, 1) == 1.);
+    dg2.elementarySolve(U2, F2, Basis2, 0, 1);
+    REQUIRE(integrate(U2 * Polynomial<2>{{0, 0, 1}}, 0, 1) == Catch::Approx(1).epsilon(1E-14));
+  }
+}
diff --git a/tests/test_EigenvalueSolver.cpp b/tests/test_EigenvalueSolver.cpp
index 325f3e39b0a6f34e028f99372956f67a226e39cc..f76faab82f397e251e3d4c10bb10681e113bb85a 100644
--- a/tests/test_EigenvalueSolver.cpp
+++ b/tests/test_EigenvalueSolver.cpp
@@ -5,6 +5,8 @@
 #include <algebra/CRSMatrixDescriptor.hpp>
 #include <algebra/EigenvalueSolver.hpp>
 #include <algebra/SmallMatrix.hpp>
+#include <algebra/TinyMatrix.hpp>
+#include <scheme/HyperelasticSolver.hpp>
 
 #include <utils/pugs_config.hpp>
 
@@ -633,5 +635,100 @@ TEST_CASE("EigenvalueSolver", "[algebra]")
         }
       }
     }
+#ifdef PUGS_HAS_SLEPC
+    SECTION("symmetric tiny matrix")
+    {
+      TinyMatrix<3> TestA{3e10, 2e10, 4e10, 2e10, 0, 2e10, 4e10, 2e10, 3e10};
+      TinyMatrix<3> TestA2{4, 2, 3, 2, 0, 2, 3, 2, 4};
+      TinyMatrix<3> TestB{1, -1, 0, -1, 1, 0, 0, 0, 3};
+      TinyMatrix<3> TestC{0, 0, 0, 0, 0, 0, 0, 0, 0};
+      TinyMatrix<3> TestD{1e10, 0, 0, 0, 1, 0, 0, 0, 1};
+      TinyMatrix<3> TestE{3e-10, 2e-10, 4e-10, 2e-10, 0, 2e-10, 4e-10, 2e-10, 3e-10};
+
+      TinyMatrix<3> expA2;
+
+      auto [eigenvalues, eigenmatrix] = EigenvalueSolver{}.findEigen(TestA);
+      TinyMatrix<3> Diag              = zero;
+      for (size_t i = 0; i < 3; ++i) {
+        Diag(i, i) = eigenvalues[i];
+      }
+      TinyMatrix<3> B = eigenmatrix * Diag * transpose(eigenmatrix);
+      for (size_t i = 0; i < 3; ++i) {
+        for (size_t j = 0; j < 3; ++j) {
+          REQUIRE((B(i, j) - TestA(i, j)) / frobeniusNorm(TestA) == Catch::Approx(0).margin(1E-8));
+        }
+      }
+      auto [eigenvalues2, eigenmatrix2] = EigenvalueSolver{}.findEigen(TestA2);
+      Diag                              = zero;
+      for (size_t i = 0; i < 3; ++i) {
+        Diag(i, i) = eigenvalues2[i];
+      }
+      B = eigenmatrix2 * Diag * transpose(eigenmatrix2);
+      for (size_t i = 0; i < 3; ++i) {
+        for (size_t j = 0; j < 3; ++j) {
+          REQUIRE((B(i, j) - TestA2(i, j)) / frobeniusNorm(TestA2) == Catch::Approx(0).margin(1E-8));
+        }
+      }
+      expA2 = EigenvalueSolver{}.computeExpForTinyMatrix(TestA2);
+      for (size_t i = 0; i < 3; ++i) {
+        Diag(i, i) = std::exp(eigenvalues2[i]);
+      }
+      B = eigenmatrix2 * Diag * transpose(eigenmatrix2);
+
+      for (size_t i = 0; i < 3; ++i) {
+        for (size_t j = 0; j < 3; ++j) {
+          REQUIRE(expA2(i, j) - B(i, j) == Catch::Approx(0).margin(1E-12));
+        }
+      }
+
+      auto [eigenvalues3, eigenmatrix3] = EigenvalueSolver{}.findEigen(TestB);
+      Diag                              = zero;
+      for (size_t i = 0; i < 3; ++i) {
+        Diag(i, i) = eigenvalues3[i];
+      }
+      B = eigenmatrix3 * Diag * transpose(eigenmatrix3);
+      for (size_t i = 0; i < 3; ++i) {
+        for (size_t j = 0; j < 3; ++j) {
+          REQUIRE((B(i, j) - TestB(i, j)) / frobeniusNorm(TestB) == Catch::Approx(0).margin(1E-8));
+        }
+      }
+
+      auto [eigenvalues4, eigenmatrix4] = EigenvalueSolver{}.findEigen(TestC);
+      Diag                              = zero;
+      for (size_t i = 0; i < 3; ++i) {
+        Diag(i, i) = eigenvalues4[i];
+      }
+      B = eigenmatrix4 * Diag * transpose(eigenmatrix4);
+      for (size_t i = 0; i < 3; ++i) {
+        for (size_t j = 0; j < 3; ++j) {
+          REQUIRE((B(i, j) - TestC(i, j)) == Catch::Approx(0).margin(1E-8));
+        }
+      }
+
+      auto [eigenvalues5, eigenmatrix5] = EigenvalueSolver{}.findEigen(TestD);
+      Diag                              = zero;
+      for (size_t i = 0; i < 3; ++i) {
+        Diag(i, i) = eigenvalues5[i];
+      }
+      B = eigenmatrix5 * Diag * transpose(eigenmatrix5);
+      for (size_t i = 0; i < 3; ++i) {
+        for (size_t j = 0; j < 3; ++j) {
+          REQUIRE((B(i, j) - TestD(i, j)) / frobeniusNorm(TestD) == Catch::Approx(0).margin(1E-8));
+        }
+      }
+
+      auto [eigenvalues6, eigenmatrix6] = EigenvalueSolver{}.findEigen(TestE);
+      Diag                              = zero;
+      for (size_t i = 0; i < 3; ++i) {
+        Diag(i, i) = eigenvalues6[i];
+      }
+      B = eigenmatrix6 * Diag * transpose(eigenmatrix6);
+      for (size_t i = 0; i < 3; ++i) {
+        for (size_t j = 0; j < 3; ++j) {
+          REQUIRE((B(i, j) - TestE(i, j)) / frobeniusNorm(TestE) == Catch::Approx(0).margin(1E-8));
+        }
+      }
+    }
+#endif
   }
 }
diff --git a/tests/test_Polynomial.cpp b/tests/test_Polynomial.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4089bf27d42b6c704e1f707ccb01387a79121565
--- /dev/null
+++ b/tests/test_Polynomial.cpp
@@ -0,0 +1,218 @@
+#include <catch2/catch_test_macros.hpp>
+
+#include <Kokkos_Core.hpp>
+
+#include <utils/PugsAssert.hpp>
+#include <utils/Types.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+#include <analysis/Polynomial.hpp>
+#include <analysis/PolynomialP.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class Polynomial<0>;
+template class Polynomial<1>;
+template class Polynomial<2>;
+template class Polynomial<3>;
+template class Polynomial<4>;
+template class Polynomial<5>;
+template class PolynomialP<0, 2>;
+template class PolynomialP<1, 2>;
+template class PolynomialP<3, 2>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("Polynomial", "[analysis]")
+{
+  SECTION("construction")
+  {
+    REQUIRE_NOTHROW(Polynomial<2>(2, 3, 4));
+  }
+
+  SECTION("degree")
+  {
+    Polynomial<2> P(2, 3, 4);
+    REQUIRE(P.degree() == 2);
+  }
+
+  SECTION("equality")
+  {
+    Polynomial<2> P(2, 3, 4);
+    Polynomial<2> Q(2, 3, 4);
+    Polynomial<2> S(2, 3, 5);
+
+    REQUIRE(P == Q);
+    REQUIRE(P != S);
+  }
+
+  SECTION("addition")
+  {
+    Polynomial<2> P(2, 3, 4);
+    Polynomial<2> Q(-1, -3, 2);
+    Polynomial<2> S(1, 0, 6);
+    Polynomial<3> T(0, 3, 1, -2);
+    Polynomial<3> U(2, 6, 5, -2);
+    REQUIRE(S == (P + Q));
+    REQUIRE((T + P) == U);
+  }
+
+  SECTION("opposed")
+  {
+    Polynomial<2> P(2, 3, 4);
+    Polynomial<2> Q = -P;
+    REQUIRE(Q == Polynomial<2>(-2, -3, -4));
+  }
+
+  SECTION("difference")
+  {
+    Polynomial<2> P(2, 3, 4);
+    Polynomial<2> Q(3, 4, 5);
+    Polynomial<2> D(-1, -1, -1);
+    REQUIRE(D == (P - Q));
+    Polynomial<3> R(2, 3, 4, 1);
+    REQUIRE(D == (P - Q));
+    REQUIRE((P - R) == Polynomial<3>{0, 0, 0, -1});
+    R -= P;
+    REQUIRE(R == Polynomial<3>(0, 0, 0, 1));
+  }
+
+  SECTION("product_by_scalar")
+  {
+    Polynomial<2> P(2, 3, 4);
+    Polynomial<2> M(6, 9, 12);
+    REQUIRE(M == (P * 3));
+    REQUIRE(M == (3 * P));
+  }
+
+  SECTION("product")
+  {
+    Polynomial<2> P(2, 3, 4);
+    Polynomial<3> Q(1, 2, -1, 1);
+    Polynomial<4> R;
+    Polynomial<5> S;
+    R = P;
+    S = P;
+    S *= Q;
+    REQUIRE(Polynomial<5>(2, 7, 8, 7, -1, 4) == (P * Q));
+    REQUIRE(Polynomial<5>(2, 7, 8, 7, -1, 4) == S);
+    // REQUIRE_THROWS_AS(R *= Q, AssertError);
+  }
+
+  SECTION("divide")
+  {
+    Polynomial<2> P(1, 0, 1);
+    Polynomial<1> Q(0, 1);
+    Polynomial<1> Q1(0, 1);
+
+    Polynomial<2> R;
+    Polynomial<2> S;
+    REQUIRE(P.realDegree() == 2);
+    REQUIRE(Q.realDegree() == 1);
+    REQUIRE(Q1.realDegree() == 1);
+
+    divide(P, Q1, R, S);
+    REQUIRE(Polynomial<2>{1, 0, 0} == S);
+    REQUIRE(Polynomial<2>{0, 1, 0} == R);
+    Polynomial<3> P2(5, 1, 3, 2);
+    Polynomial<2> Q2(1, 0, 1);
+
+    Polynomial<3> R2;
+    Polynomial<3> S2;
+
+    divide(P2, Q2, R2, S2);
+    REQUIRE(Polynomial<3>{3, 2, 0, 0} == R2);
+    REQUIRE(Polynomial<3>{2, -1, 0, 0} == S2);
+  }
+
+  SECTION("evaluation")
+  {
+    Polynomial<2> P(2, -3, 4);
+    REQUIRE(P(3) == 29);
+  }
+
+  SECTION("primitive")
+  {
+    Polynomial<2> P(2, -3, 4);
+    TinyVector<4> coefs = zero;
+    Polynomial<3> Q(coefs);
+    Q = primitive(P);
+    Polynomial<3> R(0, 2, -3. / 2, 4. / 3);
+    REQUIRE(Q == R);
+  }
+
+  SECTION("integrate")
+  {
+    Polynomial<2> P(2, -3, 3);
+    double xinf   = -1;
+    double xsup   = 1;
+    double result = integrate(P, xinf, xsup);
+    REQUIRE(result == 6);
+    result = symmetricIntegrate(P, 2);
+    REQUIRE(result == 24);
+  }
+
+  SECTION("derivative")
+  {
+    Polynomial<2> P(2, -3, 3);
+    Polynomial<1> Q = derivative(P);
+    REQUIRE(Q == Polynomial<1>(-3, 6));
+
+    Polynomial<0> P2(3);
+
+    Polynomial<0> R(0);
+    REQUIRE(derivative(P2) == R);
+  }
+
+  SECTION("affectation")
+  {
+    Polynomial<2> Q(2, -3, 3);
+    Polynomial<4> R(2, -3, 3, 0, 0);
+    Polynomial<4> P(0, 1, 2, 3, 3);
+    P = Q;
+    REQUIRE(P == R);
+  }
+
+  SECTION("affectation addition")
+  {
+    Polynomial<2> Q(2, -3, 3);
+    Polynomial<4> R(2, -2, 5, 3, 3);
+    Polynomial<4> P(0, 1, 2, 3, 3);
+    P += Q;
+    REQUIRE(P == R);
+  }
+
+  SECTION("power")
+  {
+    Polynomial<2> P(2, -3, 3);
+    Polynomial<4> R(4, -12, 21, -18, 9);
+    Polynomial<1> Q(0, 2);
+    Polynomial<2> S = Q.pow<2>(2);
+    REQUIRE(P.pow<2>(2) == R);
+    REQUIRE(S == Polynomial<2>(0, 0, 4));
+  }
+
+  SECTION("composition")
+  {
+    Polynomial<2> P(2, -3, 3);
+    Polynomial<1> Q(0, 2);
+    Polynomial<2> R(2, -1, 3);
+    Polynomial<2> S(1, 2, 2);
+    REQUIRE(P.compose(Q) == Polynomial<2>(2, -6, 12));
+    REQUIRE(P.compose2(Q) == Polynomial<2>(2, -6, 12));
+    REQUIRE(R(S) == Polynomial<4>(4, 10, 22, 24, 12));
+  }
+
+  SECTION("Lagrange polynomial")
+  {
+    Polynomial<1> S(0.5, -0.5);
+    Polynomial<1> Q;
+    Q = lagrangePolynomial<1>(TinyVector<2>{-1, 1}, 0);
+    REQUIRE(S == Q);
+    Polynomial<2> P(0, -0.5, 0.5);
+    Polynomial<2> R;
+    R = lagrangePolynomial<2>(TinyVector<3>{-1, 0, 1}, 0);
+    REQUIRE(R == P);
+    const std::array<Polynomial<2>, 3> basis = lagrangeBasis(TinyVector<3>{-1, 0, 1});
+    REQUIRE(lagrangeToCanonical(TinyVector<3>{1, 0, 1}, basis) == Polynomial<2>(TinyVector<3>{0, 0, 1}));
+  }
+}
diff --git a/tests/test_PolynomialBasis.cpp b/tests/test_PolynomialBasis.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..446a7278a36c5358b93069f5f0d5dc10ac00aa5a
--- /dev/null
+++ b/tests/test_PolynomialBasis.cpp
@@ -0,0 +1,45 @@
+#include <catch2/catch_test_macros.hpp>
+
+#include <Kokkos_Core.hpp>
+
+#include <utils/PugsAssert.hpp>
+#include <utils/Types.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+#include <analysis/Polynomial.hpp>
+#include <analysis/PolynomialBasis.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class Polynomial<0>;
+template class Polynomial<1>;
+template class Polynomial<2>;
+template class PolynomialBasis<2>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PolynomialBasis", "[analysis]")
+{
+  SECTION("construction")
+  {
+    REQUIRE_NOTHROW(PolynomialBasis<2>{});
+  }
+  SECTION("size")
+  {
+    PolynomialBasis<2> B;
+    REQUIRE(B.size() == 3);
+    REQUIRE(B.degree() == 2);
+  }
+  SECTION("build")
+  {
+    PolynomialBasis<2> B;
+    REQUIRE(B.displayType() == "undefined");
+    B.build(BasisType::canonical);
+    REQUIRE(B.elements()[1] == Polynomial<2>{0, 1, 0});
+    REQUIRE(B.elements()[2] == Polynomial<2>{0, 0, 1});
+    PolynomialBasis<2> C;
+    C.build(BasisType::taylor);
+    REQUIRE(B.elements()[1] == C.elements()[1]);
+    C.build(BasisType::taylor, 1);
+    REQUIRE(C.elements()[2] == Polynomial<2>{1, -2, 1});
+  }
+}
diff --git a/tests/test_PolynomialP.cpp b/tests/test_PolynomialP.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bd6d4a82d57f16447ffdb70bba302eee1e9267e5
--- /dev/null
+++ b/tests/test_PolynomialP.cpp
@@ -0,0 +1,369 @@
+#include <Kokkos_Core.hpp>
+#include <algebra/TinyMatrix.hpp>
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/PolynomialP.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/SquareGaussQuadrature.hpp>
+#include <analysis/TaylorPolynomial.hpp>
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/Types.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class PolynomialP<0, 2>;
+template class PolynomialP<1, 2>;
+template class PolynomialP<2, 2>;
+template class PolynomialP<3, 2>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PolynomialP", "[analysis]")
+{
+  SECTION("construction")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    REQUIRE_NOTHROW(PolynomialP<2, 2>(coef));
+  }
+
+  SECTION("degree")
+  {
+    TinyVector<3> coef(1, 2, 3);
+    PolynomialP<1, 2> P(coef);
+    REQUIRE(P.degree() == 1);
+    REQUIRE(P.dim() == 2);
+  }
+
+  SECTION("equality")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<3> coef2(1, 2, 3);
+    TinyVector<6> coef3(1, 2, 3, 3, 3, 3);
+    PolynomialP<2, 2> P(coef);
+    PolynomialP<2, 2> Q(coef);
+    PolynomialP<2, 2> R(coef3);
+
+    REQUIRE(P == Q);
+    REQUIRE(P != R);
+  }
+
+  SECTION("addition")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<6> coef2(1, 2, 3, -2, -1, -3);
+    TinyVector<6> coef3(2, 4, 6, 2, 4, 3);
+    PolynomialP<2, 2> P(coef);
+    PolynomialP<2, 2> Q(coef2);
+    PolynomialP<2, 2> R(coef3);
+    REQUIRE(R == (P + Q));
+    REQUIRE((P + Q) == R);
+  }
+
+  SECTION("opposed")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<6> coef2(-1, -2, -3, -4, -5, -6);
+    PolynomialP<2, 2> P(coef);
+    REQUIRE(-P == PolynomialP<2, 2>(coef2));
+  }
+
+  SECTION("difference")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<6> coef2(1, 2, 3, -2, -1, -3);
+    TinyVector<6> coef3(0, 0, 0, 6, 6, 9);
+    PolynomialP<2, 2> P(coef);
+    PolynomialP<2, 2> Q(coef2);
+    PolynomialP<2, 2> R(coef3);
+    R = P - Q;
+    REQUIRE(R == PolynomialP<2, 2>(coef3));
+  }
+
+  SECTION("product_by_scalar")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<6> coef2(2, 4, 6, 8, 10, 12);
+    PolynomialP<2, 2> P(coef);
+    PolynomialP<2, 2> Q(coef2);
+    REQUIRE(Q == (P * 2));
+    REQUIRE(Q == (2 * P));
+  }
+
+  SECTION("access_coef")
+  {
+    TinyVector<6> coef(1, -2, 10, 7, 2, 9);
+    TinyVector<10> coef2(2, -4, -1, -3, 3, -5, -6, 0, 1, 7);
+    TinyVector<10> coef3(2, -4, -1, -3, 3, -5, -6, 0, 1, 7);
+    TinyVector<20> coef4(2, -4, -1, -3, 3, -5, -6, -2, 1, 7, 3, -2, 1, 2.5, 6, -9, 0.5, 4, -5, -8);
+    PolynomialP<2, 2> P(coef);
+    PolynomialP<3, 2> Q(coef2);
+    PolynomialP<2, 3> R(coef3);
+    PolynomialP<3, 3> S(coef4);
+    TinyVector<2, size_t> relative_coef(1, 1);
+    TinyVector<2, size_t> relative_coef2(1, 2);
+    TinyVector<3, size_t> relative_coef3(1, 0, 1);
+    TinyVector<3, size_t> relative_coef3b(0, 0, 2);
+    TinyVector<3, size_t> relative_coef4(1, 1, 1);
+    TinyVector<3, size_t> relative_coef5(0, 2, 1);
+    REQUIRE(P[relative_coef] == 2);
+    REQUIRE(Q[relative_coef2] == 1);
+    REQUIRE(Q[relative_coef] == 3);
+    REQUIRE(R[relative_coef3] == -6);
+    REQUIRE(R[relative_coef3b] == 7);
+    REQUIRE(S[relative_coef4] == 6);
+    REQUIRE(S[relative_coef5] == 4);
+  }
+
+  SECTION("evaluation")
+  {
+    TinyVector<6> coef(1, -2, 10, 7, 2, 9);
+    TinyVector<10> coef2(2, -4, -1, -3, 3, -5, -6, 0, 1, 7);
+    TinyVector<10> coef3(2, -4, -1, -3, 3, -5, -6, 0, 1, 7);
+    PolynomialP<2, 2> P(coef);
+    PolynomialP<3, 2> Q(coef2);
+    PolynomialP<2, 3> R(coef3);
+    TinyVector<6> coefx(1, -2, 0, 7, 0, 0);
+    TinyVector<6> coefy(2, 0, -2, 0, 0, 7);
+    TinyVector<2> pos(1, -1);
+    TinyVector<2> pos2(-1, 2);
+    TinyVector<3> pos3(-1, 2, 1);
+    PolynomialP<2, 2> Px(coefx);
+    PolynomialP<2, 2> Py(coefy);
+    TinyVector<10> coef3x(2, -4, 0, 0, 3, 0, 0, 0, 0, 0);
+    TinyVector<10> coef3y(2, 0, -1, 0, 0, 0, 0, 1, 0, 0);
+    TinyVector<10> coef3z(2, 0, 0, -3, 0, 0, 0, 0, 0, 7);
+    PolynomialP<2, 3> Rx(coef3x);
+    PolynomialP<2, 3> Ry(coef3y);
+    PolynomialP<2, 3> Rz(coef3z);
+    REQUIRE(Px(pos) == 6);
+    REQUIRE(Py(pos) == 11);
+    REQUIRE(Px(pos2) == 10);
+    REQUIRE(P(pos2) == 62);
+    REQUIRE(Q(pos) == -24);
+    REQUIRE(Rx(pos3) == 9);
+    REQUIRE(Ry(pos3) == 4);
+    REQUIRE(Rz(pos3) == 6);
+    REQUIRE(R(pos3) == 29);
+  }
+
+  SECTION("derivation")
+  {
+    TinyVector<6> coef(1, -2, 10, 7, 2, 9);
+    TinyVector<6> coef2(-2, 14, 2, 0, 0, 0);
+    TinyVector<6> coef3(10, 2, 18, 0, 0, 0);
+    TinyVector<10> coef4(2, -4, -1, -3, 3, -5, -6, 1, 1, 7);
+    TinyVector<10> coef5(-1, -5, 2, 1, 0, 0, 0, 0, 0, 0);
+    PolynomialP<2, 2> P(coef);
+    PolynomialP<2, 2> Q(coef2);
+    PolynomialP<2, 2> R(coef3);
+    PolynomialP<2, 3> S(coef4);
+    PolynomialP<2, 3> T(coef5);
+
+    REQUIRE(Q == P.derivative(0));
+    REQUIRE(R == P.derivative(1));
+    REQUIRE(T == S.derivative(1));
+  }
+
+  SECTION("integrate")
+  {
+    TinyVector<6> coef(1, -2, 10, 7, 2, 9);
+    TinyVector<10> coef2(1, -2, 10, 7, 2, 9, 0, 0, 0, 1);
+    std::array<TinyVector<2>, 4> positions;
+    std::array<TinyVector<2>, 3> positions2;
+    std::array<TinyVector<2>, 2> positions4;
+    std::array<TinyVector<2>, 4> positions3;
+    std::array<TinyVector<2>, 4> positions5;
+    positions[0]  = TinyVector<2>{0, 0};
+    positions[1]  = TinyVector<2>{0, 0.5};
+    positions[2]  = TinyVector<2>{0.3, 0.7};
+    positions[3]  = TinyVector<2>{0.4, 0.1};
+    positions2[0] = TinyVector<2>{0, 0};
+    positions2[1] = TinyVector<2>{0, 0.5};
+    positions2[2] = TinyVector<2>{0.3, 0.7};
+    positions4[0] = TinyVector<2>{0, 0.5};
+    positions4[1] = TinyVector<2>{0.3, 0.7};
+    positions3[0] = TinyVector<2>{0, 0};
+    positions3[1] = TinyVector<2>{1, 0};
+    positions3[2] = TinyVector<2>{1, 1};
+    positions3[3] = TinyVector<2>{0, 1};
+    positions5[0] = TinyVector<2>{0, 0};
+    positions5[1] = TinyVector<2>{1, 0};
+    positions5[2] = TinyVector<2>{1, 3};
+    positions5[3] = TinyVector<2>{1, 1};
+
+    PolynomialP<2, 2> P(coef);
+    PolynomialP<3, 2> Q(coef2);
+    auto p1 = [](const TinyVector<2>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      return 1 - 2. * x + 10 * y + 7 * x * x + 2 * x * y + 9 * y * y;
+    };
+    auto q1 = [](const TinyVector<2>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      return 1 - 2. * x + 10 * y + 7 * x * x + 2 * x * y + 9 * y * y + y * y * y;
+    };
+    const QuadratureFormula<2>& l3 =
+      QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(4));
+    auto point_list  = l3.pointList();
+    auto weight_list = l3.weightList();
+    SquareTransformation<2> s{positions[0], positions[1], positions[2], positions[3]};
+    auto value = weight_list[0] * s.jacobianDeterminant(point_list[0]) * q1(s(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * s.jacobianDeterminant(point_list[i]) * q1(s(point_list[i]));
+    }
+    // const QuadratureFormula<2>& l3bis =
+    //   QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(4));
+    // auto point_listbis  = l3bis.pointList();
+    // auto weight_listbis = l3bis.weightList();
+    // auto valuebis       = weight_listbis[0] * s.jacobianDeterminant(point_listbis[0]) * p1(s(point_listbis[0]));
+    // for (size_t i = 1; i < weight_listbis.size(); ++i) {
+    //   valuebis += weight_listbis[i] * s.jacobianDeterminant(point_listbis[i]) * p1(s(point_listbis[i]));
+    // }
+    const QuadratureFormula<2>& t3 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(3));
+    auto point_list2               = t3.pointList();
+    auto weight_list2              = t3.weightList();
+    TriangleTransformation<2> t{positions2[0], positions2[1], positions2[2]};
+    auto value2 = weight_list2[0] * p1(t(point_list2[0]));
+    for (size_t i = 1; i < weight_list2.size(); ++i) {
+      value2 += weight_list2[i] * p1(t(point_list2[i]));
+    }
+    value2 *= t.jacobianDeterminant();
+    const QuadratureFormula<1>& l1 = QuadratureManager::instance().getLineFormula(GaussLegendreQuadratureDescriptor(2));
+    const LineTransformation<2> u{positions4[0], positions4[1]};
+    double value4 = 0.;
+    for (size_t i = 0; i < l1.numberOfPoints(); ++i) {
+      value4 += l1.weight(i) * u.velocityNorm() * p1(u(l1.point(i)));
+    }
+    SquareTransformation<2> s3{positions3[0], positions3[1], positions3[2], positions3[3]};
+    auto value3 = weight_list[0] * s3.jacobianDeterminant(point_list[0]) * p1(s3(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value3 += weight_list[i] * s3.jacobianDeterminant(point_list[i]) * p1(s3(point_list[i]));
+    }
+
+    REQUIRE(value == Catch::Approx(integrate(Q, positions)));
+    REQUIRE(value2 == Catch::Approx(integrate(P, positions2)));
+    REQUIRE(value3 == Catch::Approx(integrate(P, positions3)));
+    REQUIRE(value4 == Catch::Approx(integrate(P, positions4)));
+    // REQUIRE(valuebis == Catch::Approx(integrate(P, positions)));
+    //    REQUIRE(valuebis == value);
+  }
+
+  // // SECTION("product")
+  // {
+  //   Polynomial<2> P(2, 3, 4);
+  //   Polynomial<3> Q(1, 2, -1, 1);
+  //   Polynomial<4 > R;
+  //   Polynomial<5> S;
+  //   R = P;
+  //   S = P;
+  //   S *= Q;
+  //   REQUIRE(Polynomial<5>(2, 7, 8, 7, -1, 4) == (P * Q));
+  //   REQUIRE(Polynomial<5>(2, 7, 8, 7, -1, 4) == S);
+  //   // REQUIRE_THROWS_AS(R *= Q, AssertError);
+  // }
+
+  // SECTION("divide")
+  // {
+  //   Polynomial<2> P(1, 0, 1);
+  //   Polynomial<1> Q(0, 1);
+  //   Polynomial<1> Q1(0, 1);
+
+  //   Polynomial<2> R;
+  //   Polynomial<2> S;
+  //   REQUIRE(P.realDegree() == 2);
+  //   REQUIRE(Q.realDegree() == 1);
+  //   REQUIRE(Q1.realDegree() == 1);
+
+  //   divide(P, Q1, R, S);
+  //   REQUIRE(Polynomial<2>{1, 0, 0} == S);
+  //   REQUIRE(Polynomial<2>{0, 1, 0} == R);
+  // }
+
+  // SECTION("primitive")
+  // {
+  //   Polynomial<2> P(2, -3, 4);
+  //   TinyVector<4> coefs = zero;
+  //   Polynomial<3> Q(coefs);
+  //   Q = primitive(P);
+  //   Polynomial<3> R(0, 2, -3. / 2, 4. / 3);
+  //   REQUIRE(Q == R);
+  // }
+
+  // SECTION("integrate")
+  // {
+  //   Polynomial<2> P(2, -3, 3);
+  //   double xinf   = -1;
+  //   double xsup   = 1;
+  //   double result = integrate(P, xinf, xsup);
+  //   REQUIRE(result == 6);
+  //   result = symmetricIntegrate(P, 2);
+  //   REQUIRE(result == 24);
+  // }
+
+  // SECTION("derivative")
+  // {
+  //   Polynomial<2> P(2, -3, 3);
+  //   Polynomial<1> Q = derivative(P);
+  //   REQUIRE(Q == Polynomial<1>(-3, 6));
+
+  //   Polynomial<0> P2(3);
+
+  //   Polynomial<0> R(0);
+  //   REQUIRE(derivative(P2) == R);
+  // }
+
+  // SECTION("affectation")
+  // {
+  //   Polynomial<2> Q(2, -3, 3);
+  //   Polynomial<4> R(2, -3, 3, 0, 0);
+  //   Polynomial<4> P(0, 1, 2, 3, 3);
+  //   P = Q;
+  //   REQUIRE(P == R);
+  // }
+
+  // SECTION("affectation addition")
+  // {
+  //   Polynomial<2> Q(2, -3, 3);
+  //   Polynomial<4> R(2, -2, 5, 3, 3);
+  //   Polynomial<4> P(0, 1, 2, 3, 3);
+  //   P += Q;
+  //   REQUIRE(P == R);
+  // }
+
+  // SECTION("power")
+  // {
+  //   Polynomial<2> P(2, -3, 3);
+  //   Polynomial<4> R(4, -12, 21, -18, 9);
+  //   Polynomial<1> Q(0, 2);
+  //   Polynomial<2> S = Q.pow<2>(2);
+  //   REQUIRE(P.pow<2>(2) == R);
+  //   REQUIRE(S == Polynomial<2>(0, 0, 4));
+  // }
+
+  // SECTION("composition")
+  // {
+  //   Polynomial<2> P(2, -3, 3);
+  //   Polynomial<1> Q(0, 2);
+  //   Polynomial<2> R(2, -1, 3);
+  //   Polynomial<2> S(1, 2, 2);
+  //   REQUIRE(P.compose(Q) == Polynomial<2>(2, -6, 12));
+  //   REQUIRE(P.compose2(Q) == Polynomial<2>(2, -6, 12));
+  //   REQUIRE(R(S) == Polynomial<4>(4, 10, 22, 24, 12));
+  // }
+
+  // SECTION("Lagrange polynomial")
+  // {
+  //   Polynomial<1> S(0.5, -0.5);
+  //   Polynomial<1> Q;
+  //   Q = lagrangePolynomial<1>(TinyVector<2>{-1, 1}, 0);
+  //   REQUIRE(S == Q);
+  //   Polynomial<2> P(0, -0.5, 0.5);
+  //   Polynomial<2> R;
+  //   R = lagrangePolynomial<2>(TinyVector<3>{-1, 0, 1}, 0);
+  //   REQUIRE(R == P);
+  //   const std::array<Polynomial<2>, 3> basis = lagrangeBasis(TinyVector<3>{-1, 0, 1});
+  //   REQUIRE(lagrangeToCanonical(TinyVector<3>{1, 0, 1}, basis) == Polynomial<2>(TinyVector<3>{0, 0, 1}));
+  // }
+}
diff --git a/tests/test_TaylorPolynomial.cpp b/tests/test_TaylorPolynomial.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4d9149232202adf49de64107fb3bfbe8e20eed22
--- /dev/null
+++ b/tests/test_TaylorPolynomial.cpp
@@ -0,0 +1,357 @@
+#include <Kokkos_Core.hpp>
+#include <algebra/TinyMatrix.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/QuadratureManager.hpp>
+#include <analysis/SquareGaussQuadrature.hpp>
+#include <analysis/TaylorPolynomial.hpp>
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/Types.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class TaylorPolynomial<0, 2>;
+template class TaylorPolynomial<1, 2>;
+template class TaylorPolynomial<2, 2>;
+template class TaylorPolynomial<3, 2>;
+
+// clazy:excludeall=non-pod-global-static
+TinyVector<2> x0{1, -1};
+TinyVector<3> x1{1, -1, 1};
+
+TEST_CASE("TaylorPolynomial", "[analysis]")
+{
+  SECTION("construction")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    REQUIRE_NOTHROW(TaylorPolynomial<2, 2>(coef, x0));
+  }
+
+  SECTION("degree")
+  {
+    TinyVector<3> coef(1, 2, 3);
+    TaylorPolynomial<1, 2> P(coef, x0);
+    REQUIRE(P.degree() == 1);
+    REQUIRE(P.dim() == 2);
+  }
+
+  SECTION("equality")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<3> coef2(1, 2, 3);
+    TinyVector<6> coef3(1, 2, 3, 3, 3, 3);
+    TaylorPolynomial<2, 2> P(coef, x0);
+    TaylorPolynomial<2, 2> Q(coef, x0);
+    TaylorPolynomial<2, 2> R(coef3, x0);
+
+    REQUIRE(P == Q);
+    REQUIRE(P != R);
+  }
+
+  SECTION("addition")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<6> coef2(1, 2, 3, -2, -1, -3);
+    TinyVector<6> coef3(2, 4, 6, 2, 4, 3);
+    TaylorPolynomial<2, 2> P(coef, x0);
+    TaylorPolynomial<2, 2> Q(coef2, x0);
+    TaylorPolynomial<2, 2> R(coef3, x0);
+    REQUIRE(R == (P + Q));
+    REQUIRE((P + Q) == R);
+  }
+
+  SECTION("opposed")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<6> coef2(-1, -2, -3, -4, -5, -6);
+    TaylorPolynomial<2, 2> P(coef, x0);
+    REQUIRE(-P == TaylorPolynomial<2, 2>(coef2, x0));
+  }
+
+  SECTION("difference")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<6> coef2(1, 2, 3, -2, -1, -3);
+    TinyVector<6> coef3(0, 0, 0, 6, 6, 9);
+    TaylorPolynomial<2, 2> P(coef, x0);
+    TaylorPolynomial<2, 2> Q(coef2, x0);
+    TaylorPolynomial<2, 2> R(coef3, x0);
+    R = P - Q;
+    REQUIRE(R == TaylorPolynomial<2, 2>(coef3, x0));
+  }
+
+  SECTION("product_by_scalar")
+  {
+    TinyVector<6> coef(1, 2, 3, 4, 5, 6);
+    TinyVector<6> coef2(2, 4, 6, 8, 10, 12);
+    TaylorPolynomial<2, 2> P(coef, x0);
+    TaylorPolynomial<2, 2> Q(coef2, x0);
+    REQUIRE(Q == (P * 2));
+    REQUIRE(Q == (2 * P));
+  }
+
+  SECTION("access_coef")
+  {
+    TinyVector<6> coef(1, -2, 10, 7, 2, 9);
+    TinyVector<10> coef2(2, -4, -1, -3, 3, -5, -6, 0, 1, 7);
+    TinyVector<10> coef3(2, -4, -1, -3, 3, -5, -6, 0, 1, 7);
+    TinyVector<20> coef4(2, -4, -1, -3, 3, -5, -6, -2, 1, 7, 3, -2, 1, 2.5, 6, -9, 0.5, 4, -5, -8);
+    TaylorPolynomial<2, 2> P(coef, x0);
+    TaylorPolynomial<3, 2> Q(coef2, x0);
+    TaylorPolynomial<2, 3> R(coef3, x1);
+    TaylorPolynomial<3, 3> S(coef4, x1);
+    TinyVector<2, size_t> relative_coef(1, 1);
+    TinyVector<2, size_t> relative_coef2(1, 2);
+    TinyVector<3, size_t> relative_coef3(1, 0, 1);
+    TinyVector<3, size_t> relative_coef3b(0, 0, 2);
+    TinyVector<3, size_t> relative_coef4(1, 1, 1);
+    TinyVector<3, size_t> relative_coef5(0, 2, 1);
+    REQUIRE(P[relative_coef] == 2);
+    REQUIRE(Q[relative_coef2] == 1);
+    REQUIRE(Q[relative_coef] == 3);
+    REQUIRE(R[relative_coef3] == -6);
+    REQUIRE(R[relative_coef3b] == 7);
+    REQUIRE(S[relative_coef4] == 6);
+    REQUIRE(S[relative_coef5] == 4);
+  }
+
+  SECTION("evaluation")
+  {
+    TinyVector<6> coef(1, -2, 10, 7, 2, 9);
+    TinyVector<10> coef2(2, -4, -1, -3, 3, -5, -6, 0, 1, 7);
+    TinyVector<10> coef3(2, -4, -1, -3, 3, -5, -6, 0, 1, 7);
+    TaylorPolynomial<2, 2> P(coef, x0);
+    TaylorPolynomial<3, 2> Q(coef2, x0);
+    TaylorPolynomial<2, 3> R(coef3, x1);
+    TinyVector<6> coefx(1, -2, 0, 7, 0, 0);
+    TinyVector<6> coefy(2, 0, -2, 0, 0, 7);
+    TinyVector<2> pos(1, -1);
+    TinyVector<2> pos2(-1, 2);
+    TinyVector<3> pos3(-1, 2, 1);
+
+    TaylorPolynomial<2, 2> Px(coefx, x0);
+    TaylorPolynomial<2, 2> Py(coefy, x0);
+    REQUIRE(Px(pos) == 1);
+    REQUIRE(Py(pos) == 2);
+    REQUIRE(Px(pos2) == 33);
+    REQUIRE(P(pos2) == 132);
+    REQUIRE(Q(pos) == 2);
+    REQUIRE(R(pos3) == 49);
+  }
+
+  SECTION("derivation")
+  {
+    TinyVector<6> coef(1, -2, 10, 7, 2, 9);
+    TinyVector<6> coef2(-2, 14, 2, 0, 0, 0);
+    TinyVector<6> coef3(10, 2, 18, 0, 0, 0);
+    TinyVector<10> coef4(2, -4, -1, -3, 3, -5, -6, 1, 1, 7);
+    TinyVector<10> coef5(-1, -5, 2, 1, 0, 0, 0, 0, 0, 0);
+    TaylorPolynomial<2, 2> P(coef, x0);
+    TaylorPolynomial<2, 2> Q(coef2, x0);
+    TaylorPolynomial<2, 2> R(coef3, x0);
+    TaylorPolynomial<2, 3> S(coef4, x1);
+    TaylorPolynomial<2, 3> T(coef5, x1);
+
+    REQUIRE(Q == P.derivative(0));
+    REQUIRE(R == P.derivative(1));
+    REQUIRE(T == S.derivative(1));
+  }
+
+  SECTION("integrate")
+  {
+    TinyVector<6> coef(1, -2, 10, 7, 2, 9);
+    TinyVector<10> coef2(1, -2, 10, 7, 2, 9, 0, 0, 0, 1);
+    std::array<TinyVector<2>, 4> positions;
+    std::array<TinyVector<2>, 3> positions2;
+    std::array<TinyVector<2>, 4> positions3;
+    std::array<TinyVector<2>, 2> positions4;
+    positions[0]  = TinyVector<2>{0, 0};
+    positions[1]  = TinyVector<2>{0, 0.5};
+    positions[2]  = TinyVector<2>{0.3, 0.7};
+    positions[3]  = TinyVector<2>{0.4, 0.1};
+    positions2[0] = TinyVector<2>{0, 0};
+    positions2[1] = TinyVector<2>{0, 0.5};
+    positions2[2] = TinyVector<2>{0.3, 0.7};
+    positions3[0] = TinyVector<2>{0, 0};
+    positions3[1] = TinyVector<2>{1, 0};
+    positions3[2] = TinyVector<2>{1, 1};
+    positions3[3] = TinyVector<2>{0, 1};
+    positions4[0] = TinyVector<2>{0, 0.5};
+    positions4[1] = TinyVector<2>{0.3, 0.7};
+    TaylorPolynomial<2, 2> P(coef, x0);
+    TaylorPolynomial<2, 2> Q(coef, zero);
+    TaylorPolynomial<3, 2> R(coef2, x0);
+    auto p1 = [](const TinyVector<2>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      return 1 - 2. * (x - 1.) + 10 * (y + 1) + 7 * (x - 1.) * (x - 1) + 2 * (x - 1) * (y + 1) + 9 * (y + 1) * (y + 1) +
+             (y + 1) * (y + 1) * (y + 1);
+    };
+    auto p2 = [](const TinyVector<2>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      return 1 - 2. * (x - 1.) + 10 * (y + 1) + 7 * (x - 1.) * (x - 1) + 2 * (x - 1) * (y + 1) + 9 * (y + 1) * (y + 1);
+    };
+    auto q1 = [](const TinyVector<2>& X) {
+      const double x = X[0];
+      const double y = X[1];
+      return 1 - 2. * x + 10 * y + 7 * x * x + 2 * x * y + 9 * y * y;
+    };
+    const QuadratureFormula<2>& l3 =
+      QuadratureManager::instance().getSquareFormula(GaussLegendreQuadratureDescriptor(4));
+    auto point_list  = l3.pointList();
+    auto weight_list = l3.weightList();
+    SquareTransformation<2> s{positions[0], positions[1], positions[2], positions[3]};
+    auto value = weight_list[0] * s.jacobianDeterminant(point_list[0]) * p1(s(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value += weight_list[i] * s.jacobianDeterminant(point_list[i]) * p1(s(point_list[i]));
+    }
+    auto valuebis = weight_list[0] * s.jacobianDeterminant(point_list[0]) * q1(s(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      valuebis += weight_list[i] * s.jacobianDeterminant(point_list[i]) * q1(s(point_list[i]));
+    }
+    SquareTransformation<2> s3{positions3[0], positions3[1], positions3[2], positions3[3]};
+    auto value3 = weight_list[0] * s3.jacobianDeterminant(point_list[0]) * p2(s3(point_list[0]));
+    for (size_t i = 1; i < weight_list.size(); ++i) {
+      value3 += weight_list[i] * s3.jacobianDeterminant(point_list[i]) * p2(s3(point_list[i]));
+    }
+    const QuadratureFormula<2>& t3 = QuadratureManager::instance().getTriangleFormula(GaussQuadratureDescriptor(3));
+    auto point_list2               = t3.pointList();
+    auto weight_list2              = t3.weightList();
+    TriangleTransformation<2> t{positions2[0], positions2[1], positions2[2]};
+    auto value2 = weight_list2[0] * p2(t(point_list2[0]));
+    for (size_t i = 1; i < weight_list2.size(); ++i) {
+      value2 += weight_list2[i] * p2(t(point_list2[i]));
+    }
+    value2 *= t.jacobianDeterminant();
+    const QuadratureFormula<1>& l1 = QuadratureManager::instance().getLineFormula(GaussQuadratureDescriptor(2));
+    const LineTransformation<2> u{positions4[0], positions4[1]};
+    double value4 = 0.;
+    for (size_t i = 0; i < l1.numberOfPoints(); ++i) {
+      value4 += l1.weight(i) * u.velocityNorm() * p2(u(l1.point(i)));
+    }
+
+    REQUIRE(value == Catch::Approx(integrate(R, positions)));
+    REQUIRE(valuebis == Catch::Approx(integrate(Q, positions)));
+    REQUIRE(value2 == Catch::Approx(integrate(P, positions2)));
+    REQUIRE(value3 == Catch::Approx(integrate(P, positions3)));
+    REQUIRE(value4 == Catch::Approx(integrate(P, positions4)));
+  }
+
+  // // SECTION("product")
+  // {
+  //   Polynomial<2> P(2, 3, 4);
+  //   Polynomial<3> Q(1, 2, -1, 1);
+  //   Polynomial<4 > R;
+  //   Polynomial<5> S;
+  //   R = P;
+  //   S = P;
+  //   S *= Q;
+  //   REQUIRE(Polynomial<5>(2, 7, 8, 7, -1, 4) == (P * Q));
+  //   REQUIRE(Polynomial<5>(2, 7, 8, 7, -1, 4) == S);
+  //   // REQUIRE_THROWS_AS(R *= Q, AssertError);
+  // }
+
+  // SECTION("divide")
+  // {
+  //   Polynomial<2> P(1, 0, 1);
+  //   Polynomial<1> Q(0, 1);
+  //   Polynomial<1> Q1(0, 1);
+
+  //   Polynomial<2> R;
+  //   Polynomial<2> S;
+  //   REQUIRE(P.realDegree() == 2);
+  //   REQUIRE(Q.realDegree() == 1);
+  //   REQUIRE(Q1.realDegree() == 1);
+
+  //   divide(P, Q1, R, S);
+  //   REQUIRE(Polynomial<2>{1, 0, 0} == S);
+  //   REQUIRE(Polynomial<2>{0, 1, 0} == R);
+  // }
+
+  // SECTION("primitive")
+  // {
+  //   Polynomial<2> P(2, -3, 4);
+  //   TinyVector<4> coefs = zero;
+  //   Polynomial<3> Q(coefs);
+  //   Q = primitive(P);
+  //   Polynomial<3> R(0, 2, -3. / 2, 4. / 3);
+  //   REQUIRE(Q == R);
+  // }
+
+  // SECTION("integrate")
+  // {
+  //   Polynomial<2> P(2, -3, 3);
+  //   double xinf   = -1;
+  //   double xsup   = 1;
+  //   double result = integrate(P, xinf, xsup);
+  //   REQUIRE(result == 6);
+  //   result = symmetricIntegrate(P, 2);
+  //   REQUIRE(result == 24);
+  // }
+
+  // SECTION("derivative")
+  // {
+  //   Polynomial<2> P(2, -3, 3);
+  //   Polynomial<1> Q = derivative(P);
+  //   REQUIRE(Q == Polynomial<1>(-3, 6));
+
+  //   Polynomial<0> P2(3);
+
+  //   Polynomial<0> R(0);
+  //   REQUIRE(derivative(P2) == R);
+  // }
+
+  // SECTION("affectation")
+  // {
+  //   Polynomial<2> Q(2, -3, 3);
+  //   Polynomial<4> R(2, -3, 3, 0, 0);
+  //   Polynomial<4> P(0, 1, 2, 3, 3);
+  //   P = Q;
+  //   REQUIRE(P == R);
+  // }
+
+  // SECTION("affectation addition")
+  // {
+  //   Polynomial<2> Q(2, -3, 3);
+  //   Polynomial<4> R(2, -2, 5, 3, 3);
+  //   Polynomial<4> P(0, 1, 2, 3, 3);
+  //   P += Q;
+  //   REQUIRE(P == R);
+  // }
+
+  // SECTION("power")
+  // {
+  //   Polynomial<2> P(2, -3, 3);
+  //   Polynomial<4> R(4, -12, 21, -18, 9);
+  //   Polynomial<1> Q(0, 2);
+  //   Polynomial<2> S = Q.pow<2>(2);
+  //   REQUIRE(P.pow<2>(2) == R);
+  //   REQUIRE(S == Polynomial<2>(0, 0, 4));
+  // }
+
+  // SECTION("composition")
+  // {
+  //   Polynomial<2> P(2, -3, 3);
+  //   Polynomial<1> Q(0, 2);
+  //   Polynomial<2> R(2, -1, 3);
+  //   Polynomial<2> S(1, 2, 2);
+  //   REQUIRE(P.compose(Q) == Polynomial<2>(2, -6, 12));
+  //   REQUIRE(P.compose2(Q) == Polynomial<2>(2, -6, 12));
+  //   REQUIRE(R(S) == Polynomial<4>(4, 10, 22, 24, 12));
+  // }
+
+  // SECTION("Lagrange polynomial")
+  // {
+  //   Polynomial<1> S(0.5, -0.5);
+  //   Polynomial<1> Q;
+  //   Q = lagrangePolynomial<1>(TinyVector<2>{-1, 1}, 0);
+  //   REQUIRE(S == Q);
+  //   Polynomial<2> P(0, -0.5, 0.5);
+  //   Polynomial<2> R;
+  //   R = lagrangePolynomial<2>(TinyVector<3>{-1, 0, 1}, 0);
+  //   REQUIRE(R == P);
+  //   const std::array<Polynomial<2>, 3> basis = lagrangeBasis(TinyVector<3>{-1, 0, 1});
+  //   REQUIRE(lagrangeToCanonical(TinyVector<3>{1, 0, 1}, basis) == Polynomial<2>(TinyVector<3>{0, 0, 1}));
+  // }
+}