/* ----------------------------------------------------------------------
   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 authors: Mike Parks (SNL), Ezwanur Rahman, J.T. Foster (UTSA)
------------------------------------------------------------------------- */

#include "fix_peri_neigh.h"
#include <mpi.h>
#include <cmath>
#include "pair_peri_lps.h"
#include "pair_peri_ves.h"
#include "pair_peri_eps.h"
#include "atom.h"
#include "domain.h"
#include "force.h"
#include "comm.h"
#include "neighbor.h"
#include "neigh_list.h"
#include "neigh_request.h"
#include "pair.h"
#include "lattice.h"
#include "memory.h"
#include "error.h"

using namespace LAMMPS_NS;
using namespace FixConst;

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

FixPeriNeigh::FixPeriNeigh(LAMMPS *lmp,int narg, char **arg) :
  Fix(lmp, narg, arg)
{
  isPMB = isLPS = isVES = isEPS = 0;
  if (force->pair_match("peri/pmb",1)) isPMB = 1;
  if (force->pair_match("peri/lps",1)) isLPS = 1;
  if (force->pair_match("peri/ves",1)) isVES = 1;
  if (force->pair_match("peri/eps",1)) isEPS = 1;

  restart_global = 1;
  restart_peratom = 1;
  first = 1;

  // perform initial allocation of atom-based arrays
  // register with atom class
  // set maxpartner = 1 as placeholder

  maxpartner = 1;
  npartner = NULL;
  partner = NULL;
  deviatorextention = NULL;
  deviatorBackextention = NULL;
  deviatorPlasticextension = NULL;
  lambdaValue = NULL;
  r0 = NULL;
  vinter = NULL;
  wvolume = NULL;

  grow_arrays(atom->nmax);
  atom->add_callback(0);
  atom->add_callback(1);

  // initialize npartner to 0 so atom migration is OK the 1st time

  int nlocal = atom->nlocal;
  for (int i = 0; i < nlocal; i++) npartner[i] = 0;

  // set comm sizes needed by this fix

  comm_forward = 1;
}

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

FixPeriNeigh::~FixPeriNeigh()
{
  // unregister this fix so atom class doesn't invoke it any more

  atom->delete_callback(id,0);
  atom->delete_callback(id,1);

  // delete locally stored arrays

  memory->destroy(npartner);
  memory->destroy(partner);
  memory->destroy(deviatorextention);
  memory->destroy(deviatorBackextention);
  memory->destroy(deviatorPlasticextension);
  memory->destroy(lambdaValue);
  memory->destroy(r0);
  memory->destroy(vinter);
  memory->destroy(wvolume);
}

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

int FixPeriNeigh::setmask()
{
  int mask = 0;
  return mask;
}

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

void FixPeriNeigh::init()
{
  if (!first) return;

  // need a full neighbor list once

  int irequest = neighbor->request(this,instance_me);
  neighbor->requests[irequest]->pair = 0;
  neighbor->requests[irequest]->fix  = 1;
  neighbor->requests[irequest]->half = 0;
  neighbor->requests[irequest]->full = 1;
  neighbor->requests[irequest]->occasional = 1;

  // compute PD scale factor, stored in Atom class, used by DumpCFG

  int nlocal = atom->nlocal;
  double vone = 0.0;
  for (int i = 0; i < nlocal; i++) vone += atom->vfrac[i];
  double vave;
  MPI_Allreduce(&vone,&vave,1,MPI_DOUBLE,MPI_SUM,world);
  if (atom->natoms) vave /= atom->natoms;
  if (vave > 0.0) atom->pdscale = 1.44 / pow(vave,1.0/3.0);
  else atom->pdscale = 1.0;
}

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

void FixPeriNeigh::init_list(int /*id*/, NeighList *ptr)
{
  list = ptr;
}

/* ----------------------------------------------------------------------
   For minimization: setup as with dynamics
------------------------------------------------------------------------- */

void FixPeriNeigh::min_setup(int vflag)
{
  setup(vflag);
}

/* ----------------------------------------------------------------------
   create initial list of neighbor partners via call to neighbor->build()
   must be done in setup (not init) since fix init comes before neigh init
------------------------------------------------------------------------- */

void FixPeriNeigh::setup(int /*vflag*/)
{
  int i,j,ii,jj,itype,jtype,inum,jnum;
  double xtmp,ytmp,ztmp,delx,dely,delz,rsq;
  int *ilist,*jlist,*numneigh;
  int **firstneigh;

  double **x = atom->x;
  double *vfrac = atom->vfrac;
  int *type = atom->type;
  tagint *tag = atom->tag;
  int nlocal = atom->nlocal;

 // only build list of bonds on very first run

  if (!first) return;
  first = 0;

  // build full neighbor list, will copy or build as necessary

  neighbor->build_one(list);

  inum = list->inum;
  ilist = list->ilist;
  numneigh = list->numneigh;
  firstneigh = list->firstneigh;

  // scan neighbor list to set maxpartner

  Pair *anypair = force->pair_match("peri",0);
  double **cutsq = anypair->cutsq;

  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;

      delx = xtmp - x[j][0];
      dely = ytmp - x[j][1];
      delz = ztmp - x[j][2];
      rsq = delx*delx + dely*dely + delz*delz;
      jtype = type[j];
      if (rsq <= cutsq[itype][jtype]) npartner[i]++;
    }
  }

  maxpartner = 0;
  for (i = 0; i < nlocal; i++) maxpartner = MAX(maxpartner,npartner[i]);
  int maxall;
  MPI_Allreduce(&maxpartner,&maxall,1,MPI_INT,MPI_MAX,world);
  maxpartner = maxall;

  // realloc arrays with correct value for maxpartner

  memory->destroy(partner);
  memory->destroy(deviatorextention);
  memory->destroy(deviatorBackextention);
  memory->destroy(deviatorPlasticextension);
  memory->destroy(lambdaValue);
  memory->destroy(r0);
  memory->destroy(npartner);

  npartner = NULL;
  partner = NULL;
  deviatorextention = NULL;
  deviatorBackextention = NULL;
  deviatorPlasticextension = NULL;
  lambdaValue = NULL;
  r0 = NULL;
  grow_arrays(atom->nmax);

  // create partner list and r0 values from neighbor list
  // compute vinter for each atom

  for (i = 0; i < nlocal; i++) {
    npartner[i] = 0;
    vinter[i] = 0.0;
    wvolume[i] = 0.0;
    if (isEPS) lambdaValue[i] = 0.0;
  }

  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;

      delx = xtmp - x[j][0];
      dely = ytmp - x[j][1];
      delz = ztmp - x[j][2];
      rsq = delx*delx + dely*dely + delz*delz;
      jtype = type[j];

      if (rsq <= cutsq[itype][jtype]) {
        partner[i][npartner[i]] = tag[j];
        if (isVES)
          deviatorextention[i][npartner[i]] =
            deviatorBackextention[i][npartner[i]] = 0.0;
        if (isEPS)
           deviatorPlasticextension[i][npartner[i]] = 0.0;
        r0[i][npartner[i]] = sqrt(rsq);
        npartner[i]++;
        vinter[i] += vfrac[j];
      }
    }
  }

  // sanity check: does any atom appear twice in any neigborlist?
  // should only be possible if using pbc and domain < 2*delta

  if (domain->xperiodic || domain->yperiodic || domain->zperiodic) {
    for (i = 0; i < nlocal; i++) {
      jnum = npartner[i];
      for (jj = 0; jj < jnum; jj++) {
        for (int kk = jj+1; kk < jnum; kk++) {
          if (partner[i][jj] == partner[i][kk])
            error->one(FLERR,"Duplicate particle in PeriDynamic bond - "
                       "simulation box is too small");
        }
      }
    }
  }

  // compute wvolume for each atom

  double **x0 = atom->x0;
  double half_lc = 0.5*(domain->lattice->xlattice);
  double vfrac_scale;
  PairPeriLPS *pairlps = NULL;
  PairPeriVES *pairves = NULL;
  PairPeriEPS *paireps = NULL;
  if (isLPS)
    pairlps = static_cast<PairPeriLPS*>(anypair);
  else if (isVES)
    pairves = static_cast<PairPeriVES*>(anypair);
  else if (isEPS)
    paireps = static_cast<PairPeriEPS*>(anypair);

  for (i = 0; i < nlocal; i++) {
    double xtmp0 = x0[i][0];
    double ytmp0 = x0[i][1];
    double ztmp0 = x0[i][2];
    jnum = npartner[i];
    itype = type[i];

    // loop over partners of particle i

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

      // if bond already broken, skip this partner

      if (partner[i][jj] == 0) continue;

      // lookup local index of partner particle

      j = atom->map(partner[i][jj]);

      // skip if particle is "lost"

      if (j < 0) continue;

      double delx0 = xtmp0 - x0[j][0];
      double dely0 = ytmp0 - x0[j][1];
      double delz0 = ztmp0 - x0[j][2];

      double rsq0 = delx0*delx0 + dely0*dely0 + delz0*delz0;

      jtype = type[j];
      double delta = sqrt(cutsq[itype][jtype]);

      // scale vfrac[j] if particle j near the horizon

      if ((fabs(r0[i][jj] - delta)) <= half_lc)
        vfrac_scale = (-1.0/(2*half_lc))*(r0[i][jj]) +
          (1.0 + ((delta - half_lc)/(2*half_lc) ) );
      else vfrac_scale = 1.0;

      // for PMB, influence = 1.0, otherwise invoke influence function

      if (isPMB)
        wvolume[i] += 1.0 * rsq0 * vfrac[j] * vfrac_scale;
      else if (isLPS)
        wvolume[i] += pairlps->influence_function(delx0,dely0,delz0) *
          rsq0 * vfrac[j] * vfrac_scale;
      else if (isVES)
        wvolume[i] += pairves->influence_function(delx0,dely0,delz0) *
          rsq0 * vfrac[j] * vfrac_scale;
      else if (isEPS)
        wvolume[i] += paireps->influence_function(delx0,dely0,delz0) *
          rsq0 * vfrac[j] * vfrac_scale;
    }
  }

  // communicate wvolume to ghosts

  comm->forward_comm_fix(this);

  // bond statistics

  int n = 0;
  for (i = 0; i < nlocal; i++) n += npartner[i];
  int nall;
  MPI_Allreduce(&n,&nall,1,MPI_INT,MPI_SUM,world);

  if (comm->me == 0) {
    if (screen) {
      fprintf(screen,"Peridynamic bonds:\n");
      fprintf(screen,"  total # of bonds = %d\n",nall);
      fprintf(screen,"  bonds/atom = %g\n",(double)nall/atom->natoms);
    }
    if (logfile) {
      fprintf(logfile,"Peridynamic bonds:\n");
      fprintf(logfile,"  total # of bonds = %d\n",nall);
      fprintf(logfile,"  bonds/atom = %g\n",(double)nall/atom->natoms);
    }
  }
}

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

double FixPeriNeigh::memory_usage()
{
  int nmax = atom->nmax;
  int bytes = nmax * sizeof(int);
  bytes += nmax*maxpartner * sizeof(tagint);
  bytes += nmax*maxpartner * sizeof(double);
  if (isVES) {
    bytes += nmax*maxpartner * sizeof(double);
    bytes += nmax*maxpartner * sizeof(double);
  }
  if (isEPS) {
    bytes += nmax*maxpartner * sizeof(double);
    bytes += nmax * sizeof(double);
  }
  bytes += nmax * sizeof(double);
  bytes += nmax * sizeof(double);
  return bytes;
}

/* ----------------------------------------------------------------------
   allocate local atom-based arrays
------------------------------------------------------------------------- */

void FixPeriNeigh::grow_arrays(int nmax)
{
   memory->grow(npartner,nmax,"peri_neigh:npartner");
   memory->grow(partner,nmax,maxpartner,"peri_neigh:partner");
   if (isVES) {
     memory->grow(deviatorextention,nmax,maxpartner,
                  "peri_neigh:deviatorextention");
     memory->grow(deviatorBackextention,nmax,maxpartner,
                  "peri_neigh:deviatorBackextention");
   }
   if (isEPS) memory->grow(deviatorPlasticextension,nmax,maxpartner,
                           "peri_neigh:deviatorPlasticextension");
   memory->grow(r0,nmax,maxpartner,"peri_neigh:r0");
   if (isEPS) memory->grow(lambdaValue,nmax,"peri_neigh:lambdaValue");
   memory->grow(vinter,nmax,"peri_neigh:vinter");
   memory->grow(wvolume,nmax,"peri_neigh:wvolume");
}

/* ----------------------------------------------------------------------
   copy values within local atom-based arrays
------------------------------------------------------------------------- */

void FixPeriNeigh::copy_arrays(int i, int j, int /*delflag*/)
{
  npartner[j] = npartner[i];
  for (int m = 0; m < npartner[j]; m++) {
    partner[j][m] = partner[i][m];
    if (isVES) {
      deviatorextention[j][m] = deviatorextention[i][m];
      deviatorBackextention[j][m] = deviatorBackextention[i][m];
    }
    if (isEPS)
      deviatorPlasticextension[j][m] = deviatorPlasticextension[i][m];
    r0[j][m] = r0[i][m];
  }
  if (isEPS) lambdaValue[j] = lambdaValue[i];
  vinter[j] = vinter[i];
  wvolume[j] = wvolume[i];
}

/* ----------------------------------------------------------------------
   pack values in local atom-based arrays for exchange with another proc
------------------------------------------------------------------------- */

int FixPeriNeigh::pack_exchange(int i, double *buf)
{
  // compact list by eliminating partner = 0 entries
  // set buf[0] after compaction

  int m = 1;
  for (int n = 0; n < npartner[i]; n++) {
    if (partner[i][n] == 0) continue;
    buf[m++] = partner[i][n];
    if (isVES) {
      buf[m++] = deviatorextention[i][n];
      buf[m++] = deviatorBackextention[i][n];
    }
    if (isEPS) buf[m++] = deviatorPlasticextension[i][n];
    buf[m++] = r0[i][n];
  }
  if (isVES) buf[0] = m/4;
  else if (isEPS) buf[0] = m/3;
  else buf[0] = m/2;
  if (isEPS) buf[m++] = lambdaValue[i];
  buf[m++] = vinter[i];
  buf[m++] = wvolume[i];
  return m;
}

/* ----------------------------------------------------------------------
   unpack values in local atom-based arrays from exchange with another proc
------------------------------------------------------------------------- */

int FixPeriNeigh::unpack_exchange(int nlocal, double *buf)
{
  int m = 0;
  npartner[nlocal] = static_cast<int> (buf[m++]);
  for (int n = 0; n < npartner[nlocal]; n++) {
    partner[nlocal][n] = static_cast<tagint> (buf[m++]);
    if (isVES) {
      deviatorextention[nlocal][n] = buf[m++];
      deviatorBackextention[nlocal][n] = buf[m++];
    }
    if (isEPS) deviatorPlasticextension[nlocal][n] = buf[m++];
    r0[nlocal][n] = buf[m++];
  }
  if (isEPS) lambdaValue[nlocal] = buf[m++];
  vinter[nlocal] = buf[m++];
  wvolume[nlocal] = buf[m++];
  return m;
}

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

int FixPeriNeigh::pack_forward_comm(int n, int *list, double *buf,
                                    int /*pbc_flag*/, int * /*pbc*/)
{
  int i,j,m;

  m = 0;
  for (i = 0; i < n; i++) {
    j = list[i];
    buf[m++] = wvolume[j];
  }
  return m;
}

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

void FixPeriNeigh::unpack_forward_comm(int n, int first, double *buf)
{
  int i,m,last;

  m = 0;
  last = first + n;
  for (i = first; i < last; i++)
    wvolume[i] = buf[m++];
}

/* ----------------------------------------------------------------------
   pack entire state of Fix into one write
------------------------------------------------------------------------- */

void FixPeriNeigh::write_restart(FILE *fp)
{
  int n = 0;
  double list[2];
  list[n++] = first;
  list[n++] = maxpartner;

  if (comm->me == 0) {
    int size = n * sizeof(double);
    fwrite(&size,sizeof(int),1,fp);
    fwrite(list,sizeof(double),n,fp);
  }
}

/* ----------------------------------------------------------------------
   use state info from restart file to restart the Fix
------------------------------------------------------------------------- */

void FixPeriNeigh::restart(char *buf)
{
  int n = 0;
  double *list = (double *) buf;

  first = static_cast<int> (list[n++]);
  maxpartner = static_cast<int> (list[n++]);

  // grow 2D arrays now, cannot change size of 2nd array index later

  grow_arrays(atom->nmax);
}

/* ----------------------------------------------------------------------
   pack values in local atom-based arrays for restart file
------------------------------------------------------------------------- */

int FixPeriNeigh::pack_restart(int i, double *buf)
{
  int m = 0;
  if (isVES) buf[m++] = 4*npartner[i] + 4;
  else if (isEPS) buf[m++] = 3*npartner[i] + 5;
  else buf[m++] = 2*npartner[i] + 4;
  buf[m++] = npartner[i];
  for (int n = 0; n < npartner[i]; n++) {
    buf[m++] = partner[i][n];
    if (isVES) {
      buf[m++] = deviatorextention[i][n];
      buf[m++] = deviatorBackextention[i][n];
    }
    if (isEPS) buf[m++] = deviatorPlasticextension[i][n];
    buf[m++] = r0[i][n];
  }
  if (isEPS) buf[m++] = lambdaValue[i];
  buf[m++] = vinter[i];
  buf[m++] = wvolume[i];
  return m;
}

/* ----------------------------------------------------------------------
   unpack values from atom->extra array to restart the fix
------------------------------------------------------------------------- */

void FixPeriNeigh::unpack_restart(int nlocal, int nth)
{

  double **extra = atom->extra;

  // skip to Nth set of extra values

  int m = 0;
  for (int i = 0; i < nth; i++) m += static_cast<int> (extra[nlocal][m]);
  m++;

  npartner[nlocal] = static_cast<int> (extra[nlocal][m++]);
  for (int n = 0; n < npartner[nlocal]; n++) {
    partner[nlocal][n] = static_cast<tagint> (extra[nlocal][m++]);
    if (isVES) {
      deviatorextention[nlocal][n] = extra[nlocal][m++];
      deviatorBackextention[nlocal][n] = extra[nlocal][m++];
    }
    if (isEPS) deviatorPlasticextension[nlocal][n] = extra[nlocal][m++];
    r0[nlocal][n] = extra[nlocal][m++];
  }
  if (isEPS) lambdaValue[nlocal] = extra[nlocal][m++];
  vinter[nlocal] = extra[nlocal][m++];
  wvolume[nlocal] = extra[nlocal][m++];
}

/* ----------------------------------------------------------------------
   maxsize of any atom's restart data
------------------------------------------------------------------------- */

int FixPeriNeigh::maxsize_restart()
{
  if (isVES) return 4*maxpartner + 4;
  if (isEPS) return 3*maxpartner + 5;
  return 2*maxpartner + 4;
}

/* ----------------------------------------------------------------------
   size of atom nlocal's restart data
------------------------------------------------------------------------- */

int FixPeriNeigh::size_restart(int nlocal)
{
  if (isVES) return 4*npartner[nlocal] + 4;
  if (isEPS) return 3*npartner[nlocal] + 5;
  return 2*npartner[nlocal] + 4;
}
