//{\src2tex{textfont=tt}}
//****f* ABINIT/gpu_compute_nl_hamiltonian
// NAME
// gpu_compute_nl_hamiltonian
//
// FUNCTION
//  Kernels definitions and calling functions for
//  the application of non-local hamiltonian
//
// COPYRIGHT
// Copyright (C) 1998-2022 ABINIT group (FDahm)
// This file is distributed under the terms of the
// GNU General Public License, see ~abinit/COPYING
// or http://www.gnu.org/copyleft/gpl.txt .
// For the initials of contributors, see ~abinit/doc/developers/contributors.txt .
//
// INPUTS
//
// OUTPUT
//
// SIDE EFFECTS
//
// SOURCE

#include <stdio.h>
#include <stdlib.h>
#include "cuda_api_error_check.h"

/*******************************************************************/
/**********                                      *******************/
/**********         kernels definitions          *******************/
/**********                                      *******************/
/*******************************************************************/

__device__ static inline void reduce_complex_64(volatile double *sh_vect_x,
                                                volatile double *sh_vect_y)
{
  //We have only 2 warps, the first reduce the real part, the second the imaginary one
  //No more synchronization is needed
  if(threadIdx.x < 32)
    sh_vect_x[threadIdx.x] += sh_vect_x[threadIdx.x + 32];
  else//if(threadIdx.x > 31)
    sh_vect_y[threadIdx.x] += sh_vect_y[threadIdx.x - 32];

  if(threadIdx.x < 16)
    sh_vect_x[threadIdx.x] += sh_vect_x[threadIdx.x + 16];
  if(threadIdx.x > 47)
    sh_vect_y[threadIdx.x] += sh_vect_y[threadIdx.x - 16];

  if(threadIdx.x < 8)
    sh_vect_x[threadIdx.x] += sh_vect_x[threadIdx.x + 8];
  if(threadIdx.x > 55)
    sh_vect_y[threadIdx.x] += sh_vect_y[threadIdx.x - 8];

  if(threadIdx.x < 4)
    sh_vect_x[threadIdx.x] += sh_vect_x[threadIdx.x + 4];
  if(threadIdx.x > 59)
    sh_vect_y[threadIdx.x] += sh_vect_y[threadIdx.x - 4];

  if(threadIdx.x < 2)
    sh_vect_x[threadIdx.x] += sh_vect_x[threadIdx.x + 2];
  if(threadIdx.x > 61)
    sh_vect_y[threadIdx.x] += sh_vect_y[threadIdx.x - 2];

  if(threadIdx.x < 1)
    sh_vect_x[threadIdx.x] += sh_vect_x[threadIdx.x + 1];
  if(threadIdx.x > 62)
    sh_vect_y[threadIdx.x] += sh_vect_y[threadIdx.x - 1];
}

__device__ static inline  void reduce_double_32(volatile double *sh_vect)
{
  //We have only 1 active warp,
  //No more synchronization is needed
  if(threadIdx.x < 16)
    sh_vect[threadIdx.x] += sh_vect[threadIdx.x + 16];
  if(threadIdx.x < 8)
    sh_vect[threadIdx.x] += sh_vect[threadIdx.x + 8];
  if(threadIdx.x < 4)
    sh_vect[threadIdx.x] += sh_vect[threadIdx.x + 4];
  if(threadIdx.x < 2)
    sh_vect[threadIdx.x] += sh_vect[threadIdx.x + 2];
  if(threadIdx.x < 1)
    sh_vect[threadIdx.x] += sh_vect[threadIdx.x + 1];
}

/**
 * Kind of equivalent to opernlc_ylm + the first half of opernlb.
 *
 * For a given wave function compute gxfac, gxfac_sij, dgxdtfac
 *
 * \param[in] proj : gx or cprjin
 * \param[in] dproj :
 * \param[out] val_ajlmn  : gxfac, reduced projected scalars related to Vnl (NL operator)
 * \param[out] val_sajlmn : gxfac_sij, reduced projected scalars related to Sij (overlap)
 * \param[in] enl
 * \param[in] sij
 * \param[out] rdlmn
 * \param[out] rdproj
 */
__global__ void kernel_compute_proj_factor(const double2 *proj,
                                           const double2 *dproj,
                                           double2 *val_ajlmn,
                                           double2 *val_sajlmn,
                                           const double *enl,
                                           const double *sij,
                                           double *rdlmn,
                                           double *rdproj,
                                           const int *atindx1,
                                           const unsigned char *nlmn,
                                           const int *indlmn,
                                           const unsigned short int *atoms,
                                           const unsigned char *lmn,
                                           const unsigned char *typat,
                                           const int paw_opt,
                                           const int dimenl1,
                                           const int lmnmax,
                                           const int nb_projections,
                                           const int natoms,
                                           const int choice,
                                           const int signs,
                                           const double lambda)
{

  int iproj = threadIdx.x + blockIdx.x * blockDim.x;
  double2 a_jlmn, sa_jlmn;
  double tmp_loc;
  unsigned short int iatom, jatom;
  unsigned char jlmn, itypat;

  if (iproj < nb_projections) {

    // Get the couple (iatom,ilmn) of the thread's projection
    iatom = atoms[iproj]; // atoms's indice sorted by type
    jatom = atindx1[iatom] - 1; // atoms's indice not sorted (-1 because of fortran cpu storage)
    jlmn = lmn[iproj];
    itypat = typat[iproj];
    int l = indlmn[0 + 6 * (jlmn + lmnmax * itypat)];

    // Norm Conserving Pseudo Potential
    if (paw_opt == 0) {
      double2 proj_ilmn_R;
      proj_ilmn_R = proj[iproj];

      if (choice == 7) {
        a_jlmn.x = proj_ilmn_R.x;
        a_jlmn.y = proj_ilmn_R.y;
      } else {
        double val_enl; // enl is real when norm conserving
        int iln =
          indlmn[4 + 6 * (jlmn + lmnmax * itypat)]; // iln=indlmn(5,ilmn)
        // (pour un type fixe)
        val_enl = enl[(iln - 1) + dimenl1 * itypat]; // enl_(1)=enl(iln,itypat,ispinor)
        a_jlmn.x = val_enl * proj_ilmn_R.x; // gxfac(1:cplex,ilmn,ia,ispinor)=enl_(1)*gx(1:cplex,ilmn,ia,ispinor)
        a_jlmn.y = val_enl * proj_ilmn_R.y;
      }

    } else { // PAW - paw_opt != 0

      if (choice != 7) {
        a_jlmn.x = 0.;
        a_jlmn.y = 0.;
        sa_jlmn.x = 0.;
        sa_jlmn.y = 0.;

        // Accumulate projection;
        for (int ilmn = 0; ilmn < nlmn[itypat]; ilmn++) {
          int ijlmn = (ilmn < jlmn ? ilmn + jlmn * (jlmn + 1) / 2
                                   : jlmn + ilmn * (ilmn + 1) / 2);
          double val_enl = enl[ijlmn + dimenl1 * jatom];

          double2 proj_ilmn_R = proj[iproj + ilmn - jlmn];
          a_jlmn.x += val_enl * proj_ilmn_R.x;
          a_jlmn.y += val_enl * proj_ilmn_R.y;

          double val_sij = sij[ijlmn + dimenl1 * itypat];
          sa_jlmn.x += val_sij * proj_ilmn_R.x;
          sa_jlmn.y += val_sij * proj_ilmn_R.y;
        }

        if (paw_opt == 2) {
          a_jlmn.x -= lambda * sa_jlmn.x;
          a_jlmn.y -= lambda * sa_jlmn.y;
        }

      } else { // choice == 7, when choice = 7 paw_opt must be 3

        double2 proj_ilmn_R = proj[iproj];
        if (paw_opt == 3) {
          a_jlmn.x  = proj_ilmn_R.x;
          a_jlmn.y  = proj_ilmn_R.y;
          sa_jlmn.x = proj_ilmn_R.x;
          sa_jlmn.y = proj_ilmn_R.y;
        }

      } // choice == 7
    } // paw_opt != 0

    // Cas choice==1 && signs==2 :  a_jlmn = a_jlmn*i^(-l)
    if (choice == 1 or choice == 7) {
      tmp_loc = a_jlmn.x;
      switch (l % 4) {
        // case 0: //i^(-l) = 1 : Nothing to do
      case 1: // i^(-l) = -i
        a_jlmn.x = a_jlmn.y;
        a_jlmn.y = -tmp_loc;
        tmp_loc = sa_jlmn.x;
        sa_jlmn.x = sa_jlmn.y;
        sa_jlmn.y = -tmp_loc;
        break;
      case 2: // i^(-l) = -1
        a_jlmn.x = -a_jlmn.x;
        a_jlmn.y = -a_jlmn.y;
        sa_jlmn.x = -sa_jlmn.x;
        sa_jlmn.y = -sa_jlmn.y;
        break;
      case 3: // i^(-l) = i
        a_jlmn.x = -a_jlmn.y;
        a_jlmn.y = tmp_loc;
        tmp_loc = sa_jlmn.x;
        sa_jlmn.x = -sa_jlmn.y;
        sa_jlmn.y = tmp_loc;
        break;
      default:
        break;
      }
      // Store computed values
      if (paw_opt == 3) {
        val_ajlmn[iproj]  = sa_jlmn;
        val_sajlmn[iproj] = sa_jlmn;
      } else {
        val_ajlmn[iproj]  =  a_jlmn;
        val_sajlmn[iproj] = sa_jlmn;
      }
    } // End choice==1 or 7 && signs==2

    // Case choice==2 && signs==1: a_jlmn_alpha =
    // a_jlmn*conjuguate(dproj(jlmn,alpha))
    else if (choice == 2) {
      if (paw_opt == 3) {
        a_jlmn.x = sa_jlmn.x;
        a_jlmn.y = sa_jlmn.y;
      }
      double2 val_dproj;
      // Alpha1 : ialpha=0
      val_dproj = dproj[iproj + 0 * nb_projections];
      rdlmn[jlmn + lmnmax * (iatom + 0 * natoms)] =
          val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;
      // Alpha2 : ialpha=1
      val_dproj = dproj[iproj + 1 * nb_projections];
      rdlmn[jlmn + lmnmax * (iatom + 1 * natoms)] =
          val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;
      // Alpha3 : ialpha=2
      val_dproj = dproj[iproj + 2 * nb_projections];
      rdlmn[jlmn + lmnmax * (iatom + 2 * natoms)] =
          val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;
    }

    // Cas choice==3 && signs==1: a_jlmn_R_alphabeta =
    // a_jlmn_R*conjuguate(dproj(iproj,alphabeta))
    else if (choice == 3) {
      if (paw_opt == 3) {
        a_jlmn.x = sa_jlmn.x;
        a_jlmn.y = sa_jlmn.y;
      }
      double2 val_dproj;
      for (int ialphabeta = 0; ialphabeta < 7; ialphabeta++) {
        // ialphabeta=0
        val_dproj = dproj[iproj + ialphabeta * nb_projections];
        rdproj[iproj + ialphabeta * nb_projections] =
            val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;
      }
    }
    // Case choice==23: both choice 2 and choice 3
    else if (choice == 23) {
      if (paw_opt == 3) {
        a_jlmn.x = sa_jlmn.x;
        a_jlmn.y = sa_jlmn.y;
      }
      // Choice 2 part
      double2 val_dproj;
      // Alpha1 : ialpha=0
      val_dproj = dproj[iproj + 1 * nb_projections];
      rdlmn[jlmn + lmnmax * (iatom + 0 * natoms)] =
          val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;
      // Alpha2 : ialpha=1
      val_dproj = dproj[iproj + 2 * nb_projections];
      rdlmn[jlmn + lmnmax * (iatom + 1 * natoms)] =
          val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;
      // Alpha3 : ialpha=2
      val_dproj = dproj[iproj + 3 * nb_projections];
      rdlmn[jlmn + lmnmax * (iatom + 2 * natoms)] =
          val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;

      // choice 3 part
      // ialphabeta =0
      val_dproj = dproj[iproj];
      rdproj[iproj] = val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;
      // others
      for (int ialphabeta = 1; ialphabeta < 7; ialphabeta++) {
        val_dproj = dproj[iproj + (ialphabeta + 3) * nb_projections];
        rdproj[iproj + ialphabeta * nb_projections] =
            val_dproj.x * a_jlmn.x + val_dproj.y * a_jlmn.y;
      }
    } // end choice 23

  } // end if thread's global id < nb_projection

} // End of kernel_compute_proj_factor

/**
 * Kind of equivalent to opernlb_ylm.
 */
// Note that we assume in a first time we have only one block in X direction,
//  So we have gridDim.x = 1 and blockIdx.x=0
//  Blocks in Y direction represent a plane wave couple indexed by jpw
//  threads of the blocks take care of a couple (iaton,ilmn)
// The primary compute and the reduction are made over the threads of this block
__global__ void kernel_compute_nl_hamiltonian(const double2 *vectin,
                                              double2 *vectout,
                                              double2 *svectout,
                                              const double2 *val_ajlmn,
                                              const double2 *val_sajlmn,
                                              const double2 *ph3dout,
                                              const double *ffnlout,
                                              const unsigned short int* atoms,
                                              const unsigned char *lmn,
                                              const unsigned char *typat,
                                              const int paw_opt,
                                              const int dimffnlout,
                                              const int npwout,
                                              const int nb_projections,
                                              const int lmnmax,
                                              const double four_pi_by_ucvol,
                                              const double lambda,
                                              const int choice)
{

  // definition of locals
  unsigned short int jpw = blockIdx.y;

  double2  vect_loc = make_double2(0., 0.);
  double2 svect_loc = make_double2(0., 0.);

  // shared memory areas to compute and reduce
  extern __shared__ double sh_mem[];
  double *sh_vect_x = sh_mem ;
  double *sh_vect_y = &(sh_mem[blockDim.x]);

  // step 1: Compute value for each plane wave and reduce by thread in sh mem
  for (int iproj=threadIdx.x ; iproj<nb_projections ; iproj+=blockDim.x){
    double2 a_jlmn,sa_jlmn,ph3d;
    double ffnl;

    // get the couple (iatom,ilmn) of the thread's projection
    unsigned short int iatom = atoms[iproj];//atoms's indice sorted by type
    unsigned char jlmn   = lmn[iproj];
    unsigned char itypat = typat[iproj];

    // read the projections factor
    a_jlmn =val_ajlmn[iproj];
    sa_jlmn=val_sajlmn[iproj];

    // accumulate the contribution of the projection for this thread in register
    ffnl = ffnlout[jpw + npwout*(0 + dimffnlout*(jlmn + lmnmax*itypat))]; //ffnlout(npwout,dimffnlout,lmnmax,ntypat)
    ph3d = ph3dout[jpw + npwout*iatom];

    ph3d.x *= ffnl;
    ph3d.y *= ffnl;

    // warning: the product is between a_jlmn and conjuguate(ph3d)
    vect_loc.x  +=  a_jlmn.x*ph3d.x +  a_jlmn.y*ph3d.y;
    vect_loc.y  +=  a_jlmn.y*ph3d.x -  a_jlmn.x*ph3d.y;
    svect_loc.x += sa_jlmn.x*ph3d.x + sa_jlmn.y*ph3d.y;
    svect_loc.y += sa_jlmn.y*ph3d.x - sa_jlmn.x*ph3d.y;

  } // end loop to performs all projections

  /*
   * Compute <g|Vnl|c> for each plane wave (<g|). See opernlb.
   */
  if (paw_opt!=3)
  {
    // step2: reduce in Shared Memory
    sh_vect_x[threadIdx.x] = vect_loc.x;
    sh_vect_y[threadIdx.x] = vect_loc.y;

    for (int decalage=blockDim.x>>1; decalage>0; decalage=decalage>>1) {
      // ensure every access in shared mem is accomplished in the block
      __syncthreads();
      if( threadIdx.x <  decalage)
        sh_vect_x[threadIdx.x] += sh_vect_x[threadIdx.x + decalage];
      else if(threadIdx.x >= (blockDim.x - decalage))
        sh_vect_y[threadIdx.x] += sh_vect_y[threadIdx.x - decalage];
    }
    __syncthreads();

    // step3: thread 0 writes the results for the block (ie for the plane wave)
    if (threadIdx.x==0) {
      if (paw_opt==2) {
        vect_loc.x = -lambda*vectin[jpw].x + four_pi_by_ucvol * sh_vect_x[0];
        vect_loc.x = -lambda*vectin[jpw].y + four_pi_by_ucvol * sh_vect_y[blockDim.x-1];
      }
      else {
        vect_loc.x = four_pi_by_ucvol * sh_vect_x[0];
        vect_loc.y = four_pi_by_ucvol * sh_vect_y[blockDim.x-1];
      }
      vectout[jpw] = vect_loc;
    }
  }

  __syncthreads();

  /*
   *  Compute <g|S|c>  for each plane wave (<g|). See opernlb.
   */
  if (paw_opt>=3)
  {
    // step4: reduce for overlap
    sh_vect_x[threadIdx.x] = svect_loc.x;
    sh_vect_y[threadIdx.x] = svect_loc.y;

    for (int decalage=blockDim.x>>1; decalage>0; decalage=decalage>>1) {
      // ensure every access in shared mem is accomplished in the block
      __syncthreads();
      if( threadIdx.x <  decalage)
        sh_vect_x[threadIdx.x] += sh_vect_x[threadIdx.x + decalage];
      else if(threadIdx.x >= (blockDim.x - decalage))
        sh_vect_y[threadIdx.x] += sh_vect_y[threadIdx.x - decalage];
    }

    __syncthreads();

    // step5: thread 0 writes the results for the block (ie for the plane wave)
    if (threadIdx.x==0) {
      if (choice==7)
        svect_loc = {0., 0.};
      else
        svect_loc = vectin[jpw];

      svect_loc.x += four_pi_by_ucvol * sh_vect_x[0];
      svect_loc.y += four_pi_by_ucvol * sh_vect_y[blockDim.x-1];
      svectout[jpw] = svect_loc;
    }
  } // end of overlap calculation

} // end kernel_compute_nl_hamiltonian

/**
 * Kind of equivalent to opernlb_ylm without the first part (modifying gxfac / gxfacs).
 *
 * \param[in] vectin
 * \param[out] vectout
 * \param[out] svectout
 * \param[in]  val_ajlmn is computed in kernel_compute_proj_factor
 * \param[in] val_sajlmn is computed in kernel_compute_proj_factor
 * \param[in] ph3dout
 * \param[in] ffnlout
 */
__global__ void kernel_compute_nl_hamiltonian_64(const double2 *vectin,
                                                 double2 *vectout,
                                                 double2 *svectout,
                                                 const double2 *val_ajlmn,
                                                 const double2 *val_sajlmn,
                                                 const double2 *ph3dout,
                                                 const double *ffnlout,
                                                 const unsigned short int* atoms,
                                                 const unsigned char *lmn,
                                                 const unsigned char *typat,
                                                 const int paw_opt,
                                                 const int dimffnlout,
                                                 const int npwout,
                                                 const int nb_projections,
                                                 const int lmnmax,
                                                 const double four_pi_by_ucvol,
                                                 const double lambda,
                                                 const int choice)
{

  //Definition of locals
  unsigned short int jpw = blockIdx.y;

  double2  vect_loc = make_double2(0., 0.);
  double2 svect_loc = make_double2(0., 0.);

  // shared memory areas to compute and reduce
  extern __shared__ double sh_mem[];
  double *sh_vect_x = sh_mem ;
  double *sh_vect_y = &(sh_mem[64]);

  // step 1: Compute value for each plane wave and reduce by thread in sh mem
  for (int iproj=threadIdx.x ; iproj<nb_projections ; iproj+=64) {

    double2 a_jlmn,sa_jlmn,ph3d;
    double ffnl;

    // get the couple (iatom,ilmn) of the thread's projection
    unsigned short int iatom = atoms[iproj];//atoms's indice sorted by type
    unsigned char jlmn = lmn[iproj];
    unsigned char itypat = typat[iproj];

    // read the projections factor
    a_jlmn  = val_ajlmn[iproj];
    sa_jlmn = val_sajlmn[iproj];

    // accumulate the contribution of the projection for this thread in register
    // these 2 loads are'nt coalesced and may be costly
    ffnl = ffnlout[jpw + npwout*(0 + dimffnlout*(jlmn + lmnmax*itypat))]; //ffnlout(npwout,dimffnlout,lmnmax,ntypat)
    ph3d = ph3dout[jpw + npwout*iatom];

    ph3d.x *= ffnl;
    ph3d.y *= ffnl;

    // warning: the product is between a_jlmn and conjuguate(ph3d)
    vect_loc.x  +=  a_jlmn.x*ph3d.x +  a_jlmn.y*ph3d.y;
    vect_loc.y  +=  a_jlmn.y*ph3d.x -  a_jlmn.x*ph3d.y;
    svect_loc.x += sa_jlmn.x*ph3d.x + sa_jlmn.y*ph3d.y;
    svect_loc.y += sa_jlmn.y*ph3d.x - sa_jlmn.x*ph3d.y;

  } // end loop to performs all projections

  /*
   * Compute <g|Vnl|c> for each plane wave (<g|). See opernlb.
   */
  if (paw_opt!=3)
  {
    // step2: reduce in Shared Memory
    sh_vect_x[threadIdx.x] = vect_loc.x;
    sh_vect_y[threadIdx.x] = vect_loc.y;
    __syncthreads();

    reduce_complex_64(sh_vect_x,sh_vect_y);
    __syncthreads();

    // step3: thread 0 writes the results for the block (ie for the plane wave)
    if (threadIdx.x==0) {
      if (paw_opt==2) {
        vect_loc.x = -lambda*vectin[jpw].x + four_pi_by_ucvol * sh_vect_x[0];
        vect_loc.x = -lambda*vectin[jpw].y + four_pi_by_ucvol * sh_vect_y[63];
      }
      else {
        vect_loc.x = four_pi_by_ucvol * sh_vect_x[0];
        vect_loc.y = four_pi_by_ucvol * sh_vect_y[63];
      }
      vectout[jpw] = vect_loc;
    }
  }

  __syncthreads();

  /*
   *  Compute <g|S|c>  for each plane wave (<g|). See opernlb.
   */
  if(paw_opt>=3)
  {
    // step4: reduce for overlap
    sh_vect_x[threadIdx.x]=svect_loc.x;
    sh_vect_y[threadIdx.x]=svect_loc.y;
    __syncthreads();

    reduce_complex_64(sh_vect_x,sh_vect_y);
    __syncthreads();

    // step5: thread 0 writes the results for the block (ie for the plane wave)
    if (threadIdx.x==0) {
      if (choice==7)
        svect_loc = {0., 0.};
      else
        svect_loc = vectin[jpw];

      svect_loc.x += four_pi_by_ucvol * sh_vect_x[0];
      svect_loc.y += four_pi_by_ucvol * sh_vect_y[63];
      svectout[jpw] = svect_loc;
    }
  } // end of overlap calculation

} // end kernel_compute_nl_hamiltonian_64


//Accumulate previously calculate factors to get forces
__global__ void kernel_compute_forces(double *rdlmn,
                                      double *enlout,
                                      const int decalage_enlout,
                                      const int natoms,
                                      const int lmnmax
                                      )
{

  extern __shared__ double enl_sh_sum[];
  enl_sh_sum[threadIdx.x] = 0.;
  int ialpha=blockIdx.x;
  int iatom=blockIdx.y;

  //Load in shared memory
  for(int jlmn=threadIdx.x; jlmn<lmnmax; jlmn+=blockDim.x)
    //rdlmn is set to 0 if jlmn >= nlmn(type(iatoms))
    enl_sh_sum[threadIdx.x] += rdlmn[jlmn + lmnmax*(iatom + ialpha*natoms)];

  //Reduce in shared memory
  __syncthreads();
  reduce_double_32(enl_sh_sum);
  // for(int decalage=blockDim.x/2;decalage>0;decalage=decalage/2){
  //     __syncthreads();
  //     if(threadIdx.x < decalage)
  //       enl_sh_sum[threadIdx.x] += enl_sh_sum[threadIdx.x + decalage];
  //   }

  //Write results
  if(threadIdx.x==0)
    enlout[decalage_enlout + ialpha + 3*iatom] = 2*enl_sh_sum[0];

}//end of kernel_compute_forces


//Compute the stress tensor in reduced coordinates
__global__ void kernel_reduce_stress(double *rdproj,double *d_enlk,
				     const int nb_projections){
  extern __shared__ double sh_mem[];
  int ialphabeta=blockIdx.x;

  sh_mem[threadIdx.x]=0.;

  //Acumulate in Shared Mem
  for(int iproj= threadIdx.x; iproj < nb_projections ; iproj+=blockDim.x){
    sh_mem[threadIdx.x] += rdproj[iproj + ialphabeta*nb_projections];
  }

  //Reduce in shared memory
  for(int decalage=blockDim.x/2;decalage>0;decalage=decalage/2){
    __syncthreads();
    if(threadIdx.x < decalage)
      sh_mem[threadIdx.x] += sh_mem[threadIdx.x + decalage];
  }

  //Write results
  if(threadIdx.x==0)
    d_enlk[ialphabeta] = sh_mem[0];
}

__constant__ unsigned char imunu[] = {0,1,2,3,4,5,3,4,5};
__constant__ unsigned char imu[] = {0,1,2,1,0,0,2,2,1};
__constant__ unsigned char inu[] = {0,1,2,2,2,1,1,0,0};
__constant__ unsigned char ialpha[] = {0,1,2,1,0,0};
__constant__ unsigned char ibeta[]= {0,1,2,2,2,1};

//Convert stress tensor to cartesian coordinates
__global__ void kernel_stress_convert(double *gprimd,double *d_enlk,double *enlout){

  __shared__ double sh_gprim[9];
  __shared__ double sh_stress[6];
  __shared__ double sh_enl[9];

  sh_gprim[threadIdx.x] = gprimd[threadIdx.x];

  if(threadIdx.x < 6)
    sh_stress[threadIdx.x] = d_enlk[threadIdx.x+1];

  sh_enl[threadIdx.x] = sh_stress[imunu[threadIdx.x]] * sh_gprim[ialpha[blockIdx.x] + 3*imu[threadIdx.x]] * sh_gprim[ibeta[blockIdx.x] + 3*inu[threadIdx.x]];

  __syncthreads();

  if(threadIdx.x < 3 )
    sh_enl[threadIdx.x] += sh_enl[threadIdx.x + 3] + sh_enl[threadIdx.x + 6];

  if(threadIdx.x == 0 )
    sh_enl[0] += sh_enl[1] + sh_enl[2];

  if(threadIdx.x==0){
    if(blockIdx.x < 3 )
      enlout[blockIdx.x] = 2*sh_enl[0] - d_enlk[0];
    else
      enlout[blockIdx.x] = 2*sh_enl[0];
  }
}


/*******************************************************************/
/**********                                      *******************/
/**********          calling fonction            *******************/
/**********                                      *******************/
/*******************************************************************/

extern "C" void gpu_compute_nl_hamiltonian_(double2 *proj_gpu,
                                            double2 *dproj_gpu,
                                            double2 *vectin_gpu,
                                            double2 *vectout_gpu,
                                            double2 *svectout_gpu,
                                            double2 *val_ajlmn_gpu,
                                            double2 *val_sajlmn_gpu,
                                            double2 *ph3dout_gpu,
                                            double *ffnlout_gpu,
                                            double *rdlmn_gpu,
                                            double *rdproj_gpu,
                                            double *enlout_gpu,
                                            double *d_enlk_gpu,
                                            double *enl_gpu,
                                            double *sij_gpu,
                                            double *gprimd_gpu,
                                            int *atindx1_gpu,
                                            unsigned char *nlmn_gpu,
                                            int *indlmn_gpu,
                                            unsigned short int *atoms_gpu,
                                            unsigned char *lmn_gpu,
                                            unsigned char *typat_gpu,
                                            int *paw_opt,
                                            int *dimffnlout,
                                            int *dimenl1,
                                            int *npw,
                                            int *nb_projections,
                                            int *lmnmax,
                                            int *natoms,
                                            int *choice,
                                            int *signs,
                                            const double *four_pi_by_ucvol,
                                            const double *lambda)
{

  /************* Accumulate the projection factors ***********************/
  //Configuration of the cuda grid
  dim3 grid,block;
  block.x = 64;
  grid.x = ((*nb_projections) + block.x - 1) / block.x ;

  //If choice = 3 or 23 proj is stored in dproj
  // choice = 3 or 23 => compute 1st derivative with respect to strain or atomic positions and strains
  const double2 *effective_proj_gpu = ((*choice)==3) || ((*choice)==23) ?
    dproj_gpu :
    proj_gpu;

  // computes
  // - val_ajlmn_gpu  (gxfac)
  // - val_sajlmn_gpu (gxfac_sij)
  // - rdlmn_gpu
  // - rdproj_gpu
  kernel_compute_proj_factor<<<grid,block>>>(effective_proj_gpu,
                                             dproj_gpu,
                                             val_ajlmn_gpu,
                                             val_sajlmn_gpu,
                                             enl_gpu,
                                             sij_gpu,
                                             rdlmn_gpu,
                                             rdproj_gpu,
                                             atindx1_gpu,
                                             nlmn_gpu,
                                             indlmn_gpu,
                                             atoms_gpu,
                                             lmn_gpu,
                                             typat_gpu,
                                             *paw_opt,
                                             *dimenl1,
                                             *lmnmax,
                                             *nb_projections,
                                             *natoms,
                                             *choice,
                                             *signs,
                                             *lambda);
  CUDA_KERNEL_CHECK("kernel_compute_proj_factor");


  /************* Compute output needed with signs ***********************/

  if ( ( (*choice)==1 or (*choice)==7 ) and (*signs)==2 )
    {
      //One thread in block by projection and plane waves split amongs blocks
      block.x = 64;
      grid.x = 1;
      grid.y=*npw;

      //kernel_compute_nl_hamiltonian<<<grid,block,block.x*2*sizeof(double),0>>>(vectin_gpu,vectout_gpu,svectout_gpu,
      kernel_compute_nl_hamiltonian_64<<<grid,block,block.x*2*sizeof(double),0>>>(vectin_gpu,
                                                                                  vectout_gpu,
                                                                                  svectout_gpu,
                                                                                  val_ajlmn_gpu,
                                                                                  val_sajlmn_gpu,
                                                                                  ph3dout_gpu,
                                                                                  ffnlout_gpu,
                                                                                  atoms_gpu,
                                                                                  lmn_gpu,
                                                                                  typat_gpu,
                                                                                  *paw_opt,
                                                                                  *dimffnlout,
                                                                                  *npw,
                                                                                  *nb_projections,
                                                                                  *lmnmax,
                                                                                  *four_pi_by_ucvol,
                                                                                  *lambda,
                                                                                  *choice);
      CUDA_KERNEL_CHECK("kernel_compute_nl_hamiltonian_64");

    } //End choice==1 or 7,signs==2

  else
    {
      //Compute forces
    if( (((*choice)==2)&&(*signs==1)) || ((*choice)==23) )
      {
        //Threads In blocks will take care of lmnmax
        block.x= 32;

        grid.x= 3; //Nb alpha directions
        grid.y= *natoms ;

        int decalage_enlout_gpu = 0;
        if( (*choice)==23)
          decalage_enlout_gpu = 6;

        kernel_compute_forces<<<grid,block,block.x*sizeof(double),0>>>(rdlmn_gpu,enlout_gpu,
                                                                       decalage_enlout_gpu,
                                                                       *natoms,*lmnmax);
        CUDA_KERNEL_CHECK("kernel_compute_forces");
      } //End choice2 / signs1 or choice 23

    //Compute stress tensor
    if( (((*choice)==3) && ((*signs)==1))  || ((*choice)==23) )
      {

        grid.x= 7; //Nb alphabeta directions
        block.x= 512; // Enough threads to reduce on projection
        while(block.x > (unsigned int)(*nb_projections)){
        block.x /= 2;
        }
        block.x=64;

        kernel_reduce_stress<<<grid,block,block.x*sizeof(double),0>>>(rdproj_gpu,d_enlk_gpu,*nb_projections);
        CUDA_KERNEL_CHECK("kernel_reduce_stress");

        kernel_stress_convert<<<6,9>>>(gprimd_gpu,d_enlk_gpu,enlout_gpu);
        CUDA_KERNEL_CHECK("kernel_stress_convert");

    }

  } //end choice!=1

} //End of gpu_compute_nl_hamiltonian

//***
