Repository: gyselax/gyselalibxx
Pull request #272: Add x0 and y0 parameters to circular and czarny coordinate change operators
Author: login: EmilyBourne
Labels: name: Ready to review
description: Label to be automatically added to a PR when it is ready to be reviewed
name: Ready to merge
description: Label to be automatically added to a PR when it is approved and tests are passing
Created: 2025-04-30T08:22:35Z | Merged: 2025-04-30T15:52:44Z

Add x0 and y0 parameters to circular and czarny coordinate change operators. Fixes #271 

Linked issue #271: [FEATURE]: Add x0 and y0 parameters to CurvilinearToCartesian mappings
### Mathematical operator

Add x0 and y0 parameters to CurvilinearToCartesian mappings.

### Implementation Ideas

E.g. a circular mapping should not be:
```math
x(r,\theta) = r \cos(\theta) \quad
y(r,\theta) = r \sin(\theta)
```
but rather:
```math
x(r,\theta) = r \cos(\theta) + x_0 \quad
y(r,\theta) = r \sin(\theta) + y_0
```

### Benefits

This allows a toroidal coordinate system to be correctly defined

### Contribution

- [x] I'm willing to work on this issue

Diff:
diff --git a/.github/actions/test_filter/action.yml b/.github/actions/test_filter/action.yml
index d3c4a16b8c..f6eac91a2b 100644
--- a/.github/actions/test_filter/action.yml
+++ b/.github/actions/test_filter/action.yml
@@ -38,7 +38,7 @@ runs:
       echo "CHANGED_FILES=${changed_files}" >> $GITHUB_ENV
       FOUND_POLAR_SPLINES=$(grep "src/interpolation/polar_splines/.*" -x changed_files.txt || true)
       if [ -n "$FOUND_POLAR_SPLINES" ]; then echo "POLAR_SPLINES_TEST_DEGREE_MIN=1" >> build.env; echo "POLAR_SPLINES_TEST_DEGREE_MIN=6" >> $GITHUB_ENV; fi
-      FOUND_POISSON_2D=$(grep "src/geometryRTheta/poisson/.*" -x changed_files.txt || true)
+      FOUND_POISSON_2D=$(grep -P "(src/geometryRTheta/poisson/.*)|(tests/geometryRTheta/polar_poisson/.*)" -x changed_files.txt || true)
       if [ -z "$FOUND_POISSON_2D" ]; then echo "POISSON_2D_BUILD_TESTING=OFF" >> $GITHUB_ENV; else echo "POISSON_2D_BUILD_TESTING=ON" >> $GITHUB_ENV; fi
       FOUND_DDC=$(grep "vendor/ddc" changed_files.txt || true)
       if [ -z "$FOUND_DDC" ]; then echo "DDC_BUILD_TESTING=OFF" >> $GITHUB_ENV; else echo "DDC_BUILD_TESTING=ON" >> $GITHUB_ENV; fi
diff --git a/src/mapping/cartesian_to_circular.hpp b/src/mapping/cartesian_to_circular.hpp
index f3d0a68f62..73892181cc 100644
--- a/src/mapping/cartesian_to_circular.hpp
+++ b/src/mapping/cartesian_to_circular.hpp
@@ -17,23 +17,23 @@ class CircularToCartesian;
  *
  * The mapping @f$ (x,y)\mapsto (r,\theta) @f$ is defined as follow :
  *
- * @f$ r(x,y) = \sqrt x^2+y^2 ,@f$
+ * @f$ r(x,y) = \sqrt (x-x_0)^2+(y-y_0)^2 ,@f$
  *
- * @f$ \theta(x,y) = atan2(\frac{y}{x}) .@f$
+ * @f$ \theta(x,y) = atan2(\frac{y-y_0}{x-x_0}) .@f$
  *
  * It and its Jacobian matrix are invertible everywhere except for @f$ r = 0 @f$.
  *
  * The Jacobian matrix coefficients are defined as follow
  *
- * @f$ J_{11}(x,y)  =\frac{x}{\sqrt{x^2+y^2}}  @f$
+ * @f$ J_{11}(x,y)  =\frac{x-x_0}{\sqrt{(x-x_0)^2+(y-y_0)^2}}  @f$
  *
- * @f$ J_{12}(x,y)  =\frac{y}{\sqrt{x^2+y^2}}  @f$
+ * @f$ J_{12}(x,y)  =\frac{y-y_0}{\sqrt{(x-x_0)^2+(y-y_0)^2}}  @f$
  *
- * @f$ J_{21}(x,y)  =\frac{-y}{x^2+y^2}  @f$
+ * @f$ J_{21}(x,y)  =\frac{-(y-y_0)}{(x-x_0)^2+(y-y_0)^2}  @f$
  *
- * @f$ J_{22}(x,y)  =\frac{x}{x^2+y^2}  @f$
+ * @f$ J_{22}(x,y)  =\frac{x-x_0}{(x-x_0)^2+(y-y_0)^2}  @f$
  *
- * and the matrix determinant: @f$ det(J) = 1/(x^2+y^2) @f$.
+ * and the matrix determinant: @f$ det(J) = 1/((x-x_0)^2+(y-y_0)^2) @f$.
  *
  */
 template <class X, class Y, class R, class Theta>
@@ -63,8 +63,18 @@ class CartesianToCircular
     /// @brief The covariant form of the second logical coordinate.
     using Theta_cov = typename Theta::Dual;
 
+private:
+    double m_x0;
+    double m_y0;
+
 public:
-    CartesianToCircular() = default;
+    /**
+     * @brief Instantiate a CartesianToCircular from parameters.
+     *
+     * @param[in] x0 The x-coordinate of the centre of the circle (0 by default).
+     * @param[in] y0 The y-coordinate of the centre of the circle (0 by default).
+     */
+    explicit CartesianToCircular(double x0 = 0.0, double y0 = 0.0) : m_x0(x0), m_y0(y0) {}
 
     /**
      * @brief Instantiate a CartesianToCircular from another CartesianToCircular (lvalue).
@@ -72,7 +82,7 @@ class CartesianToCircular
      * @param[in] other
      * 		CartesianToCircular mapping used to instantiate the new one.
      */
-    KOKKOS_FUNCTION CartesianToCircular(CartesianToCircular const& other) {}
+    KOKKOS_DEFAULTED_FUNCTION CartesianToCircular(CartesianToCircular const& other) = default;
 
     /**
      * @brief Instantiate a Curvilinear2DToCartesian from another temporary CartesianToCircular (rvalue).
@@ -82,7 +92,7 @@ class CartesianToCircular
      */
     CartesianToCircular(CartesianToCircular&& x) = default;
 
-    ~CartesianToCircular() = default;
+    KOKKOS_DEFAULTED_FUNCTION ~CartesianToCircular() = default;
 
     /**
      * @brief Assign a CartesianToCircular from another CartesianToCircular (lvalue).
@@ -113,8 +123,8 @@ class CartesianToCircular
      */
     KOKKOS_FUNCTION Coord<R, Theta> operator()(Coord<X, Y> const& coord) const
     {
-        const double x = ddc::get<X>(coord);
-        const double y = ddc::get<Y>(coord);
+        const double x = ddc::get<X>(coord) - m_x0;
+        const double y = ddc::get<Y>(coord) - m_y0;
         const double r = Kokkos::sqrt(x * x + y * y);
         const double theta_pi_to_pi(Kokkos::atan2(y, x));
         const double theta = theta_pi_to_pi * (theta_pi_to_pi >= 0)
@@ -132,8 +142,8 @@ class CartesianToCircular
      */
     KOKKOS_FUNCTION double jacobian(Coord<X, Y> const& coord)
     {
-        const double x = ddc::get<X>(coord);
-        const double y = ddc::get<Y>(coord);
+        const double x = ddc::get<X>(coord) - m_x0;
+        const double y = ddc::get<Y>(coord) - m_y0;
         return 1. / Kokkos::sqrt(x * x + y * y);
     }
 
@@ -152,8 +162,8 @@ class CartesianToCircular
             Coord<X, Y> const& coord) const
 
     {
-        const double x = ddc::get<X>(coord);
-        const double y = ddc::get<Y>(coord);
+        const double x = ddc::get<X>(coord) - m_x0;
+        const double y = ddc::get<Y>(coord) - m_y0;
         DTensor<VectorIndexSet<R, Theta>, VectorIndexSet<X_cov, Y_cov>> jacobian_matrix;
         const double r2 = x * x + y * y;
         const double r = Kokkos::sqrt(r2);
@@ -178,8 +188,8 @@ class CartesianToCircular
         static_assert(ddc::in_tags_v<IndexTag1, VectorIndexSet<R, Theta>>);
         static_assert(ddc::in_tags_v<IndexTag2, VectorIndexSet<X_cov, Y_cov>>);
 
-        const double x = ddc::get<X>(coord);
-        const double y = ddc::get<Y>(coord);
+        const double x = ddc::get<X>(coord) - m_x0;
+        const double y = ddc::get<Y>(coord) - m_y0;
         if constexpr (std::is_same_v<IndexTag1, R> && std::is_same_v<IndexTag2, X_cov>) {
             // Component (1,1), i.e dr/dx
             return x / Kokkos::pow(x * x + y * y, 0.5);
@@ -202,7 +212,7 @@ class CartesianToCircular
      */
     CircularToCartesian<R, Theta, X, Y> get_inverse_mapping() const
     {
-        return CircularToCartesian<R, Theta, X, Y>();
+        return CircularToCartesian<R, Theta, X, Y>(m_x0, m_y0);
     }
 };
 
diff --git a/src/mapping/cartesian_to_czarny.hpp b/src/mapping/cartesian_to_czarny.hpp
index ee485875bb..748bad6da0 100644
--- a/src/mapping/cartesian_to_czarny.hpp
+++ b/src/mapping/cartesian_to_czarny.hpp
@@ -16,11 +16,11 @@ class CzarnyToCartesian;
  *
  * The mapping @f$ (x,y)  \mapsto (r,\theta)@f$ is defined by
  *
- * @f$ r(x,y) = \sqrt{\frac{y^2 (1+\epsilon x)^2}{e^2\xi^2+0.25(\epsilon x^2-2x-\epsilon)^2}},@f$
+ * @f$ r(x,y) = \sqrt{\frac{\hat{y}^2 (1+\epsilon \hat{x})^2}{e^2\xi^2+0.25(\epsilon \hat{x}^2-2\hat{x}-\epsilon)^2}},@f$
  *
- * @f$ \theta (x,y)) = atan2(2. y (1+\epsilon x), (e \xi (\epsilon x^2 - 2x-\epsilon))), @f$
+ * @f$ \theta (x,y)) = atan2(2. \hat{y} (1+\epsilon \hat{x}), (e \xi (\epsilon \hat{x}^2 - 2\hat{x}-\epsilon))), @f$
  *
- * with @f$ \xi = 1/\sqrt{1 - \epsilon^2 /4} @f$ and @f$ e @f$ and @f$ \epsilon @f$ given as parameters.
+ * with @f$ \hat{x} = x-x_0 @f$, @f$ \hat{y} = y-y_0 @f$, @f$ \xi = 1/\sqrt{1 - \epsilon^2 /4} @f$ and @f$ e @f$ and @f$ \epsilon @f$ given as parameters.
  */
 template <class X, class Y, class R, class Theta>
 class CartesianToCzarny
@@ -43,6 +43,8 @@ class CartesianToCzarny
 private:
     double m_epsilon;
     double m_e;
+    double m_x0;
+    double m_y0;
 
 public:
     /**
@@ -50,13 +52,20 @@ class CartesianToCzarny
      *
      * @param[in] epsilon
      * 			The @f$ \epsilon @f$ parameter in the definition of the mapping CartesianToCzarny.
-     *
      * @param[in] e
      * 			The @f$ e @f$ parameter in the definition of the mapping CartesianToCzarny.
+     * @param[in] x0 The x-coordinate of the centre of the circle (0 by default).
+     * @param[in] y0 The y-coordinate of the centre of the circle (0 by default).
      *
      * @see CartesianToCzarny
      */
-    CartesianToCzarny(double epsilon, double e) : m_epsilon(epsilon), m_e(e) {}
+    explicit CartesianToCzarny(double epsilon, double e, double x0 = 0.0, double y0 = 0.0)
+        : m_epsilon(epsilon)
+        , m_e(e)
+        , m_x0(x0)
+        , m_y0(y0)
+    {
+    }
 
     /**
      * @brief Instantiate a CartesianToCzarny from another CartesianToCzarny (lvalue).
@@ -64,11 +73,7 @@ class CartesianToCzarny
      * @param[in] other
      * 		CartesianToCzarny mapping used to instantiate the new one.
      */
-    KOKKOS_FUNCTION CartesianToCzarny(CartesianToCzarny const& other)
-        : m_epsilon(other.epsilon())
-        , m_e(other.e())
-    {
-    }
+    KOKKOS_DEFAULTED_FUNCTION CartesianToCzarny(CartesianToCzarny const& other) = default;
 
     /**
      * @brief Instantiate a CartesianToCzarny from another temporary CartesianToCzarny (rvalue).
@@ -78,7 +83,7 @@ class CartesianToCzarny
      */
     CartesianToCzarny(CartesianToCzarny&& x) = default;
 
-    ~CartesianToCzarny() = default;
+    KOKKOS_DEFAULTED_FUNCTION ~CartesianToCzarny() = default;
 
     /**
      * @brief Assign a CartesianToCzarny from another CartesianToCzarny (lvalue).
@@ -133,8 +138,8 @@ class CartesianToCzarny
      */
     KOKKOS_FUNCTION Coord<R, Theta> operator()(Coord<X, Y> const& coord) const
     {
-        const double x = ddc::get<X>(coord);
-        const double y = ddc::get<Y>(coord);
+        const double x = ddc::get<X>(coord) - m_x0;
+        const double y = ddc::get<Y>(coord) - m_y0;
         const double ex = 1. + m_epsilon * x;
         const double ex2 = (m_epsilon * x * x - 2. * x - m_epsilon);
         const double xi2 = 1. / (1. - m_epsilon * m_epsilon * 0.25);
diff --git a/src/mapping/circular_to_cartesian.hpp b/src/mapping/circular_to_cartesian.hpp
index 1d359c055a..1c8492062d 100644
--- a/src/mapping/circular_to_cartesian.hpp
+++ b/src/mapping/circular_to_cartesian.hpp
@@ -20,9 +20,9 @@ class CartesianToCircular;
  *
  * The mapping @f$ (r,\theta)\mapsto (x,y) @f$ is defined as follow :
  *
- * @f$ x(r,\theta) = r \cos(\theta),@f$
+ * @f$ x(r,\theta) = r \cos(\theta) + x_0,@f$
  *
- * @f$ y(r,\theta) = r \sin(\theta).@f$
+ * @f$ y(r,\theta) = r \sin(\theta) + y_0.@f$
  *
  * It and its Jacobian matrix are invertible everywhere except for @f$ r = 0 @f$.
  *
@@ -66,8 +66,18 @@ class CircularToCartesian
     /// @brief The covariant form of the second logical coordinate.
     using Theta_cov = typename Theta::Dual;
 
+private:
+    double m_x0;
+    double m_y0;
+
 public:
-    CircularToCartesian() = default;
+    /**
+     * @brief Instantiate a CircularToCartesian from parameters.
+     *
+     * @param[in] x0 The x-coordinate of the centre of the circle (0 by default).
+     * @param[in] y0 The y-coordinate of the centre of the circle (0 by default).
+     */
+    explicit CircularToCartesian(double x0 = 0.0, double y0 = 0.0) : m_x0(x0), m_y0(y0) {}
 
     /**
      * @brief Instantiate a CircularToCartesian from another CircularToCartesian (lvalue).
@@ -75,7 +85,7 @@ class CircularToCartesian
      * @param[in] other
      * 		CircularToCartesian mapping used to instantiate the new one.
      */
-    KOKKOS_FUNCTION CircularToCartesian(CircularToCartesian const& other) {}
+    KOKKOS_DEFAULTED_FUNCTION CircularToCartesian(CircularToCartesian const& other) = default;
 
     /**
      * @brief Instantiate a CircularToCartesian from another temporary CircularToCartesian (rvalue).
@@ -85,7 +95,7 @@ class CircularToCartesian
      */
     CircularToCartesian(CircularToCartesian&& x) = default;
 
-    ~CircularToCartesian() = default;
+    KOKKOS_DEFAULTED_FUNCTION ~CircularToCartesian() = default;
 
     /**
      * @brief Assign a CircularToCartesian from another CircularToCartesian (lvalue).
@@ -118,8 +128,8 @@ class CircularToCartesian
     {
         const double r = ddc::get<R>(coord);
         const double theta = ddc::get<Theta>(coord);
-        const double x = r * Kokkos::cos(theta);
-        const double y = r * Kokkos::sin(theta);
+        const double x = r * Kokkos::cos(theta) + m_x0;
+        const double y = r * Kokkos::sin(theta) + m_y0;
         return Coord<X, Y>(x, y);
     }
 
@@ -267,7 +277,7 @@ class CircularToCartesian
      */
     CartesianToCircular<X, Y, R, Theta> get_inverse_mapping() const
     {
-        return CartesianToCircular<X, Y, R, Theta>();
+        return CartesianToCircular<X, Y, R, Theta>(m_x0, m_y0);
     }
 };
 
diff --git a/src/mapping/czarny_to_cartesian.hpp b/src/mapping/czarny_to_cartesian.hpp
index 83dcc5bbd4..e81bca7830 100644
--- a/src/mapping/czarny_to_cartesian.hpp
+++ b/src/mapping/czarny_to_cartesian.hpp
@@ -19,9 +19,9 @@ class CartesianToCzarny;
  *
  * The mapping @f$ (r,\theta)\mapsto (x,y) @f$ is defined by
  *
- * @f$ x(r,\theta) = \frac{1}{\epsilon} \left( 1 - \sqrt{1 + \epsilon(\epsilon + 2 r \cos(\theta))} \right),@f$
+ * @f$ x(r,\theta) = \frac{1}{\epsilon} \left( 1 - \sqrt{1 + \epsilon(\epsilon + 2 r \cos(\theta))} \right) + x_0,@f$
  *
- * @f$ y(r,\theta) = \frac{e\xi r \sin(\theta)}{2 -\sqrt{1 + \epsilon(\epsilon + 2 r \cos(\theta))} },@f$
+ * @f$ y(r,\theta) = \frac{e\xi r \sin(\theta)}{2 -\sqrt{1 + \epsilon(\epsilon + 2 r \cos(\theta))} } + y_0,@f$
  *
  * with @f$ \xi = 1/\sqrt{1 - \epsilon^2 /4} @f$ and @f$ e @f$ and @f$ \epsilon @f$ given as parameters.
  * It and its Jacobian matrix are invertible everywhere except for @f$ r = 0 @f$.
@@ -76,6 +76,8 @@ class CzarnyToCartesian
 private:
     double m_epsilon;
     double m_e;
+    double m_x0;
+    double m_y0;
 
 public:
     /**
@@ -83,13 +85,20 @@ class CzarnyToCartesian
      *
      * @param[in] epsilon
      * 			The @f$ \epsilon @f$ parameter in the definition of the mapping CzarnyToCartesian.
-     *
      * @param[in] e
      * 			The @f$ e @f$ parameter in the definition of the mapping CzarnyToCartesian.
+     * @param[in] x0 The x-coordinate of the centre of the circle (0 by default).
+     * @param[in] y0 The y-coordinate of the centre of the circle (0 by default).
      *
      * @see CzarnyToCartesian
      */
-    CzarnyToCartesian(double epsilon, double e) : m_epsilon(epsilon), m_e(e) {}
+    explicit CzarnyToCartesian(double epsilon, double e, double x0 = 0.0, double y0 = 0.0)
+        : m_epsilon(epsilon)
+        , m_e(e)
+        , m_x0(x0)
+        , m_y0(y0)
+    {
+    }
 
     /**
      * @brief Instantiate a CzarnyToCartesian from another CzarnyToCartesian (lvalue).
@@ -97,11 +106,7 @@ class CzarnyToCartesian
      * @param[in] other
      * 		CzarnyToCartesian mapping used to instantiate the new one.
      */
-    KOKKOS_FUNCTION CzarnyToCartesian(CzarnyToCartesian const& other)
-        : m_epsilon(other.epsilon())
-        , m_e(other.e())
-    {
-    }
+    KOKKOS_DEFAULTED_FUNCTION CzarnyToCartesian(CzarnyToCartesian const& other) = default;
 
     /**
      * @brief Instantiate a CzarnyToCartesian from another temporary CzarnyToCartesian (rvalue).
@@ -111,7 +116,7 @@ class CzarnyToCartesian
      */
     CzarnyToCartesian(CzarnyToCartesian&& x) = default;
 
-    ~CzarnyToCartesian() = default;
+    KOKKOS_DEFAULTED_FUNCTION ~CzarnyToCartesian() = default;
 
     /**
      * @brief Assign a CzarnyToCartesian from another CzarnyToCartesian (lvalue).
@@ -171,9 +176,10 @@ class CzarnyToCartesian
         const double tmp1
                 = Kokkos::sqrt(m_epsilon * (m_epsilon + 2.0 * r * Kokkos::cos(theta)) + 1.0);
 
-        const double x = (1.0 - tmp1) / m_epsilon;
+        const double x = (1.0 - tmp1) / m_epsilon + m_x0;
         const double y = m_e * r * Kokkos::sin(theta)
-                         / (Kokkos::sqrt(1.0 - 0.25 * m_epsilon * m_epsilon) * (2.0 - tmp1));
+                                 / (Kokkos::sqrt(1.0 - 0.25 * m_epsilon * m_epsilon) * (2.0 - tmp1))
+                         + m_y0;
 
         return Coord<X, Y>(x, y);
     }
diff --git a/tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp b/tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
index 638eaffb00..648f8cd810 100644
--- a/tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
+++ b/tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
@@ -347,11 +347,12 @@ int main(int argc, char** argv)
 
 
     // SET THE DIFFERENT PARAMETERS OF THE TESTS ------------------------------------------------
-    CircularToCartMapping const from_circ_map;
-    CircularToPseudoCartMapping const to_pseudo_circ_map;
-    CartesianToCircular<X, Y, R, Theta> to_circ_map;
-    CzarnyToCartMapping const from_czarny_map(0.3, 1.4);
-    CartesianToCzarny<X, Y, R, Theta> const to_czarny_map(0.3, 1.4);
+    // Offset the centre of the circle to ensure that this is correctly handled
+    CircularToCartMapping const from_circ_map(6.2, 0.8);
+    CircularToPseudoCartMapping const to_pseudo_circ_map(6.2, 0.8);
+    CartesianToCircular<X, Y, R, Theta> to_circ_map(6.2, 0.8);
+    CzarnyToCartMapping const from_czarny_map(0.3, 1.4, 6.2, 0.8);
+    CartesianToCzarny<X, Y, R, Theta> const to_czarny_map(0.3, 1.4, 6.2, 0.8);
     DiscreteMappingBuilderHost const discrete_czarny_map_builder_host(
             Kokkos::DefaultHostExecutionSpace(),
             from_czarny_map,
diff --git a/tests/geometryRTheta/polar_poisson/polarpoissonfemsolver.cpp b/tests/geometryRTheta/polar_poisson/polarpoissonfemsolver.cpp
index ac861a3fb9..9ffc3f92b5 100644
--- a/tests/geometryRTheta/polar_poisson/polarpoissonfemsolver.cpp
+++ b/tests/geometryRTheta/polar_poisson/polarpoissonfemsolver.cpp
@@ -104,11 +104,12 @@ int main(int argc, char** argv)
     SplineRThetaBuilder const builder(grid);
     SplineRThetaBuilder_host const builder_host(grid);
 
-
+    double major_radius = 6.1;
+    double vertical_offset = 0.3;
 #if defined(CIRCULAR_MAPPING)
-    const Mapping mapping;
+    const Mapping mapping(major_radius, vertical_offset);
 #elif defined(CZARNY_MAPPING)
-    const Mapping mapping(0.3, 1.4);
+    const Mapping mapping(0.3, 1.4, major_radius, vertical_offset);
 #endif
 
     ddc::NullExtrapolationRule bv_r_min;
@@ -193,7 +194,7 @@ int main(int argc, char** argv)
                          .count()
               << "ms" << std::endl;
 
-    LHSFunction lhs(mapping);
+    LHSFunction lhs(mapping, major_radius, vertical_offset);
     RHSFunction rhs(mapping);
     host_t<FieldMemRTheta<CoordRTheta>> coords(grid);
     DFieldMemRTheta result_alloc(grid);
diff --git a/tests/geometryRTheta/polar_poisson/test_cases.hpp b/tests/geometryRTheta/polar_poisson/test_cases.hpp
index 5427d75f71..f12adb4a15 100644
--- a/tests/geometryRTheta/polar_poisson/test_cases.hpp
+++ b/tests/geometryRTheta/polar_poisson/test_cases.hpp
@@ -85,7 +85,10 @@ class CurvilinearSolution : public PoissonSolution<CurvilinearToCartesian>
      *      The mapping function which converts the logical (polar)
      *      coordinates into the physical (Cartesian) coordinates.
      */
-    explicit CurvilinearSolution(CurvilinearToCartesian const& coordinate_converter)
+    explicit CurvilinearSolution(
+            CurvilinearToCartesian const& coordinate_converter,
+            double x0,
+            double y0)
         : PoissonSolution<CurvilinearToCartesian>(coordinate_converter)
     {
     }
@@ -102,7 +105,7 @@ class CurvilinearSolution : public PoissonSolution<CurvilinearToCartesian>
  * @brief Define a Cartesian solution of the Poisson equation.
  *
  * The solution is given by
- * * @f$ \phi (x,y) = C (1+r(x,y))^6  (1 - r(x,y))^6 \cos(2\pi x) \sin(2\pi y) @f$,
+ * * @f$ \phi (x,y) = C (1+r(x,y))^6  (1 - r(x,y))^6 \cos(2\pi x-x0) \sin(2\pi y-y0) @f$,
  *
  * with @f$C = 2^{12}1e-4 @f$.
  *
@@ -119,6 +122,8 @@ class CartesianSolution : public PoissonSolution<CurvilinearToCartesian>
 {
 private:
     InverseJacobianMatrix<CurvilinearToCartesian, Coord<R, Theta>> m_inverse_jacobian;
+    double m_x0;
+    double m_y0;
 
 public:
     /**
@@ -128,9 +133,14 @@ class CartesianSolution : public PoissonSolution<CurvilinearToCartesian>
      *      The mapping function which converts the logical (polar)
      *      coordinates into the physical (Cartesian) coordinates.
      */
-    explicit CartesianSolution(CurvilinearToCartesian const& coordinate_converter)
+    explicit CartesianSolution(
+            CurvilinearToCartesian const& coordinate_converter,
+            double x0,
+            double y0)
         : PoissonSolution<CurvilinearToCartesian>(coordinate_converter)
         , m_inverse_jacobian(coordinate_converter)
+        , m_x0(x0)
+        , m_y0(y0)
     {
     }
 
@@ -139,8 +149,8 @@ class CartesianSolution : public PoissonSolution<CurvilinearToCartesian>
         const double s = ddc::get<R>(coord);
         const Coord<X, Y> cart_coord
                 = PoissonSolution<CurvilinearToCartesian>::m_coordinate_converter(coord);
-        const double x = ddc::get<X>(cart_coord);
-        const double y = ddc::get<Y>(cart_coord);
+        const double x = ddc::get<X>(cart_coord) - m_x0;
+        const double y = ddc::get<Y>(cart_coord) - m_y0;
         return 1e-4 * ipow(1 + s, 6) * ipow(1 - s, 6) * std::cos(2 * M_PI * x)
                * std::sin(2 * M_PI * y) / ipow(0.5, 12);
     }


Reviews:
user: login: gdgirard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T13:34:30Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T13:36:55Z
user: login: gdgirard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T13:47:17Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T13:49:08Z
user: login: gdgirard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T13:52:36Z
user: login: gdgirard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T13:56:51Z
user: login: gdgirard
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T13:57:40Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T14:03:35Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T14:43:55Z
user: login: gdgirard
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-30T14:47:24Z

Review comments:
diff_hunk: @@ -63,16 +63,26 @@ class CartesianToCircular
     /// @brief The covariant form of the second logical coordinate.
     using Theta_cov = typename Theta::Dual;
 
+private:
+    double m_x0;
+    double m_y0;
+
 public:
-    CartesianToCircular() = default;
+    /**
+     * @brief Instantiate a CartesianToCircular from parameters.
+     *
+     * @param[in] x0 The x-coordinate of the centre of the circle (0 by default).
+     * @param[in] y0 The y-coordinate of the centre of the circle (0 by default).
+     */
+    explicit CartesianToCircular(double x0 = 0.0, double y0 = 0.0) : m_x0(x0), m_y0(y0) {}
 
     /**
      * @brief Instantiate a CartesianToCircular from another CartesianToCircular (lvalue).
      *
      * @param[in] other
      * 		CartesianToCircular mapping used to instantiate the new one.
      */
-    KOKKOS_FUNCTION CartesianToCircular(CartesianToCircular const& other) {}
+    KOKKOS_DEFAULTED_FUNCTION CartesianToCircular(CartesianToCircular const& other) = default;
path: src/mapping/cartesian_to_circular.hpp
user: login: gdgirard
body: Can you explain again the goal of this change ?
created_at: 2025-04-30T13:34:30Z
author_association: MEMBER
diff_hunk: @@ -63,16 +63,26 @@ class CartesianToCircular
     /// @brief The covariant form of the second logical coordinate.
     using Theta_cov = typename Theta::Dual;
 
+private:
+    double m_x0;
+    double m_y0;
+
 public:
-    CartesianToCircular() = default;
+    /**
+     * @brief Instantiate a CartesianToCircular from parameters.
+     *
+     * @param[in] x0 The x-coordinate of the centre of the circle (0 by default).
+     * @param[in] y0 The y-coordinate of the centre of the circle (0 by default).
+     */
+    explicit CartesianToCircular(double x0 = 0.0, double y0 = 0.0) : m_x0(x0), m_y0(y0) {}
 
     /**
      * @brief Instantiate a CartesianToCircular from another CartesianToCircular (lvalue).
      *
      * @param[in] other
      * 		CartesianToCircular mapping used to instantiate the new one.
      */
-    KOKKOS_FUNCTION CartesianToCircular(CartesianToCircular const& other) {}
+    KOKKOS_DEFAULTED_FUNCTION CartesianToCircular(CartesianToCircular const& other) = default;
path: src/mapping/cartesian_to_circular.hpp
user: login: EmilyBourne
body: I'm not sure why we were using:
```cpp
KOKKOS_FUNCTION CartesianToCircular(CartesianToCircular const& other) {}
```
previously as this would be the default implementation of the copy constructor. Now there are 2 extra parameters I can either explicitly copy them:
```cpp
KOKKOS_FUNCTION CartesianToCircular(CartesianToCircular const& other)
    : m_x0(x0)
    , m_y0(y0)
{}
```
or use the default copy constructor which does exactly the same thing.
I prefer the method with the default copy constructor as it is more compact.
created_at: 2025-04-30T13:36:55Z
author_association: MEMBER
diff_hunk: @@ -347,11 +347,11 @@ int main(int argc, char** argv)
 
 
     // SET THE DIFFERENT PARAMETERS OF THE TESTS ------------------------------------------------
-    CircularToCartMapping const from_circ_map;
-    CircularToPseudoCartMapping const to_pseudo_circ_map;
-    CartesianToCircular<X, Y, R, Theta> to_circ_map;
-    CzarnyToCartMapping const from_czarny_map(0.3, 1.4);
-    CartesianToCzarny<X, Y, R, Theta> const to_czarny_map(0.3, 1.4);
+    CircularToCartMapping const from_circ_map(6.2);
path: tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
user: login: gdgirard
body: Is 6.2 an arbitrary choice ?
Not sure to understand but an I wrong if I understand that this line is equivalent to 
`CircularToCartMapping const from_circ_map(x_0=6.2, y_0=0); ?`
created_at: 2025-04-30T13:47:17Z
author_association: MEMBER
diff_hunk: @@ -347,11 +347,11 @@ int main(int argc, char** argv)
 
 
     // SET THE DIFFERENT PARAMETERS OF THE TESTS ------------------------------------------------
-    CircularToCartMapping const from_circ_map;
-    CircularToPseudoCartMapping const to_pseudo_circ_map;
-    CartesianToCircular<X, Y, R, Theta> to_circ_map;
-    CzarnyToCartMapping const from_czarny_map(0.3, 1.4);
-    CartesianToCzarny<X, Y, R, Theta> const to_czarny_map(0.3, 1.4);
+    CircularToCartMapping const from_circ_map(6.2);
path: tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
user: login: EmilyBourne
body: Yes. I wanted to pick something that was not 0 or 1 to ensure that the value is checked properly (6.2m is the major radius of ITER).

Yes this line is equivalent to:
> `CircularToCartMapping const from_circ_map(x_0=6.2, y_0=0); `
created_at: 2025-04-30T13:49:08Z
author_association: MEMBER
diff_hunk: @@ -347,11 +347,11 @@ int main(int argc, char** argv)
 
 
     // SET THE DIFFERENT PARAMETERS OF THE TESTS ------------------------------------------------
-    CircularToCartMapping const from_circ_map;
-    CircularToPseudoCartMapping const to_pseudo_circ_map;
-    CartesianToCircular<X, Y, R, Theta> to_circ_map;
-    CzarnyToCartMapping const from_czarny_map(0.3, 1.4);
-    CartesianToCzarny<X, Y, R, Theta> const to_czarny_map(0.3, 1.4);
+    CircularToCartMapping const from_circ_map(6.2);
path: tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
user: login: gdgirard
body: But for a unitary test why you don't choose something completely arbitrary with a non null-value for y_0 to test also that all is correct in `y` direction ?
created_at: 2025-04-30T13:52:36Z
author_association: MEMBER
diff_hunk: @@ -347,11 +347,11 @@ int main(int argc, char** argv)
 
 
     // SET THE DIFFERENT PARAMETERS OF THE TESTS ------------------------------------------------
-    CircularToCartMapping const from_circ_map;
-    CircularToPseudoCartMapping const to_pseudo_circ_map;
-    CartesianToCircular<X, Y, R, Theta> to_circ_map;
-    CzarnyToCartMapping const from_czarny_map(0.3, 1.4);
-    CartesianToCzarny<X, Y, R, Theta> const to_czarny_map(0.3, 1.4);
+    CircularToCartMapping const from_circ_map(6.2);
path: tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
user: login: gdgirard
body: More general questions when you have several default value as input of a function the deduction is made with the order of the parameter ? If yes, I would be more in favor of writing explicitly the name of the variable. Is it not a proper way to do in C++ ?
created_at: 2025-04-30T13:56:51Z
author_association: MEMBER
diff_hunk: @@ -63,16 +63,26 @@ class CartesianToCircular
     /// @brief The covariant form of the second logical coordinate.
     using Theta_cov = typename Theta::Dual;
 
+private:
+    double m_x0;
+    double m_y0;
+
 public:
-    CartesianToCircular() = default;
+    /**
+     * @brief Instantiate a CartesianToCircular from parameters.
+     *
+     * @param[in] x0 The x-coordinate of the centre of the circle (0 by default).
+     * @param[in] y0 The y-coordinate of the centre of the circle (0 by default).
+     */
+    explicit CartesianToCircular(double x0 = 0.0, double y0 = 0.0) : m_x0(x0), m_y0(y0) {}
 
     /**
      * @brief Instantiate a CartesianToCircular from another CartesianToCircular (lvalue).
      *
      * @param[in] other
      * 		CartesianToCircular mapping used to instantiate the new one.
      */
-    KOKKOS_FUNCTION CartesianToCircular(CartesianToCircular const& other) {}
+    KOKKOS_DEFAULTED_FUNCTION CartesianToCircular(CartesianToCircular const& other) = default;
path: src/mapping/cartesian_to_circular.hpp
user: login: gdgirard
body: OK, thank you for the clarification.
created_at: 2025-04-30T13:57:40Z
author_association: MEMBER
diff_hunk: @@ -347,11 +347,11 @@ int main(int argc, char** argv)
 
 
     // SET THE DIFFERENT PARAMETERS OF THE TESTS ------------------------------------------------
-    CircularToCartMapping const from_circ_map;
-    CircularToPseudoCartMapping const to_pseudo_circ_map;
-    CartesianToCircular<X, Y, R, Theta> to_circ_map;
-    CzarnyToCartMapping const from_czarny_map(0.3, 1.4);
-    CartesianToCzarny<X, Y, R, Theta> const to_czarny_map(0.3, 1.4);
+    CircularToCartMapping const from_circ_map(6.2);
path: tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
user: login: EmilyBourne
body: > But for a unitary test why you don't choose something completely arbitrary with a non null-value for y_0 to test also that all is correct in y direction ?

I will modify this :+1:


> More general questions when you have several default value as input of a function the deduction is made with the order of the parameter ?

Yes.

> If yes, I would be more in favor of writing explicitly the name of the variable.

Unfortunately there is no syntax for this in C++ so you cannot get it to compile.
created_at: 2025-04-30T14:03:35Z
author_association: MEMBER
diff_hunk: @@ -347,11 +347,11 @@ int main(int argc, char** argv)
 
 
     // SET THE DIFFERENT PARAMETERS OF THE TESTS ------------------------------------------------
-    CircularToCartMapping const from_circ_map;
-    CircularToPseudoCartMapping const to_pseudo_circ_map;
-    CartesianToCircular<X, Y, R, Theta> to_circ_map;
-    CzarnyToCartMapping const from_czarny_map(0.3, 1.4);
-    CartesianToCzarny<X, Y, R, Theta> const to_czarny_map(0.3, 1.4);
+    CircularToCartMapping const from_circ_map(6.2);
path: tests/geometryRTheta/advection_rtheta/advection_all_tests.cpp
user: login: EmilyBourne
body: Done : [f20268c](https://github.com/gyselax/gyselalibxx/pull/272/commits/f20268ca7ede9a0b83419cca7a6391de775ce475)
created_at: 2025-04-30T14:43:55Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-04-30T08:55:34Z
body: ## [Codecov](https://app.codecov.io/gh/gyselax/gyselalibxx/pull/272?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gyselax) Report
All modified and coverable lines are covered by tests :white_check_mark:

:loudspeaker: Thoughts on this report? [Let us know!](https://github.com/codecov/feedback/issues/255)
author_association: NONE
user: login: EmilyBourne
created_at: 2025-04-30T12:40:01Z
body: The Poisson failure only occurs when `m_x0` or `m_y0` is not an integer (e.g `6.1` instead of `6.0`). This implies an erroneous integer conversion buried in the code
author_association: MEMBER
user: login: EmilyBourne
created_at: 2025-04-30T12:46:39Z
body: The matrix is the same. This probably implies that the problem is with the RHS
author_association: MEMBER
user: login: EmilyBourne
created_at: 2025-04-30T12:49:58Z
body: The problem is with the manufactured solution which was not updated. The cartesian solution includes a dependency on x/y which was not updated to include x0/y0
author_association: MEMBER