Repository: gyselax/gyselalibxx
Pull request #127: Replace Matrix2x2 types with DTensor type for Jacobian computations 
Author: login: EmilyBourne
Labels: name: Ready to review
description: Label to be automatically added to a PR when it is ready to be reviewed
Created: 2025-02-26T17:07:05Z | Merged: 2025-03-24T11:28:44Z

Use DTensor for the Jacobian and its inverse.


Diff:
diff --git a/src/data_types/tensor.hpp b/src/data_types/tensor.hpp
index 06903792a5..a79f0b8ad4 100644
--- a/src/data_types/tensor.hpp
+++ b/src/data_types/tensor.hpp
@@ -296,8 +296,7 @@ class Tensor
     }
 
     /**
-     * @brief An operator to subtract one tensor from another elementwise.
-     * @param val The tensor that should be subtracted from the current tensor.
+     * @brief An operator to get the negation of a tensor elementwise.
      * @return A new tensor containing the result of the subtraction.
      */
     KOKKOS_FUNCTION Tensor operator-() const
diff --git a/src/geometryRTheta/advection/bsl_advection_rtheta.hpp b/src/geometryRTheta/advection/bsl_advection_rtheta.hpp
index 4c4cf1fc4e..60e9a191ee 100644
--- a/src/geometryRTheta/advection/bsl_advection_rtheta.hpp
+++ b/src/geometryRTheta/advection/bsl_advection_rtheta.hpp
@@ -6,6 +6,7 @@
 #include "geometry.hpp"
 #include "i_interpolator_2d.hpp"
 #include "iadvection_rtheta.hpp"
+#include "indexed_tensor.hpp"
 #include "metric_tensor_evaluator.hpp"
 #include "spline_interpolator_2d.hpp"
 #include "spline_polar_foot_finder.hpp"
@@ -186,15 +187,13 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> J;
-            m_mapping.jacobian_matrix(coord_rtheta, J);
+            Tensor J = m_mapping.jacobian_matrix(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R>(advection_field_rtheta)(irtheta) * J[0][0]
-                      + ddcHelper::get<Theta>(advection_field_rtheta)(irtheta) * J[0][1];
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R>(advection_field_rtheta)(irtheta) * J[1][0]
-                      + ddcHelper::get<Theta>(advection_field_rtheta)(irtheta) * J[1][1];
+            DVector<R, Theta> advec_field_rtheta = advection_field_rtheta(irtheta);
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(J), index<'j'>(advec_field_rtheta));
+            ddcHelper::get<X>(advection_field_xy_host)(irtheta) = ddcHelper::get<X>(advec_field_xy);
+            ddcHelper::get<Y>(advection_field_xy_host)(irtheta) = ddcHelper::get<Y>(advec_field_xy);
         });
 
         ddc::for_each(Opoint_grid, [&](IdxRTheta const irtheta) {
diff --git a/src/geometryRTheta/advection_field/advection_field_rtheta.hpp b/src/geometryRTheta/advection_field/advection_field_rtheta.hpp
index 5199ff85e4..e5f60d8cc9 100644
--- a/src/geometryRTheta/advection_field/advection_field_rtheta.hpp
+++ b/src/geometryRTheta/advection_field/advection_field_rtheta.hpp
@@ -231,15 +231,14 @@ class AdvectionFieldFinder
         });
 
         // > computation of the phi derivatives
-        host_t<DFieldMemRTheta> deriv_r_phi(grid);
-        host_t<DFieldMemRTheta> deriv_theta_phi(grid);
+        host_t<DVectorFieldMemRTheta<R_cov, Theta_cov>> deriv_phi(grid);
 
         evaluator.deriv_dim_1(
-                get_field(deriv_r_phi),
+                ddcHelper::get<R_cov>(deriv_phi),
                 get_const_field(coords),
                 get_const_field(electrostatic_potential_coef));
         evaluator.deriv_dim_2(
-                get_field(deriv_theta_phi),
+                ddcHelper::get<Theta_cov>(deriv_phi),
                 get_const_field(coords),
                 get_const_field(electrostatic_potential_coef));
 
@@ -253,18 +252,17 @@ class AdvectionFieldFinder
             if (r > m_epsilon) {
                 CoordRTheta const coord_rtheta(r, th);
 
-                Matrix_2x2 inv_J = inv_jacobian_matrix(coord_rtheta);
+                DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X, Y>> inv_J
+                        = inv_jacobian_matrix(coord_rtheta);
 
-                // Gradient of phi in the physical domain (Cartesian domain)
-                // (dx phi, dy phi) = J^{-T} (dr phi, dtheta phi)
-                double const grad_x_phi = deriv_r_phi(irtheta) * inv_J[0][0]
-                                          + deriv_theta_phi(irtheta) * inv_J[1][0];
-                double const grad_y_phi = deriv_r_phi(irtheta) * inv_J[0][1]
-                                          + deriv_theta_phi(irtheta) * inv_J[1][1];
+                // Gradient of phi in the physical index range (Cartesian index range)
+                // grad_{x,y} phi = J^{-T} grad_{r,theta} phi
+                DVector<X, Y> grad_phi
+                        = tensor_mul(index<'j', 'i'>(inv_J), index<'j'>(deriv_phi(irtheta)));
 
                 // E = -grad phi
-                ddcHelper::get<X>(electric_field)(irtheta) = -grad_x_phi;
-                ddcHelper::get<Y>(electric_field)(irtheta) = -grad_y_phi;
+                ddcHelper::get<X>(electric_field)(irtheta) = -ddcHelper::get<X>(grad_phi);
+                ddcHelper::get<Y>(electric_field)(irtheta) = -ddcHelper::get<Y>(grad_phi);
 
             } else {
                 // Linearisation of the electric field
@@ -290,48 +288,38 @@ class AdvectionFieldFinder
 
                 double const determinant = dr_x_1 * dr_y_2 - dr_x_2 * dr_y_1;
 
-                double const deriv_x_phi_0
-                        = (dr_y_2 * deriv_r_phi_1 - dr_y_1 * deriv_r_phi_2) / determinant;
-                double const deriv_y_phi_0
-                        = (-dr_x_2 * deriv_r_phi_1 + dr_x_1 * deriv_r_phi_2) / determinant;
+                DVector<X, Y> deriv_phi_0(
+                        (dr_y_2 * deriv_r_phi_1 - dr_y_1 * deriv_r_phi_2) / determinant,
+                        (-dr_x_2 * deriv_r_phi_1 + dr_x_1 * deriv_r_phi_2) / determinant);
 
                 // E = -grad phi
-                double const electric_field_x_0 = -deriv_x_phi_0;
-                double const electric_field_y_0 = -deriv_y_phi_0;
-
-
+                DVector<X, Y> electric_field_0 = -deriv_phi_0;
 
                 // --- Value at r = m_epsilon:
                 CoordRTheta const coord_rtheta_epsilon(m_epsilon, th);
 
-                Matrix_2x2 inv_J_eps = inv_jacobian_matrix(coord_rtheta_epsilon);
+                Tensor inv_J_eps = inv_jacobian_matrix(coord_rtheta_epsilon);
 
-                double const deriv_r_phi_epsilon = evaluator.deriv_dim_1(
-                        coord_rtheta_epsilon,
-                        get_const_field(electrostatic_potential_coef));
-                double const deriv_theta_phi_epsilon = evaluator.deriv_dim_2(
-                        coord_rtheta_epsilon,
-                        get_const_field(electrostatic_potential_coef));
+                DVector<R_cov, Theta_cov> deriv_phi_epsilon(
+                        evaluator.deriv_dim_1(
+                                coord_rtheta_epsilon,
+                                get_const_field(electrostatic_potential_coef)),
+                        evaluator.deriv_dim_2(
+                                coord_rtheta_epsilon,
+                                get_const_field(electrostatic_potential_coef)));
 
                 // Gradient of phi in the physical domain (Cartesian domain)
                 // (dx phi, dy phi) = J^{-T} (dr phi, dtheta phi)
-                double const grad_x_phi_epsilon = deriv_r_phi_epsilon * inv_J_eps[0][0]
-                                                  + deriv_theta_phi_epsilon * inv_J_eps[1][0];
-                double const grad_y_phi_epsilon = deriv_r_phi_epsilon * inv_J_eps[0][1]
-                                                  + deriv_theta_phi_epsilon * inv_J_eps[1][1];
-
                 // E = -grad phi
-                double const electric_field_x_epsilon = -grad_x_phi_epsilon;
-                double const electric_field_y_epsilon = -grad_y_phi_epsilon;
+                DVector<X, Y> electric_field_epsilon
+                        = -tensor_mul(index<'j', 'i'>(inv_J_eps), index<'j'>(deriv_phi_epsilon));
 
+                DVector<X, Y> E = electric_field_0 * (1 - r / m_epsilon)
+                                  + electric_field_epsilon * r / m_epsilon;
 
                 // --- Linearisation:
-                ddcHelper::get<X>(electric_field)(irtheta)
-                        = electric_field_x_0 * (1 - r / m_epsilon)
-                          + electric_field_x_epsilon * r / m_epsilon;
-                ddcHelper::get<Y>(electric_field)(irtheta)
-                        = electric_field_y_0 * (1 - r / m_epsilon)
-                          + electric_field_y_epsilon * r / m_epsilon;
+                ddcHelper::get<X>(electric_field)(irtheta) = ddcHelper::get<X>(E);
+                ddcHelper::get<Y>(electric_field)(irtheta) = ddcHelper::get<Y>(E);
             }
 
             // > computation of the advection field
@@ -488,8 +476,8 @@ class AdvectionFieldFinder
 
             DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<R, Theta>> inv_G
                     = metric_tensor.inverse(coord_rtheta);
-            std::array<std::array<double, 2>, 2> J;
-            m_mapping.jacobian_matrix(coord_rtheta, J);
+            DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> J
+                    = m_mapping.jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
             // E = -grad phi
@@ -498,14 +486,18 @@ class AdvectionFieldFinder
 
             // A (see README for the expression)
             ddcHelper::get<R>(advection_field_rtheta)(irtheta)
-                    = (J[0][0] * J[0][1] + J[1][0] * J[1][1]) * ddcHelper::get<R>(electric_field)
-                              / jacobian
-                      + (J[1][1] * J[1][1] + J[0][1] * J[0][1])
+                    = (ddcHelper::get<X, R_cov>(J) * ddcHelper::get<X, Theta_cov>(J)
+                       + ddcHelper::get<Y, R_cov>(J) * ddcHelper::get<Y, Theta_cov>(J))
+                              * ddcHelper::get<R>(electric_field) / jacobian
+                      + (ddcHelper::get<Y, Theta_cov>(J) * ddcHelper::get<Y, Theta_cov>(J)
+                         + ddcHelper::get<X, Theta_cov>(J) * ddcHelper::get<X, Theta_cov>(J))
                                 * ddcHelper::get<Theta>(electric_field) / jacobian;
             ddcHelper::get<Theta>(advection_field_rtheta)(irtheta)
-                    = -(J[0][0] * J[0][0] + J[1][0] * J[1][0]) * ddcHelper::get<R>(electric_field)
-                              / jacobian
-                      - (J[0][0] * J[0][1] + J[1][0] * J[1][1])
+                    = -(ddcHelper::get<X, R_cov>(J) * ddcHelper::get<X, R_cov>(J)
+                        + ddcHelper::get<Y, R_cov>(J) * ddcHelper::get<Y, R_cov>(J))
+                              * ddcHelper::get<R>(electric_field) / jacobian
+                      - (ddcHelper::get<X, R_cov>(J) * ddcHelper::get<X, Theta_cov>(J)
+                         + ddcHelper::get<Y, R_cov>(J) * ddcHelper::get<Y, Theta_cov>(J))
                                 * ddcHelper::get<Theta>(electric_field) / jacobian;
         });
 
diff --git a/src/mapping/cartesian_to_circular.hpp b/src/mapping/cartesian_to_circular.hpp
index 292f593fca..c1120e5f2f 100644
--- a/src/mapping/cartesian_to_circular.hpp
+++ b/src/mapping/cartesian_to_circular.hpp
@@ -5,6 +5,7 @@
 
 #include "ddc_aliases.hpp"
 #include "mapping_tools.hpp"
+#include "tensor.hpp"
 #include "view.hpp"
 
 // Pre-declaration of analytical inverse
@@ -53,6 +54,15 @@ class CartesianToCircular
     /// The type of the result of the function described by this mapping
     using CoordResult = Coord<R, Theta>;
 
+    /// @brief The covariant form of the first physical coordinate.
+    using X_cov = typename X::Dual;
+    /// @brief The covariant form of the second physical coordinate.
+    using Y_cov = typename Y::Dual;
+    /// @brief The covariant form of the first logical coordinate.
+    using R_cov = typename R::Dual;
+    /// @brief The covariant form of the second logical coordinate.
+    using Theta_cov = typename Theta::Dual;
+
 public:
     CartesianToCircular() = default;
 
@@ -135,8 +145,7 @@ class CartesianToCircular
      *
      * @param[in] coord
      * 				The coordinate where we evaluate the Jacobian matrix.
-     * @param[out] matrix
-     * 				The Jacobian matrix returned.
+     * @return The Jacobian matrix.
      *
      *
      * @see Jacobian::jacobian_11
@@ -144,15 +153,18 @@ class CartesianToCircular
      * @see Jacobian::jacobian_21
      * @see Jacobian::jacobian_22
      */
-    KOKKOS_FUNCTION void jacobian_matrix(Coord<X, Y> const& coord, Matrix_2x2& matrix)
+    KOKKOS_FUNCTION DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> jacobian_matrix(
+            Coord<X, Y> const& coord) const
 
     {
         const double x = ddc::get<X>(coord);
         const double y = ddc::get<Y>(coord);
-        matrix[0][0] = 2 * x / Kokkos::pow(x * x + y * y, 0.5);
-        matrix[0][1] = 2 * y / Kokkos::pow(x * x + y * y, 0.5);
-        matrix[1][0] = -y / Kokkos::pow(x * x + y * y, 2.);
-        matrix[1][1] = x / Kokkos::pow(x * x + y * y, 2.);
+        DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X_cov, Y_cov>> jacobian_matrix;
+        ddcHelper::get<R, X_cov>(jacobian_matrix) = 2 * x / Kokkos::pow(x * x + y * y, 0.5);
+        ddcHelper::get<R, Y_cov>(jacobian_matrix) = 2 * y / Kokkos::pow(x * x + y * y, 0.5);
+        ddcHelper::get<Theta, X_cov>(jacobian_matrix) = -y / Kokkos::pow(x * x + y * y, 2.);
+        ddcHelper::get<Theta, Y_cov>(jacobian_matrix) = x / Kokkos::pow(x * x + y * y, 2.);
+        return jacobian_matrix;
     }
 
     /**
diff --git a/src/mapping/circular_to_cartesian.hpp b/src/mapping/circular_to_cartesian.hpp
index 01d794ebdc..eefef1a444 100644
--- a/src/mapping/circular_to_cartesian.hpp
+++ b/src/mapping/circular_to_cartesian.hpp
@@ -8,6 +8,7 @@
 
 #include "ddc_aliases.hpp"
 #include "mapping_tools.hpp"
+#include "tensor.hpp"
 #include "view.hpp"
 
 // Pre-declaration of analytical inverse
@@ -56,6 +57,15 @@ class CircularToCartesian
     /// The type of the result of the function described by this mapping
     using CoordResult = Coord<X, Y>;
 
+    /// @brief The covariant form of the first physical coordinate.
+    using X_cov = typename X::Dual;
+    /// @brief The covariant form of the second physical coordinate.
+    using Y_cov = typename Y::Dual;
+    /// @brief The covariant form of the first logical coordinate.
+    using R_cov = typename R::Dual;
+    /// @brief The covariant form of the second logical coordinate.
+    using Theta_cov = typename Theta::Dual;
+
 public:
     CircularToCartesian() = default;
 
@@ -137,17 +147,19 @@ class CircularToCartesian
      *
      * @param[in] coord
      * 				The coordinate where we evaluate the Jacobian matrix.
-     * @param[out] matrix
-     * 				The Jacobian matrix returned.
+     * @return The Jacobian matrix.
      */
-    KOKKOS_FUNCTION void jacobian_matrix(Coord<R, Theta> const& coord, Matrix_2x2& matrix) const
+    KOKKOS_FUNCTION DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> jacobian_matrix(
+            Coord<R, Theta> const& coord) const
     {
         const double r = ddc::get<R>(coord);
         const double theta = ddc::get<Theta>(coord);
-        matrix[0][0] = Kokkos::cos(theta);
-        matrix[0][1] = -r * Kokkos::sin(theta);
-        matrix[1][0] = Kokkos::sin(theta);
-        matrix[1][1] = r * Kokkos::cos(theta);
+        DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> jacobian_matrix;
+        ddcHelper::get<X, R_cov>(jacobian_matrix) = Kokkos::cos(theta);
+        ddcHelper::get<X, Theta_cov>(jacobian_matrix) = -r * Kokkos::sin(theta);
+        ddcHelper::get<Y, R_cov>(jacobian_matrix) = Kokkos::sin(theta);
+        ddcHelper::get<Y, Theta_cov>(jacobian_matrix) = r * Kokkos::cos(theta);
+        return jacobian_matrix;
     }
 
     /**
@@ -231,8 +243,7 @@ class CircularToCartesian
      *
      * @param[in] coord
      * 				The coordinate where we evaluate the Jacobian matrix.
-     * @param[out] matrix
-     * 				The inverse Jacobian matrix returned.
+     * @return The inverse Jacobian matrix.
      *
      *
      * @see Jacobian::inv_jacobian_11
@@ -240,15 +251,19 @@ class CircularToCartesian
      * @see Jacobian::inv_jacobian_21
      * @see Jacobian::inv_jacobian_22
      */
-    KOKKOS_FUNCTION void inv_jacobian_matrix(Coord<R, Theta> const& coord, Matrix_2x2& matrix) const
+    KOKKOS_FUNCTION DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X_cov, Y_cov>>
+    inv_jacobian_matrix(Coord<R, Theta> const& coord) const
     {
         const double r = ddc::get<R>(coord);
         const double theta = ddc::get<Theta>(coord);
         assert(fabs(r) >= 1e-15);
-        matrix[0][0] = Kokkos::cos(theta);
-        matrix[0][1] = Kokkos::sin(theta);
-        matrix[1][0] = -1 / r * Kokkos::sin(theta);
-        matrix[1][1] = 1 / r * Kokkos::cos(theta);
+
+        DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X_cov, Y_cov>> matrix;
+        ddcHelper::get<R, X_cov>(matrix) = Kokkos::cos(theta);
+        ddcHelper::get<R, Y_cov>(matrix) = Kokkos::sin(theta);
+        ddcHelper::get<Theta, X_cov>(matrix) = -1 / r * Kokkos::sin(theta);
+        ddcHelper::get<Theta, Y_cov>(matrix) = 1 / r * Kokkos::cos(theta);
+        return matrix;
     }
 
     /**
diff --git a/src/mapping/combined_mapping.hpp b/src/mapping/combined_mapping.hpp
index ea19d6c076..a603babb8e 100644
--- a/src/mapping/combined_mapping.hpp
+++ b/src/mapping/combined_mapping.hpp
@@ -37,6 +37,20 @@ class CombinedMapping
     using CoordResult = typename Mapping1::CoordResult;
     /// The coordinate system on which the Jacobian is described.
     using CoordJacobian = typename Mapping2::CoordResult;
+    /// The type of the Jacobian matrix.
+    using JacobianMatrixType = DTensor<
+            ddc::to_type_seq_t<CoordResult>,
+            vector_index_set_dual_t<ddc::to_type_seq_t<CoordArg>>>;
+    /// The type of the inverse Jacobian matrix.
+    using InvJacobianMatrixType = DTensor<
+            ddc::to_type_seq_t<CoordArg>,
+            vector_index_set_dual_t<ddc::to_type_seq_t<CoordResult>>>;
+
+private:
+    using DimArg1 = ddc::type_seq_element_t<0, ddc::to_type_seq_t<CoordArg>>;
+    using DimArg2 = ddc::type_seq_element_t<1, ddc::to_type_seq_t<CoordArg>>;
+    using DimResult1 = ddc::type_seq_element_t<0, ddc::to_type_seq_t<CoordResult>>;
+    using DimResult2 = ddc::type_seq_element_t<1, ddc::to_type_seq_t<CoordResult>>;
 
 private:
     using InverseMapping2 = inverse_mapping_t<Mapping2>;
@@ -130,17 +144,13 @@ class CombinedMapping
      * so they can be calculated on the correct coordinate system.
      *
      * @param[in] coord The coordinate where we evaluate the Jacobian matrix.
-     * @param[out] matrix The calculated Jacobian matrix.
+     * @return The calculated Jacobian matrix.
      */
-    KOKKOS_INLINE_FUNCTION void jacobian_matrix(CoordJacobian const& coord, Matrix_2x2& matrix)
-            const
+    KOKKOS_INLINE_FUNCTION JacobianMatrixType jacobian_matrix(CoordJacobian const& coord) const
     {
-        Matrix_2x2 jacobian_mapping_1;
-        m_mapping_1.jacobian_matrix(coord, jacobian_mapping_1);
-
-        Matrix_2x2 m_jacobian_mapping_2 = m_jacobian_mapping_2(coord);
-
-        matrix = mat_mul(jacobian_mapping_1, m_jacobian_mapping_2);
+        return tensor_mul(
+                index<'i', 'j'>(m_mapping_1.jacobian_matrix(coord)),
+                index<'j', 'k'>(m_jacobian_mapping_2(coord)));
     }
 
     /**
@@ -151,9 +161,8 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double jacobian_11(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        jacobian_matrix(coord_rtheta, J);
-        return J[0][0];
+        JacobianMatrixType J = jacobian_matrix(coord_rtheta);
+        return ddcHelper::get<DimResult1, typename DimArg1::Dual>(J);
     }
 
     /**
@@ -164,9 +173,8 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double jacobian_12(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        jacobian_matrix(coord_rtheta, J);
-        return J[0][1];
+        JacobianMatrixType J = jacobian_matrix(coord_rtheta);
+        return ddcHelper::get<DimResult1, typename DimArg2::Dual>(J);
     }
 
     /**
@@ -177,9 +185,8 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double jacobian_21(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        jacobian_matrix(coord_rtheta, J);
-        return J[1][0];
+        JacobianMatrixType J = jacobian_matrix(coord_rtheta);
+        return ddcHelper::get<DimResult2, typename DimArg1::Dual>(J);
     }
 
     /**
@@ -190,9 +197,8 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double jacobian_22(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        jacobian_matrix(coord_rtheta, J);
-        return J[1][1];
+        JacobianMatrixType J = jacobian_matrix(coord_rtheta);
+        return ddcHelper::get<DimResult2, typename DimArg2::Dual>(J);
     }
 
     /**
@@ -203,9 +209,7 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double jacobian(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        jacobian_matrix(coord_rtheta, J);
-        return determinant(J);
+        return determinant(jacobian_matrix(coord_rtheta));
     }
 
     /**
@@ -219,11 +223,11 @@ class CombinedMapping
      * using non_singular_inverse_jacobian_matrix
      *
      * @param[in] coord The coordinate where we evaluate the inverse Jacobian matrix.
-     * @param[out] matrix The calculated inverse Jacobian matrix.
+     * @return The calculated inverse Jacobian matrix.
      *
      * @see non_singular_inverse_jacobian_matrix
      */
-    KOKKOS_FUNCTION void inv_jacobian_matrix(CoordJacobian const& coord, Matrix_2x2& matrix) const
+    KOKKOS_FUNCTION InvJacobianMatrixType inv_jacobian_matrix(CoordJacobian const& coord) const
     {
         if constexpr (
                 (has_singular_o_point_inv_jacobian_v<Mapping1>)
@@ -235,18 +239,14 @@ class CombinedMapping
                 InvJacobianOPoint<CombinedMapping<Mapping1, Mapping2>, CoordJacobian> o_point_val(
                         *this);
                 CoordJacobian coord_eps(m_epsilon, ddc::get<Theta>(coord));
-                Matrix_2x2 J_0 = o_point_val();
-                Matrix_2x2 J_eps;
-                non_singular_inverse_jacobian_matrix(coord_eps, J_eps);
-                matrix[0][0] = (1 - r / m_epsilon) * J_0[0][0] + r / m_epsilon * J_eps[0][0];
-                matrix[0][1] = (1 - r / m_epsilon) * J_0[0][1] + r / m_epsilon * J_eps[0][1];
-                matrix[1][0] = (1 - r / m_epsilon) * J_0[1][0] + r / m_epsilon * J_eps[1][0];
-                matrix[1][1] = (1 - r / m_epsilon) * J_0[1][1] + r / m_epsilon * J_eps[1][1];
+                Tensor J_0 = o_point_val();
+                Tensor J_eps = non_singular_inverse_jacobian_matrix(coord_eps);
+                return (1 - r / m_epsilon) * J_0 + r / m_epsilon * J_eps;
             } else {
-                non_singular_inverse_jacobian_matrix(coord, matrix);
+                return non_singular_inverse_jacobian_matrix(coord);
             }
         } else {
-            non_singular_inverse_jacobian_matrix(coord, matrix);
+            return non_singular_inverse_jacobian_matrix(coord);
         }
     }
 
@@ -258,9 +258,8 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double inv_jacobian_11(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        inv_jacobian_matrix(coord_rtheta, J);
-        return J[0][0];
+        InvJacobianMatrixType J = inv_jacobian_matrix(coord_rtheta);
+        return ddcHelper::get<DimArg1, typename DimResult1::Dual>(J);
     }
 
     /**
@@ -271,9 +270,8 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double inv_jacobian_12(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        inv_jacobian_matrix(coord_rtheta, J);
-        return J[0][1];
+        InvJacobianMatrixType J = inv_jacobian_matrix(coord_rtheta);
+        return ddcHelper::get<DimArg1, typename DimResult2::Dual>(J);
     }
 
     /**
@@ -284,9 +282,8 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double inv_jacobian_21(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        inv_jacobian_matrix(coord_rtheta, J);
-        return J[1][0];
+        InvJacobianMatrixType J = inv_jacobian_matrix(coord_rtheta);
+        return ddcHelper::get<DimArg2, typename DimResult1::Dual>(J);
     }
 
     /**
@@ -297,9 +294,8 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double inv_jacobian_22(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        inv_jacobian_matrix(coord_rtheta, J);
-        return J[1][1];
+        InvJacobianMatrixType J = inv_jacobian_matrix(coord_rtheta);
+        return ddcHelper::get<DimArg2, typename DimResult2::Dual>(J);
     }
 
     /**
@@ -310,9 +306,7 @@ class CombinedMapping
      */
     KOKKOS_INLINE_FUNCTION double inv_jacobian(CoordJacobian const& coord_rtheta) const
     {
-        Matrix_2x2 J;
-        inv_jacobian_matrix(coord_rtheta, J);
-        return determinant(J);
+        return determinant(inv_jacobian_matrix(coord_rtheta));
     }
 
     /**
@@ -348,17 +342,13 @@ class CombinedMapping
      * @param[in] coord The coordinate where we evaluate the Jacobian matrix.
      * @param[out] matrix The calculated Jacobian matrix.
      */
-    KOKKOS_INLINE_FUNCTION void non_singular_inverse_jacobian_matrix(
-            CoordJacobian const& coord,
-            Matrix_2x2& matrix) const
+    KOKKOS_INLINE_FUNCTION InvJacobianMatrixType
+    non_singular_inverse_jacobian_matrix(CoordJacobian const& coord) const
     {
         InverseJacobianMatrix<Mapping1, CoordJacobian> inv_jacobian_matrix_1(m_mapping_1);
-        Matrix_2x2 inv_jacobian_mapping_1 = inv_jacobian_matrix_1(coord);
-
-        Matrix_2x2 inv_jacobian_mapping_2;
-        m_inv_mapping_2.jacobian_matrix(coord, inv_jacobian_mapping_2);
-
-        matrix = mat_mul(inv_jacobian_mapping_2, inv_jacobian_mapping_1);
+        return tensor_mul(
+                index<'i', 'j'>(m_inv_mapping_2.jacobian_matrix(coord)),
+                index<'j', 'k'>(inv_jacobian_matrix_1(coord)));
     }
 };
 
diff --git a/src/mapping/czarny_to_cartesian.hpp b/src/mapping/czarny_to_cartesian.hpp
index 001f99d8c3..d314641481 100644
--- a/src/mapping/czarny_to_cartesian.hpp
+++ b/src/mapping/czarny_to_cartesian.hpp
@@ -7,6 +7,7 @@
 
 #include "ddc_aliases.hpp"
 #include "mapping_tools.hpp"
+#include "tensor.hpp"
 #include "view.hpp"
 
 // Pre-declaration of analytical inverse
@@ -63,6 +64,15 @@ class CzarnyToCartesian
     /// The type of the result of the function described by this mapping
     using CoordResult = Coord<X, Y>;
 
+    /// @brief The covariant form of the first physical coordinate.
+    using X_cov = typename X::Dual;
+    /// @brief The covariant form of the second physical coordinate.
+    using Y_cov = typename Y::Dual;
+    /// @brief The covariant form of the first logical coordinate.
+    using R_cov = typename R::Dual;
+    /// @brief The covariant form of the second logical coordinate.
+    using Theta_cov = typename Theta::Dual;
+
 private:
     double m_epsilon;
     double m_e;
@@ -196,8 +206,7 @@ class CzarnyToCartesian
      *
      * @param[in] coord
      * 				The coordinate where we evaluate the Jacobian matrix.
-     * @param[out] matrix
-     * 				The Jacobian matrix returned.
+     * @return The Jacobian matrix.
      *
      *
      * @see Jacobian::jacobian_11
@@ -205,7 +214,8 @@ class CzarnyToCartesian
      * @see Jacobian::jacobian_21
      * @see Jacobian::jacobian_22
      */
-    KOKKOS_FUNCTION void jacobian_matrix(Coord<R, Theta> const& coord, Matrix_2x2& matrix) const
+    KOKKOS_FUNCTION DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> jacobian_matrix(
+            Coord<R, Theta> const& coord) const
     {
         const double r = ddc::get<R>(coord);
         const double theta = ddc::get<Theta>(coord);
@@ -217,15 +227,18 @@ class CzarnyToCartesian
         const double sqrt_eps = Kokkos::sqrt(m_epsilon * (m_epsilon + 2.0 * r * cos_theta) + 1.0);
         const double sqrt_eps_2 = 2.0 - sqrt_eps;
 
-        matrix[0][0] = -cos_theta / sqrt_eps;
-        matrix[0][1] = r * sin_theta / sqrt_eps;
-        matrix[1][0] = m_e * m_epsilon * r * sin_theta * cos_theta * xi
-                               / (sqrt_eps_2 * sqrt_eps_2 * sqrt_eps)
-                       + m_e * sin_theta * xi / sqrt_eps_2;
-        matrix[1][1] = r
-                       * (-m_e * m_epsilon * r * sin_theta * sin_theta * xi
-                                  / (sqrt_eps_2 * sqrt_eps_2 * sqrt_eps)
-                          + m_e * cos_theta * xi / sqrt_eps_2);
+        DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> jacobian_matrix;
+        ddcHelper::get<X, R_cov>(jacobian_matrix) = -cos_theta / sqrt_eps;
+        ddcHelper::get<X, Theta_cov>(jacobian_matrix) = r * sin_theta / sqrt_eps;
+        ddcHelper::get<Y, R_cov>(jacobian_matrix) = m_e * m_epsilon * r * sin_theta * cos_theta * xi
+                                                            / (sqrt_eps_2 * sqrt_eps_2 * sqrt_eps)
+                                                    + m_e * sin_theta * xi / sqrt_eps_2;
+        ddcHelper::get<Y, Theta_cov>(jacobian_matrix)
+                = r
+                  * (-m_e * m_epsilon * r * sin_theta * sin_theta * xi
+                             / (sqrt_eps_2 * sqrt_eps_2 * sqrt_eps)
+                     + m_e * cos_theta * xi / sqrt_eps_2);
+        return jacobian_matrix;
     }
 
     /**
@@ -329,8 +342,7 @@ class CzarnyToCartesian
      *
      * @param[in] coord
      * 				The coordinate where we evaluate the Jacobian matrix.
-     * @param[out] matrix
-     * 				The inverse Jacobian matrix returned.
+     * @return The inverse Jacobian matrix.
      *
      *
      * @see Jacobian::inv_jacobian_11
@@ -338,7 +350,8 @@ class CzarnyToCartesian
      * @see Jacobian::inv_jacobian_21
      * @see Jacobian::inv_jacobian_22
      */
-    KOKKOS_FUNCTION void inv_jacobian_matrix(Coord<R, Theta> const& coord, Matrix_2x2& matrix) const
+    KOKKOS_FUNCTION DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X_cov, Y_cov>>
+    inv_jacobian_matrix(Coord<R, Theta> const& coord) const
     {
         const double r = ddc::get<R>(coord);
         const double theta = ddc::get<Theta>(coord);
@@ -354,10 +367,14 @@ class CzarnyToCartesian
         const double fact_2 = m_e * m_epsilon * xi * r * sin_theta * fact_1 / divisor / divisor;
         const double fact_3 = m_e * xi / divisor;
 
-        matrix[0][0] = -1 / fact_1 * (-sin_theta * fact_2 + cos_theta * fact_3) / fact_3;
-        matrix[0][1] = sin_theta / fact_3;
-        matrix[1][0] = 1 / r / fact_1 * (cos_theta * fact_2 + sin_theta * fact_3) / fact_3;
-        matrix[1][1] = 1 / r * cos_theta / fact_3;
+        DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X_cov, Y_cov>> matrix;
+        ddcHelper::get<R, X_cov>(matrix)
+                = -1 / fact_1 * (-sin_theta * fact_2 + cos_theta * fact_3) / fact_3;
+        ddcHelper::get<R, Y_cov>(matrix) = sin_theta / fact_3;
+        ddcHelper::get<Theta, X_cov>(matrix)
+                = 1 / r / fact_1 * (cos_theta * fact_2 + sin_theta * fact_3) / fact_3;
+        ddcHelper::get<Theta, Y_cov>(matrix) = 1 / r * cos_theta / fact_3;
+        return matrix;
     }
 
     /**
diff --git a/src/mapping/discrete_to_cartesian.hpp b/src/mapping/discrete_to_cartesian.hpp
index 13cb286fd2..c238d74b35 100644
--- a/src/mapping/discrete_to_cartesian.hpp
+++ b/src/mapping/discrete_to_cartesian.hpp
@@ -8,6 +8,7 @@
 #include "ddc_aliases.hpp"
 #include "mapping_tools.hpp"
 #include "math_tools.hpp"
+#include "tensor.hpp"
 #include "view.hpp"
 
 /**
@@ -57,6 +58,15 @@ class DiscreteToCartesian
     /// The type of the result of the function described by this mapping
     using CoordResult = Coord<X, Y>;
 
+    /// @brief The covariant form of the first physical coordinate.
+    using X_cov = typename X::Dual;
+    /// @brief The covariant form of the second physical coordinate.
+    using Y_cov = typename Y::Dual;
+    /// @brief The covariant form of the first logical coordinate.
+    using R_cov = typename R::Dual;
+    /// @brief The covariant form of the second logical coordinate.
+    using Theta_cov = typename Theta::Dual;
+
 private:
     using spline_idx_range = IdxRange<BSplineR, BSplineTheta>;
 
@@ -146,21 +156,21 @@ class DiscreteToCartesian
      *
      * @param[in] coord
      * 				The coordinate where we evaluate the Jacobian matrix.
-     * @param[out] matrix
-     * 				The Jacobian matrix returned.
+     * @return The Jacobian matrix.
      */
-    KOKKOS_FUNCTION void jacobian_matrix(
-            Coord<curvilinear_tag_r, curvilinear_tag_theta> const& coord,
-            Matrix_2x2& matrix) const
+    KOKKOS_FUNCTION DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> jacobian_matrix(
+            Coord<R, Theta> const& coord) const
     {
-        matrix[0][0]
+        DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> jacobian_matrix;
+        ddcHelper::get<X, R_cov>(jacobian_matrix)
                 = m_spline_evaluator.deriv_dim_1(coord, get_const_field(m_x_spline_representation));
-        matrix[0][1]
+        ddcHelper::get<X, Theta_cov>(jacobian_matrix)
                 = m_spline_evaluator.deriv_dim_2(coord, get_const_field(m_x_spline_representation));
-        matrix[1][0]
+        ddcHelper::get<Y, R_cov>(jacobian_matrix)
                 = m_spline_evaluator.deriv_dim_1(coord, get_const_field(m_y_spline_representation));
-        matrix[1][1]
+        ddcHelper::get<Y, Theta_cov>(jacobian_matrix)
                 = m_spline_evaluator.deriv_dim_2(coord, get_const_field(m_y_spline_representation));
+        return jacobian_matrix;
     }
 
     /**
@@ -262,9 +272,9 @@ class DiscreteToCartesian
     KOKKOS_FUNCTION double jacobian(
             Coord<curvilinear_tag_r, curvilinear_tag_theta> const& coord) const
     {
-        Matrix_2x2 J;
-        jacobian_matrix(coord, J);
-        return determinant(J);
+        Tensor J = jacobian_matrix(coord);
+        return ddcHelper::get<X, R_cov>(J) * ddcHelper::get<Y, Theta_cov>(J)
+               - ddcHelper::get<Y, R_cov>(J) * ddcHelper::get<X, Theta_cov>(J);
     }
 
     /**
@@ -281,19 +291,22 @@ class DiscreteToCartesian
      *
      * @return The first order expansion of the Jacobian matrix with the theta component divided by r.
      */
-    KOKKOS_INLINE_FUNCTION Matrix_2x2 first_order_jacobian_matrix_r_rtheta(
+    KOKKOS_INLINE_FUNCTION DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>>
+    first_order_jacobian_matrix_r_rtheta(
             Coord<curvilinear_tag_r, curvilinear_tag_theta> const& coord) const
     {
-        Matrix_2x2 matrix;
-        matrix[0][0]
+        DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> J;
+        ddcHelper::get<X, R_cov>(J)
                 = m_spline_evaluator.deriv_dim_1(coord, get_const_field(m_x_spline_representation));
-        matrix[0][1] = m_spline_evaluator
-                               .deriv_1_and_2(coord, get_const_field(m_x_spline_representation));
-        matrix[1][0]
+        ddcHelper::get<X, Theta_cov>(J)
+                = m_spline_evaluator
+                          .deriv_1_and_2(coord, get_const_field(m_x_spline_representation));
+        ddcHelper::get<Y, R_cov>(J)
                 = m_spline_evaluator.deriv_dim_1(coord, get_const_field(m_y_spline_representation));
-        matrix[1][1] = m_spline_evaluator
-                               .deriv_1_and_2(coord, get_const_field(m_y_spline_representation));
-        return matrix;
+        ddcHelper::get<Y, Theta_cov>(J)
+                = m_spline_evaluator
+                          .deriv_1_and_2(coord, get_const_field(m_y_spline_representation));
+        return J;
     }
 
     /**
diff --git a/src/mapping/inv_jacobian_o_point.hpp b/src/mapping/inv_jacobian_o_point.hpp
index 52bbd98e41..5ab167fde7 100644
--- a/src/mapping/inv_jacobian_o_point.hpp
+++ b/src/mapping/inv_jacobian_o_point.hpp
@@ -5,6 +5,7 @@
 #include "circular_to_cartesian.hpp"
 #include "czarny_to_cartesian.hpp"
 #include "discrete_to_cartesian.hpp"
+#include "indexed_tensor.hpp"
 #include "mapping_tools.hpp"
 #include "view.hpp"
 
@@ -47,6 +48,11 @@ class InvJacobianOPoint<
     /// The coordinate system in which the inverse of the Jacobian is calculated.
     using CoordRTheta = Coord<R, Theta>;
 
+    /// @brief The covariant form of the first physical coordinate.
+    using X_cov = typename X::Dual;
+    /// @brief The covariant form of the second physical coordinate.
+    using Y_cov = typename Y::Dual;
+
 private:
     using Mapping = CombinedMapping<
             CircularToCartesian<R, Theta, X, Y>,
@@ -76,13 +82,14 @@ class InvJacobianOPoint<
      *
      * @result The matrix evaluated at the central point.
      */
-    KOKKOS_INLINE_FUNCTION Matrix_2x2 operator()() const
+    KOKKOS_INLINE_FUNCTION DTensor<VectorIndexSet<Xpc, Ypc>, VectorIndexSet<X_cov, Y_cov>>
+    operator()() const
     {
-        Matrix_2x2 J;
-        J[0][0] = 1.;
-        J[0][1] = 0.;
-        J[1][0] = 0.;
-        J[1][1] = 1.;
+        DTensor<VectorIndexSet<Xpc, Ypc>, VectorIndexSet<X_cov, Y_cov>> J;
+        ddcHelper::get<Xpc, X_cov>(J) = 1.;
+        ddcHelper::get<Xpc, Y_cov>(J) = 0.;
+        ddcHelper::get<Ypc, X_cov>(J) = 0.;
+        ddcHelper::get<Ypc, Y_cov>(J) = 1.;
         return J;
     }
 };
@@ -103,10 +110,14 @@ class InvJacobianOPoint<
     /// The coordinate system in which the inverse of the Jacobian is calculated.
     using CoordRTheta = Coord<R, Theta>;
 
+    /// @brief The covariant form of the first physical coordinate.
+    using X_cov = typename X::Dual;
+    /// @brief The covariant form of the second physical coordinate.
+    using Y_cov = typename Y::Dual;
+
 private:
-    using Mapping = CombinedMapping<
-            CzarnyToCartesian<R, Theta, X, Y>,
-            CartesianToCircular<Xpc, Ypc, R, Theta>>;
+    using CzarnyToCart = CzarnyToCartesian<R, Theta, X, Y>;
+    using Mapping = CombinedMapping<CzarnyToCart, CartesianToCircular<Xpc, Ypc, R, Theta>>;
 
 private:
     Mapping m_mapping;
@@ -130,18 +141,19 @@ class InvJacobianOPoint<
      *
      * @result The matrix evaluated at the central point.
      */
-    KOKKOS_INLINE_FUNCTION Matrix_2x2 operator()() const
+    KOKKOS_INLINE_FUNCTION DTensor<VectorIndexSet<Xpc, Ypc>, VectorIndexSet<X_cov, Y_cov>>
+    operator()() const
     {
-        Matrix_2x2 J;
-        const double epsilon
-                = m_mapping.template get<CzarnyToCartesian<R, Theta, X, Y>>().epsilon();
-        const double e = m_mapping.template get<CzarnyToCartesian<R, Theta, X, Y>>().e();
+        const double epsilon = m_mapping.template get<CzarnyToCart>().epsilon();
+        const double e = m_mapping.template get<CzarnyToCart>().e();
         const double xi = Kokkos::sqrt(1. / (1. - epsilon * epsilon * 0.25));
         const double sqrt_eps_2 = Kokkos::sqrt(1. + epsilon * epsilon);
-        J[0][0] = -sqrt_eps_2;
-        J[0][1] = 0.;
-        J[1][0] = 0.;
-        J[1][1] = (2 - sqrt_eps_2) / e / xi;
+
+        DTensor<VectorIndexSet<Xpc, Ypc>, VectorIndexSet<X_cov, Y_cov>> J;
+        ddcHelper::get<Xpc, X_cov>(J) = -sqrt_eps_2;
+        ddcHelper::get<Xpc, Y_cov>(J) = 0.;
+        ddcHelper::get<Ypc, X_cov>(J) = 0.;
+        ddcHelper::get<Ypc, Y_cov>(J) = (2 - sqrt_eps_2) / e / xi;
         return J;
     }
 };
@@ -171,6 +183,15 @@ class InvJacobianOPoint<
     /// The coordinate system in which the inverse of the Jacobian is calculated.
     using CoordRTheta = Coord<R, Theta>;
 
+    /// @brief The covariant form of the first physical coordinate.
+    using X_cov = typename X::Dual;
+    /// @brief The covariant form of the second physical coordinate.
+    using Y_cov = typename Y::Dual;
+    using Xpc_cov = typename Xpc::Dual;
+    using Ypc_cov = typename Ypc::Dual;
+    using R_cov = typename R::Dual;
+    using Theta_cov = typename Theta::Dual;
+
 private:
     using Mapping = CombinedMapping<
             DiscreteToCartesian<X, Y, SplineEvaluator, R, Theta, MemorySpace>,
@@ -230,44 +251,36 @@ class InvJacobianOPoint<
      * @see BslAdvection
      * @see AdvectionDomain
      */
-    KOKKOS_FUNCTION Matrix_2x2 operator()() const
+    KOKKOS_FUNCTION DTensor<VectorIndexSet<Xpc, Ypc>, VectorIndexSet<X_cov, Y_cov>> operator()()
+            const
     {
         DiscreteToCartesian<X, Y, SplineEvaluator, R, Theta, MemorySpace> const& discrete_mapping
                 = m_mapping.template get<
                         DiscreteToCartesian<X, Y, SplineEvaluator, R, Theta, MemorySpace>>();
-        Matrix_2x2 J;
-        J[0][0] = 0;
-        J[0][1] = 0;
-        J[1][0] = 0;
-        J[1][1] = 0;
+        DTensor<VectorIndexSet<Xpc, Ypc>, VectorIndexSet<X_cov, Y_cov>> J(0.0);
         IdxRangeRTheta idx_range_singular_point = discrete_mapping.idx_range_singular_point();
         // Average the values at (r = 0, theta):
         IdxR ir(idx_range_singular_point.front());
         for (IdxTheta itheta : IdxRangeTheta(idx_range_singular_point)) {
             Coord<R, Theta> coord(ddc::coordinate(ir), ddc::coordinate(itheta));
-            Matrix_2x2 J_first_order = discrete_mapping.first_order_jacobian_matrix_r_rtheta(coord);
+            DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> J_first_order
+                    = discrete_mapping.first_order_jacobian_matrix_r_rtheta(coord);
 
             double th = ddc::get<Theta>(coord);
-            Matrix_2x2 J_circ_r_rtheta;
-            J_circ_r_rtheta[0][0] = Kokkos::cos(th);
-            J_circ_r_rtheta[0][1] = Kokkos::sin(th);
-            J_circ_r_rtheta[1][0] = -Kokkos::sin(th);
-            J_circ_r_rtheta[1][1] = Kokkos::cos(th);
+            DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<Xpc_cov, Ypc_cov>> J_circ_r_rtheta;
+            ddcHelper::get<R, Xpc_cov>(J_circ_r_rtheta) = Kokkos::cos(th);
+            ddcHelper::get<R, Ypc_cov>(J_circ_r_rtheta) = Kokkos::sin(th);
+            ddcHelper::get<Theta, Xpc_cov>(J_circ_r_rtheta) = -Kokkos::sin(th);
+            ddcHelper::get<Theta, Ypc_cov>(J_circ_r_rtheta) = Kokkos::cos(th);
 
-            Matrix_2x2 J_theta;
-            J_theta = inverse(mat_mul(J_first_order, J_circ_r_rtheta));
+            Tensor J_theta = inverse(
+                    tensor_mul(index<'i', 'j'>(J_first_order), index<'j', 'k'>(J_circ_r_rtheta)));
 
-            J[0][0] += J_theta[0][0];
-            J[0][1] += J_theta[0][1];
-            J[1][0] += J_theta[1][0];
-            J[1][1] += J_theta[1][1];
+            J += J_theta;
         }
 
         int const theta_size = idx_range_singular_point.size();
-        J[0][0] /= theta_size;
-        J[0][1] /= theta_size;
-        J[1][0] /= theta_size;
-        J[1][1] /= theta_size;
+        J /= theta_size;
         return J;
     }
 };
diff --git a/src/mapping/inverse_jacobian_matrix.hpp b/src/mapping/inverse_jacobian_matrix.hpp
index 94ea1eef4f..501e8eb253 100644
--- a/src/mapping/inverse_jacobian_matrix.hpp
+++ b/src/mapping/inverse_jacobian_matrix.hpp
@@ -3,6 +3,7 @@
 
 #include "ddc_aliases.hpp"
 #include "mapping_tools.hpp"
+#include "math_tools.hpp"
 #include "view.hpp"
 
 /**
@@ -16,6 +17,19 @@
 template <class Mapping, class PositionCoordinate = typename Mapping::CoordArg>
 class InverseJacobianMatrix
 {
+private:
+    using ValidArgIndices = ddc::to_type_seq_t<typename Mapping::CoordArg>;
+    using ValidResultIndices = ddc::to_type_seq_t<typename Mapping::CoordResult>;
+    using DimArg0 = ddc::type_seq_element_t<0, ValidArgIndices>;
+    using DimArg1 = ddc::type_seq_element_t<1, ValidArgIndices>;
+    using DimRes0_cov = typename ddc::type_seq_element_t<0, ValidResultIndices>::Dual;
+    using DimRes1_cov = typename ddc::type_seq_element_t<1, ValidResultIndices>::Dual;
+
+public:
+    /// The type of the tensor representing the inverse Jacobian.
+    using InverseJacobianTensor
+            = DTensor<ValidArgIndices, vector_index_set_dual_t<ValidResultIndices>>;
+
 private:
     Mapping m_mapping;
 
@@ -42,21 +56,17 @@ class InverseJacobianMatrix
      * @see inv_jacobian_21
      * @see inv_jacobian_22
      */
-    KOKKOS_INLINE_FUNCTION Matrix_2x2 operator()(PositionCoordinate const& coord) const
+    KOKKOS_INLINE_FUNCTION InverseJacobianTensor operator()(PositionCoordinate const& coord) const
     {
-        Matrix_2x2 matrix;
         if constexpr (has_2d_inv_jacobian_v<Mapping, PositionCoordinate>) {
-            m_mapping.inv_jacobian_matrix(coord, matrix);
+            return m_mapping.inv_jacobian_matrix(coord);
         } else {
             static_assert(has_2d_jacobian_v<Mapping, PositionCoordinate>);
-            double jacob = m_mapping.jacobian(coord);
-            assert(fabs(jacob) > 1e-15);
-            matrix[0][0] = m_mapping.jacobian_22(coord) / jacob;
-            matrix[0][1] = -m_mapping.jacobian_12(coord) / jacob;
-            matrix[1][0] = -m_mapping.jacobian_21(coord) / jacob;
-            matrix[1][1] = m_mapping.jacobian_11(coord) / jacob;
+            DTensor<ValidResultIndices, vector_index_set_dual_t<ValidArgIndices>> jacobian
+                    = m_mapping.jacobian_matrix(coord);
+            assert(fabs(determinant(jacobian)) > 1e-15);
+            return inverse(jacobian);
         }
-        return matrix;
     }
 
     /**
diff --git a/src/mapping/mapping_tools.hpp b/src/mapping/mapping_tools.hpp
index 5a1dfd8c32..942ed8d066 100644
--- a/src/mapping/mapping_tools.hpp
+++ b/src/mapping/mapping_tools.hpp
@@ -113,16 +113,11 @@ class Defines2DJacobian
     {
         constexpr std::tuple<bool, const char*> success = has_2d_jacobian_methods();
         if constexpr (std::get<bool>(success)) {
-            if (!std::is_invocable_r_v<
-                        void,
-                        decltype(&Type::jacobian_matrix),
-                        Type,
-                        CoordinateType,
-                        Matrix_2x2&>) {
+            if (!std::is_invocable_v<decltype(&Type::jacobian_matrix), Type, CoordinateType>) {
                 return std::make_tuple(
                         false,
-                        "The jacobian_matrix method of a 2D Mapping must take a Coordinate and a "
-                        "Matrix_2x2& as an argument and return nothing.");
+                        "The jacobian_matrix method of a 2D Mapping must take a Coordinate as an "
+                        "argument and return a 2D Tensor.");
             }
             if (!std::is_invocable_r_v<
                         double,
@@ -223,16 +218,11 @@ class Defines2DInvJacobian
     {
         constexpr std::tuple<bool, const char*> success = has_2d_inv_jacobian_methods();
         if constexpr (std::get<bool>(success)) {
-            if (!std::is_invocable_r_v<
-                        void,
-                        decltype(&Type::inv_jacobian_matrix),
-                        Type,
-                        CoordinateType,
-                        Matrix_2x2&>) {
+            if (!std::is_invocable_v<decltype(&Type::inv_jacobian_matrix), Type, CoordinateType>) {
                 return std::make_tuple(
                         false,
-                        "The inv_jacobian_matrix method of a 2D Mapping must take a Coordinate and "
-                        "a Matrix_2x2& as an argument and return nothing.");
+                        "The inv_jacobian_matrix method of a 2D Mapping must take a Coordinate as "
+                        "an argument and return a Tensor.");
             }
             if (!std::is_invocable_r_v<
                         double,
diff --git a/src/mapping/vector_mapper.hpp b/src/mapping/vector_mapper.hpp
index 260faf58d0..5ca6a3e3d8 100644
--- a/src/mapping/vector_mapper.hpp
+++ b/src/mapping/vector_mapper.hpp
@@ -73,20 +73,20 @@ class VectorMapper<VectorIndexSet<XIn, YIn>, VectorIndexSet<XOut, YOut>, Mapping
     {
         using IdxType = typename IdxRangeType::discrete_element_type;
 
-        Mapping mapping_proxy = m_mapping;
-
         if constexpr (std::is_same_v<Coord<XIn, YIn>, typename Mapping::CoordArg>) {
+            Mapping mapping_proxy = m_mapping;
             ddc::parallel_for_each(
                     exec_space,
                     get_idx_range(vector_field_input),
                     KOKKOS_LAMBDA(IdxType idx) {
-                        Matrix_2x2 map_J;
-                        mapping_proxy.jacobian_matrix(ddc::coordinate(idx), map_J);
-
-                        Coord<XOut, YOut> vector_out;
-                        vector_out.array() = mat_vec_mul(map_J, vector_field_input(idx).array());
-                        ddcHelper::get<XOut>(vector_field_output)(idx) = ddc::get<XOut>(vector_out);
-                        ddcHelper::get<YOut>(vector_field_output)(idx) = ddc::get<YOut>(vector_out);
+                        Tensor jacobian = mapping_proxy.jacobian_matrix(ddc::coordinate(idx));
+                        DVector<XOut, YOut> vector_field_out = tensor_mul(
+                                index<'i', 'j'>(jacobian),
+                                index<'j'>(vector_field_input));
+                        ddcHelper::get<XOut>(vector_field_output)(idx)
+                                = ddcHelper::get<XOut>(vector_field_out);
+                        ddcHelper::get<YOut>(vector_field_output)(idx)
+                                = ddcHelper::get<YOut>(vector_field_out);
                     });
         } else {
             InverseJacobianMatrix<Mapping, ddc::coordinate_of_t<IdxType>> inv_mapping(m_mapping);
@@ -94,16 +94,15 @@ class VectorMapper<VectorIndexSet<XIn, YIn>, VectorIndexSet<XOut, YOut>, Mapping
                     exec_space,
                     get_idx_range(vector_field_input),
                     KOKKOS_LAMBDA(IdxType idx) {
-                        Matrix_2x2 map_J = inv_mapping(ddc::coordinate(idx));
+                        Tensor map_J = inv_mapping(ddc::coordinate(idx));
 
-                        Coord<XOut, YOut> vector_out;
-                        // mat_vec_mul should be replaced with a tensor calculus function
-                        // when map_J is stored in a Tensor
-                        vector_out.array() = mat_vec_mul(
-                                map_J,
-                                ddcHelper::to_coord(vector_field_input(idx)).array());
-                        ddcHelper::get<XOut>(vector_field_output)(idx) = ddc::get<XOut>(vector_out);
-                        ddcHelper::get<YOut>(vector_field_output)(idx) = ddc::get<YOut>(vector_out);
+                        DVector<XOut, YOut> vector_out = tensor_mul(
+                                index<'i', 'j'>(map_J),
+                                index<'j'>(vector_field_input(idx)));
+                        ddcHelper::get<XOut>(vector_field_output)(idx)
+                                = ddcHelper::get<XOut>(vector_out);
+                        ddcHelper::get<YOut>(vector_field_output)(idx)
+                                = ddcHelper::get<YOut>(vector_out);
                     });
         }
     }
diff --git a/src/math_tools/math_tools.hpp b/src/math_tools/math_tools.hpp
index e6c1639db0..73543a8583 100644
--- a/src/math_tools/math_tools.hpp
+++ b/src/math_tools/math_tools.hpp
@@ -111,51 +111,29 @@ inline T max(T x, T y)
     return x > y ? x : y;
 }
 
-template <std::size_t N, std::size_t M, std::size_t P>
-KOKKOS_INLINE_FUNCTION std::array<std::array<double, N>, P> mat_mul(
-        std::array<std::array<double, N>, M> const& a,
-        std::array<std::array<double, M>, P> const& b)
+template <class RowDim1, class RowDim2, class ColDim1, class ColDim2>
+KOKKOS_INLINE_FUNCTION double determinant(
+        DTensor<VectorIndexSet<RowDim1, RowDim2>, VectorIndexSet<ColDim1, ColDim2>> arr)
 {
-    std::array<std::array<double, N>, P> result;
-    for (std::size_t i(0); i < N; ++i) {
-        for (std::size_t j(0); j < P; ++j) {
-            result[i][j] = 0.0;
-            for (std::size_t k(0); k < M; ++k) {
-                result[i][j] += a[i][k] * b[k][j];
-            }
-        }
-    }
-    return result;
-}
-
-template <std::size_t N, std::size_t M>
-KOKKOS_INLINE_FUNCTION std::array<double, N> mat_vec_mul(
-        std::array<std::array<double, N>, M> const& a,
-        std::array<double, M> const& b)
-{
-    std::array<double, N> result;
-    for (std::size_t i(0); i < N; ++i) {
-        result[i] = 0.0;
-        for (std::size_t k(0); k < M; ++k) {
-            result[i] += a[i][k] * b[k];
-        }
-    }
-    return result;
-}
-
-KOKKOS_INLINE_FUNCTION double determinant(std::array<std::array<double, 2>, 2> arr)
-{
-    return arr[0][0] * arr[1][1] - arr[0][1] * arr[1][0];
+    return ddcHelper::get<RowDim1, ColDim1>(arr) * ddcHelper::get<RowDim2, ColDim2>(arr)
+           - ddcHelper::get<RowDim2, ColDim1>(arr) * ddcHelper::get<RowDim1, ColDim2>(arr);
 }
 
-KOKKOS_INLINE_FUNCTION std::array<std::array<double, 2>, 2> inverse(
-        std::array<std::array<double, 2>, 2> arr)
+template <class RowDim1, class RowDim2, class ColDim1, class ColDim2>
+KOKKOS_INLINE_FUNCTION DTensor<
+        VectorIndexSet<typename ColDim1::Dual, typename ColDim2::Dual>,
+        VectorIndexSet<typename RowDim1::Dual, typename RowDim2::Dual>>
+inverse(DTensor<VectorIndexSet<RowDim1, RowDim2>, VectorIndexSet<ColDim1, ColDim2>> arr)
 {
-    std::array<std::array<double, 2>, 2> inv;
+    using OutRowDim1 = typename ColDim1::Dual;
+    using OutRowDim2 = typename ColDim2::Dual;
+    using OutColDim1 = typename RowDim1::Dual;
+    using OutColDim2 = typename RowDim2::Dual;
+    DTensor<VectorIndexSet<OutRowDim1, OutRowDim2>, VectorIndexSet<OutColDim1, OutColDim2>> inv;
     double det = determinant(arr);
-    inv[0][0] = arr[1][1] / det;
-    inv[1][0] = -arr[1][0] / det;
-    inv[0][1] = -arr[0][1] / det;
-    inv[1][1] = arr[0][0] / det;
+    ddcHelper::get<OutRowDim1, OutColDim1>(inv) = ddcHelper::get<RowDim2, ColDim2>(arr) / det;
+    ddcHelper::get<OutRowDim2, OutColDim1>(inv) = -ddcHelper::get<RowDim2, ColDim1>(arr) / det;
+    ddcHelper::get<OutRowDim1, OutColDim2>(inv) = -ddcHelper::get<RowDim1, ColDim2>(arr) / det;
+    ddcHelper::get<OutRowDim2, OutColDim2>(inv) = ddcHelper::get<RowDim1, ColDim1>(arr) / det;
     return inv;
 }
diff --git a/tests/geometryRTheta/advection_field_rtheta/advection_field_gtest.cpp b/tests/geometryRTheta/advection_field_rtheta/advection_field_gtest.cpp
index aca73e1e4b..500438cfe5 100644
--- a/tests/geometryRTheta/advection_field_rtheta/advection_field_gtest.cpp
+++ b/tests/geometryRTheta/advection_field_rtheta/advection_field_gtest.cpp
@@ -247,16 +247,16 @@ TEST(AdvectionFieldRThetaComputation, TestAdvectionFieldFinder)
     ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
         CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-        std::array<std::array<double, 2>, 2> J;
-        to_physical_mapping.jacobian_matrix(coord_rtheta, J);
+        // Jacobian matrix
+        DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> J
+                = to_physical_mapping.jacobian_matrix(coord_rtheta);
+
+        DVector<X, Y> adv_field
+                = tensor_mul(index<'i', 'j'>(J), index<'j'>(advection_field_rtheta(irtheta)));
 
         // computation made in BslAdvectionRTheta operator:
-        ddcHelper::get<X>(advection_field_xy_from_rtheta)(irtheta)
-                = ddcHelper::get<R>(advection_field_rtheta)(irtheta) * J[0][0]
-                  + ddcHelper::get<Theta>(advection_field_rtheta)(irtheta) * J[0][1];
-        ddcHelper::get<Y>(advection_field_xy_from_rtheta)(irtheta)
-                = ddcHelper::get<R>(advection_field_rtheta)(irtheta) * J[1][0]
-                  + ddcHelper::get<Theta>(advection_field_rtheta)(irtheta) * J[1][1];
+        ddcHelper::get<X>(advection_field_xy_from_rtheta)(irtheta) = ddcHelper::get<X>(adv_field);
+        ddcHelper::get<Y>(advection_field_xy_from_rtheta)(irtheta) = ddcHelper::get<Y>(adv_field);
 
         // compare
         ddcHelper::get<X>(difference_between_fields_xy_and_rtheta)(irtheta)
diff --git a/tests/geometryRTheta/advection_rtheta/test_cases.hpp b/tests/geometryRTheta/advection_rtheta/test_cases.hpp
index 3c823ba02c..0f7f7dea08 100644
--- a/tests/geometryRTheta/advection_rtheta/test_cases.hpp
+++ b/tests/geometryRTheta/advection_rtheta/test_cases.hpp
@@ -6,6 +6,7 @@
 #include "circular_to_cartesian.hpp"
 #include "ddc_aliases.hpp"
 #include "geometry.hpp"
+#include "indexed_tensor.hpp"
 #include "math_tools.hpp"
 #include "paraconfpp.hpp"
 #include "params.yaml.hpp"
@@ -362,8 +363,7 @@ class AdvectionField_translation
 class AdvectionField_rotation
 {
 private:
-    double const m_vr;
-    double const m_vtheta;
+    DVector<R, Theta> m_v;
     CartesianToCircular<X, Y, R, Theta> const m_physical_to_logical_mapping;
     CircularToCartesian<R, Theta, X, Y> const m_logical_to_physical_mapping;
 
@@ -378,8 +378,7 @@ class AdvectionField_rotation
      *      The constant second polar component of the advection field in the physical index range.
      */
     AdvectionField_rotation(CoordVr vr, CoordVtheta vtheta)
-        : m_vr(vr)
-        , m_vtheta(vtheta)
+        : m_v(vr, vtheta)
         , m_physical_to_logical_mapping()
         , m_logical_to_physical_mapping()
     {
@@ -401,11 +400,9 @@ class AdvectionField_rotation
     KOKKOS_FUNCTION CoordXY operator()(CoordXY const coord, double const t) const
     {
         CoordRTheta const coord_rtheta(m_physical_to_logical_mapping(coord));
-        std::array<std::array<double, 2>, 2> jacobian;
-        m_logical_to_physical_mapping.jacobian_matrix(coord_rtheta, jacobian);
-        double const vx = m_vr * jacobian[0][0] + m_vtheta * jacobian[0][1];
-        double const vy = m_vr * jacobian[1][0] + m_vtheta * jacobian[1][1];
-        return CoordXY(vx, vy);
+        Tensor jacobian = m_logical_to_physical_mapping.jacobian_matrix(coord_rtheta);
+        DVector<X, Y> v = tensor_mul(index<'i', 'j'>(jacobian), index<'j'>(m_v));
+        return CoordXY(ddcHelper::get<X>(v), ddcHelper::get<Y>(v));
     }
 
     /**
@@ -421,8 +418,7 @@ class AdvectionField_rotation
     KOKKOS_FUNCTION CoordXY exact_feet(CoordXY coord_xy, double const t) const
     {
         CoordRTheta const coord_rtheta(m_physical_to_logical_mapping(coord_xy));
-        CoordRTheta const velocity(m_vr, m_vtheta);
-        return m_logical_to_physical_mapping(coord_rtheta - t * velocity);
+        return m_logical_to_physical_mapping(coord_rtheta - t * m_v);
     }
 };
 
diff --git a/tests/interpolation/polar_bsplines.cpp b/tests/interpolation/polar_bsplines.cpp
index d1a1d95b5c..4585d5f700 100644
--- a/tests/interpolation/polar_bsplines.cpp
+++ b/tests/interpolation/polar_bsplines.cpp
@@ -22,21 +22,49 @@ struct PolarBsplineFixture<std::tuple<
         std::integral_constant<int, C>,
         std::integral_constant<bool, Uniform>>> : public testing::Test
 {
+    struct R_cov;
+    struct Theta_cov;
     struct R
     {
-        static constexpr bool PERIODIC = false;
+        static bool constexpr PERIODIC = false;
+        static bool constexpr IS_COVARIANT = false;
+        static bool constexpr IS_CONTRAVARIANT = true;
+        using Dual = R_cov;
     };
+
     struct Theta
     {
-        static constexpr bool PERIODIC = true;
+        static bool constexpr PERIODIC = true;
+        static bool constexpr IS_COVARIANT = false;
+        static bool constexpr IS_CONTRAVARIANT = true;
+        using Dual = Theta_cov;
+    };
+    struct R_cov
+    {
+        static bool constexpr PERIODIC = false;
+        static bool constexpr IS_COVARIANT = true;
+        static bool constexpr IS_CONTRAVARIANT = false;
+        using Dual = R;
+    };
+
+    struct Theta_cov
+    {
+        static bool constexpr PERIODIC = true;
+        static bool constexpr IS_COVARIANT = true;
+        static bool constexpr IS_CONTRAVARIANT = false;
+        using Dual = Theta;
     };
     struct X
     {
-        static constexpr bool PERIODIC = false;
+        static bool constexpr IS_COVARIANT = true;
+        static bool constexpr IS_CONTRAVARIANT = true;
+        using Dual = X;
     };
     struct Y
     {
-        static constexpr bool PERIODIC = false;
+        static bool constexpr IS_COVARIANT = true;
+        static bool constexpr IS_CONTRAVARIANT = true;
+        using Dual = Y;
     };
     static constexpr std::size_t spline_degree = D;
     static constexpr int continuity = C;
diff --git a/tests/interpolation/polar_splines.cpp b/tests/interpolation/polar_splines.cpp
index cb6422a168..fc45c41ce2 100644
--- a/tests/interpolation/polar_splines.cpp
+++ b/tests/interpolation/polar_splines.cpp
@@ -17,19 +17,45 @@
 
 namespace {
 
+struct R_cov;
+struct Theta_cov;
 struct R
 {
     static constexpr bool PERIODIC = false;
+    static bool constexpr IS_COVARIANT = false;
+    static bool constexpr IS_CONTRAVARIANT = true;
+    using Dual = R_cov;
 };
 struct Theta
 {
     static constexpr bool PERIODIC = true;
+    static bool constexpr IS_COVARIANT = false;
+    static bool constexpr IS_CONTRAVARIANT = true;
+    using Dual = Theta_cov;
+};
+struct R_cov
+{
+    static bool constexpr IS_COVARIANT = true;
+    static bool constexpr IS_CONTRAVARIANT = false;
+    using Dual = R;
+};
+struct Theta_cov
+{
+    static bool constexpr IS_COVARIANT = true;
+    static bool constexpr IS_CONTRAVARIANT = false;
+    using Dual = Theta;
 };
 struct X
 {
+    static bool constexpr IS_COVARIANT = true;
+    static bool constexpr IS_CONTRAVARIANT = true;
+    using Dual = X;
 };
 struct Y
 {
+    static bool constexpr IS_COVARIANT = true;
+    static bool constexpr IS_CONTRAVARIANT = true;
+    using Dual = Y;
 };
 
 static constexpr std::size_t spline_r_degree = DEGREE_R;
diff --git a/tests/mapping/mapping_jacobian.cpp b/tests/mapping/mapping_jacobian.cpp
index caccaef3b6..615c190fad 100644
--- a/tests/mapping/mapping_jacobian.cpp
+++ b/tests/mapping/mapping_jacobian.cpp
@@ -17,38 +17,6 @@
 #include "mesh_builder.hpp"
 
 
-namespace {
-
-using Matrix_2x2 = std::array<std::array<double, 2>, 2>;
-
-/**
- * @brief Check if the product of the matrix and inv_matrix gives the identity matrix.
- *
- * The error tolerance is given at 1e-15.
- *
- * @param[in] matrix
- * 			The Jacobian matrix of the mapping.
- * @param[in] inv_matrix
- * 			The inverse Jacobian matrix of the mapping.
- */
-void check_inverse(Matrix_2x2 matrix, Matrix_2x2 inv_matrix)
-{
-    const double TOL = 1e-15;
-    constexpr std::size_t N = 2;
-
-    for (std::size_t i(0); i < N; ++i) {
-        for (std::size_t j(0); j < N; ++j) {
-            double id_val = 0.0;
-            for (std::size_t k(0); k < N; ++k) {
-                id_val += matrix[i][k] * inv_matrix[k][j];
-            }
-            EXPECT_NEAR(id_val, static_cast<double>(i == j), TOL);
-        }
-    }
-}
-
-} // namespace
-
 
 /**
  * @brief A class for the Google tests.
@@ -72,12 +40,10 @@ TEST_P(InvJacobianMatrix, InverseMatrixCircMap)
 
     // Test for each coordinates if the inv_Jacobian_matrix is the inverse of the Jacobian_matrix
     ddc::for_each(grid, [&](IdxRTheta const irtheta) {
-        Matrix_2x2 Jacobian_matrix;
-        Matrix_2x2 inv_Jacobian_matrix = inv_jacobian(coords(irtheta));
-
-        mapping.jacobian_matrix(coords(irtheta), Jacobian_matrix);
-
-        check_inverse(Jacobian_matrix, inv_Jacobian_matrix);
+        check_inverse_tensor(
+                mapping.jacobian_matrix(coords(irtheta)),
+                inv_jacobian(coords(irtheta)),
+                1e-15);
     });
 }
 
@@ -95,13 +61,10 @@ TEST_P(InvJacobianMatrix, InverseMatrixCzarMap)
 
     // Test for each coordinates if the inv_Jacobian_matrix is the inverse of the Jacobian_matrix
     ddc::for_each(grid, [&](IdxRTheta const irtheta) {
-        Matrix_2x2 Jacobian_matrix;
-        Matrix_2x2 inv_Jacobian_matrix;
-
-        mapping.jacobian_matrix(coords(irtheta), Jacobian_matrix);
-        mapping.inv_jacobian_matrix(coords(irtheta), inv_Jacobian_matrix);
-
-        check_inverse(Jacobian_matrix, inv_Jacobian_matrix);
+        check_inverse_tensor(
+                mapping.jacobian_matrix(coords(irtheta)),
+                mapping.inv_jacobian_matrix(coords(irtheta)),
+                1e-15);
     });
 }
 
@@ -157,12 +120,10 @@ TEST_P(InvJacobianMatrix, InverseMatrixDiscCzarMap)
         const CoordRTheta coord_rtheta(ddc::coordinate(irtheta));
         const double r = ddc::get<R>(coord_rtheta);
         if (fabs(r) > 1e-15) {
-            Matrix_2x2 Jacobian_matrix;
-            Matrix_2x2 inv_Jacobian_matrix = inv_jacobian(coord_rtheta);
-
-            mapping.jacobian_matrix(coord_rtheta, Jacobian_matrix);
-
-            check_inverse(Jacobian_matrix, inv_Jacobian_matrix);
+            check_inverse_tensor(
+                    mapping.jacobian_matrix(coord_rtheta),
+                    inv_jacobian(coord_rtheta),
+                    1e-15);
         }
     });
 }
diff --git a/tests/mapping/mapping_jacobian_matrix_coef.cpp b/tests/mapping/mapping_jacobian_matrix_coef.cpp
index 3882ccd5ca..751fd1f8fb 100644
--- a/tests/mapping/mapping_jacobian_matrix_coef.cpp
+++ b/tests/mapping/mapping_jacobian_matrix_coef.cpp
@@ -17,35 +17,6 @@
 #include "mesh_builder.hpp"
 
 
-namespace {
-
-using Matrix_2x2 = std::array<std::array<double, 2>, 2>;
-
-/**
- * @brief Check if the two matrix given as input are the same.
- *
- * The error tolerance is given at 1e-14.
- *
- * @param[in] matrix
- * 			The matrix defined with the matrix function.
- * @param[in] matrix_coeff
- * 			The matrix defined with the matrix coefficient functions.
- */
-void check_matrix(Matrix_2x2 matrix, Matrix_2x2 matrix_coeff)
-{
-    const double TOL = 1e-13;
-    constexpr std::size_t N = 2;
-
-    for (std::size_t i(0); i < N; ++i) {
-        for (std::size_t j(0); j < N; ++j) {
-            const double id_val = fabs(matrix[i][j] - matrix_coeff[i][j]);
-            EXPECT_NEAR(id_val, 0., TOL);
-        }
-    }
-}
-
-} // namespace
-
 
 /**
  * @brief A class for the Google tests.
@@ -72,30 +43,30 @@ TEST_P(JacobianMatrixAndJacobianCoefficients, MatrixCircMap)
     //are the same as the coefficients in the matrix function.
     // --- for the Jacobian matrix:
     ddc::for_each(grid, [&](IdxRTheta const irtheta) {
-        Matrix_2x2 Jacobian_matrix;
-        Matrix_2x2 Jacobian_matrix_coeff;
+        Tensor Jacobian_matrix = mapping.jacobian_matrix(coords(irtheta));
 
-        mapping.jacobian_matrix(coords(irtheta), Jacobian_matrix);
-        Jacobian_matrix_coeff[0][0] = mapping.jacobian_11(coords(irtheta));
-        Jacobian_matrix_coeff[0][1] = mapping.jacobian_12(coords(irtheta));
-        Jacobian_matrix_coeff[1][0] = mapping.jacobian_21(coords(irtheta));
-        Jacobian_matrix_coeff[1][1] = mapping.jacobian_22(coords(irtheta));
+        DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> Jacobian_matrix_coeff;
+        ddcHelper::get<X, R_cov>(Jacobian_matrix_coeff) = mapping.jacobian_11(coords(irtheta));
+        ddcHelper::get<X, Theta_cov>(Jacobian_matrix_coeff) = mapping.jacobian_12(coords(irtheta));
+        ddcHelper::get<Y, R_cov>(Jacobian_matrix_coeff) = mapping.jacobian_21(coords(irtheta));
+        ddcHelper::get<Y, Theta_cov>(Jacobian_matrix_coeff) = mapping.jacobian_22(coords(irtheta));
 
-        check_matrix(Jacobian_matrix, Jacobian_matrix_coeff);
+        EXPECT_TRUE(Jacobian_matrix == Jacobian_matrix_coeff);
     });
 
     // --- for the inverse Jacobian matrix:
     ddc::for_each(grid, [&](IdxRTheta const irtheta) {
-        Matrix_2x2 inv_Jacobian_matrix;
-        Matrix_2x2 inv_Jacobian_matrix_coeff;
+        Tensor inv_Jacobian_matrix = mapping.inv_jacobian_matrix(coords(irtheta));
 
-        mapping.inv_jacobian_matrix(coords(irtheta), inv_Jacobian_matrix);
-        inv_Jacobian_matrix_coeff[0][0] = mapping.inv_jacobian_11(coords(irtheta));
-        inv_Jacobian_matrix_coeff[0][1] = mapping.inv_jacobian_12(coords(irtheta));
-        inv_Jacobian_matrix_coeff[1][0] = mapping.inv_jacobian_21(coords(irtheta));
-        inv_Jacobian_matrix_coeff[1][1] = mapping.inv_jacobian_22(coords(irtheta));
+        DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X, Y>> inv_Jacobian_matrix_coeff;
+        ddcHelper::get<R, X>(inv_Jacobian_matrix_coeff) = mapping.inv_jacobian_11(coords(irtheta));
+        ddcHelper::get<R, Y>(inv_Jacobian_matrix_coeff) = mapping.inv_jacobian_12(coords(irtheta));
+        ddcHelper::get<Theta, X>(inv_Jacobian_matrix_coeff)
+                = mapping.inv_jacobian_21(coords(irtheta));
+        ddcHelper::get<Theta, Y>(inv_Jacobian_matrix_coeff)
+                = mapping.inv_jacobian_22(coords(irtheta));
 
-        check_matrix(inv_Jacobian_matrix, inv_Jacobian_matrix_coeff);
+        EXPECT_TRUE(inv_Jacobian_matrix == inv_Jacobian_matrix_coeff);
     });
 }
 
@@ -116,30 +87,31 @@ TEST_P(JacobianMatrixAndJacobianCoefficients, MatrixCzarMap)
     //are the same as the coefficients in the matrix function.
     // --- for the Jacobian matrix:
     ddc::for_each(grid, [&](IdxRTheta const irtheta) {
-        Matrix_2x2 Jacobian_matrix;
-        Matrix_2x2 Jacobian_matrix_coeff;
+        Tensor Jacobian_matrix = mapping.jacobian_matrix(coords(irtheta));
 
-        mapping.jacobian_matrix(coords(irtheta), Jacobian_matrix);
-        Jacobian_matrix_coeff[0][0] = mapping.jacobian_11(coords(irtheta));
-        Jacobian_matrix_coeff[0][1] = mapping.jacobian_12(coords(irtheta));
-        Jacobian_matrix_coeff[1][0] = mapping.jacobian_21(coords(irtheta));
-        Jacobian_matrix_coeff[1][1] = mapping.jacobian_22(coords(irtheta));
+        DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> Jacobian_matrix_coeff;
+        ddcHelper::get<X, R_cov>(Jacobian_matrix_coeff) = mapping.jacobian_11(coords(irtheta));
+        ddcHelper::get<X, Theta_cov>(Jacobian_matrix_coeff) = mapping.jacobian_12(coords(irtheta));
+        ddcHelper::get<Y, R_cov>(Jacobian_matrix_coeff) = mapping.jacobian_21(coords(irtheta));
+        ddcHelper::get<Y, Theta_cov>(Jacobian_matrix_coeff) = mapping.jacobian_22(coords(irtheta));
 
-        check_matrix(Jacobian_matrix, Jacobian_matrix_coeff);
+        EXPECT_TRUE(Jacobian_matrix == Jacobian_matrix_coeff);
     });
 
     InverseJacobianMatrix inv_jacobian(mapping);
     // --- for the inverseJacobian matrix:
     ddc::for_each(grid, [&](IdxRTheta const irtheta) {
-        Matrix_2x2 inv_Jacobian_matrix = inv_jacobian(coords(irtheta));
-        Matrix_2x2 inv_Jacobian_matrix_coeff;
+        Tensor inv_Jacobian_matrix = inv_jacobian(coords(irtheta));
 
-        inv_Jacobian_matrix_coeff[0][0] = inv_jacobian.inv_jacobian_11(coords(irtheta));
-        inv_Jacobian_matrix_coeff[0][1] = inv_jacobian.inv_jacobian_12(coords(irtheta));
-        inv_Jacobian_matrix_coeff[1][0] = inv_jacobian.inv_jacobian_21(coords(irtheta));
-        inv_Jacobian_matrix_coeff[1][1] = inv_jacobian.inv_jacobian_22(coords(irtheta));
+        DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X, Y>> inv_Jacobian_matrix_coeff;
+        ddcHelper::get<R, X>(inv_Jacobian_matrix_coeff) = mapping.inv_jacobian_11(coords(irtheta));
+        ddcHelper::get<R, Y>(inv_Jacobian_matrix_coeff) = mapping.inv_jacobian_12(coords(irtheta));
+        ddcHelper::get<Theta, X>(inv_Jacobian_matrix_coeff)
+                = mapping.inv_jacobian_21(coords(irtheta));
+        ddcHelper::get<Theta, Y>(inv_Jacobian_matrix_coeff)
+                = mapping.inv_jacobian_22(coords(irtheta));
 
-        check_matrix(inv_Jacobian_matrix, inv_Jacobian_matrix_coeff);
+        EXPECT_TRUE(inv_Jacobian_matrix == inv_Jacobian_matrix_coeff);
     });
 }
 
@@ -199,28 +171,31 @@ TEST_P(JacobianMatrixAndJacobianCoefficients, MatrixDiscCzarMap)
         const double r = ddc::get<R>(coord_rtheta);
         if (fabs(r) > 1e-15) {
             // --- for the Jacobian matrix:
-            Matrix_2x2 Jacobian_matrix;
-            Matrix_2x2 Jacobian_matrix_coeff;
+            Tensor Jacobian_matrix = mapping.jacobian_matrix(coord_rtheta);
 
-            mapping.jacobian_matrix(coord_rtheta, Jacobian_matrix);
-            Jacobian_matrix_coeff[0][0] = mapping.jacobian_11(coord_rtheta);
-            Jacobian_matrix_coeff[0][1] = mapping.jacobian_12(coord_rtheta);
-            Jacobian_matrix_coeff[1][0] = mapping.jacobian_21(coord_rtheta);
-            Jacobian_matrix_coeff[1][1] = mapping.jacobian_22(coord_rtheta);
+            DTensor<VectorIndexSet<X, Y>, VectorIndexSet<R_cov, Theta_cov>> Jacobian_matrix_coeff;
+            ddcHelper::get<X, R_cov>(Jacobian_matrix_coeff) = mapping.jacobian_11(coord_rtheta);
+            ddcHelper::get<X, Theta_cov>(Jacobian_matrix_coeff) = mapping.jacobian_12(coord_rtheta);
+            ddcHelper::get<Y, R_cov>(Jacobian_matrix_coeff) = mapping.jacobian_21(coord_rtheta);
+            ddcHelper::get<Y, Theta_cov>(Jacobian_matrix_coeff) = mapping.jacobian_22(coord_rtheta);
 
-            check_matrix(Jacobian_matrix, Jacobian_matrix_coeff);
+            EXPECT_TRUE(Jacobian_matrix == Jacobian_matrix_coeff);
 
 
             // --- for the inverse Jacobian matrix:
-            Matrix_2x2 inv_Jacobian_matrix = inv_jacobian(coord_rtheta);
-            Matrix_2x2 inv_Jacobian_matrix_coeff;
-
-            inv_Jacobian_matrix_coeff[0][0] = inv_jacobian.inv_jacobian_11(coord_rtheta);
-            inv_Jacobian_matrix_coeff[0][1] = inv_jacobian.inv_jacobian_12(coord_rtheta);
-            inv_Jacobian_matrix_coeff[1][0] = inv_jacobian.inv_jacobian_21(coord_rtheta);
-            inv_Jacobian_matrix_coeff[1][1] = inv_jacobian.inv_jacobian_22(coord_rtheta);
-
-            check_matrix(inv_Jacobian_matrix, inv_Jacobian_matrix_coeff);
+            Tensor inv_Jacobian_matrix = inv_jacobian(coord_rtheta);
+
+            DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X, Y>> inv_Jacobian_matrix_coeff;
+            ddcHelper::get<R, X>(inv_Jacobian_matrix_coeff)
+                    = inv_jacobian.inv_jacobian_11(coord_rtheta);
+            ddcHelper::get<R, Y>(inv_Jacobian_matrix_coeff)
+                    = inv_jacobian.inv_jacobian_12(coord_rtheta);
+            ddcHelper::get<Theta, X>(inv_Jacobian_matrix_coeff)
+                    = inv_jacobian.inv_jacobian_21(coord_rtheta);
+            ddcHelper::get<Theta, Y>(inv_Jacobian_matrix_coeff)
+                    = inv_jacobian.inv_jacobian_22(coord_rtheta);
+
+            EXPECT_TRUE(inv_Jacobian_matrix == inv_Jacobian_matrix_coeff);
         }
     });
 }
diff --git a/tests/mapping/pseudo_cartesian_jacobian_matrix.cpp b/tests/mapping/pseudo_cartesian_jacobian_matrix.cpp
index 0bdb4134d6..25e1e0ad7b 100644
--- a/tests/mapping/pseudo_cartesian_jacobian_matrix.cpp
+++ b/tests/mapping/pseudo_cartesian_jacobian_matrix.cpp
@@ -8,6 +8,7 @@
 #include "czarny_to_cartesian.hpp"
 #include "discrete_mapping_builder.hpp"
 #include "discrete_to_cartesian.hpp"
+#include "geometry_pseudo_cartesian.hpp"
 #include "math_tools.hpp"
 
 
@@ -21,23 +22,46 @@ class PseudoCartesianJacobianMatrixTest
 public:
     struct X
     {
+        static bool constexpr IS_COVARIANT = true;
+        static bool constexpr IS_CONTRAVARIANT = true;
+        using Dual = X;
     };
     struct Y
     {
+        static bool constexpr IS_COVARIANT = true;
+        static bool constexpr IS_CONTRAVARIANT = true;
+        using Dual = Y;
     };
-    struct X_pc
+    struct R_cov;
+    struct Theta_cov;
+    struct R
     {
+        static bool constexpr PERIODIC = false;
+        static bool constexpr IS_COVARIANT = false;
+        static bool constexpr IS_CONTRAVARIANT = true;
+        using Dual = R_cov;
     };
-    struct Y_pc
+    struct Theta
     {
+        static bool constexpr PERIODIC = true;
+        static bool constexpr IS_COVARIANT = false;
+        static bool constexpr IS_CONTRAVARIANT = true;
+        using Dual = Theta_cov;
     };
-    struct R
+    struct R_cov
     {
         static bool constexpr PERIODIC = false;
+        static bool constexpr IS_COVARIANT = true;
+        static bool constexpr IS_CONTRAVARIANT = false;
+        using Dual = R;
     };
-    struct Theta
+
+    struct Theta_cov
     {
         static bool constexpr PERIODIC = true;
+        static bool constexpr IS_COVARIANT = true;
+        static bool constexpr IS_CONTRAVARIANT = false;
+        using Dual = Theta;
     };
 
 
@@ -194,18 +218,15 @@ class PseudoCartesianJacobianMatrixTest
                 theta_extrapolation_rule,
                 theta_extrapolation_rule);
 
-        Matrix_2x2 analytical_matrix;
-        Matrix_2x2 discrete_matrix;
-
 
         // --- CIRCULAR MAPPING ---------------------------------------------------------------------------
         std::cout << " - Nr x Nt  = " << Nr << " x " << Nt << std::endl
                   << "   - Circular mapping: ";
         const CircularToCartesian<R, Theta, X, Y> circ_to_cart;
-        const CartesianToCircular<X_pc, Y_pc, R, Theta> pseudo_cart_to_circ;
+        const CartesianToCircular<X_pC, Y_pC, R, Theta> pseudo_cart_to_circ;
         using PseudoCartToCircToCart = CombinedMapping<
                 CircularToCartesian<R, Theta, X, Y>,
-                CartesianToCircular<X_pc, Y_pc, R, Theta>>;
+                CartesianToCircular<X_pC, Y_pC, R, Theta>>;
         const PseudoCartToCircToCart
                 pseudo_cart_to_circ_to_cart(circ_to_cart, pseudo_cart_to_circ, 1e-12);
         DiscreteToCartesianBuilder<X, Y, SplineRThetaBuilder_host, SplineRThetaEvaluator>
@@ -217,7 +238,7 @@ class PseudoCartesianJacobianMatrixTest
         DiscreteToCartesian discrete_mapping_circ_to_cart = mapping_builder_circ();
         using DiscreteMappingCirc = CombinedMapping<
                 decltype(discrete_mapping_circ_to_cart),
-                CartesianToCircular<X_pc, Y_pc, R, Theta>>;
+                CartesianToCircular<X_pC, Y_pC, R, Theta>>;
         DiscreteMappingCirc discrete_pseudo_cart_to_circ_to_cart(
                 discrete_mapping_circ_to_cart,
                 pseudo_cart_to_circ,
@@ -227,8 +248,8 @@ class PseudoCartesianJacobianMatrixTest
                 pseudo_cart_to_circ_to_cart);
         InvJacobianOPoint<DiscreteMappingCirc, CoordRTheta> inv_o_point_discrete_circ(
                 discrete_pseudo_cart_to_circ_to_cart);
-        analytical_matrix = inv_o_point_analytical_circ();
-        discrete_matrix = inv_o_point_discrete_circ();
+        Tensor analytical_matrix = inv_o_point_analytical_circ();
+        Tensor discrete_matrix = inv_o_point_discrete_circ();
         double max_diff_circ
                 = check_same(analytical_matrix, discrete_matrix, 1e-5 * ipow(16. / double(N), 4));
         std::cout << max_diff_circ << std::endl;
@@ -240,7 +261,7 @@ class PseudoCartesianJacobianMatrixTest
         const CzarnyToCartesian<R, Theta, X, Y> czarny_to_cart(0.3, 1.4);
         using PseudoCartToCzarnyToCart = CombinedMapping<
                 CzarnyToCartesian<R, Theta, X, Y>,
-                CartesianToCircular<X_pc, Y_pc, R, Theta>>;
+                CartesianToCircular<X_pC, Y_pC, R, Theta>>;
         const PseudoCartToCzarnyToCart
                 pseudo_cart_to_czarny_to_cart(czarny_to_cart, pseudo_cart_to_circ, 1e-12);
         DiscreteToCartesianBuilder<X, Y, SplineRThetaBuilder_host, SplineRThetaEvaluator>
@@ -252,7 +273,7 @@ class PseudoCartesianJacobianMatrixTest
         DiscreteToCartesian discrete_mapping_czarny_to_cart = mapping_builder_czarny();
         using DiscreteMappingCzarny = CombinedMapping<
                 decltype(discrete_mapping_czarny_to_cart),
-                CartesianToCircular<X_pc, Y_pc, R, Theta>>;
+                CartesianToCircular<X_pC, Y_pC, R, Theta>>;
         DiscreteMappingCzarny discrete_pseudo_cart_to_czarny_to_cart(
                 discrete_mapping_czarny_to_cart,
                 pseudo_cart_to_circ,
@@ -288,20 +309,22 @@ class PseudoCartesianJacobianMatrixTest
      *
      * @return A double with the infinity norm of the difference.
      */
-    double check_same(Matrix_2x2 const& matrix_1, Matrix_2x2 const& matrix_2, double TOL)
+    double check_same(
+            DTensor<VectorIndexSet<X_pC, Y_pC>, VectorIndexSet<X, Y>> const& matrix_1,
+            DTensor<VectorIndexSet<X_pC, Y_pC>, VectorIndexSet<X, Y>> const& matrix_2,
+            double TOL)
     {
-        std::size_t size = 2;
-
-        double max_diff = 0.;
-        for (std::size_t i(0); i < size; ++i) {
-            for (std::size_t j(0); j < size; ++j) {
-                double const diff = fabs(matrix_1[i][j] - matrix_2[i][j]);
-                max_diff = diff > max_diff ? diff : max_diff;
-
-                EXPECT_NEAR(matrix_1[i][j], matrix_2[i][j], TOL);
-            }
-        }
-        return max_diff;
+        EXPECT_NEAR((ddcHelper::get<X_pC, X>(matrix_1)), (ddcHelper::get<X_pC, X>(matrix_2)), TOL);
+        EXPECT_NEAR((ddcHelper::get<X_pC, Y>(matrix_1)), (ddcHelper::get<X_pC, Y>(matrix_2)), TOL);
+        EXPECT_NEAR((ddcHelper::get<Y_pC, X>(matrix_1)), (ddcHelper::get<Y_pC, X>(matrix_2)), TOL);
+        EXPECT_NEAR((ddcHelper::get<Y_pC, Y>(matrix_1)), (ddcHelper::get<Y_pC, Y>(matrix_2)), TOL);
+
+        DTensor<VectorIndexSet<X_pC, Y_pC>, VectorIndexSet<X, Y>> diff = matrix_1 - matrix_2;
+        return std::max(
+                {fabs(ddcHelper::get<X_pC, X>(diff)),
+                 fabs(ddcHelper::get<X_pC, Y>(diff)),
+                 fabs(ddcHelper::get<Y_pC, X>(diff)),
+                 fabs(ddcHelper::get<Y_pC, Y>(diff))});
     }
 };
 
diff --git a/tests/mapping/refined_discrete_mapping.cpp b/tests/mapping/refined_discrete_mapping.cpp
index 07dc382847..e8dcbea43b 100644
--- a/tests/mapping/refined_discrete_mapping.cpp
+++ b/tests/mapping/refined_discrete_mapping.cpp
@@ -190,20 +190,15 @@ double check_Jacobian_on_grid(
     ddc::for_each(idx_range, [&](IdxRTheta const irtheta) {
         const CoordRTheta coord(ddc::coordinate(irtheta));
 
-        Matrix_2x2 discrete_Jacobian;
-        Matrix_2x2 analytical_Jacobian;
-        mapping.jacobian_matrix(coord, discrete_Jacobian);
-        analytical_mapping.jacobian_matrix(coord, analytical_Jacobian);
-
-        const double diff_11 = double(discrete_Jacobian[0][0] - analytical_Jacobian[0][0]);
-        const double diff_12 = double(discrete_Jacobian[0][1] - analytical_Jacobian[0][1]);
-        const double diff_21 = double(discrete_Jacobian[1][0] - analytical_Jacobian[1][0]);
-        const double diff_22 = double(discrete_Jacobian[1][1] - analytical_Jacobian[1][1]);
-
-        max_err = max_err > diff_11 ? max_err : diff_11;
-        max_err = max_err > diff_12 ? max_err : diff_12;
-        max_err = max_err > diff_21 ? max_err : diff_21;
-        max_err = max_err > diff_22 ? max_err : diff_22;
+        Tensor discrete_Jacobian = mapping.jacobian_matrix(coord);
+        Tensor analytical_Jacobian = analytical_mapping.jacobian_matrix(coord);
+
+        Tensor diff = discrete_Jacobian - analytical_Jacobian;
+
+        max_err = std::max(max_err, ddcHelper::get<X, R_cov>(diff));
+        max_err = std::max(max_err, ddcHelper::get<X, Theta_cov>(diff));
+        max_err = std::max(max_err, ddcHelper::get<Y, R_cov>(diff));
+        max_err = std::max(max_err, ddcHelper::get<Y, Theta_cov>(diff));
     });
 
     return max_err;
@@ -267,20 +262,15 @@ double check_Jacobian_not_on_grid(
     ddc::for_each(idx_range, [&](IdxRTheta const irtheta) {
         const CoordRTheta coord(coords(irtheta));
 
-        Matrix_2x2 discrete_Jacobian;
-        Matrix_2x2 analytical_Jacobian;
-        mapping.jacobian_matrix(coord, discrete_Jacobian);
-        analytical_mapping.jacobian_matrix(coord, analytical_Jacobian);
+        Tensor discrete_Jacobian = mapping.jacobian_matrix(coord);
+        Tensor analytical_Jacobian = analytical_mapping.jacobian_matrix(coord);
 
-        const double diff_11 = double(discrete_Jacobian[0][0] - analytical_Jacobian[0][0]);
-        const double diff_12 = double(discrete_Jacobian[0][1] - analytical_Jacobian[0][1]);
-        const double diff_21 = double(discrete_Jacobian[1][0] - analytical_Jacobian[1][0]);
-        const double diff_22 = double(discrete_Jacobian[1][1] - analytical_Jacobian[1][1]);
+        Tensor diff = discrete_Jacobian - analytical_Jacobian;
 
-        max_err = max_err > diff_11 ? max_err : diff_11;
-        max_err = max_err > diff_12 ? max_err : diff_12;
-        max_err = max_err > diff_21 ? max_err : diff_21;
-        max_err = max_err > diff_22 ? max_err : diff_22;
+        max_err = std::max(max_err, ddcHelper::get<X, R_cov>(diff));
+        max_err = std::max(max_err, ddcHelper::get<X, Theta_cov>(diff));
+        max_err = std::max(max_err, ddcHelper::get<Y, R_cov>(diff));
+        max_err = std::max(max_err, ddcHelper::get<Y, Theta_cov>(diff));
     });
 
     return max_err;
@@ -352,19 +342,17 @@ double check_pseudo_Cart(
     InvJacobianOPoint<CombinedMapping<Mapping, PseudoCartToCirc>, CoordRTheta>
             inv_jacob_analytical_mapping(analytical_pseudo_cart_to_cart);
 
-    Matrix_2x2 discrete_pseudo_Cart = inv_jacob_mapping();
-    Matrix_2x2 analytical_pseudo_Cart = inv_jacob_analytical_mapping();
+    Tensor discrete_pseudo_Cart = inv_jacob_mapping();
+    Tensor analytical_pseudo_Cart = inv_jacob_analytical_mapping();
+
+    Tensor diff = discrete_pseudo_Cart - analytical_pseudo_Cart;
 
-    const double diff_11 = double(discrete_pseudo_Cart[0][0] - analytical_pseudo_Cart[0][0]);
-    const double diff_12 = double(discrete_pseudo_Cart[0][1] - analytical_pseudo_Cart[0][1]);
-    const double diff_21 = double(discrete_pseudo_Cart[1][0] - analytical_pseudo_Cart[1][0]);
-    const double diff_22 = double(discrete_pseudo_Cart[1][1] - analytical_pseudo_Cart[1][1]);
 
     double max_err = 0.;
-    max_err = max_err > diff_11 ? max_err : diff_11;
-    max_err = max_err > diff_12 ? max_err : diff_12;
-    max_err = max_err > diff_21 ? max_err : diff_21;
-    max_err = max_err > diff_22 ? max_err : diff_22;
+    max_err = std::max(max_err, ddcHelper::get<X_pC, X>(diff));
+    max_err = std::max(max_err, ddcHelper::get<X_pC, Y>(diff));
+    max_err = std::max(max_err, ddcHelper::get<Y_pC, X>(diff));
+    max_err = std::max(max_err, ddcHelper::get<Y_pC, Y>(diff));
 
     return max_err;
 }


Reviews:
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-07T15:48:18Z
user: login: PaulineVidal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T10:19:30Z
user: login: PaulineVidal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T10:20:15Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T13:13:17Z
user: login: PaulineVidal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T13:39:19Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T14:05:43Z
user: login: PaulineVidal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T14:10:16Z
user: login: PaulineVidal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T14:14:25Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T14:52:13Z
user: login: PaulineVidal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T15:47:09Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T16:06:48Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T16:29:59Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T16:40:44Z
user: login: PaulineVidal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T18:25:37Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-10T19:11:38Z
user: login: yanmnc
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T10:35:45Z
user: login: yanmnc
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T10:41:08Z

Review comments:
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: EmilyBourne
body: @PaulineVidal Why do we have:
$A_{xy} = J^{-T} A_{r\theta} |J|$
?

The |J| does not seem to be expected. Can I remove it?
![image](https://github.com/user-attachments/assets/637073e2-71af-4897-aab6-4cc7a566483d)

created_at: 2025-03-07T15:48:18Z
author_association: MEMBER
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: PaulineVidal
body: It is part of the meeting we had yesterday. I made a mistake on the definition of $A_{r,\theta}$. 
We are supposed to have $A_{x,y} = J A_{r,\theta}$. 
created_at: 2025-03-10T10:19:30Z
author_association: COLLABORATOR
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: PaulineVidal
body: Sorry I realised my comment was apparently not sent. In the meantimes, I created the PR related to this issue: https://github.com/gyselax/gyselalibxx/pull/178

created_at: 2025-03-10T10:20:15Z
author_association: COLLABORATOR
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: EmilyBourne
body: I don't think we have $A_{x,y} = J A_{r,\theta}$ here. $A$ is expressed on the covariant basis so we have $A^{x,y} = J^{-T} A^{r,\theta}$ as indicated in Edoardo's article (see screenshot above).
created_at: 2025-03-10T13:13:17Z
author_association: MEMBER
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: PaulineVidal
body: I am not sure. We start from: $$\partial_t f + A^{x,y}\cdot \nabla^{x,y} f  = 0$$
and in Edoardo's article, $\nabla^{x,y} f  = J^{-T} \nabla^{r,\theta} f $, 
so $$\partial_t f + A^{x,y}\cdot (J^{-T} \nabla^{r,\theta} f)  = 0$$
i.e. $$\partial_t f + (J^{-1} A^{x,y})\cdot \nabla^{r,\theta} f  = 0$$
so,  $$A^{r,\theta} = J^{-1} A^{x,y}$$ 
and  $$A^{x,y} = J A^{r,\theta}$$.

created_at: 2025-03-10T13:39:19Z
author_association: COLLABORATOR
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: EmilyBourne
body: I am not sure you have the correct starting point. I would say rather:
$\partial_t f + A^{x,y}\cdot \nabla_{x,y} f = 0$

In Edoardo's article: $\nabla^{x,y} \phi  = J^{-T} \nabla^{r,\theta} \phi$. This is the same $\nabla^{x,y} \phi$ that appears in the expression for $E$
We also have $\nabla^{x,y} f = G^{ij} \partial_j f$
so:
$\partial_t f + A^{x,y}\cdot \nabla_{x,y} f = 0$ is still $\partial_t f + A^x \partial_x f + A^y \nabla_{x,y} f = 0$

We therefore need to find $A^{x,y}$ from $A^{r,\theta}$, so looking at the component $i$:

$(A^{x,y})^i = G^{ij}A_{\{x,y\},j} = G^{ij} J_j^k A_{\{r,\theta\},k}$

we know that $G = JJ^{T}$ so $G^{-1} = J^{-T}J^{-1}$, remembering that $G^{ij}$ is how the elements of $G^{-1}$ are denoted, this means that $G^{ij} J_j^k = (G^{-1} J)^i = (J^{-T}J^{-1} J)^i = (J^{-T})^i$ so finally:
$(A^{x,y})^i = (J^{-T} A_{r,\theta})^i$
created_at: 2025-03-10T14:05:43Z
author_association: MEMBER
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: PaulineVidal
body: What are the difference between $\nabla_{r,\theta} \phi$ and $\nabla^{r,\theta} \phi$? What are the definition of both? 
created_at: 2025-03-10T14:10:16Z
author_association: COLLABORATOR
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: PaulineVidal
body: Ps:  I think $G = J^T J$, 
$G^{-1}J = (J^T J)^{-1} J = J^{-1} J^{-T} J $
created_at: 2025-03-10T14:14:25Z
author_association: COLLABORATOR
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: EmilyBourne
body: > we know that $G=J J^T$

except that it's $G=J^T J$ :thinking: I still think the equation in Edoardo's article is probably correct though
created_at: 2025-03-10T14:52:12Z
author_association: MEMBER
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: PaulineVidal
body: I dont say it is incorrect, I was questionning the definition of the advection field on (r,theta). 
created_at: 2025-03-10T15:47:09Z
author_association: COLLABORATOR
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: EmilyBourne
body: > What are the difference between $\nabla_{r,\theta} \phi$ and $\nabla^{r,\theta} \phi$?

I would say that in the first case we express the contravariant components, while in the second case we express the covariant components.

> What are the definition of both? 

I think (but am not super sure right now) that:
$\nabla^{r,\theta} = (\partial_r, \partial_\theta)$
while:
$\nabla_{r,\theta} = G (\partial_r, \partial_\theta)$
created_at: 2025-03-10T16:06:48Z
author_association: MEMBER
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: EmilyBourne
body: My calculation is wrong because I am have my indices in the wrong positions ^^
created_at: 2025-03-10T16:29:59Z
author_association: MEMBER
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: EmilyBourne
body: Normally we have:
$A^i = J^i_j \hat{A}^j$
$A^i = \frac{\partial X^i}{\xi^j} \hat{A}^j$
we are interested in:
$\nabla \phi_i = ? \hat{\nabla} \phi_i$
Let us note $`\hat{B}_i=\hat{\nabla}\phi_i`$
$`B_i = ? \hat{B}_i`$
$`B_i = G_{ij}B^j = G_{ij}J^j_k \hat{B}^k = G_{ij}J^j_k \hat{G}^{kl}\hat{B}_l`$
$G_{ij}$ is the metric tensor associated with the coordinates in which $B_i$ is expressed while $\hat{G}_{ij}$ is the metric tensor associated with the coordinates in which $\hat{B}_i$ is expressed. Here we have $B_i$ in cartesian coordinates so $G$ is the identity. We can therefore simplify:
$`B_i = J^j_k \hat{G}^{kl}\hat{B}_l`$
or in tensor notation:
$`B = J G^{-1} \hat{B}`$
with $G=J^TJ$, $G^{-1} = J^{-1}J^{-T}$ so $JG^{-1} = J^{-T}$ hence:
$`B = J^{-T} \hat{B}`$
created_at: 2025-03-10T16:40:44Z
author_association: MEMBER
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: PaulineVidal
body: For the notations, I think I used to see 
![image](https://github.com/user-attachments/assets/ac733974-218d-4d29-8b95-914b33836dfa)

So the $\nabla \phi$ was implicit covariant. The "contravariant gradient" should be written $d\phi$ even if it doesn't seem to fit with the notation in Edoardo's paper. It should be $d_{xy} \phi = J^{-T} d_{r,\theta} \phi$ in his article, 
with 
```math 
\left\{
\begin{aligned}
      & d_{xy} \phi = 
     \begin{bmatrix}
           \partial_x \phi \\
           \partial_y \phi 
      \end{bmatrix}, \\
      & d_{r\theta} \phi = 
     \begin{bmatrix}
           \partial_r \phi \\
           \partial_\theta \phi 
      \end{bmatrix}.
\end{aligned}
\right. 
```

And for the gradient, knowing that $\nabla^{r,\theta}  \phi = G^{-1} d_{r,\theta} \phi $, 
```math 
\nabla^{x,y} \phi 
= d_{x,y} \phi 
= J^{-T} d_{r,\theta} \phi 
=  J^{-T} G \nabla^{r,\theta} \phi 
= J \nabla^{r,\theta} \phi,
``` 

i.e. 
```math 
\left\{
\begin{aligned}
      & d_{xy} \phi = J^{-T} d_{r,\theta} \phi, \\
      & \nabla^{x,y} \phi  = J \nabla^{r,\theta} \phi.
\end{aligned}
\right. 
```

Do you agree? 
created_at: 2025-03-10T18:25:37Z
author_association: COLLABORATOR
diff_hunk: @@ -186,19 +187,15 @@ class BslAdvectionRTheta : public IAdvectionRTheta
         ddc::for_each(grid_without_Opoint, [&](IdxRTheta const irtheta) {
             CoordRTheta const coord_rtheta(ddc::coordinate(irtheta));
 
-            std::array<std::array<double, 2>, 2> inv_J = inv_jacobian_matrix(coord_rtheta);
+            Tensor inv_J = inv_jacobian_matrix(coord_rtheta);
             double const jacobian = m_mapping.jacobian(coord_rtheta);
 
-            ddcHelper::get<X>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][0]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][0]
-                                * jacobian;
-            ddcHelper::get<Y>(advection_field_xy_host)(irtheta)
-                    = ddcHelper::get<R_cov>(advection_field_rtheta)(irtheta) * inv_J[0][1]
-                              * jacobian
-                      + ddcHelper::get<Theta_cov>(advection_field_rtheta)(irtheta) * inv_J[1][1]
-                                * jacobian;
+            DVector<R_cov, Theta_cov> advec_field_rtheta = advection_field_rtheta(irtheta);
+            // advec_field_xy = J^{-T} A
+            DVector<X, Y> advec_field_xy
+                    = tensor_mul(index<'i', 'j'>(inv_J), index<'i'>(advec_field_rtheta)) * jacobian;
path: src/geometryRTheta/advection/bsl_advection_rtheta.hpp
user: login: EmilyBourne
body: Maybe we should wait until the documentation has been updated regarding covariant/contravariant notation before we finish this discussion as I think we have been mixing indices and now we need the correct indices to arrive at the expected equations.
Using your screenshot I see that $\nabla f = F^je_j$ where $`F^j = \frac{\partial f}{\partial x^i} g^{ij}$ so $DVector<R_cov, Theta_cov>$ would contains $F^r, F^\theta$

Therefore this
> I think (but am not super sure right now) that:
> $\nabla^{r,\theta} = (\partial_r, \partial_\theta)$
> while:
> $\nabla_{r,\theta} = G (\partial_r, \partial_\theta)$

is wrong it should be:
$(\nabla f)^{r,theta} = G (\partial_r, \partial_\theta)$

the dual is then $(\nabla f)^{r,theta}$ more commonly written as indicated by wikipedia $df_{r,theta}$.

This should invert your equations and allow you to find Edoardo's expression
created_at: 2025-03-10T19:11:38Z
author_association: MEMBER
diff_hunk: @@ -296,8 +296,7 @@ class Tensor
     }
 
     /**
-     * @brief An operator to subtract one tensor from another elementwise.
-     * @param val The tensor that should be subtracted from the current tensor.
+     * @brief An operator to get the negation of a tensor elementwise.
path: src/data_types/tensor.hpp
user: login: yanmnc
body: ```suggestion
     * @brief An operator to get the subtraction of a tensor elementwise.
```
created_at: 2025-03-21T12:00:59Z
author_association: CONTRIBUTOR

PR comments:
user: login: EmilyBourne
created_at: 2025-02-27T13:19:37Z
body: As #114 is ready to review, it does not make sense to complete this PR by explicitly writing 2D inverse/determinant/mat_mul functions.
author_association: MEMBER
user: login: EmilyBourne
created_at: 2025-03-07T13:00:19Z
body: Blocked by #175
author_association: MEMBER
user: login: EmilyBourne
created_at: 2025-03-19T15:14:43Z
body: Blocked by #195 
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-03-21T07:21:26Z
body: This PR is failing tests so it has been put back into draft. Please remove the draft status when the tests pass.
author_association: NONE