/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator

   Original Version:
   http://lammps.sandia.gov, Sandia National Laboratories
   Steve Plimpton, sjplimp@sandia.gov

   See the README file in the top-level LAMMPS directory.

   -----------------------------------------------------------------------

   USER-CUDA Package and associated modifications:
   https://sourceforge.net/projects/lammpscuda/

   Christian Trott, christian.trott@tu-ilmenau.de
   Lars Winterfeld, lars.winterfeld@tu-ilmenau.de
   Theoretical Physics II, University of Technology Ilmenau, Germany

   See the README file in the USER-CUDA directory.

   This software is distributed under the GNU General Public License.
------------------------------------------------------------------------- */
#define Pi F_F(3.1415926535897932384626433832795)
#define PI Pi
#define PI2 F_F(0.5)*Pi
#define PI4 F_F(0.25)*Pi
template <const int eflag, const int vflag>
static inline __device__ void PairVirialCompute_A_Kernel_Template()
{
  __syncthreads();
  ENERGY_FLOAT* shared = sharedmem;

  if(eflag) {
    reduceBlock(shared);
    shared += blockDim.x;
  }

  if(vflag) {
    reduceBlock(shared + 0 * blockDim.x);
    reduceBlock(shared + 1 * blockDim.x);
    reduceBlock(shared + 2 * blockDim.x);
    reduceBlock(shared + 3 * blockDim.x);
    reduceBlock(shared + 4 * blockDim.x);
    reduceBlock(shared + 5 * blockDim.x);
  }

  if(threadIdx.x == 0) {
    shared = sharedmem;
    ENERGY_FLOAT* buffer = (ENERGY_FLOAT*) _buffer;

    if(eflag) {
      buffer[blockIdx.x * gridDim.y + blockIdx.y] = ENERGY_F(0.5) * shared[0];
      shared += blockDim.x;
      buffer += gridDim.x * gridDim.y;
    }

    if(vflag) {
      buffer[blockIdx.x * gridDim.y + blockIdx.y + 0 * gridDim.x * gridDim.y] = ENERGY_F(0.5) * shared[0 * blockDim.x];
      buffer[blockIdx.x * gridDim.y + blockIdx.y + 1 * gridDim.x * gridDim.y] = ENERGY_F(0.5) * shared[1 * blockDim.x];
      buffer[blockIdx.x * gridDim.y + blockIdx.y + 2 * gridDim.x * gridDim.y] = ENERGY_F(0.5) * shared[2 * blockDim.x];
      buffer[blockIdx.x * gridDim.y + blockIdx.y + 3 * gridDim.x * gridDim.y] = ENERGY_F(0.5) * shared[3 * blockDim.x];
      buffer[blockIdx.x * gridDim.y + blockIdx.y + 4 * gridDim.x * gridDim.y] = ENERGY_F(0.5) * shared[4 * blockDim.x];
      buffer[blockIdx.x * gridDim.y + blockIdx.y + 5 * gridDim.x * gridDim.y] = ENERGY_F(0.5) * shared[5 * blockDim.x];
    }
  }

  __syncthreads();
}

__global__ void virial_fdotr_compute_kernel(int eflag)
{
  int i = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;
  ENERGY_FLOAT* sharedE = (ENERGY_FLOAT*) &sharedmem[0];
  ENERGY_FLOAT* sharedVirial = (ENERGY_FLOAT*) &sharedE[blockDim.x];
  sharedE += threadIdx.x;
  sharedVirial += threadIdx.x;

  if(i < _nlocal) {

    F_FLOAT x = _x[i];
    F_FLOAT y = _x[i + _nmax];
    F_FLOAT z = _x[i + 2 * _nmax];
    F_FLOAT fx = _f[i];
    F_FLOAT fy = _f[i + _nmax];
    F_FLOAT fz = _f[i + 2 * _nmax];
    //if(fz*z*fz*z>1e-5) printf("V %i %i %e %e %e %e %e %e\n",i,_tag[i],x,y,z,fx,fy,fz);
    sharedVirial[0] = fx * x;
    sharedVirial[1 * blockDim.x] = fy * y;
    sharedVirial[2 * blockDim.x] = fz * z;
    sharedVirial[3 * blockDim.x] = fy * x;
    sharedVirial[4 * blockDim.x] = fz * x;
    sharedVirial[5 * blockDim.x] = fz * y;
  } else {
    sharedVirial[0] = 0;
    sharedVirial[1 * blockDim.x] = 0;
    sharedVirial[2 * blockDim.x] = 0;
    sharedVirial[3 * blockDim.x] = 0;
    sharedVirial[4 * blockDim.x] = 0;
    sharedVirial[5 * blockDim.x] = 0;
  }

  sharedVirial = (ENERGY_FLOAT*) &sharedmem[0];
  sharedVirial += blockDim.x;
  reduceBlockP2(sharedVirial);
  reduceBlockP2(&sharedVirial[1 * blockDim.x]);
  reduceBlockP2(&sharedVirial[2 * blockDim.x]);
  reduceBlockP2(&sharedVirial[3 * blockDim.x]);
  reduceBlockP2(&sharedVirial[4 * blockDim.x]);
  reduceBlockP2(&sharedVirial[5 * blockDim.x]);

  if(threadIdx.x < 6) {
    ENERGY_FLOAT* buffer = (ENERGY_FLOAT*) _buffer;

    if(eflag) buffer = &buffer[gridDim.x * gridDim.y];

    buffer[blockIdx.x * gridDim.y + blockIdx.y + threadIdx.x * gridDim.x * gridDim.y] = sharedVirial[threadIdx.x * blockDim.x];
  }
}

/*#define vec3_scale(K,X,Y) Y.x = K*X.x;  Y.y = K*X.y;  Y.z = K*X.z;
#define vec3_scaleadd(K,X,Y,Z) Z.x = K*X.x+Y.x;  Z.y = K*X.y+Y.y;  Z.z = K*X.z+Y.z;
#define vec3_add(X,Y,Z) Z.x = X.x+Y.x;  Z.y = X.y+Y.y;  Z.z = X.z+Y.z;
#define vec3_dot(X,Y) (X.x*Y.x + X.y*Y.y + X.z*Y.z)*/

__device__ inline void vec3_scale(F_FLOAT k, F_FLOAT3 &x, F_FLOAT3 &y)
{
  y.x = k * x.x;
  y.y = k * x.y;
  y.z = k * x.z;
}

__device__ inline void vec3_scale(F_FLOAT k, F_FLOAT4 &x, F_FLOAT3 &y)
{
  y.x = k * x.x;
  y.y = k * x.y;
  y.z = k * x.z;
}

__device__ inline void vec3_scale(F_FLOAT k, F_FLOAT4 &x, F_FLOAT4 &y)
{
  y.x = k * x.x;
  y.y = k * x.y;
  y.z = k * x.z;
}

__device__ inline void vec3_scaleadd(F_FLOAT k, F_FLOAT3 &x, F_FLOAT3 &y, F_FLOAT3 &z)
{
  z.x = k * x.x + y.x;
  z.y = k * x.y + y.y;
  z.z = k * x.z + y.z;
}

__device__ inline void vec3_add(F_FLOAT3 &x, F_FLOAT3 &y, F_FLOAT3 &z)
{
  z.x = x.x + y.x;
  z.y = x.y + y.y;
  z.z = x.z + y.z;
}

__device__ inline F_FLOAT vec3_dot(F_FLOAT3 x, F_FLOAT3 y)
{
  return x.x * y.x + x.y * y.y + x.z * y.z;
}

__device__ inline F_FLOAT vec3_dot(F_FLOAT4 x, F_FLOAT4 y)
{
  return x.x * y.x + x.y * y.y + x.z * y.z;
}

/* ----------------------------------------------------------------------
   Fermi-like smoothing function
------------------------------------------------------------------------- */

__device__ inline F_FLOAT F_fermi(F_FLOAT &r, int &iparam)
{
  return F_F(1.0) / (F_F(1.0) + exp(-params[iparam].ZBLexpscale * (r - params[iparam].ZBLcut)));
}

/* ----------------------------------------------------------------------
   Fermi-like smoothing function derivative with respect to r
------------------------------------------------------------------------- */

__device__ inline F_FLOAT F_fermi_d(F_FLOAT &r, int &iparam)
{
  volatile const F_FLOAT tmp =  exp(-params[iparam].ZBLexpscale * (r - params[iparam].ZBLcut));
  return params[iparam].ZBLexpscale * tmp /
         ((F_F(1.0) + tmp) * (F_F(1.0) + tmp));
}

__device__ inline F_FLOAT ters_fc(F_FLOAT r, F_FLOAT ters_R, F_FLOAT ters_D)
{
  return (r < ters_R - ters_D) ? F_F(1.0) : ((r > ters_R + ters_D) ?
         F_F(0.0) : F_F(0.5) * (F_F(1.0) - sin(PI2 * (r - ters_R) / ters_D)));
}

__device__ inline F_FLOAT ters_fc_d(F_FLOAT r, F_FLOAT ters_R, F_FLOAT ters_D)
{
  return ((r < ters_R - ters_D) || (r > ters_R + ters_D)) ?
         F_F(0.0) : -(PI4 / ters_D) * cos(PI2 * (r - ters_R) / ters_D);
}


__device__ inline F_FLOAT ters_gijk(F_FLOAT &cos_theta, int iparam)
{
  F_FLOAT ters_c = params[iparam].c;
  F_FLOAT ters_d = params[iparam].d;

  return params[iparam].gamma * (F_F(1.0) + pow(params[iparam].c / params[iparam].d, F_F(2.0)) -
                                 pow(ters_c, F_F(2.0)) / (pow(ters_d, F_F(2.0)) + pow(params[iparam].h - cos_theta, F_F(2.0))));
}

__device__ F_FLOAT ters_gijk2(F_FLOAT &cos_theta, int iparam)
{
  F_FLOAT ters_c = params[iparam].c;
  F_FLOAT ters_d = params[iparam].d;

  return params[iparam].gamma * (F_F(1.0) + pow(ters_c / ters_d, F_F(2.0)) -
                                 pow(ters_c, F_F(2.0)) / (pow(ters_d, F_F(2.0)) + pow(params[iparam].h - cos_theta, F_F(2.0))));
}

__device__ inline F_FLOAT ters_gijk_d(F_FLOAT costheta, int iparam)
{
  F_FLOAT numerator = -F_F(2.0) * pow(params[iparam].c, F_F(2.0)) * (params[iparam].h - costheta);
  F_FLOAT denominator = pow(pow(params[iparam].d, F_F(2.0)) +
                            pow(params[iparam].h - costheta, F_F(2.0)), F_F(2.0));
  return params[iparam].gamma * numerator / denominator;
}

__device__ inline F_FLOAT zeta(int iparam, const F_FLOAT rsqij, const F_FLOAT rsqik,
                               F_FLOAT3 &delij, F_FLOAT3 &delik)
{
  F_FLOAT rij, rik, costheta, arg, ex_delr;

  rij = sqrt(rsqij);
  rik = sqrt(rsqik);
  costheta = vec3_dot(delij, delik) / (rij * rik);

  arg = (params[iparam].powermint == 3) ? (params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik)) : params[iparam].lam3 * (rij - rik);

  if(arg > F_F(69.0776)) ex_delr = F_F(1.e30);
  else if(arg < -F_F(69.0776)) ex_delr = F_F(0.0);
  else ex_delr = exp(arg);

  return ters_fc(rik, params[iparam].bigr, params[iparam].bigd) * ex_delr * params[iparam].gamma * (F_F(1.0) + (params[iparam].c * params[iparam].c / (params[iparam].d * params[iparam].d)) -
         (params[iparam].c * params[iparam].c) / ((params[iparam].d * params[iparam].d) + (params[iparam].h - costheta) * (params[iparam].h - costheta)));
}

__device__ void repulsive(int iparam, F_FLOAT rsq, F_FLOAT &fforce,
                          int eflag, ENERGY_FLOAT &eng)
{
  F_FLOAT r, tmp_fc, tmp_fc_d, tmp_exp;

  F_FLOAT ters_R = params[iparam].bigr;
  F_FLOAT ters_D = params[iparam].bigd;
  r = sqrt(rsq);
  tmp_fc = ters_fc(r, ters_R, ters_D);
  tmp_fc_d = ters_fc_d(r, ters_R, ters_D);
  tmp_exp = exp(-params[iparam].lam1 * r);

  if(!_zbl) {
    fforce = -params[iparam].biga * tmp_exp * (tmp_fc_d - tmp_fc * params[iparam].lam1) / r;

    if(eflag) eng += tmp_fc * params[iparam].biga * tmp_exp;
  } else {
    F_FLOAT const fforce_ters = params[iparam].biga * tmp_exp * (tmp_fc_d - tmp_fc * params[iparam].lam1);
    ENERGY_FLOAT eng_ters = tmp_fc * params[iparam].biga * tmp_exp;

    F_FLOAT r_ov_a = r / params[iparam].a_ij;
    F_FLOAT phi = F_F(0.1818) * exp(-F_F(3.2) * r_ov_a) + F_F(0.5099) * exp(-F_F(0.9423) * r_ov_a) +
                  F_F(0.2802) * exp(-F_F(0.4029) * r_ov_a) + F_F(0.02817) * exp(-F_F(0.2016) * r_ov_a);
    F_FLOAT dphi = (F_F(1.0) / params[iparam].a_ij) * (-F_F(3.2) * F_F(0.1818) * exp(-F_F(3.2) * r_ov_a) -
                   F_F(0.9423) * F_F(0.5099) * exp(-F_F(0.9423) * r_ov_a) -
                   F_F(0.4029) * F_F(0.2802) * exp(-F_F(0.4029) * r_ov_a) -
                   F_F(0.2016) * F_F(0.02817) * exp(-F_F(0.2016) * r_ov_a));
    F_FLOAT fforce_ZBL = params[iparam].premult / (-r * r) * phi + params[iparam].premult / r * dphi;
    ENERGY_FLOAT eng_ZBL = params[iparam].premult * (F_F(1.0) / r) * phi;

    fforce = -(-F_fermi_d(r, iparam) * (eng_ZBL - eng_ters) + fforce_ZBL + F_fermi(r, iparam) * (fforce_ters - fforce_ZBL)) / r;

    if(eflag)
      eng += eng_ZBL + F_fermi(r, iparam) * (eng_ters - eng_ZBL);
  }


}

/* ---------------------------------------------------------------------- */

__device__ inline F_FLOAT ters_fa(F_FLOAT r, int iparam, F_FLOAT ters_R, F_FLOAT ters_D)
{
  if(r > ters_R + ters_D) return F_F(0.0);

  if(_zbl)
    return -params[iparam].bigb * exp(-params[iparam].lam2 * r) * ters_fc(r, ters_R, ters_D) * F_fermi(r, iparam);
  else
    return -params[iparam].bigb * exp(-params[iparam].lam2 * r) * ters_fc(r, ters_R, ters_D);
}

/* ---------------------------------------------------------------------- */

__device__ inline F_FLOAT ters_fa_d(F_FLOAT r, int iparam, F_FLOAT ters_R, F_FLOAT ters_D)
{
  if(r > ters_R + ters_D) return F_F(0.0);

  if(_zbl)
    return params[iparam].bigb * exp(-params[iparam].lam2 * r) *
           ((params[iparam].lam2 * ters_fc(r, ters_R, ters_D) - ters_fc_d(r, ters_R, ters_D)) * F_fermi(r, iparam)
            - ters_fc(r, ters_R, ters_D) * F_fermi_d(r, iparam));
  else
    return params[iparam].bigb * exp(-params[iparam].lam2 * r) *
           (params[iparam].lam2 * ters_fc(r, ters_R, ters_D) - ters_fc_d(r, ters_R, ters_D));
}

/* ---------------------------------------------------------------------- */

__device__ inline F_FLOAT ters_bij(F_FLOAT zeta, int iparam)
{
  F_FLOAT tmp = params[iparam].beta * zeta;

  if(tmp > params[iparam].c1) return F_F(1.0) / sqrt(tmp);

  if(tmp > params[iparam].c2)
    return (F_F(1.0) - pow(tmp, -params[iparam].powern) / (F_F(2.0) * params[iparam].powern)) / sqrt(tmp);

  if(tmp < params[iparam].c4) return F_F(1.0);

  if(tmp < params[iparam].c3)
    return F_F(1.0) - pow(tmp, params[iparam].powern) / (F_F(2.0) * params[iparam].powern);

  return pow(F_F(1.0) + pow(tmp, params[iparam].powern), -F_F(1.0) / (F_F(2.0) * params[iparam].powern));
}

/* ---------------------------------------------------------------------- */

__device__ inline F_FLOAT ters_bij_d(F_FLOAT zeta, int iparam)
{
  F_FLOAT tmp = params[iparam].beta * zeta;

  if(tmp > params[iparam].c1) return params[iparam].beta * -F_F(0.5) * pow(tmp, -F_F(1.5));

  if(tmp > params[iparam].c2)
    return params[iparam].beta * (-F_F(0.5) * pow(tmp, -F_F(1.5)) *
                                  (F_F(1.0) - F_F(0.5) * (F_F(1.0) +  F_F(1.0) / (F_F(2.0) * params[iparam].powern)) *
                                   pow(tmp, -params[iparam].powern)));

  if(tmp < params[iparam].c4) return F_F(0.0);

  if(tmp < params[iparam].c3)
    return -F_F(0.5) * params[iparam].beta * pow(tmp, params[iparam].powern - F_F(1.0));

  F_FLOAT tmp_n = pow(tmp, params[iparam].powern);
  return -F_F(0.5) * pow(F_F(1.0) + tmp_n, -F_F(1.0) - (F_F(1.0) / (F_F(2.0) * params[iparam].powern))) * tmp_n / zeta;
}

__device__ void force_zeta(int iparam, F_FLOAT rsq, F_FLOAT zeta_ij,
                           F_FLOAT &fforce, F_FLOAT &prefactor,
                           int eflag, F_FLOAT &eng)
{
  F_FLOAT r, fa, fa_d, bij;
  F_FLOAT ters_R = params[iparam].bigr;
  F_FLOAT ters_D = params[iparam].bigd;
  r = sqrt(rsq);
  fa = ters_fa(r, iparam, ters_R, ters_D);
  fa_d = ters_fa_d(r, iparam, ters_R, ters_D);
  bij = ters_bij(zeta_ij, iparam);
  fforce = F_F(0.5) * bij * fa_d / r;
  prefactor = -F_F(0.5) * fa * ters_bij_d(zeta_ij, iparam);

  if(eflag) eng += bij * fa;
}

__device__ void force_zeta_prefactor_force(int iparam, F_FLOAT rsq, F_FLOAT zeta_ij,
    F_FLOAT &fforce, F_FLOAT &prefactor)
{
  F_FLOAT r, fa, fa_d, bij;
  F_FLOAT ters_R = params[iparam].bigr;
  F_FLOAT ters_D = params[iparam].bigd;
  r = sqrt(rsq);
  fa = ters_fa(r, iparam, ters_R, ters_D);
  fa_d = ters_fa_d(r, iparam, ters_R, ters_D);
  bij = ters_bij(zeta_ij, iparam);
  fforce = F_F(0.5) * bij * fa_d / r;
  prefactor = -F_F(0.5) * fa * ters_bij_d(zeta_ij, iparam);
}

__device__ void force_zeta_prefactor(int iparam, F_FLOAT rsq, F_FLOAT zeta_ij,
                                     F_FLOAT &prefactor)
{
  F_FLOAT r, fa;
  r = sqrt(rsq);
  fa = ters_fa(r, iparam, params[iparam].bigr, params[iparam].bigd);
  prefactor = -F_F(0.5) * fa * ters_bij_d(zeta_ij, iparam);
}


__device__ void costheta_d(F_FLOAT3 &rij_hat, F_FLOAT &rij,
                           F_FLOAT3 &rik_hat, F_FLOAT &rik,
                           F_FLOAT3 &dri, F_FLOAT3 &drj, F_FLOAT3 &drk)
{
  // first element is derivative wrt Ri, second wrt Rj, third wrt Rk

  F_FLOAT cos_theta = vec3_dot(rij_hat, rik_hat);

  vec3_scaleadd(-cos_theta, rij_hat, rik_hat, drj);
  vec3_scale(F_F(1.0) / rij, drj, drj);
  vec3_scaleadd(-cos_theta, rik_hat, rij_hat, drk);
  vec3_scale(F_F(1.0) / rik, drk, drk);
  vec3_add(drj, drk, dri);
  vec3_scale(-F_F(1.0), dri, dri);
}

__device__ void ters_zetaterm_d(F_FLOAT prefactor,
                                F_FLOAT3 &rij_hat, F_FLOAT rij,
                                F_FLOAT3 &rik_hat, F_FLOAT rik,
                                F_FLOAT3 &dri, F_FLOAT3 &drj, F_FLOAT3 &drk,
                                int iparam)
{
  F_FLOAT ex_delr, ex_delr_d, tmp;
  F_FLOAT3 dcosdri, dcosdrj, dcosdrk;

  if(params[iparam].powermint == 3) tmp = (params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik));
  else tmp = params[iparam].lam3 * (rij - rik);

  if(tmp > F_F(69.0776)) ex_delr = F_F(1.e30);
  else if(tmp < -F_F(69.0776)) ex_delr = F_F(0.0);
  else ex_delr = exp(tmp);

  if(params[iparam].powermint == 3)
    ex_delr_d = F_F(3.0) * (params[iparam].lam3 * params[iparam].lam3 * params[iparam].lam3) * (rij - rik) * (rij - rik) * ex_delr;
  else ex_delr_d = params[iparam].lam3 * ex_delr;


  const F_FLOAT cos_theta = vec3_dot(rij_hat, rik_hat);
  costheta_d(rij_hat, rij, rik_hat, rik, dcosdri, dcosdrj, dcosdrk);

  const F_FLOAT gijk = params[iparam].gamma * (F_F(1.0) + (params[iparam].c * params[iparam].c) / (params[iparam].d * params[iparam].d) -
                       (params[iparam].c * params[iparam].c) / (params[iparam].d * params[iparam].d + (params[iparam].h - cos_theta) * (params[iparam].h - cos_theta)));
  const F_FLOAT numerator = -F_F(2.0) * params[iparam].c * params[iparam].c * (params[iparam].h - cos_theta);
  const F_FLOAT denominator = (params[iparam].d * params[iparam].d) +
                              (params[iparam].h - cos_theta) * (params[iparam].h - cos_theta);
  const F_FLOAT gijk_d = params[iparam].gamma * numerator / (denominator * denominator); // compute the derivative wrt Ri
  // dri = -dfc*gijk*ex_delr*rik_hat;
  // dri += fc*gijk_d*ex_delr*dcosdri;
  // dri += fc*gijk*ex_delr_d*(rik_hat - rij_hat);
  const F_FLOAT fc = ters_fc(rik, params[iparam].bigr, params[iparam].bigd);
  const F_FLOAT dfc = ters_fc_d(rik, params[iparam].bigr, params[iparam].bigd);


  vec3_scale(-dfc * gijk * ex_delr, rik_hat, dri);
  vec3_scaleadd(fc * gijk_d * ex_delr, dcosdri, dri, dri);
  vec3_scaleadd(fc * gijk * ex_delr_d, rik_hat, dri, dri);
  vec3_scaleadd(-fc * gijk * ex_delr_d, rij_hat, dri, dri);
  vec3_scale(prefactor, dri, dri);
  // compute the derivative wrt Rj
  // drj = fc*gijk_d*ex_delr*dcosdrj;
  // drj += fc*gijk*ex_delr_d*rij_hat;

  vec3_scale(fc * gijk_d * ex_delr, dcosdrj, drj);
  vec3_scaleadd(fc * gijk * ex_delr_d, rij_hat, drj, drj);
  vec3_scale(prefactor, drj, drj);

  // compute the derivative wrt Rk
  // drk = dfc*gijk*ex_delr*rik_hat;
  // drk += fc*gijk_d*ex_delr*dcosdrk;
  // drk += -fc*gijk*ex_delr_d*rik_hat;

  vec3_scale(dfc * gijk * ex_delr, rik_hat, drk);
  vec3_scaleadd(fc * gijk_d * ex_delr, dcosdrk, drk, drk);
  vec3_scaleadd(-fc * gijk * ex_delr_d, rik_hat, drk, drk);
  vec3_scale(prefactor, drk, drk);
}

__device__ void ters_zetaterm_d_fi(F_FLOAT &prefactor,
                                   F_FLOAT3 &rij_hat, F_FLOAT &rij,
                                   F_FLOAT3 &rik_hat, F_FLOAT &rik,
                                   F_FLOAT3 &dri,  int &iparam)
{
  F_FLOAT ex_delr, ex_delr_d, tmp;

  if(params[iparam].powermint == 3) tmp = (params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik));
  else tmp = params[iparam].lam3 * (rij - rik);

  if(tmp > F_F(69.0776)) ex_delr = F_F(1.e30);
  else if(tmp < -F_F(69.0776)) ex_delr = F_F(0.0);
  else ex_delr = exp(tmp);

  if(params[iparam].powermint == 3)
    ex_delr_d = F_F(3.0) * (params[iparam].lam3 * params[iparam].lam3 * params[iparam].lam3) * (rij - rik) * (rij - rik) * ex_delr;
  else ex_delr_d = params[iparam].lam3 * ex_delr;

  const F_FLOAT cos_theta = vec3_dot(rij_hat, rik_hat);
  //costheta_d(rij_hat,rij,rik_hat,rik,dcosdri,dcosdrj,dcosdrk);


  F_FLOAT3 dcosdri;
  vec3_scaleadd(-cos_theta, rij_hat, rik_hat, dri);
  vec3_scale(F_F(1.0) / rij, dri, dri);
  vec3_scaleadd(-cos_theta, rik_hat, rij_hat, dcosdri);
  vec3_scale(F_F(1.0) / rik, dcosdri, dcosdri);
  vec3_add(dri, dcosdri, dcosdri);
  vec3_scale(-F_F(1.0), dcosdri, dcosdri);

  const F_FLOAT gijk = params[iparam].gamma * (F_F(1.0) + (params[iparam].c * params[iparam].c) / (params[iparam].d * params[iparam].d) -
                       (params[iparam].c * params[iparam].c) / (params[iparam].d * params[iparam].d + (params[iparam].h - cos_theta) * (params[iparam].h - cos_theta)));
  const F_FLOAT numerator = -F_F(2.0) * params[iparam].c * params[iparam].c * (params[iparam].h - cos_theta);
  const F_FLOAT denominator = (params[iparam].d * params[iparam].d) +
                              (params[iparam].h - cos_theta) * (params[iparam].h - cos_theta);
  const F_FLOAT gijk_d = params[iparam].gamma * numerator / (denominator * denominator); // compute the derivative wrt Ri
  //
  const F_FLOAT fc = ters_fc(rik, params[iparam].bigr, params[iparam].bigd);
  const F_FLOAT dfc = ters_fc_d(rik, params[iparam].bigr, params[iparam].bigd);

  vec3_scale(-dfc * gijk * ex_delr, rik_hat, dri);
  vec3_scaleadd(fc * gijk_d * ex_delr, dcosdri, dri, dri);
  vec3_scaleadd(fc * gijk * ex_delr_d, rik_hat, dri, dri);
  vec3_scaleadd(-fc * gijk * ex_delr_d, rij_hat, dri, dri);
  vec3_scale(prefactor, dri, dri);

}

__device__ void ters_zetaterm_d_fj(F_FLOAT &prefactor,
                                   F_FLOAT3 &rij_hat, F_FLOAT &rij,
                                   F_FLOAT3 &rik_hat, F_FLOAT &rik,
                                   F_FLOAT3 &drj, int &iparam)
{
  F_FLOAT ex_delr, ex_delr_d, tmp;

  if(params[iparam].powermint == 3) tmp = (params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik));
  else tmp = params[iparam].lam3 * (rij - rik);

  if(tmp > F_F(69.0776)) ex_delr = F_F(1.e30);
  else if(tmp < -F_F(69.0776)) ex_delr = F_F(0.0);
  else ex_delr = exp(tmp);

  if(params[iparam].powermint == 3)
    ex_delr_d = F_F(3.0) * (params[iparam].lam3 * params[iparam].lam3 * params[iparam].lam3) * (rij - rik) * (rij - rik) * ex_delr;
  else ex_delr_d = params[iparam].lam3 * ex_delr;

  const F_FLOAT cos_theta = vec3_dot(rij_hat, rik_hat);
  vec3_scaleadd(-cos_theta, rij_hat, rik_hat, drj);
  vec3_scale(F_F(1.0) / rij, drj, drj);

  const F_FLOAT gijk = params[iparam].gamma * (F_F(1.0) + (params[iparam].c * params[iparam].c) / (params[iparam].d * params[iparam].d) -
                       (params[iparam].c * params[iparam].c) / (params[iparam].d * params[iparam].d + (params[iparam].h - cos_theta) * (params[iparam].h - cos_theta)));
  const F_FLOAT numerator = -F_F(2.0) * params[iparam].c * params[iparam].c * (params[iparam].h - cos_theta);
  const F_FLOAT denominator = (params[iparam].d * params[iparam].d) +
                              (params[iparam].h - cos_theta) * (params[iparam].h - cos_theta);
  const F_FLOAT gijk_d = params[iparam].gamma * numerator / (denominator * denominator); // compute the derivative wrt Ri

  const F_FLOAT fc = ters_fc(rik, params[iparam].bigr, params[iparam].bigd);

  vec3_scale(fc * gijk_d * ex_delr, drj, drj);
  vec3_scaleadd(fc * gijk * ex_delr_d, rij_hat, drj, drj);
  vec3_scale(prefactor, drj, drj);
}

__device__ void ters_zetaterm_d_fk(F_FLOAT &prefactor,
                                   F_FLOAT3 &rij_hat, F_FLOAT &rij,
                                   F_FLOAT3 &rik_hat, F_FLOAT &rik,
                                   F_FLOAT3 &drk, int &iparam)
{
  F_FLOAT ex_delr, ex_delr_d, tmp;

  if(params[iparam].powermint == 3) tmp = (params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik) * params[iparam].lam3 * (rij - rik));
  else tmp = params[iparam].lam3 * (rij - rik);

  if(tmp > F_F(69.0776)) ex_delr = F_F(1.e30);
  else if(tmp < -F_F(69.0776)) ex_delr = F_F(0.0);
  else ex_delr = exp(tmp);

  if(params[iparam].powermint == 3)
    ex_delr_d = F_F(3.0) * (params[iparam].lam3 * params[iparam].lam3 * params[iparam].lam3) * (rij - rik) * (rij - rik) * ex_delr;
  else ex_delr_d = params[iparam].lam3 * ex_delr;

  const F_FLOAT cos_theta = vec3_dot(rij_hat, rik_hat);
  vec3_scaleadd(-cos_theta, rik_hat, rij_hat, drk);
  vec3_scale(F_F(1.0) / rik, drk, drk);

  const F_FLOAT gijk = params[iparam].gamma * (F_F(1.0) + (params[iparam].c * params[iparam].c) / (params[iparam].d * params[iparam].d) -
                       (params[iparam].c * params[iparam].c) / (params[iparam].d * params[iparam].d + (params[iparam].h - cos_theta) * (params[iparam].h - cos_theta)));
  const F_FLOAT numerator = -F_F(2.0) * params[iparam].c * params[iparam].c * (params[iparam].h - cos_theta);
  const F_FLOAT denominator = (params[iparam].d * params[iparam].d) +
                              (params[iparam].h - cos_theta) * (params[iparam].h - cos_theta);
  const F_FLOAT gijk_d = params[iparam].gamma * numerator / (denominator * denominator); // compute the derivative wrt Ri

  const F_FLOAT fc = ters_fc(rik, params[iparam].bigr, params[iparam].bigd);
  const F_FLOAT dfc = ters_fc_d(rik, params[iparam].bigr, params[iparam].bigd);

  vec3_scale(fc * gijk_d * ex_delr, drk, drk);
  vec3_scaleadd(dfc * gijk * ex_delr, rik_hat, drk, drk);
  vec3_scaleadd(-fc * gijk * ex_delr_d, rik_hat, drk, drk);
  vec3_scale(prefactor, drk, drk);
}

__device__ void attractive(int iparam, F_FLOAT prefactor,
                           F_FLOAT4 &delij,
                           F_FLOAT4 &delik,
                           F_FLOAT3 &fi, F_FLOAT3 &fj, F_FLOAT3 &fk)
{
  F_FLOAT3 rij_hat, rik_hat;
  F_FLOAT rij, rijinv, rik, rikinv;

  rij = sqrt(delij.w);
  rijinv = F_F(1.0) / rij;
  vec3_scale(rijinv, delij, rij_hat);

  rik = sqrt(delik.w);
  rikinv = F_F(1.0) / rik;
  vec3_scale(rikinv, delik, rik_hat);

  ters_zetaterm_d(prefactor, rij_hat, rij, rik_hat, rik, fi, fj, fk, iparam);
}

__device__ void attractive_fi(int &iparam, F_FLOAT &prefactor,
                              F_FLOAT4 &delij,
                              F_FLOAT4 &delik,
                              F_FLOAT3 &f)
{
  F_FLOAT3 rij_hat, rik_hat;
  F_FLOAT rij, rijinv, rik, rikinv;

  rij = sqrt(delij.w);
  rijinv = F_F(1.0) / rij;
  vec3_scale(rijinv, delij, rij_hat);

  rik = sqrt(delik.w);
  rikinv = F_F(1.0) / rik;
  vec3_scale(rikinv, delik, rik_hat);

  ters_zetaterm_d_fi(prefactor, rij_hat, rij, rik_hat, rik, f, iparam);
}

__device__ void attractive_fj(int iparam, F_FLOAT prefactor,
                              F_FLOAT4 &delij,
                              F_FLOAT4 &delik,
                              F_FLOAT3 &f)
{
  F_FLOAT3 rij_hat, rik_hat;
  F_FLOAT rij, rijinv, rik, rikinv;

  rij = sqrt(delij.w);
  rijinv = F_F(1.0) / rij;
  vec3_scale(rijinv, delij, rij_hat);

  rik = sqrt(delik.w);
  rikinv = F_F(1.0) / rik;
  vec3_scale(rikinv, delik, rik_hat);

  ters_zetaterm_d_fj(prefactor, rij_hat, rij, rik_hat, rik, f, iparam);
}

__device__ void attractive_fk(int iparam, F_FLOAT prefactor,
                              F_FLOAT4 &delij,
                              F_FLOAT4 &delik,
                              F_FLOAT3 &f)
{
  F_FLOAT3 rij_hat, rik_hat;
  F_FLOAT rij, rijinv, rik, rikinv;

  rij = sqrt(delij.w);
  rijinv = F_F(1.0) / rij;
  vec3_scale(rijinv, delij, rij_hat);

  rik = sqrt(delik.w);
  rikinv = F_F(1.0) / rik;
  vec3_scale(rikinv, delik, rik_hat);

  ters_zetaterm_d_fk(prefactor, rij_hat, rij, rik_hat, rik, f, iparam);
}

__global__ void Pair_Tersoff_Kernel_TpA_RIJ()//F_FLOAT4* _glob_r_ij,int* _glob_numneigh_red,int* _glob_neighbors_red,int* _glob_neightype_red)
{
  int ii = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  if(ii >= _nall) return;

  X_FLOAT4 myxtype;
  F_FLOAT4 delij;
  F_FLOAT xtmp, ytmp, ztmp;
  int itype, jnum, i, j;
  int* jlist;
  int neigh_red = 0;
  i = ii;//_ilist[ii];
  myxtype = fetchXType(i);

  xtmp = myxtype.x;
  ytmp = myxtype.y;
  ztmp = myxtype.z;
  itype = map[(static_cast <int>(myxtype.w))];

  jnum = _numneigh[i];
  jlist = &_neighbors[i];

  __syncthreads();

  for(int jj = 0; jj < jnum; jj++) {
    if(jj < jnum) {

      j = jlist[jj * _nall];
      j &= NEIGHMASK;
      myxtype = fetchXType(j);
      delij.x = xtmp - myxtype.x;
      delij.y = ytmp - myxtype.y;
      delij.z = ztmp - myxtype.z;
      int jtype = map[(static_cast <int>(myxtype.w))];
      int iparam_ij = elem2param[(itype * nelements + jtype) * nelements + jtype];
      delij.w = vec3_dot(delij, delij);

      if(delij.w < params[iparam_ij].cutsq) {
        _glob_neighbors_red[i + neigh_red * _nall] = j;
        _glob_neightype_red[i + neigh_red * _nall] = jtype;
        _glob_r_ij[i + neigh_red * _nall] = delij;
        neigh_red++;
      }
    }
  }

  _glob_numneigh_red[i] = neigh_red;
}


__global__ void Pair_Tersoff_Kernel_TpA_ZetaIJ()//F_FLOAT* _glob_zeta_ij,F_FLOAT4* _glob_r_ij,int* _glob_numneigh_red,int* _glob_neighbors_red,int* _glob_neightype_red)
{

  int ii = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  if(ii >= _nall) return;


  F_FLOAT4 delij;
  F_FLOAT4 delik;

  int itype, jnum, i, j;
  int* jlist;
  i = ii;
  itype = map[(static_cast <int>(_type[i]))];

  jnum = _glob_numneigh_red[i];
  jlist = &_glob_neighbors_red[i];

  __syncthreads();

  for(int jj = 0; jj < jnum; jj++) {
    if(jj < jnum) {

      j = jlist[jj * _nall];
      j &= NEIGHMASK;
      int jtype = _glob_neightype_red[i + jj * _nall];
      delij = _glob_r_ij[i + jj * _nall];

      int iparam_ij = elem2param[(itype * nelements + jtype) * nelements + jtype];

      if(delij.w < params[iparam_ij].cutsq) {
        F_FLOAT zeta_ij = 0.0;
        F_FLOAT3 delij3 = {delij.x, delij.y, delij.z};

        for(int kk = 0; kk < jnum; kk++) {
          if(jj == kk) continue;

          int k = jlist[kk * _nall];
          k &= NEIGHMASK;

          int ktype = _glob_neightype_red[i + kk * _nall];
          delik = _glob_r_ij[i + kk * _nall];
          F_FLOAT3 delik3 = {delik.x, delik.y, delik.z};
          int iparam_ijk = elem2param[(itype * nelements + jtype) * nelements + ktype];
          const F_FLOAT rsqki = delik.w;

          if(rsqki <= params[iparam_ijk].cutsq)
            zeta_ij += zeta(iparam_ijk, delij.w, rsqki, delij3, delik3);
        }

        _glob_zeta_ij[i + jj * _nall] = zeta_ij;
      }
    }
  }
}

//back3: num 12 steps 10: ZetaIJ/TPA 0.255/0.106
//back5: num 12 steps 10: ZetaIJ/TPA 0.257/0.098
//back6: num 12 steps 10: ZetaIJ/TPA 0.027/0.097 /rij berechnung extra
//back12: num 12 steps 10: ZetaIJ/TPA 0.026/0.070
//back15: num 12 steps 10: ZetaIJ/TPA 0.0137/0.0287 //pow beseitigt
//        num 12 steps 10: ZetaIJ/TPA 0.0137/0.027
template <int eflag, int vflagm>
__global__ void Pair_Tersoff_Kernel_TpA(int eflag_atom, int vflag_atom) //,F_FLOAT* _glob_zeta_ij,F_FLOAT4* _glob_r_ij,int* _glob_numneigh_red,int* _glob_neighbors_red,int* _glob_neightype_red)
{
  ENERGY_FLOAT evdwl = ENERGY_F(0.0);

  ENERGY_FLOAT* sharedE = &sharedmem[threadIdx.x];
  ENERGY_FLOAT* sharedV = &sharedmem[threadIdx.x];

  F_FLOAT* shared_F_F = (F_FLOAT*) sharedmem;

  if((eflag || eflag_atom) && (vflagm || vflag_atom)) shared_F_F = (F_FLOAT*) &sharedmem[7 * blockDim.x];
  else if(eflag) shared_F_F = (F_FLOAT*) &sharedmem[blockDim.x];
  else if(vflagm) shared_F_F = (F_FLOAT*) &sharedmem[6 * blockDim.x];

  shared_F_F += threadIdx.x;

  if(eflag_atom || eflag) {
    sharedE[0] = ENERGY_F(0.0);
    sharedV += blockDim.x;
  }

  if(vflagm || vflag_atom) {
    sharedV[0 * blockDim.x] = ENERGY_F(0.0);
    sharedV[1 * blockDim.x] = ENERGY_F(0.0);
    sharedV[2 * blockDim.x] = ENERGY_F(0.0);
    sharedV[3 * blockDim.x] = ENERGY_F(0.0);
    sharedV[4 * blockDim.x] = ENERGY_F(0.0);
    sharedV[5 * blockDim.x] = ENERGY_F(0.0);
  }

  int jnum_red = 0;
#define fxtmp shared_F_F[0]
#define fytmp shared_F_F[blockDim.x]
#define fztmp shared_F_F[2*blockDim.x]
  //#define jnum_red (static_cast <int> (shared_F_F[3*blockDim.x]))

  int ii = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  X_FLOAT4 myxtype_i, myxtype_j, myxtype_k;
  F_FLOAT4 delij, delik, deljk;
  F_FLOAT fpair;
  F_FLOAT prefactor_ij, prefactor_ji;

  int itype, i, j;
  int* jlist_red;

  if(ii < _inum) {
    i = _ilist[ii];

    if(vflagm)
      myxtype_i = fetchXType(i);

    //itype=map[(static_cast <int> (myxtype_i.w))];
    itype = map[_type[i]];


    fxtmp = F_F(0.0);
    fytmp = F_F(0.0);
    fztmp = F_F(0.0);


    //shared_F_F[3*blockDim.x] = _glob_numneigh_red[i];
    jnum_red = _glob_numneigh_red[i];
    jlist_red = &_glob_neighbors_red[i];
  }

  __syncthreads();

#pragma unroll 1

  for(int jj = 0; jj < jnum_red; jj++) {
    if(i < _nlocal) {
      fpair = F_F(0.0);
      j = jlist_red[jj * _nall];
      j &= NEIGHMASK;

      if(vflagm)
        myxtype_j = fetchXType(j);

      int jtype = _glob_neightype_red[i + jj * _nall];
      delij = _glob_r_ij[i + jj * _nall];

      volatile int iparam_ij = elem2param[(itype * nelements + jtype) * nelements + jtype];
      volatile int iparam_ji = elem2param[(jtype * nelements + itype) * nelements + itype];

      if(delij.w < params[iparam_ij].cutsq) {
        F_FLOAT dxfp, dyfp, dzfp;
        repulsive(iparam_ij, delij.w, fpair, eflag, evdwl);
        fxtmp += dxfp = delij.x * fpair;
        fytmp += dyfp = delij.y * fpair;
        fztmp += dzfp = delij.z * fpair;

        if(vflagm) {
          sharedV[0 * blockDim.x] += delij.x * dxfp;
          sharedV[1 * blockDim.x] += delij.y * dyfp;
          sharedV[2 * blockDim.x] += delij.z * dzfp;
          sharedV[3 * blockDim.x] += delij.x * dyfp;
          sharedV[4 * blockDim.x] += delij.x * dzfp;
          sharedV[5 * blockDim.x] += delij.y * dzfp;
        }



        force_zeta(iparam_ij, delij.w, _glob_zeta_ij[i + jj * _nall], fpair, prefactor_ij, eflag, evdwl);
        fxtmp -=
          dxfp = delij.x * fpair;
        fytmp -=
          dyfp = delij.y * fpair;
        fztmp -=
          dzfp = delij.z * fpair;

        if(vflagm) {
          sharedV[0 * blockDim.x] -= ENERGY_F(2.0) * delij.x * dxfp;
          sharedV[1 * blockDim.x] -= ENERGY_F(2.0) * delij.y * dyfp;
          sharedV[2 * blockDim.x] -= ENERGY_F(2.0) * delij.z * dzfp;
          sharedV[3 * blockDim.x] -= ENERGY_F(2.0) * delij.x * dyfp;
          sharedV[4 * blockDim.x] -= ENERGY_F(2.0) * delij.x * dzfp;
          sharedV[5 * blockDim.x] -= ENERGY_F(2.0) * delij.y * dzfp;
        }

        int j_jj = 0;

        //#pragma unroll 1
        for(int kk = 0; kk < _glob_numneigh_red[j]; kk++) {
          if(_glob_neighbors_red[j + kk * _nall] == i) j_jj = kk;
        }

        force_zeta_prefactor_force(iparam_ji, delij.w, _glob_zeta_ij[j + j_jj * _nall], fpair, prefactor_ji);

        fxtmp -=
          dxfp = delij.x * fpair;
        fytmp -=
          dyfp = delij.y * fpair;
        fztmp -=
          dzfp = delij.z * fpair;



        vec3_scale(F_F(-1.0), delij, delij);

#pragma unroll 1

        for(int kk = 0; kk < jnum_red; kk++) {
          if(jj == kk) continue;

          int k = jlist_red[kk * _nall];
          k &= NEIGHMASK;

          if(vflagm)
            myxtype_k = fetchXType(k);

          delik = _glob_r_ij[i + kk * _nall];

          int ktype = _glob_neightype_red[i + kk * _nall];
          int iparam_ijk = elem2param[(itype * nelements + jtype) * nelements + ktype];
          vec3_scale(F_F(-1.0), delik, delik);

          if(delik.w <= params[iparam_ijk].cutsq) {
            if(vflagm) {
              F_FLOAT3 fi, fj, fk;
              attractive(iparam_ijk, prefactor_ij,
                         delij, delik, fi, fj, fk);
              fxtmp += fi.x;
              fytmp += fi.y;
              fztmp += fi.z;

              sharedV[0 * blockDim.x] += ENERGY_F(2.0) * myxtype_i.x * fi.x;
              sharedV[1 * blockDim.x] += ENERGY_F(2.0) * myxtype_i.y * fi.y;
              sharedV[2 * blockDim.x] += ENERGY_F(2.0) * myxtype_i.z * fi.z;
              sharedV[3 * blockDim.x] += ENERGY_F(2.0) * myxtype_i.x * fi.y;
              sharedV[4 * blockDim.x] += ENERGY_F(2.0) * myxtype_i.x * fi.z;
              sharedV[5 * blockDim.x] += ENERGY_F(2.0) * myxtype_i.y * fi.z;

              sharedV[0 * blockDim.x] += ENERGY_F(2.0) * myxtype_j.x * fj.x;
              sharedV[1 * blockDim.x] += ENERGY_F(2.0) * myxtype_j.y * fj.y;
              sharedV[2 * blockDim.x] += ENERGY_F(2.0) * myxtype_j.z * fj.z;
              sharedV[3 * blockDim.x] += ENERGY_F(2.0) * myxtype_j.x * fj.y;
              sharedV[4 * blockDim.x] += ENERGY_F(2.0) * myxtype_j.x * fj.z;
              sharedV[5 * blockDim.x] += ENERGY_F(2.0) * myxtype_j.y * fj.z;

              sharedV[0 * blockDim.x] += ENERGY_F(2.0) * myxtype_k.x * fk.x;
              sharedV[1 * blockDim.x] += ENERGY_F(2.0) * myxtype_k.y * fk.y;
              sharedV[2 * blockDim.x] += ENERGY_F(2.0) * myxtype_k.z * fk.z;
              sharedV[3 * blockDim.x] += ENERGY_F(2.0) * myxtype_k.x * fk.y;
              sharedV[4 * blockDim.x] += ENERGY_F(2.0) * myxtype_k.x * fk.z;
              sharedV[5 * blockDim.x] += ENERGY_F(2.0) * myxtype_k.y * fk.z;
            } else {
              F_FLOAT3 fi; //local variable
              attractive_fi(iparam_ijk, prefactor_ij,
                            delij, delik, fi);
              fxtmp += fi.x;
              fytmp += fi.y;
              fztmp += fi.z;

            }
          }
        }

        int j_jnum_red = _glob_numneigh_red[j];
        int* j_jlist_red = &_glob_neighbors_red[j];

        int j_ii = 0;

        //#pragma unroll 1
        for(int j_kk = 0; j_kk < j_jnum_red; j_kk++) {
          if(j_jlist_red[j_kk * _nall] == i) j_ii = j_kk;
        }

#pragma unroll 1

        for(int kk = 0; kk < j_jnum_red; kk++) {
          if(j_ii == kk) continue;

          int k = j_jlist_red[kk * _nall];
          k &= NEIGHMASK;
          deljk = _glob_r_ij[j + kk * _nall];
          vec3_scale(F_F(-1.0), deljk, deljk);
          int ktype = _glob_neightype_red[j + kk * _nall];

          int iparam_jik = elem2param[(jtype * nelements + itype) * nelements + ktype];
          int iparam_jki = elem2param[(jtype * nelements + ktype) * nelements + itype];


          vec3_scale(F_F(-1.0), delij, delij);

          if(deljk.w <= params[iparam_jik].cutsq) {
            F_FLOAT3 ftmp; //local variable

            attractive_fj(iparam_jik, prefactor_ji,
                          delij, deljk, ftmp);
            fxtmp += ftmp.x;
            fytmp += ftmp.y;
            fztmp += ftmp.z;
            int iparam_jk = elem2param[(jtype * nelements + ktype) * nelements + ktype];
            F_FLOAT prefactor_jk;
            force_zeta_prefactor(iparam_jk, deljk.w, _glob_zeta_ij[j + kk * _nall], prefactor_jk);

            attractive_fk(iparam_jki, prefactor_jk,
                          deljk, delij, ftmp);
            fxtmp += ftmp.x;
            fytmp += ftmp.y;
            fztmp += ftmp.z;

          }

          vec3_scale(F_F(-1.0), delij, delij);
        }
      }
    }

  }

  __syncthreads();

  if(ii < _inum) {
    F_FLOAT* my_f;

    if(_collect_forces_later) {
      ENERGY_FLOAT* buffer = (ENERGY_FLOAT*) _buffer;

      if(eflag) {
        buffer = &buffer[1 * gridDim.x * gridDim.y];
      }

      if(vflagm) {
        buffer = &buffer[6 * gridDim.x * gridDim.y];
      }

      my_f = (F_FLOAT*) buffer;
      my_f += i;
      *my_f = fxtmp;
      my_f += _nmax;
      *my_f = fytmp;
      my_f += _nmax;
      *my_f = fztmp;
    } else {
      my_f = _f + i;
      *my_f += fxtmp;
      my_f += _nmax;
      *my_f += fytmp;
      my_f += _nmax;
      *my_f += fztmp;
    }
  }

  __syncthreads();

  if(eflag) {
    sharedE[0] = evdwl;
  }

  if(eflag_atom && i < _nlocal) {
    _eatom[i] = ENERGY_F(0.5) * evdwl;
  }

  if(vflag_atom && i < _nlocal) {
    _vatom[i]         = ENERGY_F(0.5) * sharedV[0 * blockDim.x];
    _vatom[i + _nmax]   = ENERGY_F(0.5) * sharedV[1 * blockDim.x];
    _vatom[i + 2 * _nmax] = ENERGY_F(0.5) * sharedV[2 * blockDim.x];
    _vatom[i + 3 * _nmax] = ENERGY_F(0.5) * sharedV[3 * blockDim.x];
    _vatom[i + 4 * _nmax] = ENERGY_F(0.5) * sharedV[4 * blockDim.x];
    _vatom[i + 5 * _nmax] = ENERGY_F(0.5) * sharedV[5 * blockDim.x];
  }

  if(vflagm && eflag) PairVirialCompute_A_Kernel_Template<1, 1>();
  else if(eflag) PairVirialCompute_A_Kernel_Template<1, 0>();
  else if(vflagm) PairVirialCompute_A_Kernel_Template<0, 1>();

#undef fxtmp
#undef fytmp
#undef fztmp
  //#undef jnum_red
}
