/* ----------------------------------------------------------------------
   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.
------------------------------------------------------------------------- */




static __device__ inline F_FLOAT4 fetchRhor(int i)
{
		#ifdef CUDA_USE_TEXTURE
		  #if F_PRECISION == 1
		     return tex1Dfetch(_rhor_spline_tex,i);
		  #else
		     return tex1Dfetch_double_f(_rhor_spline_tex,i);
		  #endif
		#else
		  return _rhor_spline[i];
		#endif		
}

static __device__ inline F_FLOAT4 fetchZ2r(int i)
{
		#ifdef CUDA_USE_TEXTURE
		  #if F_PRECISION == 1
		     return tex1Dfetch(_z2r_spline_tex,i);
		  #else
		     return tex1Dfetch_double_f(_z2r_spline_tex,i);
		  #endif
		#else
		  return _z2r_spline[i];
		#endif		
}

__global__ void PairEAMCuda_Kernel1(int eflag, int vflag,int eflag_atom,int vflag_atom)
{
	ENERGY_FLOAT* sharedE;
	ENERGY_FLOAT* sharedV = &sharedmem[threadIdx.x];

	
	if(eflag||eflag_atom)
    {
      sharedE = &sharedmem[threadIdx.x];
      sharedE[0] = ENERGY_F(0.0); 
      sharedV += blockDim.x;
    }
    if(vflag||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 ii = (blockIdx.x*gridDim.y+blockIdx.y)*blockDim.x+threadIdx.x;

	X_FLOAT xtmp,ytmp,ztmp;
	X_FLOAT4 myxtype;
	F_FLOAT delx,dely,delz;
	int itype;
	int i=_nlocal;
	int jnum=0;
	int* jlist;

	if(ii < _inum)
	{
		i = _ilist[ii];
		
		myxtype=fetchXType(i);
		xtmp=myxtype.x;
		ytmp=myxtype.y;
		ztmp=myxtype.z;
		itype=static_cast <int> (myxtype.w);

		jnum = _numneigh[i];

		jlist = &_neighbors[i];
		if(i<_nlocal)
		_rho[i]=F_F(0.0);
	} 
	__syncthreads();

	for (int jj = 0; jj < jnum; jj++)
	{
		  if(ii < _inum)
		  if(jj<jnum)
		  {
			const int j = jlist[jj*_nlocal]; 
			myxtype = fetchXType(j);
			delx = xtmp - myxtype.x;
			dely = ytmp - myxtype.y;
			delz = ztmp - myxtype.z;
			int jtype = static_cast <int> (myxtype.w);
			const F_FLOAT rsq = delx*delx + dely*dely + delz*delz;
			if (rsq < _cutsq_global)
			{
				F_FLOAT p = sqrt(rsq)*_rdr + F_F(1.0);
				int m = static_cast<int> (p);
				m = MIN(m,_nr-1);
				p -= m;
				p = MIN(p,F_F(1.0));

				int k=(static_cast <int> (_type2rhor[jtype*_cuda_ntypes+itype])*(_nr+1)+m)*2;
				F_FLOAT4 c=fetchRhor(k+1);
				_rho[i] += ((c.w*p+c.x)*p+c.y)*p+c.z;
			}
		  }
		}
 
 	if(ii < _inum)
	{
   				
   		F_FLOAT p = _rho[i]*_rdrho + F_F(1.0);
    	int m = static_cast<int> (p);
    	m = MAX(1,MIN(m,_nrho-1));
    	p -= m;
    	p = MIN(p,F_F(1.0));
    	F_FLOAT* coeff = &_frho_spline[(static_cast <int> (_type2frho[itype])*(_nrho+1)+m)*EAM_COEFF_LENGTH];
    	_fp[i] = (coeff[0]*p + coeff[1])*p + coeff[2];
      	if (eflag||eflag_atom) {
      		sharedmem[threadIdx.x] += ((coeff[3]*p + coeff[4])*p + coeff[5])*p + coeff[6];
   		}
        
	}
	__syncthreads();
	if(eflag||eflag_atom)
	{
	  if(i<_nlocal&&eflag_atom)
	  _eatom[i]+=sharedmem[threadIdx.x];
      reduceBlock(sharedmem);
	  ENERGY_FLOAT* buffer = (ENERGY_FLOAT*) _buffer;
	  buffer[blockIdx.x * gridDim.y + blockIdx.y] = ENERGY_F(2.0)*sharedmem[0];        
	}
}

__global__ void PairEAMCuda_Kernel2(int eflag, int vflag,int eflag_atom,int vflag_atom)
{
	ENERGY_FLOAT evdwl = ENERGY_F(0.0);

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

	
	if(eflag||eflag_atom)
    {
      sharedE = &sharedmem[threadIdx.x];
      sharedE[0] = ENERGY_F(0.0); 
      sharedV += blockDim.x;
    }
    if(vflag||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 ii = (blockIdx.x*gridDim.y+blockIdx.y)*blockDim.x+threadIdx.x;

	X_FLOAT xtmp,ytmp,ztmp;
	X_FLOAT4 myxtype;
	F_FLOAT fxtmp,fytmp,fztmp,fpair;
	F_FLOAT delx,dely,delz;
	int itype,i;
	int jnum=0;
	int* jlist;

	if(ii < _inum)
	{
		i = _ilist[ii];
		
		myxtype=fetchXType(i);
		xtmp=myxtype.x;
		ytmp=myxtype.y;
		ztmp=myxtype.z;
		itype=static_cast <int> (myxtype.w);
		fxtmp = F_F(0.0);
		fytmp = F_F(0.0);
		fztmp = F_F(0.0);
		
		jnum = _numneigh[i];

		jlist = &_neighbors[i];
		if(i<_nlocal)
		_rho[i]=F_F(0.0);
	} 
	if(ii<gridDim.x*gridDim.y) evdwl=((ENERGY_FLOAT*) _buffer)[ii];
	__syncthreads();

	for (int jj = 0; jj < jnum; jj++)
	{
		  if(ii < _inum)
		  if(jj<jnum)
		  {
			const int j = jlist[jj*_nlocal]; 
			myxtype = fetchXType(j);
			delx = xtmp - myxtype.x;
			dely = ytmp - myxtype.y;
			delz = ztmp - myxtype.z;
			int jtype = static_cast <int> (myxtype.w);
			const F_FLOAT rsq = delx*delx + dely*dely + delz*delz;

			if (rsq < _cutsq_global)
			{
				F_FLOAT r = _SQRT_(rsq);
				F_FLOAT p = r*_rdr + F_F(1.0);
				int m = static_cast<int> (p);
				m = MIN(m,_nr-1);
				p -= m;
				p = MIN(p,F_F(1.0));
				
				int k=(static_cast <int> (_type2rhor[itype*_cuda_ntypes+jtype])*(_nr+1)+m)*2;
				F_FLOAT4 c=fetchRhor(k);
				F_FLOAT rhoip = (c.x*p + c.y)*p + c.z;
				k=(static_cast <int> (_type2rhor[jtype*_cuda_ntypes+itype])*(_nr+1)+m)*2;
				c=fetchRhor(k);
				F_FLOAT rhojp = (c.x*p + c.y)*p + c.z;
				k=(static_cast <int> (_type2z2r[itype*_cuda_ntypes+jtype])*(_nr+1)+m)*2;
				c=fetchZ2r(k);
				F_FLOAT z2p = (c.x*p + c.y)*p + c.z;
				c=fetchZ2r(k+1);
				F_FLOAT z2 = ((c.w*p + c.x)*p + c.y)*p+c.z;

				F_FLOAT recip = F_F(1.0)/r;
				F_FLOAT phi = z2*recip;
				F_FLOAT phip = z2p*recip - phi*recip;
				F_FLOAT psip = _fp[i]*rhojp + _fp[j]*rhoip + phip;
				fpair = -psip*recip;

				F_FLOAT dxfp,dyfp,dzfp;
				fxtmp += dxfp = delx*fpair;
				fytmp += dyfp = dely*fpair; 
				fztmp += dzfp = delz*fpair;
				evdwl+=phi;
				if(vflag||vflag_atom)
				{
				  sharedV[0 * blockDim.x]+= delx*dxfp;
    			  sharedV[1 * blockDim.x]+= dely*dyfp;
    			  sharedV[2 * blockDim.x]+= delz*dzfp;
    			  sharedV[3 * blockDim.x]+= delx*dyfp;
    			  sharedV[4 * blockDim.x]+= delx*dzfp;
    			  sharedV[5 * blockDim.x]+= dely*dzfp;
				}
			}
		  }
	}

 	__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(vflag)
	    	{
		  		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] += 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(vflag||eflag) PairVirialCompute_A_Kernel(eflag,vflag,0);
}

__global__ void PairEAMCuda_PackComm_Kernel(int* sendlist,int n,int maxlistlength,int iswap,F_FLOAT* buffer)
{
  int i=(blockIdx.x*gridDim.y+blockIdx.y)*blockDim.x+threadIdx.x;
  int* list=sendlist+iswap*maxlistlength;
  if(i<n)
  {
    int j=list[i];
    buffer[i]=_fp[j];
  }
}

__global__ void PairEAMCuda_UnpackComm_Kernel(int n,int first,F_FLOAT* buffer)
{
  int i=(blockIdx.x*gridDim.y+blockIdx.y)*blockDim.x+threadIdx.x;
  if(i<n)
  {
    _fp[i+first]=buffer[i];
  }
}
