/**
 * @file   fem_gradient_operator.hh
 *
 * @author Lars Pastewka <lars.pastewka@imtek.uni-freiburg.de>
 *
 * @date   25 Dec 2024
 *
 * @brief  Hard-coded linear FEM gradient operator
 *
 * This class provides optimized implementations of the gradient operator
 * for linear finite elements on structured grids:
 * - 2D: 2 triangles per pixel, 4 nodal points, 2 quadrature points
 * - 3D: 5 tetrahedra per voxel, 8 nodal points, 5 quadrature points
 *
 * Copyright © 2024 Lars Pastewka
 *
 * µGrid is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3, or (at
 * your option) any later version.
 *
 * µGrid is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with µGrid; see the file COPYING. If not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Additional permission under GNU GPL version 3 section 7
 *
 * If you modify this Program, or any covered work, by linking or combining it
 * with proprietary FFT implementations or numerical libraries, containing parts
 * covered by the terms of those libraries' licenses, the licensors of this
 * Program grant you additional permission to convey the resulting work.
 *
 */

#ifndef SRC_LIBMUGRID_OPERATORS_FEM_GRADIENT_OPERATOR_HH_
#define SRC_LIBMUGRID_OPERATORS_FEM_GRADIENT_OPERATOR_HH_

#include "core/types.hh"
#include "field/field_typed.hh"
#include "memory/memory_space.hh"
#include "operators/convolution_operator_base.hh"

#include <array>
#include <vector>

namespace muGrid {

    // Forward declaration
    class GlobalFieldCollection;

    /**
     * @class FEMGradientOperator
     * @brief Hard-coded linear FEM gradient operator with optimized implementation.
     *
     * This class provides optimized implementations of the gradient operator
     * for linear finite elements on structured grids:
     *
     * **2D (Linear Triangles):**
     * - 4 nodal points per pixel (corners at [0,0], [1,0], [0,1], [1,1])
     * - 2 triangles per pixel (lower-left and upper-right)
     * - 2 quadrature points (one per triangle, at centroid)
     * - 2 gradient components (d/dx, d/dy)
     *
     * **3D (Linear Tetrahedra):**
     * - 8 nodal points per voxel (corners of unit cube)
     * - 5 tetrahedra per voxel (Kuhn triangulation)
     * - 5 quadrature points (one per tetrahedron, at centroid)
     * - 3 gradient components (d/dx, d/dy, d/dz)
     *
     * The apply() method computes the gradient (nodal → quadrature points).
     * The transpose() method computes the divergence (quadrature → nodal points).
     *
     * Shape function gradients are compile-time constants for linear elements,
     * enabling SIMD vectorization and optimal performance.
     */
    class FEMGradientOperator : public ConvolutionOperatorBase {
    public:
        using Parent = ConvolutionOperatorBase;

        /**
         * @brief Construct a FEM gradient operator for the given dimension.
         * @param spatial_dim Spatial dimension (2 or 3)
         * @param scale Scale factor applied to the output (default: 1.0)
         *
         * The scale factor can be used to incorporate grid spacing.
         * For a grid with spacing (hx, hy), use scale = 1.0 and the
         * shape function gradients will be scaled by 1/h internally.
         */
        explicit FEMGradientOperator(Index_t spatial_dim,
                                      std::vector<Real> grid_spacing = {});

        //! Default constructor is deleted
        FEMGradientOperator() = delete;

        //! Copy constructor is deleted
        FEMGradientOperator(const FEMGradientOperator &other) = delete;

        //! Move constructor
        FEMGradientOperator(FEMGradientOperator &&other) = default;

        //! Destructor
        ~FEMGradientOperator() override = default;

        //! Copy assignment operator is deleted
        FEMGradientOperator &operator=(const FEMGradientOperator &other) = delete;

        //! Move assignment operator
        FEMGradientOperator &operator=(FEMGradientOperator &&other) = default;

        /**
         * @brief Apply the gradient operator (nodal → quadrature).
         *
         * Input field: nodal values with shape [nb_nodal_pts, ...]
         * Output field: gradient at quadrature points with shape [dim, nb_quad_pts, ...]
         *
         * @param nodal_field Input field at nodal points
         * @param gradient_field Output gradient field at quadrature points
         */
        void apply(const TypedFieldBase<Real> &nodal_field,
                   TypedFieldBase<Real> &gradient_field) const override;

        /**
         * @brief Apply the gradient operator with increment.
         *
         * Computes: gradient_field += alpha * grad(nodal_field)
         *
         * @param nodal_field Input field at nodal points
         * @param alpha Scaling factor for the increment
         * @param gradient_field Output gradient field to increment
         */
        void apply_increment(const TypedFieldBase<Real> &nodal_field,
                             const Real &alpha,
                             TypedFieldBase<Real> &gradient_field) const override;

        /**
         * @brief Apply the transpose (divergence) operator (quadrature → nodal).
         *
         * Computes: nodal_field = -div(gradient_field)
         *           (negative divergence for consistency with weak form)
         *
         * @param gradient_field Input gradient field at quadrature points
         * @param nodal_field Output field at nodal points
         * @param weights Quadrature weights (optional, default: equal weights)
         */
        void transpose(const TypedFieldBase<Real> &gradient_field,
                       TypedFieldBase<Real> &nodal_field,
                       const std::vector<Real> &weights = {}) const override;

        /**
         * @brief Apply the transpose (divergence) with increment.
         *
         * Computes: nodal_field += alpha * (-div(gradient_field))
         *
         * @param gradient_field Input gradient field at quadrature points
         * @param alpha Scaling factor for the increment
         * @param nodal_field Output field at nodal points to increment
         * @param weights Quadrature weights (optional)
         */
        void transpose_increment(const TypedFieldBase<Real> &gradient_field,
                                 const Real &alpha,
                                 TypedFieldBase<Real> &nodal_field,
                                 const std::vector<Real> &weights = {}) const override;

#if defined(MUGRID_ENABLE_CUDA) || defined(MUGRID_ENABLE_HIP)
        /**
         * @brief Apply the gradient operator on device memory fields.
         */
        void apply(const TypedFieldBase<Real, DefaultDeviceSpace> &nodal_field,
                   TypedFieldBase<Real, DefaultDeviceSpace> &gradient_field) const;

        /**
         * @brief Apply the gradient operator with increment on device memory.
         */
        void apply_increment(const TypedFieldBase<Real, DefaultDeviceSpace> &nodal_field,
                             const Real &alpha,
                             TypedFieldBase<Real, DefaultDeviceSpace> &gradient_field) const;

        /**
         * @brief Apply the transpose on device memory.
         */
        void transpose(const TypedFieldBase<Real, DefaultDeviceSpace> &gradient_field,
                       TypedFieldBase<Real, DefaultDeviceSpace> &nodal_field,
                       const std::vector<Real> &weights = {}) const;

        /**
         * @brief Apply the transpose with increment on device memory.
         */
        void transpose_increment(const TypedFieldBase<Real, DefaultDeviceSpace> &gradient_field,
                                 const Real &alpha,
                                 TypedFieldBase<Real, DefaultDeviceSpace> &nodal_field,
                                 const std::vector<Real> &weights = {}) const;
#endif

        /**
         * @brief Get the number of gradient components (same as spatial_dim).
         * @return Number of operators (2 for 2D, 3 for 3D)
         */
        Index_t get_nb_operators() const override { return spatial_dim; }

        /**
         * @brief Get the number of quadrature points per pixel/voxel.
         * @return 2 for 2D (triangles), 5 for 3D (tetrahedra)
         */
        Index_t get_nb_quad_pts() const override {
            return spatial_dim == 2 ? 2 : 5;
        }

        /**
         * @brief Get the number of nodal points per pixel/voxel.
         *
         * For continuous FEM, nodes are shared between pixels via ghost
         * communication. Each pixel uses values from 4 (2D) or 8 (3D) grid
         * points, but the field itself has only 1 value per grid point.
         * Ghost communication handles the periodic boundary conditions.
         *
         * @return 1 (one scalar value per grid point, neighbors accessed via ghosts)
         */
        Index_t get_nb_nodal_pts() const override {
            return 1;
        }

        /**
         * @brief Get the spatial dimension.
         * @return Spatial dimension (2 or 3)
         */
        Index_t get_spatial_dim() const override { return spatial_dim; }

        /**
         * @brief Get the grid spacing.
         * @return Grid spacing vector
         */
        const std::vector<Real>& get_grid_spacing() const { return grid_spacing; }

        /**
         * @brief Get the quadrature weights.
         * @return Vector of quadrature weights (one per quadrature point)
         *
         * For 2D: Each triangle has weight = 0.5 (half the pixel area)
         * For 3D: Tetrahedra have weights summing to 1.0 (voxel volume)
         */
        std::vector<Real> get_quadrature_weights() const;

        /**
         * @brief Get the stencil offset.
         * @return Stencil offset in pixels ([0,0] for 2D, [0,0,0] for 3D)
         */
        Shape_t get_pixel_offset() const {
            return Shape_t(spatial_dim, 0);
        }

        /**
         * @brief Get the stencil shape.
         * @return Shape of the stencil ([2,2] for 2D, [2,2,2] for 3D)
         */
        Shape_t get_conv_pts_shape() const {
            return Shape_t(spatial_dim, 2);
        }

        /**
         * @brief Get the shape function gradients as pixel operator.
         * @return Vector of shape function gradients [nb_ops * nb_quad * nb_nodal * conv_pts_size]
         *
         * Returns the shape function gradients scaled by grid spacing.
         * Shape: (nb_operators, nb_quad_pts, nb_nodal_pts, *conv_pts_shape)
         * where conv_pts_shape is [2,2] for 2D or [2,2,2] for 3D.
         */
        std::vector<Real> get_pixel_operator() const;

    private:
        Index_t spatial_dim;
        std::vector<Real> grid_spacing;

        /**
         * @brief Validate that fields are compatible with this operator.
         * @param nodal_field Input field at nodal points
         * @param gradient_field Output field at gradient/quadrature points
         * @param nb_components Output: number of components in nodal field
         * @throws RuntimeError if validation fails
         */
        const GlobalFieldCollection& validate_fields(
            const Field &nodal_field,
            const Field &gradient_field,
            Index_t &nb_components) const;

        /**
         * @brief Internal implementation of apply with optional increment.
         */
        void apply_impl(const TypedFieldBase<Real> &nodal_field,
                        TypedFieldBase<Real> &gradient_field,
                        Real alpha,
                        bool increment) const;

        /**
         * @brief Internal implementation of transpose with optional increment.
         */
        void transpose_impl(const TypedFieldBase<Real> &gradient_field,
                            TypedFieldBase<Real> &nodal_field,
                            Real alpha,
                            bool increment,
                            const std::vector<Real> &weights) const;

#if defined(MUGRID_ENABLE_CUDA) || defined(MUGRID_ENABLE_HIP)
        void apply_impl(const TypedFieldBase<Real, DefaultDeviceSpace> &nodal_field,
                        TypedFieldBase<Real, DefaultDeviceSpace> &gradient_field,
                        Real alpha,
                        bool increment) const;

        void transpose_impl(const TypedFieldBase<Real, DefaultDeviceSpace> &gradient_field,
                            TypedFieldBase<Real, DefaultDeviceSpace> &nodal_field,
                            Real alpha,
                            bool increment,
                            const std::vector<Real> &weights) const;
#endif
    };

    // Kernel implementations
    namespace fem_gradient_kernels {

        // =====================================================================
        // 2D Linear Triangle Shape Function Gradients
        // =====================================================================
        //
        // Pixel corners (nodal points):
        //   Node 0: (0, 0)  - bottom-left
        //   Node 1: (1, 0)  - bottom-right
        //   Node 2: (0, 1)  - top-left
        //   Node 3: (1, 1)  - top-right
        //
        // Two triangles per pixel:
        //   Triangle 0 (lower-left):  Nodes 0, 1, 2
        //   Triangle 1 (upper-right): Nodes 1, 3, 2
        //
        // For Triangle 0 with vertices at (0,0), (hx,0), (0,hy):
        //   N0 = 1 - x/hx - y/hy → dN0/dx = -1/hx, dN0/dy = -1/hy
        //   N1 = x/hx            → dN1/dx =  1/hx, dN1/dy =  0
        //   N2 = y/hy            → dN2/dx =  0,    dN2/dy =  1/hy
        //   N3 = 0               → dN3/dx =  0,    dN3/dy =  0
        //
        // For Triangle 1 with vertices at (hx,0), (hx,hy), (0,hy):
        //   N0 = 0               → dN0/dx =  0,    dN0/dy =  0
        //   N1 = 1 - y/hy        → dN1/dx =  0,    dN1/dy = -1/hy
        //   N2 = 1 - x/hx        → dN2/dx = -1/hx, dN2/dy =  0
        //   N3 = x/hx + y/hy - 1 → dN3/dx =  1/hx, dN3/dy =  1/hy
        //
        // =====================================================================

        // Number of nodes and quadrature points for 2D
        constexpr Index_t NB_NODES_2D = 4;
        constexpr Index_t NB_QUAD_2D = 2;
        constexpr Index_t DIM_2D = 2;

        // Shape function gradients for 2D [dim][quad][node]
        // Scaled by grid spacing at runtime
        constexpr Real B_2D_REF[DIM_2D][NB_QUAD_2D][NB_NODES_2D] = {
            // d/dx gradients
            {
                {-1.0,  1.0,  0.0,  0.0},  // Triangle 0: nodes 0,1,2,3
                { 0.0,  0.0, -1.0,  1.0}   // Triangle 1: nodes 0,1,2,3
            },
            // d/dy gradients
            {
                {-1.0,  0.0,  1.0,  0.0},  // Triangle 0
                { 0.0, -1.0,  0.0,  1.0}   // Triangle 1
            }
        };

        // Node offsets within pixel for 2D [node][dim]
        // Node ordering: 0=(0,0), 1=(1,0), 2=(0,1), 3=(1,1)
        constexpr Index_t NODE_OFFSET_2D[NB_NODES_2D][DIM_2D] = {
            {0, 0},  // Node 0: bottom-left
            {1, 0},  // Node 1: bottom-right
            {0, 1},  // Node 2: top-left
            {1, 1}   // Node 3: top-right
        };

        // Quadrature weights for 2D (area of each triangle / total pixel area)
        constexpr Real QUAD_WEIGHT_2D[NB_QUAD_2D] = {0.5, 0.5};

        // =====================================================================
        // 3D Linear Tetrahedra Shape Function Gradients (Kuhn Triangulation)
        // =====================================================================
        //
        // Voxel corners (nodal points) - binary indexing:
        //   Node 0: (0,0,0), Node 1: (1,0,0), Node 2: (0,1,0), Node 3: (1,1,0)
        //   Node 4: (0,0,1), Node 5: (1,0,1), Node 6: (0,1,1), Node 7: (1,1,1)
        //
        // 5 tetrahedra per voxel (Kuhn triangulation):
        //   Tet 0: Nodes 0, 1, 2, 4
        //   Tet 1: Nodes 1, 2, 4, 5
        //   Tet 2: Nodes 2, 4, 5, 6
        //   Tet 3: Nodes 1, 2, 3, 5
        //   Tet 4: Nodes 2, 3, 5, 7
        //   (Note: Center tetrahedron connects nodes 2, 5 which is the main diagonal)
        //
        // =====================================================================

        // Number of nodes and quadrature points for 3D
        constexpr Index_t NB_NODES_3D = 8;
        constexpr Index_t NB_QUAD_3D = 5;
        constexpr Index_t DIM_3D = 3;

        // Node offsets within voxel for 3D [node][dim]
        constexpr Index_t NODE_OFFSET_3D[NB_NODES_3D][DIM_3D] = {
            {0, 0, 0},  // Node 0
            {1, 0, 0},  // Node 1
            {0, 1, 0},  // Node 2
            {1, 1, 0},  // Node 3
            {0, 0, 1},  // Node 4
            {1, 0, 1},  // Node 5
            {0, 1, 1},  // Node 6
            {1, 1, 1}   // Node 7
        };

        // Tetrahedra node indices [tet][4 nodes]
        constexpr Index_t TET_NODES[NB_QUAD_3D][4] = {
            {0, 1, 2, 4},  // Tet 0
            {1, 2, 4, 5},  // Tet 1
            {2, 4, 5, 6},  // Tet 2
            {1, 2, 3, 5},  // Tet 3
            {2, 3, 5, 7}   // Tet 4 (using node 7 instead of 6 for proper Kuhn)
        };

        // Shape function gradients for 3D [dim][quad][node]
        // For a tetrahedron with vertices v0, v1, v2, v3:
        // grad(N_i) = (1/6V) * (face_normal_opposite_to_i)
        // These are pre-computed for the Kuhn triangulation
        extern const Real B_3D_REF[DIM_3D][NB_QUAD_3D][NB_NODES_3D];

        // Quadrature weights for 3D (volume of each tet / total voxel volume)
        // For Kuhn triangulation: 4 corner tets have volume 1/6, center tet has volume 1/3
        // But with 5 tets: each corner tet = 1/6, total = 5/6... need to verify
        // Actually for 5-tet Kuhn: volumes are 1/6, 1/6, 1/6, 1/6, 1/6 (all equal)
        constexpr Real QUAD_WEIGHT_3D[NB_QUAD_3D] = {0.2, 0.2, 0.2, 0.2, 0.2};

        // =====================================================================
        // Host Kernel Declarations
        // =====================================================================

        /**
         * @brief Apply 2D FEM gradient operator on host.
         *
         * @param nodal_input Input nodal field [nb_nodes * nx * ny]
         * @param gradient_output Output gradient field [dim * nb_quad * nx * ny]
         * @param nx Grid size in x (including ghosts)
         * @param ny Grid size in y (including ghosts)
         * @param nodal_stride_x Stride for nodal field in x
         * @param nodal_stride_y Stride for nodal field in y
         * @param nodal_stride_n Stride between nodal points
         * @param grad_stride_x Stride for gradient field in x
         * @param grad_stride_y Stride for gradient field in y
         * @param grad_stride_q Stride between quadrature points
         * @param grad_stride_d Stride between gradient components
         * @param hx Grid spacing in x
         * @param hy Grid spacing in y
         * @param alpha Scale factor
         * @param increment If true, add to output; if false, overwrite
         */
        void fem_gradient_2d_host(
            const Real* MUGRID_RESTRICT nodal_input,
            Real* MUGRID_RESTRICT gradient_output,
            Index_t nx, Index_t ny,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_n,
            Index_t grad_stride_x, Index_t grad_stride_y,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Real hx, Real hy,
            Real alpha,
            bool increment);

        /**
         * @brief Apply 2D FEM transpose (divergence) operator on host.
         */
        void fem_divergence_2d_host(
            const Real* MUGRID_RESTRICT gradient_input,
            Real* MUGRID_RESTRICT nodal_output,
            Index_t nx, Index_t ny,
            Index_t grad_stride_x, Index_t grad_stride_y,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_n,
            Real hx, Real hy,
            const Real* quad_weights,
            Real alpha,
            bool increment);

        /**
         * @brief Apply 3D FEM gradient operator on host.
         */
        void fem_gradient_3d_host(
            const Real* MUGRID_RESTRICT nodal_input,
            Real* MUGRID_RESTRICT gradient_output,
            Index_t nx, Index_t ny, Index_t nz,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_z,
            Index_t nodal_stride_n,
            Index_t grad_stride_x, Index_t grad_stride_y, Index_t grad_stride_z,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Real hx, Real hy, Real hz,
            Real alpha,
            bool increment);

        /**
         * @brief Apply 3D FEM transpose (divergence) operator on host.
         */
        void fem_divergence_3d_host(
            const Real* MUGRID_RESTRICT gradient_input,
            Real* MUGRID_RESTRICT nodal_output,
            Index_t nx, Index_t ny, Index_t nz,
            Index_t grad_stride_x, Index_t grad_stride_y, Index_t grad_stride_z,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_z,
            Index_t nodal_stride_n,
            Real hx, Real hy, Real hz,
            const Real* quad_weights,
            Real alpha,
            bool increment);

#if defined(MUGRID_ENABLE_CUDA)
        void fem_gradient_2d_cuda(
            const Real* nodal_input,
            Real* gradient_output,
            Index_t nx, Index_t ny,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_n,
            Index_t grad_stride_x, Index_t grad_stride_y,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Real hx, Real hy,
            Real alpha,
            bool increment);

        void fem_divergence_2d_cuda(
            const Real* gradient_input,
            Real* nodal_output,
            Index_t nx, Index_t ny,
            Index_t grad_stride_x, Index_t grad_stride_y,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_n,
            Real hx, Real hy,
            const Real* quad_weights,
            Real alpha,
            bool increment);

        void fem_gradient_3d_cuda(
            const Real* nodal_input,
            Real* gradient_output,
            Index_t nx, Index_t ny, Index_t nz,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_z,
            Index_t nodal_stride_n,
            Index_t grad_stride_x, Index_t grad_stride_y, Index_t grad_stride_z,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Real hx, Real hy, Real hz,
            Real alpha,
            bool increment);

        void fem_divergence_3d_cuda(
            const Real* gradient_input,
            Real* nodal_output,
            Index_t nx, Index_t ny, Index_t nz,
            Index_t grad_stride_x, Index_t grad_stride_y, Index_t grad_stride_z,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_z,
            Index_t nodal_stride_n,
            Real hx, Real hy, Real hz,
            const Real* quad_weights,
            Real alpha,
            bool increment);
#endif

#if defined(MUGRID_ENABLE_HIP)
        void fem_gradient_2d_hip(
            const Real* nodal_input,
            Real* gradient_output,
            Index_t nx, Index_t ny,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_n,
            Index_t grad_stride_x, Index_t grad_stride_y,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Real hx, Real hy,
            Real alpha,
            bool increment);

        void fem_divergence_2d_hip(
            const Real* gradient_input,
            Real* nodal_output,
            Index_t nx, Index_t ny,
            Index_t grad_stride_x, Index_t grad_stride_y,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_n,
            Real hx, Real hy,
            const Real* quad_weights,
            Real alpha,
            bool increment);

        void fem_gradient_3d_hip(
            const Real* nodal_input,
            Real* gradient_output,
            Index_t nx, Index_t ny, Index_t nz,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_z,
            Index_t nodal_stride_n,
            Index_t grad_stride_x, Index_t grad_stride_y, Index_t grad_stride_z,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Real hx, Real hy, Real hz,
            Real alpha,
            bool increment);

        void fem_divergence_3d_hip(
            const Real* gradient_input,
            Real* nodal_output,
            Index_t nx, Index_t ny, Index_t nz,
            Index_t grad_stride_x, Index_t grad_stride_y, Index_t grad_stride_z,
            Index_t grad_stride_q, Index_t grad_stride_d,
            Index_t nodal_stride_x, Index_t nodal_stride_y, Index_t nodal_stride_z,
            Index_t nodal_stride_n,
            Real hx, Real hy, Real hz,
            const Real* quad_weights,
            Real alpha,
            bool increment);
#endif

    }  // namespace fem_gradient_kernels

}  // namespace muGrid

#endif  // SRC_LIBMUGRID_OPERATORS_FEM_GRADIENT_OPERATOR_HH_
