/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   http://lammps.sandia.gov, Sandia National Laboratories
   Steve Plimpton, sjplimp@sandia.gov

   Copyright (2003) Sandia Corporation.  Under the terms of Contract
   DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
   certain rights in this software.  This software is distributed under 
   the GNU General Public License.

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

/* ----------------------------------------------------------------------
   Contributing author: Xiaowang Zhou (SNL)
------------------------------------------------------------------------- */

#include "math.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "pair_eim.h"
#include "atom.h"
#include "force.h"
#include "comm.h"
#include "neighbor.h"
#include "neigh_list.h"
#include "memory.h"
#include "error.h"

using namespace LAMMPS_NS;

#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))

#define MAXLINE 1024

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

PairEIM::PairEIM(LAMMPS *lmp) : Pair(lmp)
{
  single_enable = 0;
  one_coeff = 1;

  setfl = NULL;
  nmax = 0;
  rho = NULL;
  fp = NULL;

  nelements = 0;
  elements = NULL;

  negativity = NULL;
  q0 = NULL;
  cutforcesq = NULL;
  Fij = NULL;
  Gij = NULL;
  phiij = NULL;

  Fij_spline = NULL;
  Gij_spline = NULL;
  phiij_spline = NULL;

  // set comm size needed by this Pair

  comm_forward = 1;
  comm_reverse = 1;
}

/* ----------------------------------------------------------------------
   check if allocated, since class can be destructed when incomplete
------------------------------------------------------------------------- */

PairEIM::~PairEIM()
{
  memory->destroy(rho);
  memory->destroy(fp);

  if (allocated) {
    memory->destroy(setflag);
    memory->destroy(cutsq);
    delete [] map;
    memory->destroy(type2Fij);
    memory->destroy(type2Gij);
    memory->destroy(type2phiij);
  }

  for (int i = 0; i < nelements; i++) delete [] elements[i];
  delete [] elements;
  
  deallocate_setfl();

  delete [] negativity;
  delete [] q0;
  memory->destroy(cutforcesq);
  memory->destroy(Fij);
  memory->destroy(Gij);
  memory->destroy(phiij);

  memory->destroy(Fij_spline);
  memory->destroy(Gij_spline);
  memory->destroy(phiij_spline);
}

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

void PairEIM::compute(int eflag, int vflag)
{
  int i,j,ii,jj,m,inum,jnum,itype,jtype;
  double xtmp,ytmp,ztmp,delx,dely,delz,evdwl,fpair;
  double rsq,r,p,rhoip,rhojp,phip,phi,coul,coulp,recip,psip;
  double *coeff;
  int *ilist,*jlist,*numneigh,**firstneigh;

  evdwl = 0.0;
  if (eflag || vflag) ev_setup(eflag,vflag);
  else evflag = vflag_fdotr = eflag_global = eflag_atom = 0;

  // grow energy array if necessary

  if (atom->nmax > nmax) {
    memory->destroy(rho);
    memory->destroy(fp);
    nmax = atom->nmax;
    memory->create(rho,nmax,"pair:rho");
    memory->create(fp,nmax,"pair:fp");
  }

  double **x = atom->x;
  double **f = atom->f;
  int *type = atom->type;
  int nlocal = atom->nlocal;
  int newton_pair = force->newton_pair;

  inum = list->inum;
  ilist = list->ilist;
  numneigh = list->numneigh;
  firstneigh = list->firstneigh;
  
  // zero out density

  if (newton_pair) {
    m = nlocal + atom->nghost;
    for (i = 0; i < m; i++) {
      rho[i] = 0.0;
      fp[i] = 0.0;
    }
  } else {
    for (i = 0; i < nlocal; i++) {
      rho[i] = 0.0;
      fp[i] = 0.0;
    }
  }

  // rho = density at each atom
  // loop over neighbors of my atoms

  for (ii = 0; ii < inum; ii++) {
    i = ilist[ii];
    xtmp = x[i][0];
    ytmp = x[i][1];
    ztmp = x[i][2];
    itype = type[i];
    jlist = firstneigh[i];
    jnum = numneigh[i];

    for (jj = 0; jj < jnum; jj++) {
      j = jlist[jj];
      j &= NEIGHMASK;
      jtype = type[j];
      delx = xtmp - x[j][0];
      dely = ytmp - x[j][1];
      delz = ztmp - x[j][2];
      rsq = delx*delx + dely*dely + delz*delz;

      if (rsq < cutforcesq[itype][jtype]) {
	p = sqrt(rsq)*rdr + 1.0;
	m = static_cast<int> (p);
	m = MIN(m,nr-1);
	p -= m;
	p = MIN(p,1.0);
	coeff = Fij_spline[type2Fij[itype][jtype]][m];
	rho[i] += ((coeff[3]*p + coeff[4])*p + coeff[5])*p + coeff[6];
	if (newton_pair || j < nlocal) {
	  coeff = Fij_spline[type2Fij[jtype][itype]][m];
	  rho[j] += ((coeff[3]*p + coeff[4])*p + coeff[5])*p + coeff[6];
	}
      }
    }
  }

  // communicate and sum densities

  rhofp = 1;
  if (newton_pair) comm->reverse_comm_pair(this);
  comm->forward_comm_pair(this);

  for (ii = 0; ii < inum; ii++) {
    i = ilist[ii];
    xtmp = x[i][0];
    ytmp = x[i][1];
    ztmp = x[i][2];
    itype = type[i];
    jlist = firstneigh[i];
    jnum = numneigh[i];
 
    for (jj = 0; jj < jnum; jj++) {
      j = jlist[jj];
      j &= NEIGHMASK;
      jtype = type[j];
 
      delx = xtmp - x[j][0];
      dely = ytmp - x[j][1];
      delz = ztmp - x[j][2];
      rsq = delx*delx + dely*dely + delz*delz;
 
      if (rsq < cutforcesq[itype][jtype]) {
        p = sqrt(rsq)*rdr + 1.0;
        m = static_cast<int> (p);
        m = MIN(m,nr-1);
        p -= m;
        p = MIN(p,1.0);
        coeff = Gij_spline[type2Gij[itype][jtype]][m];
        fp[i] += rho[j]*(((coeff[3]*p + coeff[4])*p + coeff[5])*p + coeff[6]);
        if (newton_pair || j < nlocal) {
          fp[j] += rho[i]*(((coeff[3]*p + coeff[4])*p + coeff[5])*p + 
			   coeff[6]);
        }
      }
    }
  }

  // communicate and sum modified densities

  rhofp = 2;
  if (newton_pair) comm->reverse_comm_pair(this);
  comm->forward_comm_pair(this);

  for (ii = 0; ii < inum; ii++) {
    i = ilist[ii];
    itype = type[i];
    if (eflag) {
      phi = 0.5*rho[i]*fp[i];
      if (eflag_global) eng_vdwl += phi;
      if (eflag_atom) eatom[i] += phi;
    }
  }

  // compute forces on each atom
  // loop over neighbors of my atoms

  for (ii = 0; ii < inum; ii++) {
    i = ilist[ii];
    xtmp = x[i][0];
    ytmp = x[i][1];
    ztmp = x[i][2];
    itype = type[i];

    jlist = firstneigh[i];
    jnum = numneigh[i];

    for (jj = 0; jj < jnum; jj++) {
      j = jlist[jj];
      j &= NEIGHMASK;
      jtype = type[j];
      delx = xtmp - x[j][0];
      dely = ytmp - x[j][1];
      delz = ztmp - x[j][2];
      rsq = delx*delx + dely*dely + delz*delz;

      if (rsq < cutforcesq[itype][jtype]) {
        r = sqrt(rsq);
        p = r*rdr + 1.0;
        m = static_cast<int> (p);
        m = MIN(m,nr-1);
        p -= m;
        p = MIN(p,1.0);

        // rhoip = derivative of (density at atom j due to atom i)
        // rhojp = derivative of (density at atom i due to atom j)
        // phi = pair potential energy
        // phip = phi'

        coeff = Fij_spline[type2Fij[jtype][itype]][m];
        rhoip = (coeff[0]*p + coeff[1])*p + coeff[2];
        coeff = Fij_spline[type2Fij[itype][jtype]][m];
        rhojp = (coeff[0]*p + coeff[1])*p + coeff[2];
        coeff = phiij_spline[type2phiij[itype][jtype]][m];
        phip = (coeff[0]*p + coeff[1])*p + coeff[2];
        phi = ((coeff[3]*p + coeff[4])*p + coeff[5])*p + coeff[6];
        coeff = Gij_spline[type2Gij[itype][jtype]][m];
        coul = ((coeff[3]*p + coeff[4])*p + coeff[5])*p + coeff[6];
        coulp = (coeff[0]*p + coeff[1])*p + coeff[2];
        psip = phip + (rho[i]*rho[j]-q0[itype]*q0[jtype])*coulp +
               fp[i]*rhojp + fp[j]*rhoip;
        recip = 1.0/r;
        fpair = -psip*recip;
        f[i][0] += delx*fpair;
        f[i][1] += dely*fpair;
        f[i][2] += delz*fpair;
        if (newton_pair || j < nlocal) {
          f[j][0] -= delx*fpair;
          f[j][1] -= dely*fpair;
          f[j][2] -= delz*fpair;
        }

        if (eflag) evdwl = phi-q0[itype]*q0[jtype]*coul;
        if (evflag) ev_tally(i,j,nlocal,newton_pair,
                             evdwl,0.0,fpair,delx,dely,delz);
      }
    }
  }

  if (vflag_fdotr) virial_compute();
}

/* ----------------------------------------------------------------------
   allocate all arrays
------------------------------------------------------------------------- */

void PairEIM::allocate()
{
  allocated = 1;
  int n = atom->ntypes;

  memory->create(setflag,n+1,n+1,"pair:setflag");
  for (int i = 1; i <= n; i++)
    for (int j = i; j <= n; j++)
      setflag[i][j] = 0;

  memory->create(cutsq,n+1,n+1,"pair:cutsq");

  map = new int[n+1];
  for (int i = 1; i <= n; i++) map[i] = -1;

  memory->create(type2Fij,n+1,n+1,"pair:type2Fij");
  memory->create(type2Gij,n+1,n+1,"pair:type2Gij");
  memory->create(type2phiij,n+1,n+1,"pair:type2phiij");
}

/* ----------------------------------------------------------------------
   global settings
------------------------------------------------------------------------- */

void PairEIM::settings(int narg, char **arg)
{
  if (narg > 0) error->all("Illegal pair_style command");
}

/* ----------------------------------------------------------------------
   set coeffs from set file
------------------------------------------------------------------------- */

void PairEIM::coeff(int narg, char **arg)
{
  int i,j,m,n;

  if (!allocated) allocate();

  if (narg < 5) error->all("Incorrect args for pair coefficients");

  // insure I,J args are * *

  if (strcmp(arg[0],"*") != 0 || strcmp(arg[1],"*") != 0)
    error->all("Incorrect args for pair coefficients");

  // read EIM element names before filename
  // nelements = # of EIM elements to read from file
  // elements = list of unique element names

  if (nelements) {
    for (i = 0; i < nelements; i++) delete [] elements[i];
    delete [] elements;
  }
  nelements = narg - 3 - atom->ntypes;
  if (nelements < 1) error->all("Incorrect args for pair coefficients");
  elements = new char*[nelements];
  
  for (i = 0; i < nelements; i++) {
    n = strlen(arg[i+2]) + 1;
    elements[i] = new char[n];
    strcpy(elements[i],arg[i+2]);
  }

  // read EIM file

  deallocate_setfl();
  setfl = new Setfl();
  read_file(arg[2+nelements]);

  // read args that map atom types to elements in potential file
  // map[i] = which element the Ith atom type is, -1 if NULL

  for (i = 3 + nelements; i < narg; i++) {
    m = i - (3+nelements) + 1;
    for (j = 0; j < nelements; j++)
      if (strcmp(arg[i],elements[j]) == 0) break;
    if (j < nelements) map[m] = j;
    else if (strcmp(arg[i],"NULL") == 0) map[m] = -1;
    else error->all("Incorrect args for pair coefficients");
  }

  // clear setflag since coeff() called once with I,J = * *

  n = atom->ntypes;
  for (i = 1; i <= n; i++)
    for (j = i; j <= n; j++)
      setflag[i][j] = 0;

  // set setflag i,j for type pairs where both are mapped to elements
  // set mass of atom type if i = j

  int count = 0;
  for (i = 1; i <= n; i++)
    for (j = i; j <= n; j++)
      if (map[i] >= 0 && map[j] >= 0) {
	setflag[i][j] = 1;
	if (i == j) atom->set_mass(i,setfl->mass[map[i]]);
	count++;
      }

  if (count == 0) error->all("Incorrect args for pair coefficients");
}

/* ----------------------------------------------------------------------
   init specific to this pair style
------------------------------------------------------------------------- */

void PairEIM::init_style()
{
  // convert read-in file(s) to arrays and spline them

  file2array();
  array2spline();

  int irequest = neighbor->request(this);
}

/* ----------------------------------------------------------------------
   init for one type pair i,j and corresponding j,i
------------------------------------------------------------------------- */

double PairEIM::init_one(int i, int j)
{
  cutmax = sqrt(cutforcesq[i][j]);
  return cutmax;
}

/* ----------------------------------------------------------------------
   read potential values from a set file
------------------------------------------------------------------------- */

void PairEIM::read_file(char *filename)
{
  // open potential file

  int me = comm->me;
  FILE *fptr;

  if (me == 0) {
    fptr = fopen(filename,"r");
    if (fptr == NULL) {
      char str[128];
      sprintf(str,"Cannot open EIM potential file %s",filename);
      error->one(str);
    }
  }

  int npair = nelements*(nelements+1)/2;
  setfl->ielement = new int[nelements];
  setfl->mass = new double[nelements];
  setfl->negativity = new double[nelements];
  setfl->ra = new double[nelements];
  setfl->ri = new double[nelements];
  setfl->Ec = new double[nelements];
  setfl->q0 = new double[nelements];
  setfl->rcutphiA = new double[npair];
  setfl->rcutphiR = new double[npair];
  setfl->Eb = new double[npair];
  setfl->r0 = new double[npair];
  setfl->alpha = new double[npair];
  setfl->beta = new double[npair];
  setfl->rcutq = new double[npair];
  setfl->Asigma = new double[npair];
  setfl->rq = new double[npair];
  setfl->rcutsigma = new double[npair];
  setfl->Ac = new double[npair];
  setfl->zeta = new double[npair];
  setfl->rs = new double[npair];
  setfl->tp = new int[npair];

  if (me == 0)
    if (!grabglobal(fptr))
      error->one("Could not grab global entry from EIM potential file");
  MPI_Bcast(&setfl->division,1,MPI_DOUBLE,0,world);
  MPI_Bcast(&setfl->rbig,1,MPI_DOUBLE,0,world);
  MPI_Bcast(&setfl->rsmall,1,MPI_DOUBLE,0,world);

  for (int i = 0; i < nelements; i++) {
    if (me == 0)
      if (!grabsingle(fptr,i))
	error->one("Could not grab element entry from EIM potential file");
    MPI_Bcast(&setfl->ielement[i],1,MPI_INT,0,world);
    MPI_Bcast(&setfl->mass[i],1,MPI_DOUBLE,0,world);
    MPI_Bcast(&setfl->negativity[i],1,MPI_DOUBLE,0,world);
    MPI_Bcast(&setfl->ra[i],1,MPI_DOUBLE,0,world);
    MPI_Bcast(&setfl->ri[i],1,MPI_DOUBLE,0,world);
    MPI_Bcast(&setfl->Ec[i],1,MPI_DOUBLE,0,world);
    MPI_Bcast(&setfl->q0[i],1,MPI_DOUBLE,0,world);
  }

  for (int i = 0; i < nelements; i++) {
    for (int j = i; j < nelements; j++) {
      int ij;
      if (i == j) ij = i;
      else if (i < j) ij = nelements*(i+1) - (i+1)*(i+2)/2 + j;
      else ij = nelements*(j+1) - (j+1)*(j+2)/2 + i;
      if (me == 0)
        if (grabpair(fptr,i,j) == 0)
	  error->one("Could not grab pair entry from EIM potential file");
      MPI_Bcast(&setfl->rcutphiA[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->rcutphiR[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->Eb[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->r0[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->alpha[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->beta[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->rcutq[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->Asigma[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->rq[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->rcutsigma[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->Ac[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->zeta[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->rs[ij],1,MPI_DOUBLE,0,world);
      MPI_Bcast(&setfl->tp[ij],1,MPI_INT,0,world);
    }
  }

  setfl->nr = 5000;
  setfl->cut = 0.0;
  for (int i = 0; i < npair; i++) {
    if (setfl->cut < setfl->rcutphiA[i]) setfl->cut = setfl->rcutphiA[i];
    if (setfl->cut < setfl->rcutphiR[i]) setfl->cut = setfl->rcutphiR[i];
    if (setfl->cut < setfl->rcutq[i]) setfl->cut = setfl->rcutq[i];
    if (setfl->cut < setfl->rcutsigma[i]) setfl->cut = setfl->rcutsigma[i];
  }
  setfl->dr = setfl->cut/(setfl->nr-1.0);

  memory->create(setfl->cuts,nelements,nelements,"pair:cuts");
  for (int i = 0; i < nelements; i++) {
    for (int j = 0; j < nelements; j++) {
      if (i > j) {
        setfl->cuts[i][j] = setfl->cuts[j][i];
      } else {
        int ij;
        if (i == j) {
          ij = i;
        } else {
          ij = nelements*(i+1) - (i+1)*(i+2)/2 + j;
        }
        setfl->cuts[i][j] = setfl->rcutphiA[ij];
        if (setfl->cuts[i][j] < setfl->rcutphiR[ij]) 
	  setfl->cuts[i][j] = setfl->rcutphiR[ij];
        if (setfl->cuts[i][j] < setfl->rcutq[ij]) 
	  setfl->cuts[i][j] = setfl->rcutq[ij];
        if (setfl->cuts[i][j] < setfl->rcutsigma[ij]) 
	  setfl->cuts[i][j] = setfl->rcutsigma[ij];
      }
    }
  }
  
  memory->create(setfl->Fij,nelements,nelements,setfl->nr+1,"pair:Fij");
  memory->create(setfl->Gij,nelements,nelements,setfl->nr+1,"pair:Gij");
  memory->create(setfl->phiij,nelements,nelements,setfl->nr+1,"pair:phiij");

  for (int i = 0; i < nelements; i++)  
    for (int j = 0; j < nelements; j++) {
      for (int k = 0; k < setfl->nr; k++) {
        if (i > j) {
          setfl->phiij[i][j][k+1] = setfl->phiij[j][i][k+1];
        } else {
          double r = k*setfl->dr;
          setfl->phiij[i][j][k+1] = funcphi(i,j,r);
        }
      }
    }

  for (int i = 0; i < nelements; i++)
    for (int j = 0; j < nelements; j++) {
      for (int k = 0; k < setfl->nr; k++) {
        double r = k*setfl->dr;
        setfl->Fij[i][j][k+1] = funcsigma(i,j,r);
      }
    }

  for (int i = 0; i < nelements; i++)
    for (int j = 0; j < nelements; j++) {
      for (int k = 0; k < setfl->nr; k++) {
        if (i > j) {
          setfl->Gij[i][j][k+1] = setfl->Gij[j][i][k+1];
        } else {
          double r = k*setfl->dr;
          setfl->Gij[i][j][k+1] = funccoul(i,j,r);
        }
      }
    }

  // close the potential file

  if (me == 0) fclose(fptr);
}

/* ----------------------------------------------------------------------
   deallocate data associated with setfl file
------------------------------------------------------------------------- */

void PairEIM::deallocate_setfl()
{
  if (!setfl) return;
  delete [] setfl->ielement;
  delete [] setfl->mass;
  delete [] setfl->negativity;
  delete [] setfl->ra;
  delete [] setfl->ri;
  delete [] setfl->Ec;
  delete [] setfl->q0;
  delete [] setfl->rcutphiA;
  delete [] setfl->rcutphiR;
  delete [] setfl->Eb;
  delete [] setfl->r0;
  delete [] setfl->alpha;
  delete [] setfl->beta;
  delete [] setfl->rcutq;
  delete [] setfl->Asigma;
  delete [] setfl->rq;
  delete [] setfl->rcutsigma;
  delete [] setfl->Ac;
  delete [] setfl->zeta;
  delete [] setfl->rs;
  delete [] setfl->tp;
  memory->destroy(setfl->cuts);
  memory->destroy(setfl->Fij);
  memory->destroy(setfl->Gij);
  memory->destroy(setfl->phiij);
  delete setfl;
}

/* ----------------------------------------------------------------------
   convert read-in potentials to standard array format
   interpolate all file values to a single grid and cutoff
------------------------------------------------------------------------- */

void PairEIM::file2array()
{
  int i,j,m,n;
  int irow,icol;
  int ntypes = atom->ntypes;

  delete [] negativity;
  delete [] q0;
  delete [] cutforcesq;
  negativity = new double[ntypes+1];
  q0 = new double[ntypes+1];
  memory->create(cutforcesq,ntypes+1,ntypes+1,"pair:cutforcesq");
  for (i = 1; i <= ntypes; i++) {
    if (map[i] == -1) {
      negativity[i]=0.0;
      q0[i]=0.0;
    } else {
      negativity[i]=setfl->negativity[map[i]];
      q0[i]=setfl->q0[map[i]];
    }
  }

  for (i = 1; i <= ntypes; i++)
    for (j = 1; j <= ntypes; j++) {
      if (map[i] == -1 || map[j] == -1) {
        cutforcesq[i][j] = setfl->cut;
        cutforcesq[i][j] =  cutforcesq[i][j]*cutforcesq[i][j];
      } else {
        cutforcesq[i][j] = setfl->cuts[map[i]][map[j]];
        cutforcesq[i][j] =  cutforcesq[i][j]*cutforcesq[i][j];
      }
    }

  nr = setfl->nr;
  dr = setfl->dr;

  // ------------------------------------------------------------------
  // setup Fij arrays
  // ------------------------------------------------------------------

  nFij = nelements*nelements + 1;
  memory->destroy(Fij);
  memory->create(Fij,nFij,nr+1,"pair:Fij");

  // copy each element's Fij to global Fij

  n=0;
  for (i = 0; i < nelements; i++)
    for (j = 0; j < nelements; j++) {
      for (m = 1; m <= nr; m++) Fij[n][m] = setfl->Fij[i][j][m];
      n++;
    }

  // add extra Fij of zeroes for non-EIM types to point to (pair hybrid)
 
  for (m = 1; m <= nr; m++) Fij[nFij-1][m] = 0.0;
 
  // type2Fij[i][j] = which Fij array (0 to nFij-1) each type pair maps to
  // setfl of Fij arrays 
  // value = n = sum over rows of matrix until reach irow,icol
  // if atom type doesn't point to element (non-EIM atom in pair hybrid)
  // then map it to last Fij array of zeroes

  for (i = 1; i <= ntypes; i++) {
    for (j = 1; j <= ntypes; j++) {
      irow = map[i];
      icol = map[j];
      if (irow == -1 || icol == -1) {
        type2Fij[i][j] = nFij-1;
      } else {
        n = 0;
        for (m = 0; m < irow; m++) n += nelements;
        n += icol;
        type2Fij[i][j] = n;
      }
    }
  }

  // ------------------------------------------------------------------
  // setup Gij arrays
  // ------------------------------------------------------------------

  nGij = nelements * (nelements+1) / 2 + 1;
  memory->destroy(Gij);
  memory->create(Gij,nGij,nr+1,"pair:Gij");

  // copy each element's Gij to global Gij, only for I >= J

  n=0;
  for (i = 0; i < nelements; i++)
    for (j = 0; j <= i; j++) {
      for (m = 1; m <= nr; m++) Gij[n][m] = setfl->Gij[i][j][m];
      n++;
    }

  // add extra Gij of zeroes for non-EIM types to point to (pair hybrid)

  for (m = 1; m <= nr; m++) Gij[nGij-1][m] = 0.0;

  // type2Gij[i][j] = which Gij array (0 to nGij-1) each type pair maps to
  // setfl of Gij arrays only fill lower triangular Nelement matrix
  // value = n = sum over rows of lower-triangular matrix until reach irow,icol
  // swap indices when irow < icol to stay lower triangular
  // if atom type doesn't point to element (non-EIM atom in pair hybrid)
  // then map it to last Gij array of zeroes

  for (i = 1; i <= ntypes; i++) {
    for (j = 1; j <= ntypes; j++) {
      irow = map[i];
      icol = map[j];
      if (irow == -1 || icol == -1) {
        type2Gij[i][j] = nGij-1;
      } else {
        if (irow < icol) {
          irow = map[j];
          icol = map[i];
        }
        n = 0;
        for (m = 0; m < irow; m++) n += m + 1;
        n += icol;
        type2Gij[i][j] = n;
      }
    }
  }

  // ------------------------------------------------------------------
  // setup phiij arrays
  // ------------------------------------------------------------------

  nphiij = nelements * (nelements+1) / 2 + 1;
  memory->destroy(phiij);
  memory->create(phiij,nphiij,nr+1,"pair:phiij");

  // copy each element pair phiij to global phiij, only for I >= J

  n = 0;
  for (i = 0; i < nelements; i++)
    for (j = 0; j <= i; j++) {
      for (m = 1; m <= nr; m++) phiij[n][m] = setfl->phiij[i][j][m];
      n++;
    }

  // add extra phiij of zeroes for non-EIM types to point to (pair hybrid)

  for (m = 1; m <= nr; m++) phiij[nphiij-1][m] = 0.0;

  // type2phiij[i][j] = which phiij array (0 to nphiij-1) 
  //                    each type pair maps to
  // setfl of phiij arrays only fill lower triangular Nelement matrix
  // value = n = sum over rows of lower-triangular matrix until reach irow,icol
  // swap indices when irow < icol to stay lower triangular
  // if atom type doesn't point to element (non-EIM atom in pair hybrid)
  // then map it to last phiij array of zeroes

  for (i = 1; i <= ntypes; i++) {
    for (j = 1; j <= ntypes; j++) {
      irow = map[i];
      icol = map[j];
      if (irow == -1 || icol == -1) {
        type2phiij[i][j] = nphiij-1;
      } else {
        if (irow < icol) {
          irow = map[j];
          icol = map[i];
        }
        n = 0;
        for (m = 0; m < irow; m++) n += m + 1;
        n += icol;
        type2phiij[i][j] = n;
      }
    }
  }
}

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

void PairEIM::array2spline()
{
  rdr = 1.0/dr;

  memory->destroy(Fij_spline);
  memory->destroy(Gij_spline);
  memory->destroy(phiij_spline);

  memory->create(Fij_spline,nFij,nr+1,7,"pair:Fij");
  memory->create(Gij_spline,nGij,nr+1,7,"pair:Gij");
  memory->create(phiij_spline,nphiij,nr+1,7,"pair:phiij");

  for (int i = 0; i < nFij; i++)
    interpolate(nr,dr,Fij[i],Fij_spline[i],0.0);

  for (int i = 0; i < nGij; i++)
    interpolate(nr,dr,Gij[i],Gij_spline[i],0.0);

  for (int i = 0; i < nphiij; i++)
    interpolate(nr,dr,phiij[i],phiij_spline[i],0.0);
}

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

void PairEIM::interpolate(int n, double delta, double *f, 
			  double **spline, double origin)
{
  for (int m = 1; m <= n; m++) spline[m][6] = f[m];

  spline[1][5] = spline[2][6] - spline[1][6];
  spline[2][5] = 0.5 * (spline[3][6]-spline[1][6]);
  spline[n-1][5] = 0.5 * (spline[n][6]-spline[n-2][6]);
  spline[n][5] = 0.0;
  
  for (int m = 3; m <= n-2; m++)
    spline[m][5] = ((spline[m-2][6]-spline[m+2][6]) + 
		    8.0*(spline[m+1][6]-spline[m-1][6])) / 12.0;
  
  for (int m = 1; m <= n-1; m++) {
    spline[m][4] = 3.0*(spline[m+1][6]-spline[m][6]) - 
      2.0*spline[m][5] - spline[m+1][5];
    spline[m][3] = spline[m][5] + spline[m+1][5] - 
      2.0*(spline[m+1][6]-spline[m][6]);
  }
  
  spline[n][4] = 0.0;
  spline[n][3] = 0.0;
  
  for (int m = 1; m <= n; m++) {
    spline[m][2] = spline[m][5]/delta;
    spline[m][1] = 2.0*spline[m][4]/delta;
    spline[m][0] = 3.0*spline[m][3]/delta;
  }
}

/* ----------------------------------------------------------------------
   grab global line from file and store info in setfl
   return 0 if error
------------------------------------------------------------------------- */

int PairEIM::grabglobal(FILE *fptr)
{
  char line[MAXLINE];

  char *pch = NULL, *data = NULL;
  while (pch == NULL) {
    if (fgets(line,MAXLINE,fptr) == NULL) break;
    pch = strstr(line,"global");
    if (pch != NULL) {
      data = strtok (line," \t\n\r\f");
      data = strtok (NULL,"?");
      sscanf(data,"%lg %lg %lg",&setfl->division,&setfl->rbig,&setfl->rsmall);
    }
  }
  if (pch == NULL) return 0;
  return 1;
}

/* ----------------------------------------------------------------------
   grab elemental line from file and store info in setfl
   return 0 if error
------------------------------------------------------------------------- */

int PairEIM::grabsingle(FILE *fptr, int i)
{
  char line[MAXLINE];

  rewind(fptr);

  char *pch1 = NULL, *pch2 = NULL, *data = NULL;
  while (pch1 == NULL || pch2 == NULL) {
    if (fgets(line,MAXLINE,fptr) == NULL) break;
    pch1 = strtok (line," \t\n\r\f");
    pch1 = strstr(pch1,"element:");
    if (pch1 != NULL) {
      pch2 = strtok(NULL, " \t\n\r\f");
      if (pch2 != NULL) data = strtok (NULL, "?");
      if (strcmp(pch2,elements[i]) == 0) {
        sscanf(data,"%d %lg %lg %lg %lg %lg %lg",&setfl->ielement[i],
          &setfl->mass[i],&setfl->negativity[i],&setfl->ra[i],
          &setfl->ri[i],&setfl->Ec[i],&setfl->q0[i]);
      } else {
        pch2 = NULL;
      }
    }
  }
  if (pch1 == NULL || pch2 == NULL) return 0;
  return 1;
}

/* ----------------------------------------------------------------------
   grab pair line from file and store info in setfl
   return 0 if error
------------------------------------------------------------------------- */

int PairEIM::grabpair(FILE *fptr, int i, int j)
{
  char line[MAXLINE];

  rewind(fptr);

  int ij;
  if (i == j) ij = i;
  else if (i < j) ij = nelements*(i+1) - (i+1)*(i+2)/2 + j;
  else ij = nelements*(j+1) - (j+1)*(j+2)/2 + i;

  char *pch1 = NULL, *pch2 = NULL, *pch3 = NULL, *data = NULL;
  while (pch1 == NULL || pch2 == NULL || pch3 == NULL) {
    if (fgets(line,MAXLINE,fptr) == NULL) break;
    pch1 = strtok (line," \t\n\r\f");
    pch1 = strstr(pch1,"pair:");
    if (pch1 != NULL) {
      pch2 = strtok (NULL, " \t\n\r\f");
      if (pch2 != NULL) pch3 = strtok (NULL, " \t\n\r\f");
      if (pch3 != NULL) data = strtok (NULL, "?");
      if ((strcmp(pch2,elements[i]) == 0 &&
        strcmp(pch3,elements[j]) == 0) ||
        (strcmp(pch2,elements[j]) == 0 &&
        strcmp(pch3,elements[i]) == 0)) {
        sscanf(data,"%lg %lg %lg %lg %lg",
          &setfl->rcutphiA[ij],&setfl->rcutphiR[ij],
          &setfl->Eb[ij],&setfl->r0[ij],&setfl->alpha[ij]);
        fgets(line,MAXLINE,fptr);
        sscanf(line,"%lg %lg %lg %lg %lg",
          &setfl->beta[ij],&setfl->rcutq[ij],&setfl->Asigma[ij],
          &setfl->rq[ij],&setfl->rcutsigma[ij]);
        fgets(line,MAXLINE,fptr);
        sscanf(line,"%lg %lg %lg %d",
          &setfl->Ac[ij],&setfl->zeta[ij],&setfl->rs[ij],
          &setfl->tp[ij]);
      } else {
         pch1 = NULL;
         pch2 = NULL;
         pch3 = NULL;
      }
    }
  }
  if (pch1 == NULL || pch2 == NULL || pch3 == NULL) return 0;
  return 1;
}

/* ----------------------------------------------------------------------
   cutoff function
------------------------------------------------------------------------- */

double PairEIM::funccutoff(double rp, double rc, double r)
{
  double rbig = setfl->rbig;
  double rsmall = setfl->rsmall;
  
  double a = (rsmall-rbig)/(rc-rp)*(r-rp)+rbig;
  a = erfc(a);
  double b = erfc(rbig);
  double c = erfc(rsmall);
  return (a-c)/(b-c);
}

/* ----------------------------------------------------------------------
   pair interaction function phi
------------------------------------------------------------------------- */

double PairEIM::funcphi(int i, int j, double r)
{
  int ij;
  double value = 0.0;
  if (i == j) ij = i;
  else if (i < j) ij = nelements*(i+1) - (i+1)*(i+2)/2 + j;
  else ij = nelements*(j+1) - (j+1)*(j+2)/2 + i;
  if (r < 0.2) r = 0.2;
  if (setfl->tp[ij] == 1) {
    double a = setfl->Eb[ij]*setfl->alpha[ij] / 
      (setfl->beta[ij]-setfl->alpha[ij]);
    double b = setfl->Eb[ij]*setfl->beta[ij] / 
      (setfl->beta[ij]-setfl->alpha[ij]);
    if (r < setfl->rcutphiA[ij]) {
      value -= a*exp(-setfl->beta[ij]*(r/setfl->r0[ij]-1.0))*
        funccutoff(setfl->r0[ij],setfl->rcutphiA[ij],r);
    }
    if (r < setfl-> rcutphiR[ij]) {
      value += b*exp(-setfl->alpha[ij]*(r/setfl->r0[ij]-1.0))*
        funccutoff(setfl->r0[ij],setfl->rcutphiR[ij],r);
    }
  } else if (setfl->tp[ij] == 2) {
    double a=setfl->Eb[ij]*setfl->alpha[ij]*pow(setfl->r0[ij],setfl->beta[ij])/
      (setfl->beta[ij]-setfl->alpha[ij]);
    double b=a*setfl->beta[ij]/setfl->alpha[ij]*
      pow(setfl->r0[ij],setfl->alpha[ij]-setfl->beta[ij]);
    if (r < setfl->rcutphiA[ij]) {
      value -= a/pow(r,setfl->beta[ij])*
        funccutoff(setfl->r0[ij],setfl->rcutphiA[ij],r);
    }
    if (r < setfl-> rcutphiR[ij]) {
      value += b/pow(r,setfl->alpha[ij])*
        funccutoff(setfl->r0[ij],setfl->rcutphiR[ij],r);
    }
  }
  return value;
}

/* ----------------------------------------------------------------------
   ion propensity function sigma
------------------------------------------------------------------------- */

double PairEIM::funcsigma(int i, int j, double r)
{
  int ij;
  double value = 0.0;
  if (i == j) ij = i;
  else if (i < j) ij = nelements*(i+1) - (i+1)*(i+2)/2 + j;
  else ij = nelements*(j+1) - (j+1)*(j+2)/2 + i;
  if (r < 0.2) r = 0.2;
  if (r < setfl->rcutq[ij]) {
    value = setfl->Asigma[ij]*(setfl->negativity[j]-setfl->negativity[i]) *
      funccutoff(setfl->rq[ij],setfl->rcutq[ij],r);
  }
  return value;
}

/* ----------------------------------------------------------------------
   charge-charge interaction function sigma
------------------------------------------------------------------------- */

double PairEIM::funccoul(int i, int j, double r)
{
  int ij;
  double value = 0.0;
  if (i == j) ij = i;
  else if (i < j) ij = nelements*(i+1) - (i+1)*(i+2)/2 + j;
  else ij = nelements*(j+1) - (j+1)*(j+2)/2 + i;
  if (r < 0.2) r = 0.2;
  if (r < setfl->rcutsigma[ij]) {
    value = setfl->Ac[ij]*exp(-setfl->zeta[ij]*r)*
      funccutoff(setfl->rs[ij],setfl->rcutsigma[ij],r);
  }
  return value;
}

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

int PairEIM::pack_comm(int n, int *list, double *buf, int pbc_flag, int *pbc)
{
  int i,j,m;
 
  m = 0;
  if (rhofp == 1) {
    for (i = 0; i < n; i++) {
      j = list[i];
      buf[m++] = rho[j];
    }
  }
  if (rhofp == 2) {
    for (i = 0; i < n; i++) {
      j = list[i];
      buf[m++] = fp[j];
    }
  }
  return 1;
}

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

void PairEIM::unpack_comm(int n, int first, double *buf)
{
  int i,m,last;
 
  m = 0;
  last = first + n;
  if (rhofp == 1) {
    for (i = first; i < last; i++) rho[i] = buf[m++];
  }
  if (rhofp == 2) {
    for (i = first; i < last; i++) fp[i] = buf[m++];
  }
}

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

int PairEIM::pack_reverse_comm(int n, int first, double *buf)
{
  int i,m,last;

  m = 0;
  last = first + n;
  if (rhofp == 1) {
    for (i = first; i < last; i++) buf[m++] = rho[i];
  }
  if (rhofp == 2) {
    for (i = first; i < last; i++) buf[m++] = fp[i];
  }
  return 1;
}

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

void PairEIM::unpack_reverse_comm(int n, int *list, double *buf)
{
  int i,j,m;

  m = 0;
  if (rhofp == 1) {
    for (i = 0; i < n; i++) {
      j = list[i];
      rho[j] += buf[m++];
    }
  }
  if (rhofp == 2) { 
    for (i = 0; i < n; i++) {
      j = list[i];
      fp[j] += buf[m++];
    }
  }
}

/* ----------------------------------------------------------------------
   memory usage of local atom-based arrays 
------------------------------------------------------------------------- */

double PairEIM::memory_usage()
{
  double bytes = maxeatom * sizeof(double);
  bytes += maxvatom*6 * sizeof(double);
  bytes += 2 * nmax * sizeof(double);
  return bytes;
}
