Repository: gyselax/gyselalibxx
Pull request #188: Add convergence test for partial derivatives classes
Author: login: yanmnc
Created: 2025-03-17T13:35:32Z | Merged: 2025-03-24T17:25:10Z



Diff:
diff --git a/src/math_tools/central_fdm_partial_derivatives.hpp b/src/math_tools/central_fdm_partial_derivatives.hpp
index 31237ecdb4..cf2b1c373a 100644
--- a/src/math_tools/central_fdm_partial_derivatives.hpp
+++ b/src/math_tools/central_fdm_partial_derivatives.hpp
@@ -161,6 +161,11 @@ class CentralFDMPartialDerivativeCreator
     using DConstFieldType = DConstField<IdxRangeFull>;
 
 public:
+    /**
+     * @brief Construct an instance of the CentralFDMPartialDerivativeCreator class.
+     */
+    CentralFDMPartialDerivativeCreator() = default;
+
     /**
      * Create a pointer to an instance of the abstract class IPartialDerivative.
      * The type of the returned object will be determined when the pointer is 
diff --git a/src/utils/mesh_builder.hpp b/src/utils/mesh_builder.hpp
index cc63781aea..fdfbb1b00a 100644
--- a/src/utils/mesh_builder.hpp
+++ b/src/utils/mesh_builder.hpp
@@ -28,7 +28,8 @@ template <class Dim, class Grid1D>
 std::vector<Coord<Dim>> build_random_non_uniform_break_points(
         Coord<Dim> min,
         Coord<Dim> max,
-        IdxStep<Grid1D> n_cells)
+        IdxStep<Grid1D> n_cells,
+        double const non_uniformity = 1.)
 {
     static_assert(std::is_same_v<typename Grid1D::continuous_dimension_type, Dim>);
 
@@ -40,7 +41,7 @@ std::vector<Coord<Dim>> build_random_non_uniform_break_points(
     break_points[0] = min;
     for (int i(1); i < n_cells; ++i) {
         double const random_perturbation = double(rand()) / RAND_MAX - 0.5;
-        break_points[i] = min + (i + random_perturbation) * delta;
+        break_points[i] = min + (i + random_perturbation * non_uniformity) * delta;
     }
     break_points[n_cells] = max;
     return break_points;
diff --git a/tests/math_tools/test_partial_derivatives.cpp b/tests/math_tools/test_partial_derivatives.cpp
index 3343ea815c..756fcd4b63 100644
--- a/tests/math_tools/test_partial_derivatives.cpp
+++ b/tests/math_tools/test_partial_derivatives.cpp
@@ -6,6 +6,8 @@
 
 #include "central_fdm_partial_derivatives.hpp"
 #include "ddc_aliases.hpp"
+#include "ddc_helper.hpp"
+#include "math_tools.hpp"
 #include "mesh_builder.hpp"
 #include "spline_1d_partial_derivative.hpp"
 
@@ -15,164 +17,463 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+std::size_t static constexpr spline_degree = 3;
+
+using CoordX = Coord<X>;
+using CoordY = Coord<Y>;
+using CoordXY = Coord<X, Y>;
+
+
+/**
+ * @brief A class that represents a test function for computing partial derivatives.
+ * The test function is defined as the product of two cosine functions.
+ */
+class FunctionToDifferentiateCosine
 {
+public:
+    /**
+     * @brief Default constructor.
+     */
+    KOKKOS_DEFAULTED_FUNCTION FunctionToDifferentiateCosine() = default;
+
+    /**
+     * @brief Default copy constructor.
+     */
+    KOKKOS_DEFAULTED_FUNCTION FunctionToDifferentiateCosine(FunctionToDifferentiateCosine const&)
+            = default;
+
+    /**
+     * @brief Default destructor.
+     */
+    KOKKOS_DEFAULTED_FUNCTION ~FunctionToDifferentiateCosine() = default;
+
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordXY const coord_xy) const
+    {
+        double const x = ddc::get<X>(coord_xy);
+        double const y = ddc::get<Y>(coord_xy);
+        return Kokkos::cos(x) * Kokkos::cos(y);
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    template <class DerivativeDimension>
+    KOKKOS_FUNCTION double differentiate(CoordXY const coord_xy) const
+    {
+        static_assert(
+                std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+        double const x = ddc::get<X>(coord_xy);
+        double const y = ddc::get<Y>(coord_xy);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return -Kokkos::sin(x) * Kokkos::cos(y);
+        } else {
+            return -Kokkos::cos(x) * Kokkos::sin(y);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <std::size_t ncells_x, std::size_t ncells_y>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+public:
+    struct GridX : NonUniformGridBase<X>
+    {
+    };
+
+    struct GridY : NonUniformGridBase<Y>
+    {
+    };
+
+    using IdxRangeX = IdxRange<GridX>;
+    using IdxRangeY = IdxRange<GridY>;
+    using IdxRangeXY = IdxRange<GridX, GridY>;
+
+    using IdxX = Idx<GridX>;
+    using IdxY = Idx<GridY>;
+    using IdxXY = Idx<GridX, GridY>;
+
+    using IdxStepX = IdxStep<GridX>;
+    using IdxStepY = IdxStep<GridY>;
+
+    using DFieldMemType = DFieldMem<IdxRangeXY>;
+    using DFieldType = DField<IdxRangeXY>;
+
+protected:
+    CoordX const m_xmin;
+    CoordX const m_xmax;
+    CoordY const m_ymin;
+    CoordY const m_ymax;
+
+    IdxStepX const m_ncells_x;
+    IdxStepY const m_ncells_y;
+
+public:
+    PartialDerivativeTest(
+            double const xmin,
+            double const xmax,
+            double const ymin,
+            double const ymax)
+        : m_xmin(xmin)
+        , m_xmax(xmax)
+        , m_ymin(ymin)
+        , m_ymax(ymax)
+        , m_ncells_x(ncells_x)
+        , m_ncells_y(ncells_y)
+    {
+    }
+
+    template <class DerivativeDimension, class FunctionToDifferentiate>
+    double compute_max_error(
+            IdxRangeXY const& idxrange_xy,
+            FunctionToDifferentiate const& function_to_differentiate,
+            IPartialDerivativeCreator<IdxRangeXY, DerivativeDimension> const&
+                    partial_derivative_creator,
+            double& max_distance) const
+    {
+        using GridDDim = std::conditional_t<std::is_same_v<DerivativeDimension, X>, GridX, GridY>;
+
+        max_distance = ddcHelper::maximum_distance_between_adjacent_points(
+                ddc::select<GridDDim>(idxrange_xy));
+
+        // field to be differentiated
+        DFieldMemType field_to_differentiate(idxrange_xy);
+        DFieldType field_to_differentiate_proxy = get_field(field_to_differentiate);
+        ddc::parallel_for_each(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange_xy,
+                KOKKOS_LAMBDA(IdxXY const idx) {
+                    field_to_differentiate_proxy(idx)
+                            = function_to_differentiate(ddc::coordinate(idx));
+                });
+
+        std::unique_ptr<IPartialDerivative<IdxRangeXY, DerivativeDimension>> const
+                partial_derivative_creator_pointer
+                = partial_derivative_creator.create_instance(
+                        get_const_field(field_to_differentiate));
+
+        IPartialDerivative<IdxRangeXY, DerivativeDimension> const& partial_derivative
+                = *partial_derivative_creator_pointer;
+
+        DFieldMemType field_differentiated_alloc(idxrange_xy);
+        DFieldType field_differentiated = get_field(field_differentiated_alloc);
+        partial_derivative(field_differentiated);
+
+        double const max_error = ddc::parallel_transform_reduce(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange_xy,
+                0.,
+                ddc::reducer::max<double>(),
+                KOKKOS_LAMBDA(IdxXY const idx) {
+                    return Kokkos::abs(
+                            field_differentiated(idx)
+                            - function_to_differentiate.template differentiate<DerivativeDimension>(
+                                    ddc::coordinate(idx)));
+                });
+
+        return max_error;
+    }
 };
-using CoordY = Coord<Y>;
-struct BSplinesY : ddc::UniformBSplines<Y, 3>
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with 1d splines for computing partial 
+ * derivatives.
+ */
+template <class DerivativeDimension, std::size_t ncells_x, std::size_t ncells_y>
+class PartialDerivativeTestSpline1D : public PartialDerivativeTest<ncells_x, ncells_y>
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    using base_type = PartialDerivativeTest<ncells_x, ncells_y>;
+    using DDim = DerivativeDimension;
+
+    using typename base_type::GridX;
+    using typename base_type::GridY;
+
+    using typename base_type::IdxRangeX;
+    using typename base_type::IdxRangeXY;
+    using typename base_type::IdxRangeY;
+
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
+
+
+    struct BSplinesY : ddc::NonUniformBSplines<Y, spline_degree>
+    {
+    };
+    using SplineInterpPointsY
+            = ddc::GrevilleInterpolationPoints<BSplinesY, SplineBoundary, SplineBoundary>;
+
+    using BSplinesDDim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesX, BSplinesY>;
+    using BSplinesODim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesY, BSplinesX>;
+
+    using GridDDim = std::conditional_t<std::is_same_v<DDim, X>, GridX, GridY>;
+    using GridODim = std::conditional_t<std::is_same_v<DDim, X>, GridY, GridX>;
+
+    using SplineInterpPointsDDim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsX, SplineInterpPointsY>;
+    using SplineInterpPointsODim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsY, SplineInterpPointsX>;
+
+    using SplineDDimBuilder = ddc::SplineBuilder<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            BSplinesDDim,
+            GridDDim,
+            SplineBoundary,
+            SplineBoundary,
+            ddc::SplineSolver::LAPACK,
+            GridX,
+            GridY>;
+
+    using SplineDDimEvaluator = ddc::SplineEvaluator<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            BSplinesDDim,
+            GridDDim,
+            ddc::ConstantExtrapolationRule<DDim>,
+            ddc::ConstantExtrapolationRule<DDim>,
+            GridX,
+            GridY>;
+
+public:
+    PartialDerivativeTestSpline1D(
+            double const xmin,
+            double const xmax,
+            double const ymin,
+            double const ymax)
+        : base_type(xmin, xmax, ymin, ymax)
+    {
+        std::vector<CoordX> point_sampling_x = build_random_non_uniform_break_points(
+                base_type::m_xmin,
+                base_type::m_xmax,
+                base_type::m_ncells_x,
+                0.2);
+
+        ddc::init_discrete_space<BSplinesX>(point_sampling_x);
+        ddc::init_discrete_space<GridX>(SplineInterpPointsX::template get_sampling<GridX>());
+
+        std::vector<CoordY> point_sampling_y = build_random_non_uniform_break_points(
+                base_type::m_ymin,
+                base_type::m_ymax,
+                base_type::m_ncells_y,
+                0.2);
+
+        ddc::init_discrete_space<BSplinesY>(point_sampling_y);
+        ddc::init_discrete_space<GridY>(SplineInterpPointsY::template get_sampling<GridY>());
+    }
+
+    double compute_error(double& max_distance) const
+    {
+        IdxRangeX const idxrange_x = SplineInterpPointsX::template get_domain<GridX>();
+        IdxRangeY const idxrange_y = SplineInterpPointsY::template get_domain<GridY>();
+        IdxRangeXY const idxrange = IdxRangeXY(idxrange_x, idxrange_y);
+
+        double dmin, dmax;
+        if constexpr (std::is_same_v<DDim, X>) {
+            dmin = base_type::m_xmin;
+            dmax = base_type::m_xmax;
+        } else {
+            dmin = base_type::m_ymin;
+            dmax = base_type::m_ymax;
+        }
+        ddc::ConstantExtrapolationRule<DDim> const bv_min(Coord<DDim> {dmin});
+        ddc::ConstantExtrapolationRule<DDim> const bv_max(Coord<DDim> {dmax});
+
+        SplineDDimBuilder const spline_builder(idxrange);
+        SplineDDimEvaluator const spline_evaluator(bv_min, bv_max);
+
+        Spline1DPartialDerivativeCreator<SplineDDimBuilder, SplineDDimEvaluator> const
+                derivative_creator(spline_builder, spline_evaluator);
+
+        FunctionToDifferentiateCosine function_to_differentiate;
+        double const max_error = base_type::
+                template compute_max_error<DerivativeDimension, FunctionToDifferentiateCosine>(
+                        idxrange,
+                        function_to_differentiate,
+                        derivative_creator,
+                        max_distance);
+
+        return max_error;
+    }
 };
-auto constexpr SplineYBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsY
-        = ddc::GrevilleInterpolationPoints<BSplinesY, SplineYBoundary, SplineYBoundary>;
-struct GridY : SplineInterpPointsY::interpolation_discrete_dimension_type
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with finite difference method for
+ * computing partial derivatives.
+ */
+template <std::size_t ncells_x, std::size_t ncells_y>
+class PartialDerivativeTestFDM : public PartialDerivativeTest<ncells_x, ncells_y>
 {
+private:
+    using base_type = PartialDerivativeTest<ncells_x, ncells_y>;
+
+    using typename base_type::IdxRangeX;
+    using typename base_type::IdxRangeXY;
+    using typename base_type::IdxRangeY;
+
+public:
+    PartialDerivativeTestFDM(
+            double const xmin,
+            double const xmax,
+            double const ymin,
+            double const ymax)
+        : base_type(xmin, xmax, ymin, ymax)
+    {
+        std::vector<CoordX> point_sampling_x = build_random_non_uniform_break_points(
+                base_type::m_xmin,
+                base_type::m_xmax,
+                base_type::m_ncells_x,
+                0.2);
+        ddc::init_discrete_space<typename base_type::GridX>(point_sampling_x);
+
+        std::vector<CoordY> point_sampling_y = build_random_non_uniform_break_points(
+                base_type::m_ymin,
+                base_type::m_ymax,
+                base_type::m_ncells_y,
+                0.2);
+        ddc::init_discrete_space<typename base_type::GridY>(point_sampling_y);
+    }
+
+    template <class DerivativeDimension>
+    double compute_error(double& max_distance) const
+    {
+        IdxRangeX idxrange_x(typename base_type::IdxX(0), base_type::m_ncells_x + 1);
+        IdxRangeY idxrange_y(typename base_type::IdxY(0), base_type::m_ncells_y + 1);
+        IdxRangeXY idxrange_xy(idxrange_x, idxrange_y);
+
+        CentralFDMPartialDerivativeCreator<IdxRangeXY, DerivativeDimension> const
+                derivative_creator;
+
+        FunctionToDifferentiateCosine function_to_differentiate;
+        double const max_error = base_type::
+                template compute_max_error<DerivativeDimension, FunctionToDifferentiateCosine>(
+                        idxrange_xy,
+                        function_to_differentiate,
+                        derivative_creator,
+                        max_distance);
+
+        return max_error;
+    }
 };
-using IdxY = Idx<GridY>;
-using IdxStepY = IdxStep<GridY>;
-using IdxRangeY = IdxRange<GridY>;
-
-using IdxXY = Idx<GridX, GridY>;
-using IdxRangeXY = IdxRange<GridX, GridY>;
-using DFieldMemXY = DFieldMem<IdxRangeXY>;
-using DFieldXY = DField<IdxRangeXY>;
-using DConstFieldXY = DConstField<IdxRangeXY>;
-
-// --- Operators ---
-using SplineXBuilder = ddc::SplineBuilder<
-        Kokkos::DefaultExecutionSpace,
-        Kokkos::DefaultExecutionSpace::memory_space,
-        BSplinesX,
-        GridX,
-        SplineXBoundary,
-        SplineXBoundary,
-        ddc::SplineSolver::LAPACK,
-        GridX,
-        GridY>;
-using SplineXEvaluator = ddc::SplineEvaluator<
-        Kokkos::DefaultExecutionSpace,
-        Kokkos::DefaultExecutionSpace::memory_space,
-        BSplinesX,
-        GridX,
-        ddc::ConstantExtrapolationRule<X>,
-        ddc::ConstantExtrapolationRule<X>,
-        GridX,
-        GridY>;
-
-template <class Dimension>
-void test_partial_derivative_dx(
-        IPartialDerivativeCreator<IdxRangeXY, Dimension> const& partial_dx_creator,
-        IdxRangeXY const& idxrange_xy)
-{
-    DFieldMemXY field_xy_alloc(idxrange_xy);
-    DFieldXY field_xy = get_field(field_xy_alloc);
-
-    ddc::parallel_for_each(
-            Kokkos::DefaultExecutionSpace(),
-            idxrange_xy,
-            KOKKOS_LAMBDA(IdxXY const idx_xy) {
-                IdxX idx_x(idx_xy);
-                IdxY idx_y(idx_xy);
-                field_xy(idx_xy)
-                        = ddc::coordinate(idx_x) * ddc::coordinate(idx_x) * ddc::coordinate(idx_y);
-            });
-
-
-    std::unique_ptr<IPartialDerivative<IdxRangeXY, X>> const partial_dx_pointer
-            = partial_dx_creator.create_instance(get_const_field(field_xy));
-    IPartialDerivative<IdxRangeXY, X> const& partial_dx = *partial_dx_pointer;
-
-    DFieldMemXY dfield_dx_xy_alloc(idxrange_xy);
-    DFieldXY dfield_dx_xy = get_field(dfield_dx_xy_alloc);
-    partial_dx(dfield_dx_xy);
-    double max_error = ddc::parallel_transform_reduce(
-            Kokkos::DefaultExecutionSpace(),
-            idxrange_xy,
-            0.,
-            ddc::reducer::max<double>(),
-            KOKKOS_LAMBDA(IdxXY const idx_xy) {
-                IdxX idx_x(idx_xy);
-                IdxY idx_y(idx_xy);
-                double const dfield_dx_anal = 2. * ddc::coordinate(idx_x) * ddc::coordinate(idx_y);
-                return Kokkos::abs(dfield_dx_xy(idx_xy) - dfield_dx_anal);
-            });
-    EXPECT_LE(max_error, 1e-12);
-}
 
 
+/** 
+ * We expect a convergence of the error following error ~ (dx)^d
+ * with d the degree of the splines. 
+ * We approximate d using two values for dx, dx1 and dx2
+ * following @f$ log(error1/error2) / log(dx1/dx2) ~ d @f$. 
+ * We check that this relation is satisfied with a given tolerance. 
+ * Since we use non uniform interpolation points, dx depends on the 
+ * position. We take dx as the maximum distance between two points.
+ */
 TEST(PartialDerivative, Spline1DPartialDerivative)
 {
-    int n_elems_x(10);
-    int n_elems_y(20);
-
-    Coord<X> const x_min(0.0);
-    Coord<X> const x_max(1.0);
-    IdxStepX x_ncells(n_elems_x);
+    double const xmin(0.1);
+    double const xmax(1.1);
+    double const ymin(0.2);
+    double const ymax(1.2);
+    // relative error of convergence should be less than 15%
+    double const TOL = 0.15;
 
-    Coord<Y> const y_min(0.0);
-    Coord<Y> const y_max(2.0);
-    IdxStepY y_ncells(n_elems_y);
+    // Partial Derivative in X direction
+    double delta_low_x,
+            delta_high_x; // the maximum distance between points in the derivative direction
+    PartialDerivativeTestSpline1D<X, 10, 5> const test_low_x(xmin, xmax, ymin, ymax);
+    PartialDerivativeTestSpline1D<X, 100, 5> const test_high_x(xmin, xmax, ymin, ymax);
+    double const error_low_x = test_low_x.compute_error(delta_low_x);
+    double const error_high_x = test_high_x.compute_error(delta_high_x);
 
-    ddc::init_discrete_space<BSplinesX>(x_min, x_max, x_ncells);
-    ddc::init_discrete_space<GridX>(SplineInterpPointsX::get_sampling<GridX>());
-    IdxRangeX idxrange_x(SplineInterpPointsX::get_domain<GridX>());
 
-    ddc::init_discrete_space<BSplinesY>(y_min, y_max, y_ncells);
-    ddc::init_discrete_space<GridY>(SplineInterpPointsY::get_sampling<GridY>());
-    IdxRangeY idxrange_y(SplineInterpPointsY::get_domain<GridY>());
+    double const order_x
+            = std::log(error_high_x / error_low_x) / std::log(delta_high_x / delta_low_x);
+    double const relative_error_order_x = std::fabs((spline_degree - order_x) / spline_degree);
 
-    IdxRangeXY idxrange_xy(idxrange_x, idxrange_y);
+    EXPECT_LE(relative_error_order_x, TOL);
 
-    SplineXBuilder const builder_x(idxrange_xy);
-    ddc::ConstantExtrapolationRule<X> bv_x_min(x_min);
-    ddc::ConstantExtrapolationRule<X> bv_x_max(x_max);
-    SplineXEvaluator const spline_evaluator_x(bv_x_min, bv_x_max);
+    // Partial Derivative in Y direction
+    double delta_low_y,
+            delta_high_y; // the maximum distance between points in the derivative direction
+    PartialDerivativeTestSpline1D<Y, 5, 10> const test_low_y(xmin, xmax, ymin, ymax);
+    PartialDerivativeTestSpline1D<Y, 5, 100> const test_high_y(xmin, xmax, ymin, ymax);
+    double const error_low_y = test_low_y.compute_error(delta_low_y);
+    double const error_high_y = test_high_y.compute_error(delta_high_y);
 
-    Spline1DPartialDerivativeCreator<SplineXBuilder, SplineXEvaluator> const
-            partial_dx_creator(builder_x, spline_evaluator_x);
+    double const order_y
+            = std::log(error_high_y / error_low_y) / std::log(delta_high_y / delta_low_y);
+    double const relative_error_order_y = std::fabs((spline_degree - order_y) / spline_degree);
 
-    test_partial_derivative_dx<X>(partial_dx_creator, idxrange_xy);
+    EXPECT_LE(relative_error_order_y, TOL);
 }
 
-
-TEST(PartialDerivative, CentralFDMPartialDerivativeDx)
+TEST(PartialDerivative, CentralFDMPartialDerivative)
 {
-    int n_elems_x(10);
-    int n_elems_y(20);
+    double const xmin(0.1);
+    double const xmax(1.1);
+    double const ymin(0.2);
+    double const ymax(1.2);
+    // relative error of convergence should be less than 15%
+    double const TOL = 0.15;
+    int const expected_order(2);
+
+    PartialDerivativeTestFDM<10, 10> const test_low_res(xmin, xmax, ymin, ymax);
+    PartialDerivativeTestFDM<100, 100> const test_high_res(xmin, xmax, ymin, ymax);
 
-    Coord<X> const x_min(0.0);
-    Coord<X> const x_max(1.0);
-    IdxStepX x_ncells(n_elems_x);
+    // Partial Derivative in X direction
+    double delta_low_x, delta_high_x; // max distance between points in the derivative direction
+    double const error_low_x = test_low_res.compute_error<X>(delta_low_x);
+    double const error_high_x = test_high_res.compute_error<X>(delta_high_x);
 
-    Coord<Y> const y_min(0.0);
-    Coord<Y> const y_max(2.0);
-    IdxStepY y_ncells(n_elems_y);
+    double const order_x
+            = std::log(error_high_x / error_low_x) / std::log(delta_high_x / delta_low_x);
+    double const relative_error_order_x = std::fabs((expected_order - order_x) / expected_order);
 
-    ddc::init_discrete_space<GridX>(build_random_non_uniform_break_points(x_min, x_max, x_ncells));
-    IdxRangeX idxrange_x(IdxX {0}, x_ncells);
+    EXPECT_LE(relative_error_order_x, TOL);
 
-    ddc::init_discrete_space<GridY>(build_random_non_uniform_break_points(y_min, y_max, y_ncells));
-    IdxRangeY idxrange_y(IdxY {0}, y_ncells);
+    // Partial Derivative in Y direction
+    double delta_low_y, delta_high_y;
+    double const error_low_y = test_low_res.compute_error<Y>(delta_low_y);
+    double const error_high_y = test_high_res.compute_error<Y>(delta_high_y);
 
-    IdxRangeXY idxrange_xy(idxrange_x, idxrange_y);
+    double const order_y
+            = std::log(error_high_y / error_low_y) / std::log(delta_high_y / delta_low_y);
+    double const relative_error_order_y = std::fabs((expected_order - order_y) / expected_order);
 
-    CentralFDMPartialDerivativeCreator<IdxRangeXY, X> const partial_dx_creator;
-    test_partial_derivative_dx(partial_dx_creator, idxrange_xy);
+    EXPECT_LE(relative_error_order_y, TOL);
 }
 } // namespace


Reviews:
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T16:42:17Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T16:42:32Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T16:46:14Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T16:54:01Z
user: login: yanmnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T09:40:04Z
user: login: yanmnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T09:43:17Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:25:36Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T07:26:50Z
user: login: yanmnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T10:38:54Z
user: login: yanmnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T08:34:38Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T09:48:39Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T11:27:39Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T14:52:17Z
user: login: EmilyBourne
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T14:52:23Z
user: login: yanmnc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T14:55:34Z
user: login: EmilyBourne
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-24T17:25:09Z

Review comments:
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: We may want to consider a little more randomness. E.g. see:
https://github.com/CExA-project/ddc/blob/main/tests/splines/polynomial_evaluator.hpp

created_at: 2025-03-19T16:42:17Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: I am ok with this though
created_at: 2025-03-19T16:42:32Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    KOKKOS_FUNCTION double differentiate(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return (1. + m_degree) * ipow(x, m_degree) * y;
+        } else {
+            return ipow(x, m_degree + 1) + (m_degree + 1) * ipow(y, m_degree);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+
+    auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: Should these objects be in the subclass?
created_at: 2025-03-19T16:46:14Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    KOKKOS_FUNCTION double differentiate(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return (1. + m_degree) * ipow(x, m_degree) * y;
+        } else {
+            return ipow(x, m_degree + 1) + (m_degree + 1) * ipow(y, m_degree);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+
+    auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
+
+    struct GridX : NonUniformGridBase<X>
+    {
+    };
+
+    struct BSplinesY : ddc::NonUniformBSplines<Y, spline_degree>
+    {
+    };
+
+    using SplineInterpPointsY
+            = ddc::GrevilleInterpolationPoints<BSplinesY, SplineBoundary, SplineBoundary>;
+    struct GridY : NonUniformGridBase<Y>
+    {
+    };
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordDDim = Coord<DDim>;
+    using CoordODim = Coord<ODim>;
+
+    using GridDDim = std::conditional_t<std::is_same_v<DDim, X>, GridX, GridY>;
+    using GridODim = std::conditional_t<std::is_same_v<DDim, X>, GridY, GridX>;
+
+    using IdxRangeDDim = IdxRange<GridDDim>;
+    using IdxRangeODim = IdxRange<GridODim>;
+    using IdxRangeFull = IdxRange<GridDDim, GridODim>;
+
+    using IdxFull = Idx<GridDDim, GridODim>;
+
+    using IdxStepDDim = IdxStep<GridDDim>;
+    using IdxStepODim = IdxStep<GridODim>;
+    using IdxStepFull = IdxStep<GridDDim, GridODim>;
+
+    using BSplinesDDim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesX, BSplinesY>;
+    using BSplinesODim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesY, BSplinesX>;
+
+    using SplineInterpPointsDDim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsX, SplineInterpPointsY>;
+    using SplineInterpPointsODim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsY, SplineInterpPointsX>;
+
+    using DFieldMemType = DFieldMem<IdxRangeFull>;
+    using DFieldType = DField<IdxRangeFull>;
+
+    CoordDDim const m_ddim_min;
+    CoordDDim const m_ddim_max;
+    CoordODim const m_odim_min;
+    CoordODim const m_odim_max;
+    IdxStepDDim const m_ncells_ddim;
+    IdxStepODim const m_ncells_odim;
+
+    PartialDerivativeTest(
+            double const ddim_min,
+            double const ddim_max,
+            double const odim_min,
+            double const odim_max)
+        : m_ddim_min(ddim_min)
+        , m_ddim_max(ddim_max)
+        , m_odim_min(odim_min)
+        , m_odim_max(odim_max)
+        , m_ncells_ddim(N_ddim)
+        , m_ncells_odim(N_odim)
+    {
+        std::vector<CoordDDim> point_sampling_ddim
+                = build_random_non_uniform_break_points(m_ddim_min, m_ddim_max, m_ncells_ddim);
+
+        ddc::init_discrete_space<BSplinesDDim>(point_sampling_ddim);
+        ddc::init_discrete_space<GridDDim>(
+                SplineInterpPointsDDim::template get_sampling<GridDDim>());
+
+
+        std::vector<CoordODim> point_sampling_odim
+                = build_random_non_uniform_break_points(m_odim_min, m_odim_max, m_ncells_odim);
+
+        ddc::init_discrete_space<BSplinesODim>(point_sampling_odim);
+        ddc::init_discrete_space<GridODim>(
+                SplineInterpPointsODim::template get_sampling<GridODim>());
+    }
+
+    template <class FunctionToDifferentiate>
+    double compute_max_error(
+            FunctionToDifferentiate const& function_to_differentiate,
+            IPartialDerivativeCreator<IdxRangeFull, DerivativeDimension> const&
+                    partial_derivative_creator,
+            double& delta_ddim) const
+    {
+        IdxRangeDDim idxrange_ddim(SplineInterpPointsDDim::template get_domain<GridDDim>());
+        IdxRangeODim idxrange_odim(SplineInterpPointsODim::template get_domain<GridODim>());
+        IdxRangeFull idxrange(idxrange_ddim, idxrange_odim);
+
+        delta_ddim = ddcHelper::maximum_distance_between_adjacent_points(idxrange_ddim);
+
+        // field to be differentiated
+        DFieldMemType field_to_differentiate(idxrange);
+        DFieldType field_to_differentiate_proxy = get_field(field_to_differentiate);
+        ddc::parallel_for_each(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange,
+                KOKKOS_LAMBDA(IdxFull const idx) {
+                    field_to_differentiate_proxy(idx)
+                            = function_to_differentiate(ddc::coordinate(idx));
+                });
+
+        std::unique_ptr<IPartialDerivative<IdxRangeFull, DerivativeDimension>> const
+                partial_derivative_creator_pointer
+                = partial_derivative_creator.create_instance(
+                        get_const_field(field_to_differentiate));
+
+        IPartialDerivative<IdxRangeFull, DerivativeDimension> const& partial_derivative
+                = *partial_derivative_creator_pointer;
+
+        DFieldMemType field_differentiated_alloc(idxrange);
+        DFieldType field_differentiated = get_field(field_differentiated_alloc);
+        partial_derivative(field_differentiated);
+
+        double const max_error = ddc::parallel_transform_reduce(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange,
+                0.,
+                ddc::reducer::max<double>(),
+                KOKKOS_LAMBDA(IdxFull const idx) {
+                    return Kokkos::abs(
+                            field_differentiated(idx)
+                            - function_to_differentiate.differentiate(ddc::coordinate(idx)));
+                });
+
+        return max_error;
+    }
 };
-using CoordY = Coord<Y>;
-struct BSplinesY : ddc::UniformBSplines<Y, 3>
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with 1d splines for computing partial 
+ * derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTestSpline1D
+    : public PartialDerivativeTest<DerivativeDimension, N_ddim, N_odim, spline_degree>
 {
+private:
+    using base_type = PartialDerivativeTest<DerivativeDimension, N_ddim, N_odim, spline_degree>;
+    using DDim = DerivativeDimension;
+
+    using SplineDDimBuilder = ddc::SplineBuilder<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            typename base_type::BSplinesDDim,
+            typename base_type::GridDDim,
+            base_type::SplineBoundary,
+            base_type::SplineBoundary,
+            ddc::SplineSolver::LAPACK,
+            typename base_type::GridDDim,
+            typename base_type::GridODim>;
+
+    using SplineDDimEvaluator = ddc::SplineEvaluator<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            typename base_type::BSplinesDDim,
+            typename base_type::GridDDim,
+            ddc::ConstantExtrapolationRule<DDim>,
+            ddc::ConstantExtrapolationRule<DDim>,
+            typename base_type::GridDDim,
+            typename base_type::GridODim>;
+
+    using IdxRangeDDim = typename base_type::IdxRangeDDim;
+    using IdxRangeODim = typename base_type::IdxRangeODim;
+    using IdxRangeFull = typename base_type::IdxRangeFull;
+
+    using SplineInterpPointsDDim = typename base_type::SplineInterpPointsDDim;
+    using SplineInterpPointsODim = typename base_type::SplineInterpPointsODim;
+
+    using GridDDim = typename base_type::GridDDim;
+    using GridODim = typename base_type::GridODim;
+
+    ddc::ConstantExtrapolationRule<DDim> const m_bv_min;
+    ddc::ConstantExtrapolationRule<DDim> const m_bv_max;
+
+public:
+    PartialDerivativeTestSpline1D(
+            double const ddim_min,
+            double const ddim_max,
+            double const odim_min,
+            double const odim_max)
+        : base_type(ddim_min, ddim_max, odim_min, odim_max)
+        , m_bv_min(typename base_type::CoordDDim(ddim_min))
+        , m_bv_max(typename base_type::CoordDDim(ddim_max))
+    {
+    }
+
+    double const operator()(double& delta_ddim) const
+    {
+        IdxRangeDDim const idxrange_ddim = SplineInterpPointsDDim::template get_domain<GridDDim>();
+        IdxRangeODim const idxrange_odim = SplineInterpPointsODim::template get_domain<GridODim>();
+        IdxRangeFull const idxrange = IdxRangeFull(idxrange_ddim, idxrange_odim);
+        SplineDDimBuilder const builder(idxrange);
+
+        SplineDDimEvaluator const spline_evaluator(m_bv_min, m_bv_max);
+
+        Spline1DPartialDerivativeCreator<SplineDDimBuilder, SplineDDimEvaluator> const
+                derivative_creator(builder, spline_evaluator);
+
+        using FunDiff = FunctionToDifferentiatePolynomial<typename base_type::DDim, spline_degree>;
+        FunDiff function_to_differentiate;
+        double const max_error = base_type::template compute_max_error<
+                FunDiff>(function_to_differentiate, derivative_creator, delta_ddim);
+
+        return max_error;
+    }
 };
-auto constexpr SplineYBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsY
-        = ddc::GrevilleInterpolationPoints<BSplinesY, SplineYBoundary, SplineYBoundary>;
-struct GridY : SplineInterpPointsY::interpolation_discrete_dimension_type
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with finite difference method for
+ * computing partial derivatives.
+ */
+template <
+        class DerivativeDimension,
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: Is the `DerivativeDimension` necessary as a template argument? I guess it is needed by `FunctionToDifferentiatePolynomial` but not necessarily by this class if we want to be able to test different functions?
created_at: 2025-03-19T16:54:00Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    KOKKOS_FUNCTION double differentiate(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return (1. + m_degree) * ipow(x, m_degree) * y;
+        } else {
+            return ipow(x, m_degree + 1) + (m_degree + 1) * ipow(y, m_degree);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+
+    auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
path: tests/math_tools/test_partial_derivatives.cpp
user: login: yanmnc
body: I think they should stay in the `PartialDerivativeTest` class: the constructor of this class calls 
```
ddc::init_discrete_space<GridDDim>(SplineInterpPointsDDim::template get_sampling<GridDDim>());
```
Thus it requires `SplineInterpPointsDDim`, which depends on `SplineInterpPointsX` or `SplineInterpPointsX`, which depend on BSplinesX and BSplinesY. 
created_at: 2025-03-20T09:40:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    KOKKOS_FUNCTION double differentiate(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return (1. + m_degree) * ipow(x, m_degree) * y;
+        } else {
+            return ipow(x, m_degree + 1) + (m_degree + 1) * ipow(y, m_degree);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+
+    auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
+
+    struct GridX : NonUniformGridBase<X>
+    {
+    };
+
+    struct BSplinesY : ddc::NonUniformBSplines<Y, spline_degree>
+    {
+    };
+
+    using SplineInterpPointsY
+            = ddc::GrevilleInterpolationPoints<BSplinesY, SplineBoundary, SplineBoundary>;
+    struct GridY : NonUniformGridBase<Y>
+    {
+    };
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordDDim = Coord<DDim>;
+    using CoordODim = Coord<ODim>;
+
+    using GridDDim = std::conditional_t<std::is_same_v<DDim, X>, GridX, GridY>;
+    using GridODim = std::conditional_t<std::is_same_v<DDim, X>, GridY, GridX>;
+
+    using IdxRangeDDim = IdxRange<GridDDim>;
+    using IdxRangeODim = IdxRange<GridODim>;
+    using IdxRangeFull = IdxRange<GridDDim, GridODim>;
+
+    using IdxFull = Idx<GridDDim, GridODim>;
+
+    using IdxStepDDim = IdxStep<GridDDim>;
+    using IdxStepODim = IdxStep<GridODim>;
+    using IdxStepFull = IdxStep<GridDDim, GridODim>;
+
+    using BSplinesDDim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesX, BSplinesY>;
+    using BSplinesODim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesY, BSplinesX>;
+
+    using SplineInterpPointsDDim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsX, SplineInterpPointsY>;
+    using SplineInterpPointsODim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsY, SplineInterpPointsX>;
+
+    using DFieldMemType = DFieldMem<IdxRangeFull>;
+    using DFieldType = DField<IdxRangeFull>;
+
+    CoordDDim const m_ddim_min;
+    CoordDDim const m_ddim_max;
+    CoordODim const m_odim_min;
+    CoordODim const m_odim_max;
+    IdxStepDDim const m_ncells_ddim;
+    IdxStepODim const m_ncells_odim;
+
+    PartialDerivativeTest(
+            double const ddim_min,
+            double const ddim_max,
+            double const odim_min,
+            double const odim_max)
+        : m_ddim_min(ddim_min)
+        , m_ddim_max(ddim_max)
+        , m_odim_min(odim_min)
+        , m_odim_max(odim_max)
+        , m_ncells_ddim(N_ddim)
+        , m_ncells_odim(N_odim)
+    {
+        std::vector<CoordDDim> point_sampling_ddim
+                = build_random_non_uniform_break_points(m_ddim_min, m_ddim_max, m_ncells_ddim);
+
+        ddc::init_discrete_space<BSplinesDDim>(point_sampling_ddim);
+        ddc::init_discrete_space<GridDDim>(
+                SplineInterpPointsDDim::template get_sampling<GridDDim>());
+
+
+        std::vector<CoordODim> point_sampling_odim
+                = build_random_non_uniform_break_points(m_odim_min, m_odim_max, m_ncells_odim);
+
+        ddc::init_discrete_space<BSplinesODim>(point_sampling_odim);
+        ddc::init_discrete_space<GridODim>(
+                SplineInterpPointsODim::template get_sampling<GridODim>());
+    }
+
+    template <class FunctionToDifferentiate>
+    double compute_max_error(
+            FunctionToDifferentiate const& function_to_differentiate,
+            IPartialDerivativeCreator<IdxRangeFull, DerivativeDimension> const&
+                    partial_derivative_creator,
+            double& delta_ddim) const
+    {
+        IdxRangeDDim idxrange_ddim(SplineInterpPointsDDim::template get_domain<GridDDim>());
+        IdxRangeODim idxrange_odim(SplineInterpPointsODim::template get_domain<GridODim>());
+        IdxRangeFull idxrange(idxrange_ddim, idxrange_odim);
+
+        delta_ddim = ddcHelper::maximum_distance_between_adjacent_points(idxrange_ddim);
+
+        // field to be differentiated
+        DFieldMemType field_to_differentiate(idxrange);
+        DFieldType field_to_differentiate_proxy = get_field(field_to_differentiate);
+        ddc::parallel_for_each(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange,
+                KOKKOS_LAMBDA(IdxFull const idx) {
+                    field_to_differentiate_proxy(idx)
+                            = function_to_differentiate(ddc::coordinate(idx));
+                });
+
+        std::unique_ptr<IPartialDerivative<IdxRangeFull, DerivativeDimension>> const
+                partial_derivative_creator_pointer
+                = partial_derivative_creator.create_instance(
+                        get_const_field(field_to_differentiate));
+
+        IPartialDerivative<IdxRangeFull, DerivativeDimension> const& partial_derivative
+                = *partial_derivative_creator_pointer;
+
+        DFieldMemType field_differentiated_alloc(idxrange);
+        DFieldType field_differentiated = get_field(field_differentiated_alloc);
+        partial_derivative(field_differentiated);
+
+        double const max_error = ddc::parallel_transform_reduce(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange,
+                0.,
+                ddc::reducer::max<double>(),
+                KOKKOS_LAMBDA(IdxFull const idx) {
+                    return Kokkos::abs(
+                            field_differentiated(idx)
+                            - function_to_differentiate.differentiate(ddc::coordinate(idx)));
+                });
+
+        return max_error;
+    }
 };
-using CoordY = Coord<Y>;
-struct BSplinesY : ddc::UniformBSplines<Y, 3>
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with 1d splines for computing partial 
+ * derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTestSpline1D
+    : public PartialDerivativeTest<DerivativeDimension, N_ddim, N_odim, spline_degree>
 {
+private:
+    using base_type = PartialDerivativeTest<DerivativeDimension, N_ddim, N_odim, spline_degree>;
+    using DDim = DerivativeDimension;
+
+    using SplineDDimBuilder = ddc::SplineBuilder<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            typename base_type::BSplinesDDim,
+            typename base_type::GridDDim,
+            base_type::SplineBoundary,
+            base_type::SplineBoundary,
+            ddc::SplineSolver::LAPACK,
+            typename base_type::GridDDim,
+            typename base_type::GridODim>;
+
+    using SplineDDimEvaluator = ddc::SplineEvaluator<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            typename base_type::BSplinesDDim,
+            typename base_type::GridDDim,
+            ddc::ConstantExtrapolationRule<DDim>,
+            ddc::ConstantExtrapolationRule<DDim>,
+            typename base_type::GridDDim,
+            typename base_type::GridODim>;
+
+    using IdxRangeDDim = typename base_type::IdxRangeDDim;
+    using IdxRangeODim = typename base_type::IdxRangeODim;
+    using IdxRangeFull = typename base_type::IdxRangeFull;
+
+    using SplineInterpPointsDDim = typename base_type::SplineInterpPointsDDim;
+    using SplineInterpPointsODim = typename base_type::SplineInterpPointsODim;
+
+    using GridDDim = typename base_type::GridDDim;
+    using GridODim = typename base_type::GridODim;
+
+    ddc::ConstantExtrapolationRule<DDim> const m_bv_min;
+    ddc::ConstantExtrapolationRule<DDim> const m_bv_max;
+
+public:
+    PartialDerivativeTestSpline1D(
+            double const ddim_min,
+            double const ddim_max,
+            double const odim_min,
+            double const odim_max)
+        : base_type(ddim_min, ddim_max, odim_min, odim_max)
+        , m_bv_min(typename base_type::CoordDDim(ddim_min))
+        , m_bv_max(typename base_type::CoordDDim(ddim_max))
+    {
+    }
+
+    double const operator()(double& delta_ddim) const
+    {
+        IdxRangeDDim const idxrange_ddim = SplineInterpPointsDDim::template get_domain<GridDDim>();
+        IdxRangeODim const idxrange_odim = SplineInterpPointsODim::template get_domain<GridODim>();
+        IdxRangeFull const idxrange = IdxRangeFull(idxrange_ddim, idxrange_odim);
+        SplineDDimBuilder const builder(idxrange);
+
+        SplineDDimEvaluator const spline_evaluator(m_bv_min, m_bv_max);
+
+        Spline1DPartialDerivativeCreator<SplineDDimBuilder, SplineDDimEvaluator> const
+                derivative_creator(builder, spline_evaluator);
+
+        using FunDiff = FunctionToDifferentiatePolynomial<typename base_type::DDim, spline_degree>;
+        FunDiff function_to_differentiate;
+        double const max_error = base_type::template compute_max_error<
+                FunDiff>(function_to_differentiate, derivative_creator, delta_ddim);
+
+        return max_error;
+    }
 };
-auto constexpr SplineYBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsY
-        = ddc::GrevilleInterpolationPoints<BSplinesY, SplineYBoundary, SplineYBoundary>;
-struct GridY : SplineInterpPointsY::interpolation_discrete_dimension_type
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with finite difference method for
+ * computing partial derivatives.
+ */
+template <
+        class DerivativeDimension,
path: tests/math_tools/test_partial_derivatives.cpp
user: login: yanmnc
body: I think you still need to pass the `DerivativeDimension` type as a template parameter of this class given that `operator()` instantiates a member of a class that inherits from IPartialDerivativeCreator, and this latter class is templated by the dimension along which we want to evaluate partial derivatives  
created_at: 2025-03-20T09:43:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    KOKKOS_FUNCTION double differentiate(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return (1. + m_degree) * ipow(x, m_degree) * y;
+        } else {
+            return ipow(x, m_degree + 1) + (m_degree + 1) * ipow(y, m_degree);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+
+    auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
+
+    struct GridX : NonUniformGridBase<X>
+    {
+    };
+
+    struct BSplinesY : ddc::NonUniformBSplines<Y, spline_degree>
+    {
+    };
+
+    using SplineInterpPointsY
+            = ddc::GrevilleInterpolationPoints<BSplinesY, SplineBoundary, SplineBoundary>;
+    struct GridY : NonUniformGridBase<Y>
+    {
+    };
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordDDim = Coord<DDim>;
+    using CoordODim = Coord<ODim>;
+
+    using GridDDim = std::conditional_t<std::is_same_v<DDim, X>, GridX, GridY>;
+    using GridODim = std::conditional_t<std::is_same_v<DDim, X>, GridY, GridX>;
+
+    using IdxRangeDDim = IdxRange<GridDDim>;
+    using IdxRangeODim = IdxRange<GridODim>;
+    using IdxRangeFull = IdxRange<GridDDim, GridODim>;
+
+    using IdxFull = Idx<GridDDim, GridODim>;
+
+    using IdxStepDDim = IdxStep<GridDDim>;
+    using IdxStepODim = IdxStep<GridODim>;
+    using IdxStepFull = IdxStep<GridDDim, GridODim>;
+
+    using BSplinesDDim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesX, BSplinesY>;
+    using BSplinesODim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesY, BSplinesX>;
+
+    using SplineInterpPointsDDim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsX, SplineInterpPointsY>;
+    using SplineInterpPointsODim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsY, SplineInterpPointsX>;
+
+    using DFieldMemType = DFieldMem<IdxRangeFull>;
+    using DFieldType = DField<IdxRangeFull>;
+
+    CoordDDim const m_ddim_min;
+    CoordDDim const m_ddim_max;
+    CoordODim const m_odim_min;
+    CoordODim const m_odim_max;
+    IdxStepDDim const m_ncells_ddim;
+    IdxStepODim const m_ncells_odim;
+
+    PartialDerivativeTest(
+            double const ddim_min,
+            double const ddim_max,
+            double const odim_min,
+            double const odim_max)
+        : m_ddim_min(ddim_min)
+        , m_ddim_max(ddim_max)
+        , m_odim_min(odim_min)
+        , m_odim_max(odim_max)
+        , m_ncells_ddim(N_ddim)
+        , m_ncells_odim(N_odim)
+    {
+        std::vector<CoordDDim> point_sampling_ddim
+                = build_random_non_uniform_break_points(m_ddim_min, m_ddim_max, m_ncells_ddim);
+
+        ddc::init_discrete_space<BSplinesDDim>(point_sampling_ddim);
+        ddc::init_discrete_space<GridDDim>(
+                SplineInterpPointsDDim::template get_sampling<GridDDim>());
+
+
+        std::vector<CoordODim> point_sampling_odim
+                = build_random_non_uniform_break_points(m_odim_min, m_odim_max, m_ncells_odim);
+
+        ddc::init_discrete_space<BSplinesODim>(point_sampling_odim);
+        ddc::init_discrete_space<GridODim>(
+                SplineInterpPointsODim::template get_sampling<GridODim>());
+    }
+
+    template <class FunctionToDifferentiate>
+    double compute_max_error(
+            FunctionToDifferentiate const& function_to_differentiate,
+            IPartialDerivativeCreator<IdxRangeFull, DerivativeDimension> const&
+                    partial_derivative_creator,
+            double& delta_ddim) const
+    {
+        IdxRangeDDim idxrange_ddim(SplineInterpPointsDDim::template get_domain<GridDDim>());
+        IdxRangeODim idxrange_odim(SplineInterpPointsODim::template get_domain<GridODim>());
+        IdxRangeFull idxrange(idxrange_ddim, idxrange_odim);
+
+        delta_ddim = ddcHelper::maximum_distance_between_adjacent_points(idxrange_ddim);
+
+        // field to be differentiated
+        DFieldMemType field_to_differentiate(idxrange);
+        DFieldType field_to_differentiate_proxy = get_field(field_to_differentiate);
+        ddc::parallel_for_each(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange,
+                KOKKOS_LAMBDA(IdxFull const idx) {
+                    field_to_differentiate_proxy(idx)
+                            = function_to_differentiate(ddc::coordinate(idx));
+                });
+
+        std::unique_ptr<IPartialDerivative<IdxRangeFull, DerivativeDimension>> const
+                partial_derivative_creator_pointer
+                = partial_derivative_creator.create_instance(
+                        get_const_field(field_to_differentiate));
+
+        IPartialDerivative<IdxRangeFull, DerivativeDimension> const& partial_derivative
+                = *partial_derivative_creator_pointer;
+
+        DFieldMemType field_differentiated_alloc(idxrange);
+        DFieldType field_differentiated = get_field(field_differentiated_alloc);
+        partial_derivative(field_differentiated);
+
+        double const max_error = ddc::parallel_transform_reduce(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange,
+                0.,
+                ddc::reducer::max<double>(),
+                KOKKOS_LAMBDA(IdxFull const idx) {
+                    return Kokkos::abs(
+                            field_differentiated(idx)
+                            - function_to_differentiate.differentiate(ddc::coordinate(idx)));
+                });
+
+        return max_error;
+    }
 };
-using CoordY = Coord<Y>;
-struct BSplinesY : ddc::UniformBSplines<Y, 3>
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with 1d splines for computing partial 
+ * derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTestSpline1D
+    : public PartialDerivativeTest<DerivativeDimension, N_ddim, N_odim, spline_degree>
 {
+private:
+    using base_type = PartialDerivativeTest<DerivativeDimension, N_ddim, N_odim, spline_degree>;
+    using DDim = DerivativeDimension;
+
+    using SplineDDimBuilder = ddc::SplineBuilder<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            typename base_type::BSplinesDDim,
+            typename base_type::GridDDim,
+            base_type::SplineBoundary,
+            base_type::SplineBoundary,
+            ddc::SplineSolver::LAPACK,
+            typename base_type::GridDDim,
+            typename base_type::GridODim>;
+
+    using SplineDDimEvaluator = ddc::SplineEvaluator<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            typename base_type::BSplinesDDim,
+            typename base_type::GridDDim,
+            ddc::ConstantExtrapolationRule<DDim>,
+            ddc::ConstantExtrapolationRule<DDim>,
+            typename base_type::GridDDim,
+            typename base_type::GridODim>;
+
+    using IdxRangeDDim = typename base_type::IdxRangeDDim;
+    using IdxRangeODim = typename base_type::IdxRangeODim;
+    using IdxRangeFull = typename base_type::IdxRangeFull;
+
+    using SplineInterpPointsDDim = typename base_type::SplineInterpPointsDDim;
+    using SplineInterpPointsODim = typename base_type::SplineInterpPointsODim;
+
+    using GridDDim = typename base_type::GridDDim;
+    using GridODim = typename base_type::GridODim;
+
+    ddc::ConstantExtrapolationRule<DDim> const m_bv_min;
+    ddc::ConstantExtrapolationRule<DDim> const m_bv_max;
+
+public:
+    PartialDerivativeTestSpline1D(
+            double const ddim_min,
+            double const ddim_max,
+            double const odim_min,
+            double const odim_max)
+        : base_type(ddim_min, ddim_max, odim_min, odim_max)
+        , m_bv_min(typename base_type::CoordDDim(ddim_min))
+        , m_bv_max(typename base_type::CoordDDim(ddim_max))
+    {
+    }
+
+    double const operator()(double& delta_ddim) const
+    {
+        IdxRangeDDim const idxrange_ddim = SplineInterpPointsDDim::template get_domain<GridDDim>();
+        IdxRangeODim const idxrange_odim = SplineInterpPointsODim::template get_domain<GridODim>();
+        IdxRangeFull const idxrange = IdxRangeFull(idxrange_ddim, idxrange_odim);
+        SplineDDimBuilder const builder(idxrange);
+
+        SplineDDimEvaluator const spline_evaluator(m_bv_min, m_bv_max);
+
+        Spline1DPartialDerivativeCreator<SplineDDimBuilder, SplineDDimEvaluator> const
+                derivative_creator(builder, spline_evaluator);
+
+        using FunDiff = FunctionToDifferentiatePolynomial<typename base_type::DDim, spline_degree>;
+        FunDiff function_to_differentiate;
+        double const max_error = base_type::template compute_max_error<
+                FunDiff>(function_to_differentiate, derivative_creator, delta_ddim);
+
+        return max_error;
+    }
 };
-auto constexpr SplineYBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsY
-        = ddc::GrevilleInterpolationPoints<BSplinesY, SplineYBoundary, SplineYBoundary>;
-struct GridY : SplineInterpPointsY::interpolation_discrete_dimension_type
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with finite difference method for
+ * computing partial derivatives.
+ */
+template <
+        class DerivativeDimension,
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: You could template the operator instead
created_at: 2025-03-21T07:25:35Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    KOKKOS_FUNCTION double differentiate(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return (1. + m_degree) * ipow(x, m_degree) * y;
+        } else {
+            return ipow(x, m_degree + 1) + (m_degree + 1) * ipow(y, m_degree);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+
+    auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: Why does the FDM tests need to rely on splines though? Wouldn't it make more sense to have this in the splines and use random points for FDM?
created_at: 2025-03-21T07:26:50Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    KOKKOS_FUNCTION double differentiate(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return (1. + m_degree) * ipow(x, m_degree) * y;
+        } else {
+            return ipow(x, m_degree + 1) + (m_degree + 1) * ipow(y, m_degree);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+
+    auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
path: tests/math_tools/test_partial_derivatives.cpp
user: login: yanmnc
body: should be fixed by 10eda0d44dafa5b50b8122394034565a2d6528e3
created_at: 2025-03-21T10:38:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,164 +17,439 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+/**
+ * @brief A class that represents a polynomial test function for computing partial derivatives.
+ * The polynomial depends on two variables.
+ */
+template <class DerivativeDimension, std::size_t spline_degree>
+class FunctionToDifferentiatePolynomial
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordFull = Coord<DDim, ODim>;
+
+    int const m_degree;
+
+public:
+    FunctionToDifferentiatePolynomial() : m_degree(spline_degree + 1) {}
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+        return (ipow(x, m_degree + 1) + ipow(y, m_degree)) * y;
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    KOKKOS_FUNCTION double differentiate(CoordFull const coord) const
+    {
+        double const x = ddc::get<X>(coord);
+        double const y = ddc::get<Y>(coord);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return (1. + m_degree) * ipow(x, m_degree) * y;
+        } else {
+            return ipow(x, m_degree + 1) + (m_degree + 1) * ipow(y, m_degree);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+public:
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+
+    auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
+
+    struct GridX : NonUniformGridBase<X>
+    {
+    };
+
+    struct BSplinesY : ddc::NonUniformBSplines<Y, spline_degree>
+    {
+    };
+
+    using SplineInterpPointsY
+            = ddc::GrevilleInterpolationPoints<BSplinesY, SplineBoundary, SplineBoundary>;
+    struct GridY : NonUniformGridBase<Y>
+    {
+    };
+
+    using DDim = DerivativeDimension;
+    using ODim = std::conditional_t<std::is_same_v<DDim, X>, Y, X>;
+
+    using CoordDDim = Coord<DDim>;
+    using CoordODim = Coord<ODim>;
+
+    using GridDDim = std::conditional_t<std::is_same_v<DDim, X>, GridX, GridY>;
+    using GridODim = std::conditional_t<std::is_same_v<DDim, X>, GridY, GridX>;
+
+    using IdxRangeDDim = IdxRange<GridDDim>;
+    using IdxRangeODim = IdxRange<GridODim>;
+    using IdxRangeFull = IdxRange<GridDDim, GridODim>;
+
+    using IdxFull = Idx<GridDDim, GridODim>;
+
+    using IdxStepDDim = IdxStep<GridDDim>;
+    using IdxStepODim = IdxStep<GridODim>;
+    using IdxStepFull = IdxStep<GridDDim, GridODim>;
+
+    using BSplinesDDim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesX, BSplinesY>;
+    using BSplinesODim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesY, BSplinesX>;
+
+    using SplineInterpPointsDDim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsX, SplineInterpPointsY>;
+    using SplineInterpPointsODim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsY, SplineInterpPointsX>;
+
+    using DFieldMemType = DFieldMem<IdxRangeFull>;
+    using DFieldType = DField<IdxRangeFull>;
+
+    CoordDDim const m_ddim_min;
+    CoordDDim const m_ddim_max;
+    CoordODim const m_odim_min;
+    CoordODim const m_odim_max;
+    IdxStepDDim const m_ncells_ddim;
+    IdxStepODim const m_ncells_odim;
+
+    PartialDerivativeTest(
+            double const ddim_min,
+            double const ddim_max,
+            double const odim_min,
+            double const odim_max)
+        : m_ddim_min(ddim_min)
+        , m_ddim_max(ddim_max)
+        , m_odim_min(odim_min)
+        , m_odim_max(odim_max)
+        , m_ncells_ddim(N_ddim)
+        , m_ncells_odim(N_odim)
+    {
+        std::vector<CoordDDim> point_sampling_ddim
+                = build_random_non_uniform_break_points(m_ddim_min, m_ddim_max, m_ncells_ddim);
+
+        ddc::init_discrete_space<BSplinesDDim>(point_sampling_ddim);
+        ddc::init_discrete_space<GridDDim>(
+                SplineInterpPointsDDim::template get_sampling<GridDDim>());
+
+
+        std::vector<CoordODim> point_sampling_odim
+                = build_random_non_uniform_break_points(m_odim_min, m_odim_max, m_ncells_odim);
+
+        ddc::init_discrete_space<BSplinesODim>(point_sampling_odim);
+        ddc::init_discrete_space<GridODim>(
+                SplineInterpPointsODim::template get_sampling<GridODim>());
+    }
+
+    template <class FunctionToDifferentiate>
+    double compute_max_error(
+            FunctionToDifferentiate const& function_to_differentiate,
+            IPartialDerivativeCreator<IdxRangeFull, DerivativeDimension> const&
+                    partial_derivative_creator,
+            double& delta_ddim) const
+    {
+        IdxRangeDDim idxrange_ddim(SplineInterpPointsDDim::template get_domain<GridDDim>());
+        IdxRangeODim idxrange_odim(SplineInterpPointsODim::template get_domain<GridODim>());
+        IdxRangeFull idxrange(idxrange_ddim, idxrange_odim);
+
+        delta_ddim = ddcHelper::maximum_distance_between_adjacent_points(idxrange_ddim);
+
+        // field to be differentiated
+        DFieldMemType field_to_differentiate(idxrange);
+        DFieldType field_to_differentiate_proxy = get_field(field_to_differentiate);
+        ddc::parallel_for_each(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange,
+                KOKKOS_LAMBDA(IdxFull const idx) {
+                    field_to_differentiate_proxy(idx)
+                            = function_to_differentiate(ddc::coordinate(idx));
+                });
+
+        std::unique_ptr<IPartialDerivative<IdxRangeFull, DerivativeDimension>> const
+                partial_derivative_creator_pointer
+                = partial_derivative_creator.create_instance(
+                        get_const_field(field_to_differentiate));
+
+        IPartialDerivative<IdxRangeFull, DerivativeDimension> const& partial_derivative
+                = *partial_derivative_creator_pointer;
+
+        DFieldMemType field_differentiated_alloc(idxrange);
+        DFieldType field_differentiated = get_field(field_differentiated_alloc);
+        partial_derivative(field_differentiated);
+
+        double const max_error = ddc::parallel_transform_reduce(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange,
+                0.,
+                ddc::reducer::max<double>(),
+                KOKKOS_LAMBDA(IdxFull const idx) {
+                    return Kokkos::abs(
+                            field_differentiated(idx)
+                            - function_to_differentiate.differentiate(ddc::coordinate(idx)));
+                });
+
+        return max_error;
+    }
 };
-using CoordY = Coord<Y>;
-struct BSplinesY : ddc::UniformBSplines<Y, 3>
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with 1d splines for computing partial 
+ * derivatives.
+ */
+template <
+        class DerivativeDimension,
+        std::size_t N_ddim,
+        std::size_t N_odim,
+        std::size_t spline_degree>
+class PartialDerivativeTestSpline1D
+    : public PartialDerivativeTest<DerivativeDimension, N_ddim, N_odim, spline_degree>
 {
+private:
+    using base_type = PartialDerivativeTest<DerivativeDimension, N_ddim, N_odim, spline_degree>;
+    using DDim = DerivativeDimension;
+
+    using SplineDDimBuilder = ddc::SplineBuilder<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            typename base_type::BSplinesDDim,
+            typename base_type::GridDDim,
+            base_type::SplineBoundary,
+            base_type::SplineBoundary,
+            ddc::SplineSolver::LAPACK,
+            typename base_type::GridDDim,
+            typename base_type::GridODim>;
+
+    using SplineDDimEvaluator = ddc::SplineEvaluator<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            typename base_type::BSplinesDDim,
+            typename base_type::GridDDim,
+            ddc::ConstantExtrapolationRule<DDim>,
+            ddc::ConstantExtrapolationRule<DDim>,
+            typename base_type::GridDDim,
+            typename base_type::GridODim>;
+
+    using IdxRangeDDim = typename base_type::IdxRangeDDim;
+    using IdxRangeODim = typename base_type::IdxRangeODim;
+    using IdxRangeFull = typename base_type::IdxRangeFull;
+
+    using SplineInterpPointsDDim = typename base_type::SplineInterpPointsDDim;
+    using SplineInterpPointsODim = typename base_type::SplineInterpPointsODim;
+
+    using GridDDim = typename base_type::GridDDim;
+    using GridODim = typename base_type::GridODim;
+
+    ddc::ConstantExtrapolationRule<DDim> const m_bv_min;
+    ddc::ConstantExtrapolationRule<DDim> const m_bv_max;
+
+public:
+    PartialDerivativeTestSpline1D(
+            double const ddim_min,
+            double const ddim_max,
+            double const odim_min,
+            double const odim_max)
+        : base_type(ddim_min, ddim_max, odim_min, odim_max)
+        , m_bv_min(typename base_type::CoordDDim(ddim_min))
+        , m_bv_max(typename base_type::CoordDDim(ddim_max))
+    {
+    }
+
+    double const operator()(double& delta_ddim) const
+    {
+        IdxRangeDDim const idxrange_ddim = SplineInterpPointsDDim::template get_domain<GridDDim>();
+        IdxRangeODim const idxrange_odim = SplineInterpPointsODim::template get_domain<GridODim>();
+        IdxRangeFull const idxrange = IdxRangeFull(idxrange_ddim, idxrange_odim);
+        SplineDDimBuilder const builder(idxrange);
+
+        SplineDDimEvaluator const spline_evaluator(m_bv_min, m_bv_max);
+
+        Spline1DPartialDerivativeCreator<SplineDDimBuilder, SplineDDimEvaluator> const
+                derivative_creator(builder, spline_evaluator);
+
+        using FunDiff = FunctionToDifferentiatePolynomial<typename base_type::DDim, spline_degree>;
+        FunDiff function_to_differentiate;
+        double const max_error = base_type::template compute_max_error<
+                FunDiff>(function_to_differentiate, derivative_creator, delta_ddim);
+
+        return max_error;
+    }
 };
-auto constexpr SplineYBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsY
-        = ddc::GrevilleInterpolationPoints<BSplinesY, SplineYBoundary, SplineYBoundary>;
-struct GridY : SplineInterpPointsY::interpolation_discrete_dimension_type
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with finite difference method for
+ * computing partial derivatives.
+ */
+template <
+        class DerivativeDimension,
path: tests/math_tools/test_partial_derivatives.cpp
user: login: yanmnc
body: should be fixed now 
created_at: 2025-03-24T08:34:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,164 +17,447 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+std::size_t static constexpr spline_degree = 3;
+
+using CoordX = Coord<X>;
+using CoordY = Coord<Y>;
+using CoordXY = Coord<X, Y>;
+
+
+/**
+ * @brief A class that represents a test function for computing partial derivatives.
+ * The test function is defined as the product of two cosine functions.
+ */
+class FunctionToDifferentiateCosine
 {
+public:
+    /**
+     * @brief Get the value of the function at given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate
+     * the function.
+     *
+     * @return The value of the function at the coordinate.
+     */
+    KOKKOS_FUNCTION double operator()(CoordXY const coord_xy) const
+    {
+        double const x = ddc::get<X>(coord_xy);
+        double const y = ddc::get<Y>(coord_xy);
+        return Kokkos::cos(x) * Kokkos::cos(y);
+    }
+
+    /**
+     * @brief Get the value of the partial derivative of the function
+     * in the DerivativeDimension direction at a given coordinate.
+     *
+     * @param[in] coord_xy The coordinate where we want to evaluate 
+     * the partial derivative.
+     *
+     * @return The value of the partial derivative of the function
+     * at the coordinate.
+     */
+    template <class DerivativeDimension>
+    KOKKOS_FUNCTION double differentiate(CoordXY const coord_xy) const
+    {
+        static_assert(
+                std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+        double const x = ddc::get<X>(coord_xy);
+        double const y = ddc::get<Y>(coord_xy);
+
+        if constexpr (std::is_same_v<DerivativeDimension, X>) {
+            return -Kokkos::sin(x) * Kokkos::cos(y);
+        } else {
+            return -Kokkos::cos(x) * Kokkos::sin(y);
+        }
+    }
 };
-using IdxX = Idx<GridX>;
-using IdxStepX = IdxStep<GridX>;
-using IdxRangeX = IdxRange<GridX>;
 
-struct Y
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with several implementations for computing
+ * partial derivatives.
+ */
+template <std::size_t ncells_x, std::size_t ncells_y>
+class PartialDerivativeTest
 {
-    static bool constexpr PERIODIC = false;
+public:
+    struct GridX : NonUniformGridBase<X>
+    {
+    };
+
+    struct GridY : NonUniformGridBase<Y>
+    {
+    };
+
+    using IdxRangeX = IdxRange<GridX>;
+    using IdxRangeY = IdxRange<GridY>;
+    using IdxRangeXY = IdxRange<GridX, GridY>;
+
+    using IdxX = Idx<GridX>;
+    using IdxY = Idx<GridY>;
+    using IdxXY = Idx<GridX, GridY>;
+
+    using IdxStepX = IdxStep<GridX>;
+    using IdxStepY = IdxStep<GridY>;
+
+    using DFieldMemType = DFieldMem<IdxRangeXY>;
+    using DFieldType = DField<IdxRangeXY>;
+
+protected:
+    CoordX const m_xmin;
+    CoordX const m_xmax;
+    CoordY const m_ymin;
+    CoordY const m_ymax;
+
+    IdxStepX const m_ncells_x;
+    IdxStepY const m_ncells_y;
+
+public:
+    PartialDerivativeTest(
+            double const xmin,
+            double const xmax,
+            double const ymin,
+            double const ymax)
+        : m_xmin(xmin)
+        , m_xmax(xmax)
+        , m_ymin(ymin)
+        , m_ymax(ymax)
+        , m_ncells_x(ncells_x)
+        , m_ncells_y(ncells_y)
+    {
+    }
+
+    template <class DerivativeDimension, class FunctionToDifferentiate>
+    double compute_max_error(
+            IdxRangeXY const& idxrange_xy,
+            FunctionToDifferentiate const& function_to_differentiate,
+            IPartialDerivativeCreator<IdxRangeXY, DerivativeDimension> const&
+                    partial_derivative_creator,
+            double& max_distance) const
+    {
+        using GridDDim = std::conditional_t<std::is_same_v<DerivativeDimension, X>, GridX, GridY>;
+
+        max_distance = ddcHelper::maximum_distance_between_adjacent_points(
+                ddc::select<GridDDim>(idxrange_xy));
+
+        // field to be differentiated
+        DFieldMemType field_to_differentiate(idxrange_xy);
+        DFieldType field_to_differentiate_proxy = get_field(field_to_differentiate);
+        ddc::parallel_for_each(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange_xy,
+                KOKKOS_LAMBDA(IdxXY const idx) {
+                    field_to_differentiate_proxy(idx)
+                            = function_to_differentiate(ddc::coordinate(idx));
+                });
+
+        std::unique_ptr<IPartialDerivative<IdxRangeXY, DerivativeDimension>> const
+                partial_derivative_creator_pointer
+                = partial_derivative_creator.create_instance(
+                        get_const_field(field_to_differentiate));
+
+        IPartialDerivative<IdxRangeXY, DerivativeDimension> const& partial_derivative
+                = *partial_derivative_creator_pointer;
+
+        DFieldMemType field_differentiated_alloc(idxrange_xy);
+        DFieldType field_differentiated = get_field(field_differentiated_alloc);
+        partial_derivative(field_differentiated);
+
+        double const max_error = ddc::parallel_transform_reduce(
+                Kokkos::DefaultExecutionSpace(),
+                idxrange_xy,
+                0.,
+                ddc::reducer::max<double>(),
+                KOKKOS_LAMBDA(IdxXY const idx) {
+                    return Kokkos::abs(
+                            field_differentiated(idx)
+                            - function_to_differentiate.template differentiate<DerivativeDimension>(
+                                    ddc::coordinate(idx)));
+                });
+
+        return max_error;
+    }
 };
-using CoordY = Coord<Y>;
-struct BSplinesY : ddc::UniformBSplines<Y, 3>
+
+/**
+ * @brief A class that represents a test for partial derivatives.
+ * The test can be used with 1d splines for computing partial 
+ * derivatives.
+ */
+template <class DerivativeDimension, std::size_t ncells_x, std::size_t ncells_y>
+class PartialDerivativeTestSpline1D : public PartialDerivativeTest<ncells_x, ncells_y>
 {
+    static_assert(std::is_same_v<DerivativeDimension, X> || std::is_same_v<DerivativeDimension, Y>);
+
+private:
+    using base_type = PartialDerivativeTest<ncells_x, ncells_y>;
+    using DDim = DerivativeDimension;
+
+    using typename base_type::GridX;
+    using typename base_type::GridY;
+
+    using typename base_type::IdxRangeX;
+    using typename base_type::IdxRangeXY;
+    using typename base_type::IdxRangeY;
+
+    struct BSplinesX : ddc::NonUniformBSplines<X, spline_degree>
+    {
+    };
+    using SplineInterpPointsX
+            = ddc::GrevilleInterpolationPoints<BSplinesX, SplineBoundary, SplineBoundary>;
+
+
+    struct BSplinesY : ddc::NonUniformBSplines<Y, spline_degree>
+    {
+    };
+    using SplineInterpPointsY
+            = ddc::GrevilleInterpolationPoints<BSplinesY, SplineBoundary, SplineBoundary>;
+
+    using BSplinesDDim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesX, BSplinesY>;
+    using BSplinesODim = std::conditional_t<std::is_same_v<DDim, X>, BSplinesY, BSplinesX>;
+
+    using GridDDim = std::conditional_t<std::is_same_v<DDim, X>, GridX, GridY>;
+    using GridODim = std::conditional_t<std::is_same_v<DDim, X>, GridY, GridX>;
+
+    using SplineInterpPointsDDim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsX, SplineInterpPointsY>;
+    using SplineInterpPointsODim
+            = std::conditional_t<std::is_same_v<DDim, X>, SplineInterpPointsY, SplineInterpPointsX>;
+
+    using SplineDDimBuilder = ddc::SplineBuilder<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            BSplinesDDim,
+            GridDDim,
+            SplineBoundary,
+            SplineBoundary,
+            ddc::SplineSolver::LAPACK,
+            GridX,
+            GridY>;
+
+    using SplineDDimEvaluator = ddc::SplineEvaluator<
+            Kokkos::DefaultExecutionSpace,
+            Kokkos::DefaultExecutionSpace::memory_space,
+            BSplinesDDim,
+            GridDDim,
+            ddc::ConstantExtrapolationRule<DDim>,
+            ddc::ConstantExtrapolationRule<DDim>,
+            GridX,
+            GridY>;
+
+public:
+    PartialDerivativeTestSpline1D(
+            double const xmin,
+            double const xmax,
+            double const ymin,
+            double const ymax)
+        : base_type(xmin, xmax, ymin, ymax)
+    {
+        std::vector<CoordX> point_sampling_x = build_random_non_uniform_break_points(
+                base_type::m_xmin,
+                base_type::m_xmax,
+                base_type::m_ncells_x,
+                0.2);
+
+        ddc::init_discrete_space<BSplinesX>(point_sampling_x);
+        ddc::init_discrete_space<GridX>(SplineInterpPointsX::template get_sampling<GridX>());
+
+        std::vector<CoordY> point_sampling_y = build_random_non_uniform_break_points(
+                base_type::m_ymin,
+                base_type::m_ymax,
+                base_type::m_ncells_y,
+                0.2);
+
+        ddc::init_discrete_space<BSplinesY>(point_sampling_y);
+        ddc::init_discrete_space<GridY>(SplineInterpPointsY::template get_sampling<GridY>());
+    }
+
+    double const compute_error(double& max_distance) const
+    {
+        IdxRangeX const idxrange_x = SplineInterpPointsX::template get_domain<GridX>();
+        IdxRangeY const idxrange_y = SplineInterpPointsY::template get_domain<GridY>();
+        IdxRangeXY const idxrange = IdxRangeXY(idxrange_x, idxrange_y);
+
+        double dmin, dmax;
+        if (std::is_same_v<DDim, X>) {
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: ```suggestion
        if constexpr (std::is_same_v<DDim, X>) {
```
created_at: 2025-03-24T09:48:38Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,447 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+std::size_t static constexpr spline_degree = 3;
+
+using CoordX = Coord<X>;
+using CoordY = Coord<Y>;
+using CoordXY = Coord<X, Y>;
+
+
+/**
+ * @brief A class that represents a test function for computing partial derivatives.
+ * The test function is defined as the product of two cosine functions.
+ */
+class FunctionToDifferentiateCosine
 {
+public:
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: ```suggestion
public:
    KOKKOS_DEFAULTED_FUNCTION FunctionToDifferentiateCosine() = default;

```
created_at: 2025-03-24T11:27:38Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,463 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+std::size_t static constexpr spline_degree = 3;
+
+using CoordX = Coord<X>;
+using CoordY = Coord<Y>;
+using CoordXY = Coord<X, Y>;
+
+
+/**
+ * @brief A class that represents a test function for computing partial derivatives.
+ * The test function is defined as the product of two cosine functions.
+ */
+class FunctionToDifferentiateCosine
 {
+public:
+    /**
+     * @brief Default constructor.
+     */
+    FunctionToDifferentiateCosine() = default;
+
+    /**
+     * @brief Default copy constructor.
+     */
+    KOKKOS_DEFAULTED_FUNCTION FunctionToDifferentiateCosine(FunctionToDifferentiateCosine const&)
+            = default;
+
+    /**
+     * @brief Default destructor.
+     */
+    ~FunctionToDifferentiateCosine() = default;
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: ```suggestion
    KOKKOS_DEFAULTED_FUNCTION ~FunctionToDifferentiateCosine() = default;
```
created_at: 2025-03-24T14:52:17Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,463 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+std::size_t static constexpr spline_degree = 3;
+
+using CoordX = Coord<X>;
+using CoordY = Coord<Y>;
+using CoordXY = Coord<X, Y>;
+
+
+/**
+ * @brief A class that represents a test function for computing partial derivatives.
+ * The test function is defined as the product of two cosine functions.
+ */
+class FunctionToDifferentiateCosine
 {
+public:
+    /**
+     * @brief Default constructor.
+     */
+    FunctionToDifferentiateCosine() = default;
path: tests/math_tools/test_partial_derivatives.cpp
user: login: EmilyBourne
body: ```suggestion
    KOKKOS_DEFAULTED_FUNCTION FunctionToDifferentiateCosine() = default;
```
created_at: 2025-03-24T14:52:22Z
author_association: MEMBER
diff_hunk: @@ -15,164 +17,463 @@ struct X
 {
     static bool constexpr PERIODIC = false;
 };
-using CoordX = Coord<X>;
-struct BSplinesX : ddc::UniformBSplines<X, 3>
+
+struct Y
 {
+    static bool constexpr PERIODIC = false;
 };
-auto constexpr SplineXBoundary = ddc::BoundCond::GREVILLE;
-using SplineInterpPointsX
-        = ddc::GrevilleInterpolationPoints<BSplinesX, SplineXBoundary, SplineXBoundary>;
 
-struct GridX : NonUniformGridBase<X>
+auto static constexpr SplineBoundary = ddc::BoundCond::GREVILLE;
+
+std::size_t static constexpr spline_degree = 3;
+
+using CoordX = Coord<X>;
+using CoordY = Coord<Y>;
+using CoordXY = Coord<X, Y>;
+
+
+/**
+ * @brief A class that represents a test function for computing partial derivatives.
+ * The test function is defined as the product of two cosine functions.
+ */
+class FunctionToDifferentiateCosine
 {
+public:
+    /**
+     * @brief Default constructor.
+     */
+    FunctionToDifferentiateCosine() = default;
path: tests/math_tools/test_partial_derivatives.cpp
user: login: yanmnc
body: 89e351dbb41dbe018c4b8c81e0da1a50d7954bba
created_at: 2025-03-24T14:55:34Z
author_association: CONTRIBUTOR

PR comments:
user: login: EmilyBourne
created_at: 2025-03-21T08:34:48Z
body: Please fix conflicts with main branch
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-03-21T10:10:19Z
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
user: login: EmilyBourne
created_at: 2025-03-24T11:24:53Z
body: ![image](https://github.com/user-attachments/assets/1751a6fa-c466-403a-972e-d73abdeb9da5)

author_association: MEMBER
user: login: EmilyBourne
created_at: 2025-03-24T11:26:52Z
body: ![image](https://github.com/user-attachments/assets/c01e94d7-24f0-4a6b-a4d6-ab07e254bdb4)

![image](https://github.com/user-attachments/assets/634bab06-6244-41f7-bad4-c50b47c93bd6)

![image](https://github.com/user-attachments/assets/e3d08c27-4403-4d54-8cc2-c7842ca8a903)

![image](https://github.com/user-attachments/assets/a88efcf4-edf7-424a-8efd-be9e1551cc07)

![image](https://github.com/user-attachments/assets/5de6f280-bcf7-4952-8662-e059a8e4d9eb)

![image](https://github.com/user-attachments/assets/b4ea40ff-23dc-4467-b4ce-bfb046539e02)

author_association: MEMBER