/*!
 * \file CElement.hpp
 * \brief Main header of the Finite Element structure declaring the abstract
 *        interface and the available finite element types.
 * \author R. Sanchez
 * \version 7.5.1 "Blackbird"
 *
 * SU2 Project Website: https://su2code.github.io
 *
 * The SU2 Project is maintained by the SU2 Foundation
 * (http://su2foundation.org)
 *
 * Copyright 2012-2023, SU2 Contributors (cf. AUTHORS.md)
 *
 * SU2 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 2.1 of the License, or (at your option) any later version.
 *
 * SU2 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 SU2. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "CGaussVariable.hpp"
#include "CElementProperty.hpp"
#include <vector>

/*!
 * \class CElement
 * \ingroup FemAlgos
 * \brief Abstract class for defining finite elements.
 * \note Usage: Element instances are used to compute gradients (in reference or current
 *       coordinates), element matrices (stiffness, mass, etc.), and nodal residuals.
 *       The use-case is to set the coordinates, compute the gradients, and then
 *       incrementally build the matrices using the various "Add_XXX" methods.
 * \note Implementation: This base class provides a generic interface to an element,
 *       so that client numerics do not need to care about specific details (e.g. shape
 *       functions). However, more efficient code can be generated by caring about them,
 *       we try to strike a balance by tailoring the gradient computation to the specific
 *       sizes (number of integration points, nodes, and dimensions) of derived elements.
 *       This is done via the intermediate template class CElementWithKnownSizes from
 *       which derived elements can inherit the implementation of "ComputeGrad_XXX".
 *       To do so their constructors should consist of:
 *        - Call CElementWithKnownSizes's ctor to allocate parent class members.
 *        - Populate the "GaussWeights", and set the shape function values (Ni) in "GaussPoint".
 *        - Populate the "GaussCoords" and "dNiXj" fields of CElementWithKnownSizes.
 *        - Define the coordinates for nodal extrapolation of quantities at the Gauss points.
 *       Derived elements can also implement cheap methods of computing their area/volume.
 * \author R. Sanchez
 */
class CElement {
protected:
  enum : size_t {MAXNDIM = 3};

  std::vector<CGaussVariable> GaussPoint;  /*!< \brief Vector of Gaussian Points. */

  su2activematrix CurrentCoord;       /*!< \brief Coordinates in the current frame. */
  su2activematrix RefCoord;           /*!< \brief Coordinates in the reference frame. */
  su2activevector GaussWeight;        /*!< \brief Weight of the Gaussian Points for the integration. */
  su2activematrix NodalExtrap;        /*!< \brief Coordinates of the nodal points for Gaussian extrapolation. */
  su2activematrix NodalStress;        /*!< \brief Stress at the nodes. */

  /*--- Stiffness and load matrices. ---*/
  std::vector<su2activematrix> Kab;   /*!< \brief Structure for the constitutive component of the tangent matrix. */
  su2activematrix Mab;                /*!< \brief Structure for the nodal components of the mass matrix. */
  su2activematrix Ks_ab;              /*!< \brief Structure for the stress component of the tangent matrix. */
  su2activematrix Kt_a;               /*!< \brief Matrix of nodal stress terms for residual computation. */
  su2activematrix FDL_a;              /*!< \brief Matrix of dead loads for residual computation. */

  su2double el_Pressure = 0.0;        /*!< \brief Pressure in the element. */

  unsigned long iProp = 0;            /*!< \brief ID of the Element Property. */
  unsigned long iDV = 0;              /*!< \brief ID of the Design Variable (if it is element based). */
  unsigned long iDe = 0;              /*!< \brief ID of the dielectric elastomer. */

  unsigned short nGaussPoints;        /*!< \brief Number of gaussian points. */
  unsigned short nNodes;              /*!< \brief Number of geometric points. */
  unsigned short nDim;                /*!< \brief Number of dimension of the problem. */

  su2activematrix HiHj = 0.0;                        /*!< \brief Scalar product of 2 ansatz functions. */
  std::vector<std::vector<su2activematrix>> DHiDHj;  /*!< \brief Scalar product of the gradients of 2 ansatz functions. */

public:
  enum FrameType {REFERENCE=1, CURRENT=2}; /*!< \brief Type of nodal coordinates. */

  /*!
   * \brief Default constructor of the class, deleted to make sure derived
   *        classes always call the allocating constructor.
   */
  CElement(void) = delete;

  /*!
   * \brief Constructor of the class.
   * \param[in] ngauss - Number of Gaussian integration points.
   * \param[in] nnodes - Number of nodes of the element.
   * \param[in] ndim - Number of dimensions of the problem (2D, 3D).
   */
  CElement(unsigned short ngauss, unsigned short nnodes, unsigned short ndim);

  /*!
   * \brief Destructor of the class.
   */
  virtual ~CElement(void) = default;

  /*!
   * \brief Set the value of the gradient of the shape functions wrt the reference configuration.
   * \note The reference coordinates need to be set before calling this method.
   */
  virtual void ComputeGrad_Linear(void) = 0;

  /*!
   * \brief Set the value of the gradient of the shape functions wrt the current configuration.
   * \note The current coordinates need to be set before calling this method.
   */
  virtual void ComputeGrad_NonLinear(void) = 0;

  /*!
   * \brief Set the value of the gradient of the shape functions wrt the reference configuration.
   * \note This method assumes that we have a surface element embedded in higher dimensions.
   */
  virtual void ComputeGrad_SurfaceEmbedded(void) = 0;

  /*!
   * \brief Sets matrices to 0.
   */
  void ClearElement(void);

  /*!
   * \brief Retrieve the number of nodes of the element.
   * \return Number of nodes of the element.
   */
  inline unsigned short GetnNodes(void) const {return nNodes;}

  /*!
   * \brief Retrieve the number of nodes of the element.
   * \return Number of Gaussian Points of the element.
   */
  inline unsigned short GetnGaussPoints(void) const {return nGaussPoints;}

  /*!
   * \brief Set the value of the coordinate of the nodes in the reference configuration.
   * \param[in] iNode - Index of node.
   * \param[in] iDim - Dimension.
   * \param[in] val_CoordRef - Value of the coordinate.
   */
  inline void SetRef_Coord(unsigned short iNode, unsigned short iDim, su2double val_CoordRef) {
    RefCoord(iNode,iDim) = val_CoordRef;
  }

  /*!
   * \brief Set the value of the coordinate of the nodes in the current configuration.
   * \param[in] iNode - Index of node.
   * \param[in] iDim - Dimension.
   * \param[in] val_CoordRef - Value of the coordinate.
   */
  inline void SetCurr_Coord(unsigned short iNode, unsigned short iDim, su2double val_CoordCurr) {
    CurrentCoord(iNode,iDim) = val_CoordCurr;
  }

  /*!
   * \brief Get the value of the coordinate of the nodes in the reference configuration.
   * \param[in] iNode - Index of node.
   * \param[in] iDim - Dimension.
   * \return Reference coordinate.
   */
  inline su2double GetRef_Coord(unsigned short iNode, unsigned short iDim) const {
    return RefCoord(iNode,iDim);
  }

  /*!
   * \brief Get the value of the coordinate of the nodes in the current configuration.
   * \param[in] iNode - Index of node.
   * \param[in] iDim - Dimension.
   * \return Current coordinate.
   */
  inline su2double GetCurr_Coord(unsigned short iNode, unsigned short iDim) const {
    return CurrentCoord(iNode,iDim);
  }

  /*!
   * \brief Get the weight of the corresponding Gaussian Point.
   * \param[in] iGauss - index of the Gaussian point.
   * \return Weight.
   */
  inline su2double GetWeight(unsigned short iGauss) const {
    return GaussWeight(iGauss);
  }

  /*!
   * \brief Get the Jacobian respect to the reference configuration for the Gaussian Point iGauss.
   * \param[in] iGauss - index of the Gaussian point.
   * \return Jacobian.
   */
  inline su2double GetJ_X(unsigned short iGauss) const {
    return GaussPoint[iGauss].GetJ_X();
  }

  /*!
   * \brief Get the jacobian respect to the current configuration for the Gaussian Point iGauss.
   * \param[in] iGauss - index of the Gaussian point.
   * \return Jacobian.
   */
  inline su2double GetJ_x(unsigned short iGauss) const {
    return GaussPoint[iGauss].GetJ_x();
  }

  /*!
   * \brief Retrieve the value of the pressure in the element for incompressible materials.
   * \return Value of the pressure.
   */
  inline su2double GetElement_Pressure(void) const { return el_Pressure; }

  /*!
   * \brief Add the value of the diagonal term for the mass matrix.
   * \param[in] nodeA - index of Node a.
   * \param[in] nodeB - index of Node b.
   * \param[in] val_Ks_ab - value of the term that will constitute the diagonal of the stress contribution.
   */
  inline void Add_Mab(unsigned short nodeA, unsigned short nodeB, su2double val_Mab) {
    Mab(nodeA,nodeB) += val_Mab;
  }

  /*!
   * \brief Add the value of a submatrix K relating nodes a and b, for the constitutive term.
   * \param[in] nodeA - index of Node a.
   * \param[in] nodeB - index of Node b.
   * \param[in] val_Kab - value of the matrix K.
   */
  inline void Add_Kab(unsigned short nodeA, unsigned short nodeB, su2double **val_Kab) {
    for (unsigned short iDim = 0; iDim < nDim; iDim++)
      for (unsigned short jDim = 0; jDim < nDim; jDim++)
        Kab[nodeA](nodeB, iDim*nDim+jDim) += val_Kab[iDim][jDim];
  }

  /*!
   * \brief Add the value of a submatrix K relating nodes a and b, for the constitutive term (symmetric terms need transpose)
   * \param[in] nodeA - index of Node a.
   * \param[in] nodeB - index of Node b.
   * \param[in] val_Kab - value of the matrix K.
   */
  inline void Add_Kab_T(unsigned short nodeA, unsigned short nodeB, su2double **val_Kab) {
    for (unsigned short iDim = 0; iDim < nDim; iDim++)
      for (unsigned short jDim = 0; jDim < nDim; jDim++)
        Kab[nodeA](nodeB, iDim*nDim+jDim) += val_Kab[jDim][iDim];
  }

  /*!
   * \brief Add the value of the diagonal term for the stress contribution to the stiffness of the system.
   * \param[in] nodeA - index of Node a.
   * \param[in] nodeB - index of Node b.
   * \param[in] val_Ks_ab - value of the term that will constitute the diagonal of the stress contribution.
   */
  inline void Add_Ks_ab(unsigned short nodeA, unsigned short nodeB, su2double val_Ks_ab) {
    Ks_ab(nodeA,nodeB) += val_Ks_ab;
  }

  /*!
   * \brief Add the value of the nodal stress term for the computation of the residual.
   * \param[in] nodeA - index of Node a.
   * \param[in] val_Kt_a - value of the term that will constitute the diagonal of the stress contribution.
   */
  inline void Add_Kt_a(unsigned short nodeA, const su2double *val_Kt_a) {
    for(unsigned short iDim = 0; iDim < nDim; iDim++)
      Kt_a(nodeA,iDim) += val_Kt_a[iDim];
  }

  /*!
   * \brief Add the value of the dead load for the computation of the residual.
   * \param[in] nodeA - index of Node a.
   * \param[in] val_FDL_a - value of the term that will constitute the diagonal of the stress contribution.
   */
  inline void Add_FDL_a(unsigned short nodeA, const su2double *val_FDL_a) {
    for(unsigned short iDim = 0; iDim < nDim; iDim++)
      FDL_a(nodeA,iDim) += val_FDL_a[iDim];
  }

  /*!
   * \brief Restarts the values of stress in the element.
   */
  inline void ClearStress(void) {NodalStress.setConstant(0.0);}

  /*!
   * \brief Return the value of the diagonal term for the mass matrix, relating nodes a and b.
   * \param[in] nodeA - index of Node a.
   * \param[in] nodeB - index of Node b.
   * \return Value of the diagonal term of Mab.
   */
  inline su2double Get_Mab(unsigned short nodeA, unsigned short nodeB) const {
    return Mab(nodeA,nodeB);
  }

  /*!
   * \brief Return the value of the submatrix K relating nodes a and b.
   * \param[in] nodeA - index of Node a.
   * \param[in] nodeB - index of Node b.
   * \return Values of the matrix K.
   */
  inline const su2double *Get_Kab(unsigned short nodeA, unsigned short nodeB) const {
    return Kab[nodeA][nodeB];
  }

  /*!
   * \brief Return the value of the diagonal term for the stress contribution, relating nodes a and b.
   * \param[in] nodeA - index of Node a.
   * \param[in] nodeB - index of Node b.
   * \return Value of the matrix Ks.
   */
  inline su2double Get_Ks_ab(unsigned short nodeA, unsigned short nodeB) const {
    return Ks_ab(nodeA,nodeB);
  }

  /*!
   * \brief Return the values of the nodal stress components of the residual for node a.
   * \param[in] nodeA - index of Node a.
   * \return Values of the stress term.
   */
  inline const su2double *Get_Kt_a(unsigned short nodeA) const {
    return Kt_a[nodeA];
  }

  /*!
   * \brief Return the values of the dead load components of the residual for node a.
   * \param[in] nodeA - index of Node a.
   * \return Value of the dead loads.
   */
  inline const su2double *Get_FDL_a(unsigned short nodeA) const {
    return FDL_a[nodeA];
  }

  /*!
   * \brief Retrieve the value of the shape functions.
   * \param[in] iNode - Index of the node.
   * \param[in] iGauss - Index of the Gaussian Point.
   * \return Gradient of the shape function related to node iNode and evaluated at Gaussian Point iGauss
   */
  inline su2double GetNi(unsigned short iNode, unsigned short iGauss) const {
    return GaussPoint[iGauss].GetNi(iNode);
  }

  /*!
   * \brief Retrieve the value of the gradient of the shape functions respect to the reference configuration.
   * \param[in] iNode - Index of the node.
   * \param[in] iGauss - Index of the Gaussian Point.
   * \param[in] iDim - Coordinate index.
   * \return Gradient of the shape function related to node iNode and evaluated at Gaussian Point iGauss
   */
  inline su2double GetGradNi_X(unsigned short iNode, unsigned short iGauss, unsigned short iDim) const {
    return GaussPoint[iGauss].GetGradNi_Xj(iNode,iDim);
  }

  /*!
   * \brief Retrieve the value of the gradient of the shape functions respect to the current configuration.
   * \param[in] iNode - Index of the node.
   * \param[in] iGauss - Index of the Gaussian Point.
   * \param[in] iDim - Coordinate index.
   * \return Gradient of the shape function related to node iNode and evaluated at Gaussian Point iGauss
   */
  inline su2double GetGradNi_x(unsigned short iNode, unsigned short iGauss, unsigned short iDim) const {
    return GaussPoint[iGauss].GetGradNi_xj(iNode,iDim);
  }

  /*!
   * \brief Retrieve the value of the gradient of the shape functions respect to the reference configuration.
   * \param[in] iNode - Index of the node.
   * \param[in] iGauss - Index of the Gaussian Point.
   * \return Value of the shape function at the nodes for extrapolation purposes
   */
  inline su2double GetNi_Extrap(unsigned short iNode, unsigned short iGauss) const {
    return NodalExtrap(iNode,iGauss);
  }

  /*!
   * \brief Add a value to the nodal stress for an element.
   * \param[in] iNode - Index of the node.
   * \param[in] iVar - Variable index.
   * \param[in] val_Stress - Value of the stress added.
   */
  inline void Add_NodalStress(unsigned short iNode, unsigned short iVar, su2double val_Stress) {
    NodalStress(iNode,iVar) += val_Stress;
  }

  /*!
   * \brief Retrieve the value of the nodal stress for an element.
   * \param[in] iNode - Index of the node.
   * \param[in] iVar - Variable index.
   * \return Value of the stress.
   */
  inline su2double Get_NodalStress(unsigned short iNode, unsigned short iVar) const {
    return NodalStress(iNode,iVar);
  }

  /*!
   * \brief Store the values of the identifiers for element properties.
   * \param[in] element_property - element properties container.
   */
  inline void Set_ElProperties(const CProperty *element_property) {
    iDV   = element_property->GetDV();
    iProp = element_property->GetMat_Prop();
    iDe   = element_property->GetElectric_Prop();
  }

  /*!
   * \brief Store the value of the identifier for the Dielectric Elastomers.
   * \param[in] val_iDe - identifier of the DE property.
   */
  inline void Set_iDe(unsigned long val_iDe) {iDe = val_iDe;}

  /*!
   * \brief Return the value of the identifier for the Dielectric Elastomers.
   * \return Identifier of the DE property.
   */
  inline unsigned long Get_iDe(void) const {return iDe;}

  /*!
   * \brief Return the value of the identifier for the Design Variable.
   * \return Identifier of the DV.
   */
  inline unsigned long Get_iDV(void) const {return iDV;}

  /*!
   * \brief Return the value of the identifier for the Element Property.
   * \return Identifier of the property.
   */
  inline unsigned long Get_iProp(void) const {return iProp;}

  /*!
   * \brief Compute the value of the length of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Length of the (1D) element.
   */
  inline virtual su2double ComputeLength(const FrameType mode = REFERENCE) const {return 0.0;}

  /*!
   * \brief Compute the value of the area of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Area of the (2D) element.
   */
  inline virtual su2double ComputeArea(const FrameType mode = REFERENCE) const {return 0.0;}

  /*!
   * \brief Compute the value of the volume of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Volume of the (3D) element.
   */
  inline virtual su2double ComputeVolume(const FrameType mode = REFERENCE) const {return 0.0;}

  /*!
   * \brief Compute the value of the area of the element in current coordinates (wrapper to ComputeArea(CURRENT)).
   * \return Current area of the (2D) element.
   */
  inline su2double ComputeCurrentArea(void) const {return ComputeArea(CURRENT);}

  /*!
   * \brief Compute the value of the volume of the element in current coordinates (wrapper to ComputeVolume(CURRENT)).
   * \return Current volume of the (3D) element.
   */
  inline su2double ComputeCurrentVolume(void) const {return ComputeVolume(CURRENT);}

  /*!
   * \brief Register the current and reference coordinates of the element as pre-accumulation inputs
   * the latter are needed for compatibility with shape derivatives, there is no problem registering
   * because inactive variables are ignored.
   */
  inline void SetPreaccIn_Coords(bool nonlinear = true) {
    AD::SetPreaccIn(RefCoord.data(), nNodes*MAXNDIM);
    if (nonlinear)
      AD::SetPreaccIn(CurrentCoord.data(), nNodes*MAXNDIM);
  }

  /*!
   * \brief Register the stress residual as a pre-accumulation output. When computing the element
   * stiffness matrix this is the only term that sees its way into the RHS of the system.
   */
  inline void SetPreaccOut_Kt_a(void) {
    AD::SetPreaccOut(Kt_a.data(), nNodes*nDim);
  }

  /*!
   * \brief Register the mass matrix as a pre-accumulation output.
   */
  inline void SetPreaccOut_Mab(void) {
    AD::SetPreaccOut(Mab.data(), nNodes*nNodes);
  }

  /*!
   * \brief Register the dead load as a pre-accumulation output.
   */
  inline void SetPreaccOut_FDL_a(void) {
    AD::SetPreaccOut(FDL_a.data(), nNodes*nDim);
  }

 /*!
  * \brief Add the scalar product of the shape functions to the tangent matrix.
  * \param[in] nodeA - index of Node a.
  * \param[in] nodeB - index of Node b.
  * \param[in] val - value of the scalar product of ansatz function.
  */
  inline void Add_HiHj(su2double val, unsigned short nodeA, unsigned short nodeB) { HiHj[nodeA][nodeB] += val; }

  /*!
   * \brief Add the scalar product of the gradients of shape functions to the tangent matrix.
   * \param[in] nodeA - index of Node a.
   * \param[in] nodeB - index of Node b.
   * \param[in] val - value of the scalar product of gradients of ansatz function.
   */
  template <class MatrixType>
  inline void Add_DHiDHj(const MatrixType& val, unsigned short nodeA, unsigned short nodeB) {
    unsigned short iDim, jDim;
    for (iDim = 0; iDim < nDim; iDim++) {
      for (jDim = 0; jDim < nDim; jDim++) {
        DHiDHj[nodeA][nodeB][iDim][jDim] += val[iDim][jDim];
      }
    }
  }

 /*!
  * \brief Add the transposed scalar product of the gradients of shape functions to the tangent matrix.
  * \param[in] nodeA - index of Node a.
  * \param[in] nodeB - index of Node b.
  * \param[in] val - value of the term that will contribute.
  */
  template <class MatrixType>
  inline void Add_DHiDHj_T(const MatrixType& val, unsigned short nodeA, unsigned short nodeB) {
    unsigned short iDim, jDim;
    for (iDim = 0; iDim < nDim; iDim++) {
      for (jDim = 0; jDim < nDim; jDim++) {
        DHiDHj[nodeA][nodeB][iDim][jDim] += val[jDim][iDim];
      }
    }
  }

 /*!
  * \brief Get the scalar product of the shape functions to the tangent matrix.
  * \param[in] nodeA - index of Node a.
  * \param[in] nodeB - index of Node b.
  * \param[out] val - value of the scalar product of ansatz function.
  */
 inline su2double Get_HiHj(unsigned short nodeA, unsigned short nodeB)  { return HiHj[nodeA][nodeB]; }

 /*!
  * \brief Get the scalar product of the gradients of shape functions to the tangent matrix.
  * \param[in] nodeA - index of Node a.
  * \param[in] nodeB - index of Node b.
  * \return val - value of the scalar product of gradients of ansatz function.
  */
 inline su2activematrix& Get_DHiDHj(unsigned short nodeA, unsigned short nodeB) { return DHiDHj[nodeA][nodeB];}

};

/*!
 * \class CElementWithKnownSizes
 * \ingroup FemAlgos
 * \brief Templated class to implement the computation of gradients for specific element sizes.
 * \author P. Gomes, R. Sanchez
 */
template<unsigned short NGAUSS, unsigned short NNODE, unsigned short NDIM>
class CElementWithKnownSizes : public CElement {
private:

  FORCEINLINE static su2double JacobianAdjoint(const su2double Jacobian[][1], su2double ad[][1]) {
    /*--- Adjoint to Jacobian, we put 1.0 here so that ad/detJac is the inverse later ---*/
    ad[0][0] = 1.0;
    /*--- Determinant of Jacobian ---*/
    return Jacobian[0][0];
  }

  FORCEINLINE static su2double JacobianAdjoint(const su2double Jacobian[][2], su2double ad[][2]) {
    ad[0][0] =  Jacobian[1][1];  ad[0][1] = -Jacobian[0][1];
    ad[1][0] = -Jacobian[1][0];  ad[1][1] =  Jacobian[0][0];
    /*--- Determinant of Jacobian ---*/
    return ad[0][0]*ad[1][1]-ad[0][1]*ad[1][0];
  }

  FORCEINLINE static su2double JacobianAdjoint(const su2double Jacobian[][3], su2double ad[][3]) {
    ad[0][0] = Jacobian[1][1]*Jacobian[2][2]-Jacobian[1][2]*Jacobian[2][1];
    ad[0][1] = Jacobian[0][2]*Jacobian[2][1]-Jacobian[0][1]*Jacobian[2][2];
    ad[0][2] = Jacobian[0][1]*Jacobian[1][2]-Jacobian[0][2]*Jacobian[1][1];
    ad[1][0] = Jacobian[1][2]*Jacobian[2][0]-Jacobian[1][0]*Jacobian[2][2];
    ad[1][1] = Jacobian[0][0]*Jacobian[2][2]-Jacobian[0][2]*Jacobian[2][0];
    ad[1][2] = Jacobian[0][2]*Jacobian[1][0]-Jacobian[0][0]*Jacobian[1][2];
    ad[2][0] = Jacobian[1][0]*Jacobian[2][1]-Jacobian[1][1]*Jacobian[2][0];
    ad[2][1] = Jacobian[0][1]*Jacobian[2][0]-Jacobian[0][0]*Jacobian[2][1];
    ad[2][2] = Jacobian[0][0]*Jacobian[1][1]-Jacobian[0][1]*Jacobian[1][0];
    /*--- Determinant of Jacobian ---*/
    return Jacobian[0][0]*ad[0][0]+Jacobian[0][1]*ad[1][0]+Jacobian[0][2]*ad[2][0];
  }

protected:
  static_assert(NDIM==1 || NDIM==2 || NDIM==3, "ComputeGrad_impl expects 1D, 2D or 3D");

  su2double GaussCoord[NGAUSS][NDIM];   /*!< \brief Coordinates of the integration points. */
  su2double dNiXj[NGAUSS][NNODE][NDIM]; /*!< \brief Shape function derivatives evaluated at the Gauss points. */

  /*!
   * \brief Constructor of the class.
   * \note This constructor is protected to prevent this class from being instantiated.
   *       Its only role is to ensure the parent constructor is called with the right arguments.
   */
  CElementWithKnownSizes() : CElement(NGAUSS, NNODE, NDIM) {}

  /*!
   * \brief Implementation of gradient computation leveraging the static sizes.
   * \param[in] FRAME - template, REFERENCE or CURRENT coordinates.
   */
  template<FrameType FRAME>
  void ComputeGrad_impl(void) {

    su2double Jacobian[NDIM][NDIM], ad[NDIM][NDIM];
    unsigned short iNode, iDim, jDim, iGauss;

    /*--- Select the appropriate source for the nodal coordinates depending on the frame requested
          for the gradient computation, REFERENCE (undeformed) or CURRENT (deformed) ---*/
    const su2activematrix& Coord = (FRAME==REFERENCE) ? RefCoord : CurrentCoord;

    for (iGauss = 0; iGauss < NGAUSS; iGauss++) {

      /*--- Jacobian transformation ---*/
      /*--- This does dX/dXi transpose ---*/

      for (iDim = 0; iDim < NDIM; iDim++)
        for (jDim = 0; jDim < NDIM; jDim++)
          Jacobian[iDim][jDim] = 0.0;

      for (iNode = 0; iNode < NNODE; iNode++)
        for (iDim = 0; iDim < NDIM; iDim++)
          for (jDim = 0; jDim < NDIM; jDim++)
            Jacobian[iDim][jDim] += Coord(iNode,jDim) * dNiXj[iGauss][iNode][iDim];

      if (NDIM==1) {
        /*--- Obviously the Jacobian is the slope of the line ---*/
        Jacobian[0][0] = (Coord[1][0] - Coord[0][0]);
      }

      /*--- Adjoint to the Jacobian and determinant ---*/

      auto detJac = JacobianAdjoint(Jacobian, ad);

      if (FRAME==REFERENCE)
        GaussPoint[iGauss].SetJ_X(detJac);
      else
        GaussPoint[iGauss].SetJ_x(detJac);

      /*--- Jacobian inverse (it was already computed as transpose) ---*/

      for (iDim = 0; iDim < NDIM; iDim++)
        for (jDim = 0; jDim < NDIM; jDim++)
          Jacobian[iDim][jDim] = ad[iDim][jDim]/detJac;

      /*--- Derivatives with respect to global coordinates ---*/

      for (iNode = 0; iNode < NNODE; iNode++) {
        for (iDim = 0; iDim < NDIM; iDim++) {
          su2double GradNi_Xj = 0.0;
          for (jDim = 0; jDim < NDIM; jDim++)
            GradNi_Xj += Jacobian[iDim][jDim] * dNiXj[iGauss][iNode][jDim];

          if (FRAME==REFERENCE)
            GaussPoint[iGauss].SetGradNi_Xj(GradNi_Xj, iDim, iNode);
          else
            GaussPoint[iGauss].SetGradNi_xj(GradNi_Xj, iDim, iNode);
        }
      }

    }

  }

  template<FrameType FRAME>
  void ComputeGrad_impl_surf_embedded() {

    /*--- Select the appropriate source for the nodal coordinates depending on the frame requested
          for the gradient computation, REFERENCE (undeformed) or CURRENT (deformed) ---*/
    const su2activematrix& Coord = (FRAME==REFERENCE) ? RefCoord : CurrentCoord;

    if (NDIM==1) {
      unsigned short iNode, iDim, iGauss;
      su2double Jacobian[2];
      su2double val_grad;

      Jacobian[0] = (Coord[1][0] - Coord[0][0]);
      Jacobian[1] = (Coord[1][1] - Coord[0][1]);
      const su2double JTJ = Jacobian[0]*Jacobian[0]+Jacobian[1]*Jacobian[1];
      const su2double volJacobian = sqrt(JTJ);

      for (iGauss = 0; iGauss < NGAUSS; iGauss++) {

        GaussPoint[iGauss].SetJ_X(volJacobian);

        for (iNode = 0; iNode < NNODE; iNode++) {
          for (iDim=0; iDim<NDIM+1; iDim++) {
            val_grad = Jacobian[iDim]*dNiXj[iGauss][iNode][0]/JTJ;
            GaussPoint[iGauss].SetGradNi_Xj( val_grad, iDim, iNode);
          }
        }
      }

    } else if (NDIM==2) {

      su2double Jacobian[NDIM+1][NDIM], JTJ[NDIM][NDIM];
      su2double volJacobian;
      unsigned short iShape, iDim, jDim, jVertex, kDim, iGauss;

      for (iGauss = 0; iGauss < NGAUSS; iGauss++) {

        /*--- Jacobian transformation ---*/
        /*--- This does dX/dXi transpose ---*/
        if(NNODE==3) {
          for (iDim = 0; iDim < NDIM+1; iDim++) {
            for (jVertex = 0; jVertex < NDIM; jVertex++) {
              Jacobian[iDim][jVertex] = Coord[jVertex+1][iDim] - Coord[0][iDim];
            }
          }
        }
        if (NNODE==4) {
          for (iDim = 0; iDim < NDIM+1; iDim++) {
            Jacobian[iDim][0] = (Coord[1][iDim] - Coord[0][iDim])/2;
            Jacobian[iDim][1] = (Coord[3][iDim] - Coord[0][iDim])/2;
          }
        }

        for (iDim = 0; iDim < NDIM; iDim++) {
          for (jDim = 0; jDim < NDIM; jDim++) {
            JTJ[iDim][jDim] = 0.0;
            for (kDim = 0; kDim < NDIM+1; kDim++) {
              JTJ[iDim][jDim] += Jacobian[kDim][iDim]*Jacobian[kDim][jDim];
            }
          }
        }

        /*--- matrix volume of Jacobian ---*/
        const su2double detJacobian=JTJ[0][0]*JTJ[1][1]-JTJ[0][1]*JTJ[1][0];

        volJacobian = sqrt(detJacobian);

        GaussPoint[iGauss].SetJ_X(volJacobian);

        /*--- Derivatives with respect to global coordinates ---*/

        su2double JTJinv[2][2];
        JTJinv[0][0]=JTJ[1][1]/detJacobian;
        JTJinv[1][1]=JTJ[0][0]/detJacobian;
        JTJinv[0][1]=-JTJ[0][1]/detJacobian;
        JTJinv[1][0]=-JTJ[1][0]/detJacobian;

        su2double Jdagger[2][3];

        for (iDim = 0; iDim < NDIM; iDim++) {
          for (jDim = 0; jDim < NDIM+1; jDim++) {
            Jdagger[iDim][jDim] = 0.0;
            for (kDim = 0; kDim < NDIM; kDim++) {
              Jdagger[iDim][jDim] += JTJinv[iDim][kDim]*Jacobian[jDim][kDim];
            }
          }
        }

        su2double GradOut[NDIM+1];
        for (iShape = 0; iShape < NNODE; iShape++) {
          for (iDim = 0; iDim < NDIM+1; iDim++) {
            GradOut[iDim] = 0.0;
            for (jDim = 0; jDim < NDIM; jDim++) {
              GradOut[iDim] += Jdagger[jDim][iDim]*dNiXj[iGauss][iShape][jDim];
            }
            GaussPoint[iGauss].SetGradNi_Xj(GradOut[iDim], iDim, iShape);
          }
        }

      }
    }
  }

public:
  /*!
   * \brief Set the value of the gradient of the shape functions wrt the reference configuration.
   */
  void ComputeGrad_Linear(void) final { ComputeGrad_impl<REFERENCE>(); }

  /*!
   * \brief Set the value of the gradient of the shape functions wrt the current configuration.
   */
  void ComputeGrad_NonLinear(void) final { ComputeGrad_impl<CURRENT>(); }

  /*!
   * \brief Overload needed for deformed 2D elements on a surface in 3D or 1D elements on a 2D curve.
   */
  void ComputeGrad_SurfaceEmbedded() final { ComputeGrad_impl_surf_embedded<REFERENCE>(); }

};

/*!
 * \class CTRIA1
 * \ingroup FemAlgos
 * \brief Tria element with 1 Gauss Points
 * \author R. Sanchez
 */
class CTRIA1 final : public CElementWithKnownSizes<1,3,2> {
private:
  enum : unsigned short {NGAUSS = 1};
  enum : unsigned short {NNODE = 3};
  enum : unsigned short {NDIM = 2};

public:
  /*!
   * \brief Constructor of the class.
   */
  CTRIA1();

  /*!
   * \brief Compute the value of the area of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Area of the element.
   */
  su2double ComputeArea(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CQUAD4
 * \ingroup FemAlgos
 * \brief Quadrilateral element with 4 Gauss Points
 * \author R. Sanchez
 */
class CQUAD4 final : public CElementWithKnownSizes<4,4,2> {
private:
  enum : unsigned short {NGAUSS = 4};
  enum : unsigned short {NNODE = 4};
  enum : unsigned short {NDIM = 2};

public:
  /*!
   * \brief Constructor of the class.
   */
  CQUAD4();

  /*!
   * \brief Shape functions (Ni) evaluated at point Xi,Eta.
   */
  inline static void ShapeFunctions(su2double Xi, su2double Eta, su2double* Ni) {
    Ni[0] = 0.25*(1.0-Xi)*(1.0-Eta);
    Ni[1] = 0.25*(1.0+Xi)*(1.0-Eta);
    Ni[2] = 0.25*(1.0+Xi)*(1.0+Eta);
    Ni[3] = 0.25*(1.0-Xi)*(1.0+Eta);
  }

  /*!
   * \brief Shape function Jacobian (dNi) evaluated at point Xi,Eta.
   */
  inline static void ShapeFunctionJacobian(su2double Xi, su2double Eta, su2double dNi[][2]) {
    dNi[0][0] = -0.25*(1.0-Eta);  dNi[0][1] = -0.25*(1.0-Xi);
    dNi[1][0] =  0.25*(1.0-Eta);  dNi[1][1] = -0.25*(1.0+Xi);
    dNi[2][0] =  0.25*(1.0+Eta);  dNi[2][1] =  0.25*(1.0+Xi);
    dNi[3][0] = -0.25*(1.0+Eta);  dNi[3][1] =  0.25*(1.0-Xi);
  }

  /*!
   * \brief Compute the value of the area of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Area of the element.
   */
  su2double ComputeArea(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CTETRA1
 * \ingroup FemAlgos
 * \brief Tetrahedral element with 1 Gauss Point
 * \author R. Sanchez
 */
class CTETRA1 final : public CElementWithKnownSizes<1,4,3> {
private:
  enum : unsigned short {NGAUSS = 1};
  enum : unsigned short {NNODE = 4};
  enum : unsigned short {NDIM = 3};

public:
  /*!
   * \brief Constructor of the class.
   */
  CTETRA1();

  /*!
   * \brief Compute the value of the volume of the element.
   * \return Volume of the element.
   */
  su2double ComputeVolume(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CHEXA8
 * \ingroup FemAlgos
 * \brief Hexahedral element with 8 Gauss Points
 * \author R. Sanchez
 */
class CHEXA8 final : public CElementWithKnownSizes<8,8,3> {
private:
  enum : unsigned short {NGAUSS = 8};
  enum : unsigned short {NNODE = 8};
  enum : unsigned short {NDIM = 3};

public:
  /*!
   * \brief Constructor of the class.
   */
  CHEXA8();

  /*!
   * \brief Compute the value of the volume of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Volume of the element.
   */
  su2double ComputeVolume(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CPYRAM5
 * \ingroup FemAlgos
 * \brief Pyramid element with 5 Gauss Points
 * \author R. Sanchez, F. Palacios, A. Bueno, T. Economon, S. Padron.
 */
class CPYRAM5 final : public CElementWithKnownSizes<5,5,3> {
private:
  enum : unsigned short {NGAUSS = 5};
  enum : unsigned short {NNODE = 5};
  enum : unsigned short {NDIM = 3};

public:
  /*!
   * \brief Constructor of the class.
   */
  CPYRAM5();

  /*!
   * \brief Compute the value of the volume of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Volume of the element.
   */
  su2double ComputeVolume(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CPRISM6
 * \ingroup FemAlgos
 * \brief Prism element with 6 Gauss Points
 * \author R. Sanchez, F. Palacios, A. Bueno, T. Economon, S. Padron.
 * \version 7.5.1 "Blackbird"
 */
class CPRISM6 final : public CElementWithKnownSizes<6,6,3> {
private:
  enum : unsigned short {NGAUSS = 6};
  enum : unsigned short {NNODE = 6};
  enum : unsigned short {NDIM = 3};

public:
  /*!
   * \brief Constructor of the class.
   */
  CPRISM6();

  /*!
   * \brief Compute the value of the volume of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Volume of the element.
   */
  su2double ComputeVolume(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CTRIA3
 * \ingroup FemAlgos
 * \brief Tria element with 3 Gauss Points
 * \author T.Dick
 */
class CTRIA3 final : public CElementWithKnownSizes<3,3,2> {
private:
  enum : unsigned short {NGAUSS = 3};
  enum : unsigned short {NNODE = 3};
  enum : unsigned short {NDIM = 2};

public:
  /*!
   * \brief Constructor of the class.
   */
  CTRIA3();

  /*!
   * \brief Compute the value of the area of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Area of the element.
   */
  su2double ComputeArea(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CTETRA4
 * \ingroup FemAlgos
 * \brief Tetrahedral element with 4 Gauss Points
 * \author T.Dick
 */
class CTETRA4 final : public CElementWithKnownSizes<4,4,3> {
private:
  enum : unsigned short {NGAUSS = 4};
  enum : unsigned short {NNODE = 4};
  enum : unsigned short {NDIM = 3};

public:
  /*!
   * \brief Constructor of the class.
   */
  CTETRA4();

  /*!
   * \brief Compute the value of the area of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Area of the element.
   */
  su2double ComputeVolume(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CPYRAM6
 * \ingroup FemAlgos
 * \brief Pyramid element with 6 Gauss Points
 * \author T.Dick
 */
class CPYRAM6 final : public CElementWithKnownSizes<6,5,3> {
private:
  enum : unsigned short {NGAUSS = 6};
  enum : unsigned short {NNODE = 5};
  enum : unsigned short {NDIM = 3};

public:
  /*!
   * \brief Constructor of the class.
   */
  CPYRAM6();

  /*!
   * \brief Compute the value of the volume of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Volume of the element.
   */
  su2double ComputeVolume(const FrameType mode = REFERENCE) const override;

};

/*!
 * \class CLINE
 * \ingroup FemAlgos
 * \brief Line element with 2 Gauss Points
 * \author T.Dick
 */
class CLINE final : public CElementWithKnownSizes<2,2,1> {
private:
  enum : unsigned short {NGAUSS = 2};
  enum : unsigned short {NNODE = 2};
  enum : unsigned short {NDIM = 1};

public:
  /*!
   * \brief Constructor of the class.
   */
  CLINE();

  /*!
   * \brief Compute the value of the area of the element.
   * \param[in] mode - Type of coordinates to consider in the computation.
   * \return Area of the element.
   */
  su2double ComputeLength(const FrameType mode = REFERENCE) const override;

};
