// clang-format off
/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   https://www.lammps.org/, 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.
------------------------------------------------------------------------- */

#include "fix_pour.h"

#include "atom.h"
#include "atom_vec.h"
#include "comm.h"
#include "domain.h"
#include "error.h"
#include "fix_gravity.h"
#include "force.h"
#include "math_const.h"
#include "math_extra.h"
#include "memory.h"
#include "modify.h"
#include "molecule.h"
#include "random_park.h"
#include "region.h"
#include "region_block.h"
#include "region_cylinder.h"
#include "update.h"

#include <cmath>
#include <cstring>

using namespace LAMMPS_NS;
using namespace FixConst;
using namespace MathConst;

enum{ATOM,MOLECULE};
enum{ONE,RANGE,POLY};
enum{CONSTANT,EQUAL};    // same as FixGravity

#define EPSILON 0.001
#define SMALL 1.0e-10

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

FixPour::FixPour(LAMMPS *lmp, int narg, char **arg) :
  Fix(lmp, narg, arg), radius_poly(nullptr), frac_poly(nullptr),
  idrigid(nullptr), idshake(nullptr), onemols(nullptr), molfrac(nullptr), coords(nullptr),
  imageflags(nullptr), fixrigid(nullptr), fixshake(nullptr), recvcounts(nullptr),
  displs(nullptr), random(nullptr), random2(nullptr)
{
  if (narg < 6) error->all(FLERR,"Illegal fix pour command");

  if (lmp->kokkos)
    error->all(FLERR,"Cannot yet use fix pour with the KOKKOS package");

  time_depend = 1;

  if (!atom->radius_flag || !atom->rmass_flag)
    error->all(FLERR,"Fix pour requires atom attributes radius, rmass");

  // required args

  ninsert = utils::inumeric(FLERR,arg[3],false,lmp);
  ntype = utils::inumeric(FLERR,arg[4],false,lmp);
  seed = utils::inumeric(FLERR,arg[5],false,lmp);

  if (seed <= 0) error->all(FLERR,"Illegal fix pour command");

  // read options from end of input line

  options(narg-6,&arg[6]);

  // error check on type

  if (mode == ATOM && (ntype <= 0 || ntype > atom->ntypes))
    error->all(FLERR,"Invalid atom type in fix pour command");

  // error checks on region and its extent being inside simulation box

  if (iregion == -1) error->all(FLERR,"Must specify a region in fix pour");
  if (domain->regions[iregion]->bboxflag == 0)
    error->all(FLERR,"Fix pour region does not support a bounding box");
  if (domain->regions[iregion]->dynamic_check())
    error->all(FLERR,"Fix pour region cannot be dynamic");

  if (strcmp(domain->regions[iregion]->style,"block") == 0) {
    region_style = 1;
    xlo = ((RegBlock *) domain->regions[iregion])->xlo;
    xhi = ((RegBlock *) domain->regions[iregion])->xhi;
    ylo = ((RegBlock *) domain->regions[iregion])->ylo;
    yhi = ((RegBlock *) domain->regions[iregion])->yhi;
    zlo = ((RegBlock *) domain->regions[iregion])->zlo;
    zhi = ((RegBlock *) domain->regions[iregion])->zhi;
    if (xlo < domain->boxlo[0] || xhi > domain->boxhi[0] ||
        ylo < domain->boxlo[1] || yhi > domain->boxhi[1] ||
        zlo < domain->boxlo[2] || zhi > domain->boxhi[2])
      error->all(FLERR,"Insertion region extends outside simulation box");
  } else if (strcmp(domain->regions[iregion]->style,"cylinder") == 0) {
    region_style = 2;
    char axis = ((RegCylinder *) domain->regions[iregion])->axis;
    xc = ((RegCylinder *) domain->regions[iregion])->c1;
    yc = ((RegCylinder *) domain->regions[iregion])->c2;
    rc = ((RegCylinder *) domain->regions[iregion])->radius;
    zlo = ((RegCylinder *) domain->regions[iregion])->lo;
    zhi = ((RegCylinder *) domain->regions[iregion])->hi;
    if (axis != 'z')
      error->all(FLERR,"Must use a z-axis cylinder region with fix pour");
    if (xc-rc < domain->boxlo[0] || xc+rc > domain->boxhi[0] ||
        yc-rc < domain->boxlo[1] || yc+rc > domain->boxhi[1] ||
        zlo < domain->boxlo[2] || zhi > domain->boxhi[2])
      error->all(FLERR,"Insertion region extends outside simulation box");
  } else error->all(FLERR,"Must use a block or cylinder region with fix pour");

  if (region_style == 2 && domain->dimension == 2)
    error->all(FLERR,
               "Must use a block region with fix pour for 2d simulations");

  // error check and further setup for mode = MOLECULE

  if (atom->tag_enable == 0)
    error->all(FLERR,"Cannot use fix_pour unless atoms have IDs");

  if (mode == MOLECULE) {
    for (int i = 0; i < nmol; i++) {
      if (onemols[i]->xflag == 0)
        error->all(FLERR,"Fix pour molecule must have coordinates");
      if (onemols[i]->typeflag == 0)
        error->all(FLERR,"Fix pour molecule must have atom types");
      if (ntype+onemols[i]->ntypes <= 0 ||
          ntype+onemols[i]->ntypes > atom->ntypes)
        error->all(FLERR,"Invalid atom type in fix pour mol command");

      if (atom->molecular == Atom::TEMPLATE && onemols != atom->avec->onemols)
        error->all(FLERR,"Fix pour molecule template ID must be same "
                   "as atom style template ID");
      onemols[i]->check_attributes(0);

      // fix pour uses geoemetric center of molecule for insertion

      onemols[i]->compute_center();
    }
  }

  if (rigidflag && mode == ATOM)
    error->all(FLERR,"Cannot use fix pour rigid and not molecule");
  if (shakeflag && mode == ATOM)
    error->all(FLERR,"Cannot use fix pour shake and not molecule");
  if (rigidflag && shakeflag)
    error->all(FLERR,"Cannot use fix pour rigid and shake");

  // setup of coords and imageflags array

  if (mode == ATOM) natom_max = 1;
  else {
    natom_max = 0;
    for (int i = 0; i < nmol; i++)
      natom_max = MAX(natom_max,onemols[i]->natoms);
  }
  memory->create(coords,natom_max,4,"pour:coords");
  memory->create(imageflags,natom_max,"pour:imageflags");

  // find max atom and molecule IDs just once

  if (idnext) find_maxid();

  // random number generator, same for all procs
  // warm up the generator 30x to avoid correlations in first-particle
  // positions if runs are repeated with consecutive seeds

  random = new RanPark(lmp,seed);
  for (int ii=0; ii < 30; ii++) random->uniform();

  // allgather arrays

  MPI_Comm_rank(world,&me);
  MPI_Comm_size(world,&nprocs);
  recvcounts = new int[nprocs];
  displs = new int[nprocs];

  // grav = gravity in distance/time^2 units
  // assume grav = -magnitude at this point, enforce in init()

  int ifix = modify->find_fix_by_style("^gravity");
  if (ifix == -1)
    error->all(FLERR,"No fix gravity defined for fix pour");
  grav = - ((FixGravity *) modify->fix[ifix])->magnitude * force->ftm2v;

  // nfreq = timesteps between insertions
  // should be time for a particle to fall from top of insertion region
  //   to bottom, taking into account that the region may be moving
  // set these 2 eqs equal to each other, solve for smallest positive t
  //   x = zhi + vz*t + 1/2 grav t^2
  //   x = zlo + rate*t
  //   gives t = [-(vz-rate) - sqrt((vz-rate)^2 - 2*grav*(zhi-zlo))] / grav
  //   where zhi-zlo > 0, grav < 0, and vz & rate can be either > 0 or < 0

  double v_relative,delta;
  if (domain->dimension == 3) {
    v_relative = vz - rate;
    delta = zhi - zlo;
  } else {
    v_relative = vy - rate;
    delta = yhi - ylo;
  }
  double t =
    (-v_relative - sqrt(v_relative*v_relative - 2.0*grav*delta)) / grav;
  nfreq = static_cast<int> (t/update->dt + 0.5);

  // 1st insertion on next timestep

  force_reneighbor = 1;
  next_reneighbor = update->ntimestep + 1;
  nfirst = next_reneighbor;
  ninserted = 0;

  // nper = # to insert each time
  // depends on specified volume fraction
  // volume = volume of insertion region
  // volume_one = volume of inserted particle (with max possible radius)
  // in 3d, insure dy >= 1, for quasi-2d simulations

  double volume,volume_one=1.0;

  molradius_max = 0.0;
  if (mode == MOLECULE) {
    for (int i = 0; i < nmol; i++)
      molradius_max = MAX(molradius_max,onemols[i]->molradius);
  }

  if (domain->dimension == 3) {
    if (region_style == 1) {
      double dy = yhi - ylo;
      if (dy < 1.0) dy = 1.0;
      volume = (xhi-xlo) * dy * (zhi-zlo);
    } else volume = MY_PI*rc*rc * (zhi-zlo);
    if (mode == MOLECULE) {
      volume_one = 4.0/3.0 * MY_PI * molradius_max*molradius_max*molradius_max;
    } else if (dstyle == ONE || dstyle == RANGE) {
      volume_one = 4.0/3.0 * MY_PI * radius_max*radius_max*radius_max;
    } else if (dstyle == POLY) {
      volume_one = 0.0;
      for (int i = 0; i < npoly; i++)
        volume_one += (4.0/3.0 * MY_PI *
          radius_poly[i]*radius_poly[i]*radius_poly[i]) * frac_poly[i];
    }
  } else {
    volume = (xhi-xlo) * (yhi-ylo);
    if (mode == MOLECULE) {
      volume_one = MY_PI * molradius_max*molradius_max;
    } else if (dstyle == ONE || dstyle == RANGE) {
      volume_one = MY_PI * radius_max*radius_max;
    } else if (dstyle == POLY) {
      volume_one = 0.0;
      for (int i = 0; i < npoly; i++)
        volume_one += (MY_PI * radius_poly[i]*radius_poly[i]) * frac_poly[i];
    }
  }

  nper = static_cast<int> (volfrac*volume/volume_one);
  if (nper == 0) error->all(FLERR,"Fix pour insertion count per timestep is 0");
  int nfinal = update->ntimestep + 1 + ((bigint)ninsert-1)/nper * nfreq;

  // print stats

  if (me == 0) {
    if (screen)
      fprintf(screen,
              "Particle insertion: %d every %d steps, %d by step %d\n",
              nper,nfreq,ninsert,nfinal);
    if (logfile)
      fprintf(logfile,
              "Particle insertion: %d every %d steps, %d by step %d\n",
              nper,nfreq,ninsert,nfinal);
  }
}

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

FixPour::~FixPour()
{
  delete random;
  delete [] molfrac;
  delete [] idrigid;
  delete [] idshake;
  delete [] radius_poly;
  delete [] frac_poly;
  memory->destroy(coords);
  memory->destroy(imageflags);
  delete [] recvcounts;
  delete [] displs;
}

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

int FixPour::setmask()
{
  int mask = 0;
  mask |= PRE_EXCHANGE;
  return mask;
}

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

void FixPour::init()
{
  if (domain->triclinic)
    error->all(FLERR,"Cannot use fix pour with triclinic box");

  // insure gravity fix (still) exists
  // for 3d must point in -z, for 2d must point in -y
  // else insertion cannot work

  int ifix = modify->find_fix_by_style("^gravity");
  if (ifix == -1)
    error->all(FLERR,"No fix gravity defined for fix pour");

  int varflag = ((FixGravity *) modify->fix[ifix])->varflag;
  if (varflag != CONSTANT)
    error->all(FLERR,"Fix gravity for fix pour must be constant");

  double xgrav = ((FixGravity *) modify->fix[ifix])->xgrav;
  double ygrav = ((FixGravity *) modify->fix[ifix])->ygrav;
  double zgrav = ((FixGravity *) modify->fix[ifix])->zgrav;

  if (domain->dimension == 3) {
    if (fabs(xgrav) > EPSILON || fabs(ygrav) > EPSILON ||
        fabs(zgrav+1.0) > EPSILON)
      error->all(FLERR,"Gravity must point in -z to use with fix pour in 3d");
  } else {
    if (fabs(xgrav) > EPSILON || fabs(ygrav+1.0) > EPSILON ||
        fabs(zgrav) > EPSILON)
      error->all(FLERR,"Gravity must point in -y to use with fix pour in 2d");
  }

  double gnew = - ((FixGravity *) modify->fix[ifix])->magnitude * force->ftm2v;
  if (gnew != grav)
    error->all(FLERR,"Gravity changed since fix pour was created");

  // if rigidflag defined, check for rigid/small fix
  // its molecule template must be same as this one

  fixrigid = nullptr;
  if (rigidflag) {
    int ifix = modify->find_fix(idrigid);
    if (ifix < 0) error->all(FLERR,"Fix pour rigid fix does not exist");
    fixrigid = modify->fix[ifix];
    int tmp;
    if (onemols != (Molecule **) fixrigid->extract("onemol",tmp))
      error->all(FLERR,
                 "Fix pour and fix rigid/small not using "
                 "same molecule template ID");
  }

  // if shakeflag defined, check for SHAKE fix
  // its molecule template must be same as this one

  fixshake = nullptr;
  if (shakeflag) {
    int ifix = modify->find_fix(idshake);
    if (ifix < 0) error->all(FLERR,"Fix pour shake fix does not exist");
    fixshake = modify->fix[ifix];
    int tmp;
    if (onemols != (Molecule **) fixshake->extract("onemol",tmp))
      error->all(FLERR,"Fix pour and fix shake not using "
                 "same molecule template ID");
  }
}

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

void FixPour::setup_pre_exchange()
{
  if (ninserted < ninsert) next_reneighbor = update->ntimestep + 1;
  else next_reneighbor = 0;
}

/* ----------------------------------------------------------------------
   perform particle insertion
------------------------------------------------------------------------- */

void FixPour::pre_exchange()
{
  int i,m,flag,nlocalprev,imol,natom;
  double r[3],rotmat[3][3],quat[4],vnew[3];
  double *newcoord;

  // just return if should not be called on this timestep

  if (next_reneighbor != update->ntimestep) return;

  // clear ghost count (and atom map) and any ghost bonus data
  //   internal to AtomVec
  // same logic as beginning of Comm::exchange()
  // do it now b/c inserting atoms will overwrite ghost atoms

  if (atom->map_style != Atom::MAP_NONE) atom->map_clear();
  atom->nghost = 0;
  atom->avec->clear_bonus();

  // find current max atom and molecule IDs on every insertion step

  if (!idnext) find_maxid();

  // nnew = # of particles (atoms or molecules) to insert this timestep

  int nnew = nper;
  if (ninserted + nnew > ninsert) nnew = ninsert - ninserted;

  // lo/hi current = z (or y) bounds of insertion region this timestep

  int dimension = domain->dimension;
  if (dimension == 3) {
    lo_current = zlo + (update->ntimestep - nfirst) * update->dt * rate;
    hi_current = zhi + (update->ntimestep - nfirst) * update->dt * rate;
  } else {
    lo_current = ylo + (update->ntimestep - nfirst) * update->dt * rate;
    hi_current = yhi + (update->ntimestep - nfirst) * update->dt * rate;
  }

  // ncount = # of my atoms that overlap the insertion region
  // nprevious = total of ncount across all procs

  int ncount = 0;
  for (i = 0; i < atom->nlocal; i++)
    if (overlap(i)) ncount++;

  int nprevious;
  MPI_Allreduce(&ncount,&nprevious,1,MPI_INT,MPI_SUM,world);

  // xmine is for my atoms
  // xnear is for atoms from all procs + atoms to be inserted

  double **xmine,**xnear;
  memory->create(xmine,ncount,4,"fix_pour:xmine");
  memory->create(xnear,nprevious+nnew*natom_max,4,"fix_pour:xnear");
  int nnear = nprevious;

  // setup for allgatherv

  int n = 4*ncount;
  MPI_Allgather(&n,1,MPI_INT,recvcounts,1,MPI_INT,world);

  displs[0] = 0;
  for (int iproc = 1; iproc < nprocs; iproc++)
    displs[iproc] = displs[iproc-1] + recvcounts[iproc-1];

  // load up xmine array

  double **x = atom->x;
  double *radius = atom->radius;

  ncount = 0;
  for (i = 0; i < atom->nlocal; i++)
    if (overlap(i)) {
      xmine[ncount][0] = x[i][0];
      xmine[ncount][1] = x[i][1];
      xmine[ncount][2] = x[i][2];
      xmine[ncount][3] = radius[i];
      ncount++;
    }

  // perform allgatherv to acquire list of nearby particles on all procs

  double *ptr = nullptr;
  if (ncount) ptr = xmine[0];
  MPI_Allgatherv(ptr,4*ncount,MPI_DOUBLE,
                 xnear[0],recvcounts,displs,MPI_DOUBLE,world);

  // insert new particles into xnear list, one by one
  // check against all nearby atoms and previously inserted ones
  // if there is an overlap then try again at same z (3d) or y (2d) coord
  // else insert by adding to xnear list
  // max = maximum # of insertion attempts for all particles
  // h = height, biased to give uniform distribution in time of insertion
  // for MOLECULE mode:
  //   coords = coords of all atoms in particle
  //   perform random rotation around center pt
  //   apply PBC so final coords are inside box
  //   store image flag modified due to PBC

  int success;
  double radtmp,delx,dely,delz,rsq,radsum,rn,h;
  double coord[3];

  double denstmp;
  double *sublo = domain->sublo;
  double *subhi = domain->subhi;

  int nsuccess = 0;
  int attempt = 0;
  int maxiter = nnew * maxattempt;

  while (nsuccess < nnew) {
    rn = random->uniform();
    h = hi_current - rn*rn * (hi_current-lo_current);
    if (mode == ATOM) radtmp = radius_sample();

    success = 0;
    while (attempt < maxiter) {
      attempt++;
      xyz_random(h,coord);

      if (mode == ATOM) {
        natom = 1;
        coords[0][0] = coord[0];
        coords[0][1] = coord[1];
        coords[0][2] = coord[2];
        coords[0][3] = radtmp;
        imageflags[0] = ((imageint) IMGMAX << IMG2BITS) |
            ((imageint) IMGMAX << IMGBITS) | IMGMAX;
      } else {
        double rng = random->uniform();
        imol = 0;
        while (rng > molfrac[imol]) imol++;
        natom = onemols[imol]->natoms;
        if (dimension == 3) {
          r[0] = random->uniform() - 0.5;
          r[1] = random->uniform() - 0.5;
          r[2] = random->uniform() - 0.5;
        } else {
          r[0] = r[1] = 0.0;
          r[2] = 1.0;
        }
        double theta = random->uniform() * MY_2PI;
        MathExtra::norm3(r);
        MathExtra::axisangle_to_quat(r,theta,quat);
        MathExtra::quat_to_mat(quat,rotmat);
        for (i = 0; i < natom; i++) {
          MathExtra::matvec(rotmat,onemols[imol]->dx[i],coords[i]);
          coords[i][0] += coord[0];
          coords[i][1] += coord[1];
          coords[i][2] += coord[2];

          // coords[3] = particle radius
          // default to 0.5, if radii not defined in Molecule
          //   same as atom->avec->create_atom(), invoked below

          if (onemols[imol]->radiusflag)
            coords[i][3] = onemols[imol]->radius[i];
          else coords[i][3] = 0.5;

          imageflags[i] = ((imageint) IMGMAX << IMG2BITS) |
            ((imageint) IMGMAX << IMGBITS) | IMGMAX;
          domain->remap(coords[i],imageflags[i]);
        }
      }

      // if any pair of atoms overlap, try again
      // use minimum_image() to account for PBC

      for (m = 0; m < natom; m++) {
        for (i = 0; i < nnear; i++) {
          delx = coords[m][0] - xnear[i][0];
          dely = coords[m][1] - xnear[i][1];
          delz = coords[m][2] - xnear[i][2];
          domain->minimum_image(delx,dely,delz);
          rsq = delx*delx + dely*dely + delz*delz;
          radsum = coords[m][3] + xnear[i][3];
          if (rsq <= radsum*radsum) break;
        }
        if (i < nnear) break;
      }
      if (m == natom) {
        success = 1;
        break;
      }
    }

    if (!success) break;

    // proceed with insertion

    nsuccess++;
    nlocalprev = atom->nlocal;

    // add all atoms in particle to xnear

    for (m = 0; m < natom; m++) {
      xnear[nnear][0] = coords[m][0];
      xnear[nnear][1] = coords[m][1];
      xnear[nnear][2] = coords[m][2];
      xnear[nnear][3] = coords[m][3];
      nnear++;
    }

    // choose random velocity for new particle
    // used for every atom in molecule
    // z velocity set to what velocity would be if particle
    //   had fallen from top of insertion region
    //   this gives continuous stream of atoms
    //   solution for v from these 2 eqs, after eliminate t:
    //     v = vz + grav*t
    //     coord[2] = hi_current + vz*t + 1/2 grav t^2

    if (dimension == 3) {
      vnew[0] = vxlo + random->uniform() * (vxhi-vxlo);
      vnew[1] = vylo + random->uniform() * (vyhi-vylo);
      vnew[2] = -sqrt(vz*vz + 2.0*grav*(coord[2]-hi_current));
    } else {
      vnew[0] = vxlo + random->uniform() * (vxhi-vxlo);
      vnew[1] = -sqrt(vy*vy + 2.0*grav*(coord[1]-hi_current));
      vnew[2] = 0.0;
    }

    // check if new atoms are in my sub-box or above it if I am highest proc
    // if so, add atom to my list via create_atom()
    // initialize additional info about the atoms
    // set group mask to "all" plus fix group

    for (m = 0; m < natom; m++) {
      if (mode == ATOM)
        denstmp = density_lo + random->uniform() * (density_hi-density_lo);
      newcoord = coords[m];

      flag = 0;
      if (newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] &&
          newcoord[1] >= sublo[1] && newcoord[1] < subhi[1] &&
          newcoord[2] >= sublo[2] && newcoord[2] < subhi[2]) flag = 1;
      else if (dimension == 3 && newcoord[2] >= domain->boxhi[2]) {
        if (comm->layout != Comm::LAYOUT_TILED) {
          if (comm->myloc[2] == comm->procgrid[2]-1 &&
              newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] &&
              newcoord[1] >= sublo[1] && newcoord[1] < subhi[1]) flag = 1;
        } else {
          if (comm->mysplit[2][1] == 1.0 &&
              newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] &&
              newcoord[1] >= sublo[1] && newcoord[1] < subhi[1]) flag = 1;
        }
      } else if (dimension == 2 && newcoord[1] >= domain->boxhi[1]) {
        if (comm->layout != Comm::LAYOUT_TILED) {
          if (comm->myloc[1] == comm->procgrid[1]-1 &&
              newcoord[0] >= sublo[0] && newcoord[0] < subhi[0]) flag = 1;
        } else {
          if (comm->mysplit[1][1] == 1.0 &&
              newcoord[0] >= sublo[0] && newcoord[0] < subhi[0]) flag = 1;
        }
      }

      if (flag) {
        if (mode == ATOM) atom->avec->create_atom(ntype,coords[m]);
        else atom->avec->create_atom(ntype+onemols[imol]->type[m],coords[m]);
        int n = atom->nlocal - 1;
        atom->tag[n] = maxtag_all + m+1;
        if (mode == MOLECULE) {
          if (atom->molecule_flag) {
            if (onemols[imol]->moleculeflag) {
              atom->molecule[n] = maxmol_all + onemols[imol]->molecule[m];
            } else {
              atom->molecule[n] = maxmol_all+1;
            }
          }
          if (atom->molecular == Atom::TEMPLATE) {
            atom->molindex[n] = 0;
            atom->molatom[n] = m;
          }
        }
        atom->mask[n] = 1 | groupbit;
        atom->image[n] = imageflags[m];
        atom->v[n][0] = vnew[0];
        atom->v[n][1] = vnew[1];
        atom->v[n][2] = vnew[2];
        if (mode == ATOM) {
          radtmp = newcoord[3];
          atom->radius[n] = radtmp;
          atom->rmass[n] = 4.0*MY_PI/3.0 * radtmp*radtmp*radtmp * denstmp;
        } else {
          onemols[imol]->quat_external = quat;
          atom->add_molecule_atom(onemols[imol],m,n,maxtag_all);
        }

        modify->create_attribute(n);
      }
    }

    // FixRigidSmall::set_molecule stores rigid body attributes
    //   coord is new position of geometric center of mol, not COM
    // FixShake::set_molecule stores shake info for molecule

    if (rigidflag)
      fixrigid->set_molecule(nlocalprev,maxtag_all,imol,coord,vnew,quat);
    else if (shakeflag)
      fixshake->set_molecule(nlocalprev,maxtag_all,imol,coord,vnew,quat);

    maxtag_all += natom;
    if (mode == MOLECULE && atom->molecule_flag) {
      if (onemols[imol]->moleculeflag) {
        maxmol_all += onemols[imol]->nmolecules;
      } else {
        maxmol_all++;
      }
    }
  }

  // warn if not successful with all insertions b/c too many attempts

  int ninserted_atoms = nnear - nprevious;
  int ninserted_mols = ninserted_atoms / natom;
  ninserted += ninserted_mols;
  if (ninserted_mols < nnew && me == 0)
    error->warning(FLERR,"Less insertions than requested");

  // reset global natoms,nbonds,etc
  // increment maxtag_all and maxmol_all if necessary
  // if global map exists, reset it now instead of waiting for comm
  //   since other pre-exchange fixes may use it
  //   invoke map_init() b/c atom count has grown

  if (ninserted_atoms) {
    atom->natoms += ninserted_atoms;
    if (atom->natoms < 0)
      error->all(FLERR,"Too many total atoms");
    if (mode == MOLECULE) {
      atom->nbonds += (bigint)onemols[imol]->nbonds * ninserted_mols;
      atom->nangles += (bigint)onemols[imol]->nangles * ninserted_mols;
      atom->ndihedrals += (bigint)onemols[imol]->ndihedrals * ninserted_mols;
      atom->nimpropers += (bigint)onemols[imol]->nimpropers * ninserted_mols;
    }
    if (maxtag_all >= MAXTAGINT)
      error->all(FLERR,"New atom IDs exceed maximum allowed ID");
  }

  // rebuild atom map

  if (atom->map_style != Atom::MAP_NONE) {
    if (success) atom->map_init();
    atom->map_set();
  }

  // free local memory

  memory->destroy(xmine);
  memory->destroy(xnear);

  // next timestep to insert

  if (ninserted < ninsert) next_reneighbor += nfreq;
  else next_reneighbor = 0;
}

/* ----------------------------------------------------------------------
   maxtag_all = current max atom ID for all atoms
   maxmol_all = current max molecule ID for all atoms
------------------------------------------------------------------------- */

void FixPour::find_maxid()
{
  tagint *tag = atom->tag;
  tagint *molecule = atom->molecule;
  int nlocal = atom->nlocal;

  tagint max = 0;
  for (int i = 0; i < nlocal; i++) max = MAX(max,tag[i]);
  MPI_Allreduce(&max,&maxtag_all,1,MPI_LMP_TAGINT,MPI_MAX,world);

  if (mode == MOLECULE && molecule) {
    max = 0;
    for (int i = 0; i < nlocal; i++) max = MAX(max,molecule[i]);
    MPI_Allreduce(&max,&maxmol_all,1,MPI_LMP_TAGINT,MPI_MAX,world);
  }
}

/* ----------------------------------------------------------------------
   check if particle i could overlap with a particle inserted into region
   return 1 if yes, 0 if no
   for ATOM mode, use delta with maximum size for inserted atoms
   for MOLECULE mode, use delta with max radius of inserted molecules
   if ignore line/tri set, ignore line or tri particles
   account for PBC in overlap decision via outside() and minimum_image()
------------------------------------------------------------------------- */

int FixPour::overlap(int i)
{
  double delta;

  if (ignoreflag) {
    if (ignoreline && atom->line[i] >= 0) return 0;
    if (ignoretri && atom->tri[i] >= 0) return 0;
  }

  if (mode == ATOM) delta = atom->radius[i] + radius_max;
  else delta = atom->radius[i] + molradius_max;

  double *x = atom->x[i];

  if (domain->dimension == 3) {
    if (region_style == 1) {
      if (outside(0,x[0],xlo-delta,xhi+delta)) return 0;
      if (outside(1,x[1],ylo-delta,yhi+delta)) return 0;
      if (outside(2,x[2],lo_current-delta,hi_current+delta)) return 0;
    } else {
      double delx = x[0] - xc;
      double dely = x[1] - yc;
      double delz = 0.0;
      domain->minimum_image(delx,dely,delz);
      double rsq = delx*delx + dely*dely;
      double r = rc + delta;
      if (rsq > r*r) return 0;
      if (outside(2,x[2],lo_current-delta,hi_current+delta)) return 0;
    }
  } else {
    if (outside(0,x[0],xlo-delta,xhi+delta)) return 0;
    if (outside(1,x[1],lo_current-delta,hi_current+delta)) return 0;
  }

  return 1;
}

/* ----------------------------------------------------------------------
   check if value is inside/outside lo/hi bounds in dimension
   account for PBC if needed
   return 1 if value is outside, 0 if inside
------------------------------------------------------------------------- */

bool FixPour::outside(int dim, double value, double lo, double hi)
{
  double boxlo = domain->boxlo[dim];
  double boxhi = domain->boxhi[dim];

  // check for value inside/outside range, ignoring periodicity
  // if inside or dim is non-periodic, only this test is needed

  bool outside_range = (value < lo || value > hi);
  if (!outside_range || !domain->periodicity[dim]) return outside_range;

  // for periodic dimension:
  // must perform additional tests if range wraps around the periodic box

  bool outside_pbc_range = true;

  if ((lo < boxlo && hi > boxhi) || (hi - lo) > domain->prd[dim]) {
    // value is always inside
    outside_pbc_range = false;
  } else if (lo < boxlo) {
    // lower boundary crosses periodic boundary
    outside_pbc_range = (value > hi && value < lo + domain->prd[dim]);
  } else if (hi > boxhi) {
    // upper boundary crosses periodic boundary
    outside_pbc_range = (value < lo && value > hi - domain->prd[dim]);
  }

  return outside_pbc_range;
}

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

void FixPour::xyz_random(double h, double *coord)
{
  if (domain->dimension == 3) {
    if (region_style == 1) {
      coord[0] = xlo + random->uniform() * (xhi-xlo);
      coord[1] = ylo + random->uniform() * (yhi-ylo);
      coord[2] = h;
    } else {
      double r1,r2;
      while (1) {
        r1 = random->uniform() - 0.5;
        r2 = random->uniform() - 0.5;
        if (r1*r1 + r2*r2 < 0.25) break;
      }
      coord[0] = xc + 2.0*r1*rc;
      coord[1] = yc + 2.0*r2*rc;
      coord[2] = h;
    }
  } else {
    coord[0] = xlo + random->uniform() * (xhi-xlo);
    coord[1] = h;
    coord[2] = 0.0;
  }
}

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

double FixPour::radius_sample()
{
  if (dstyle == ONE) return radius_one;
  if (dstyle == RANGE) return radius_lo +
                         random->uniform()*(radius_hi-radius_lo);

  double value = random->uniform();

  int i = 0;
  double sum = 0.0;
  while (sum < value) {
    sum += frac_poly[i];
    i++;
  }
  return radius_poly[i-1];
}

/* ----------------------------------------------------------------------
   parse optional parameters at end of input line
------------------------------------------------------------------------- */

void FixPour::options(int narg, char **arg)
{
  // defaults

  iregion = -1;
  mode = ATOM;
  molfrac = nullptr;
  rigidflag = 0;
  idrigid = nullptr;
  shakeflag = 0;
  idshake = nullptr;
  idnext = 0;
  ignoreflag = ignoreline = ignoretri = 0;
  dstyle = ONE;
  radius_max = radius_one = 0.5;
  radius_poly = frac_poly = nullptr;
  density_lo = density_hi = 1.0;
  volfrac = 0.25;
  maxattempt = 50;
  rate = 0.0;
  vxlo = vxhi = vylo = vyhi = vy = vz = 0.0;

  int iarg = 0;
  while (iarg < narg) {
    if (strcmp(arg[iarg],"region") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal fix pour command");
      iregion = domain->find_region(arg[iarg+1]);
      if (iregion == -1) error->all(FLERR,"Fix pour region ID does not exist");
      iarg += 2;

    } else if (strcmp(arg[iarg],"mol") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal fix pour command");
      int imol = atom->find_molecule(arg[iarg+1]);
      if (imol == -1)
        error->all(FLERR,"Molecule template ID for fix pour does not exist");
      mode = MOLECULE;
      onemols = &atom->molecules[imol];
      nmol = onemols[0]->nset;
      delete [] molfrac;
      molfrac = new double[nmol];
      molfrac[0] = 1.0/nmol;
      for (int i = 1; i < nmol-1; i++) molfrac[i] = molfrac[i-1] + 1.0/nmol;
      molfrac[nmol-1] = 1.0;
      iarg += 2;
    } else if (strcmp(arg[iarg],"molfrac") == 0) {
      if (mode != MOLECULE) error->all(FLERR,"Illegal fix pour command");
      if (iarg+nmol+1 > narg) error->all(FLERR,"Illegal fix pour command");
      molfrac[0] = utils::numeric(FLERR,arg[iarg+1],false,lmp);
      for (int i = 1; i < nmol; i++)
        molfrac[i] = molfrac[i-1] + utils::numeric(FLERR,arg[iarg+i+1],false,lmp);
      if (molfrac[nmol-1] < 1.0-EPSILON || molfrac[nmol-1] > 1.0+EPSILON)
        error->all(FLERR,"Illegal fix pour command");
      molfrac[nmol-1] = 1.0;
      iarg += nmol+1;

    } else if (strcmp(arg[iarg],"rigid") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal fix pour command");
      delete [] idrigid;
      idrigid = utils::strdup(arg[iarg+1]);
      rigidflag = 1;
      iarg += 2;
    } else if (strcmp(arg[iarg],"shake") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal fix pour command");
      delete [] idshake;
      idshake = utils::strdup(arg[iarg+1]);
      shakeflag = 1;
      iarg += 2;

    } else if (strcmp(arg[iarg],"id") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal fix pour command");
      if (strcmp(arg[iarg+1],"max") == 0) idnext = 0;
      else if (strcmp(arg[iarg+1],"next") == 0) idnext = 1;
      else error->all(FLERR,"Illegal fix pour command");
      iarg += 2;

    } else if (strcmp(arg[iarg],"ignore") == 0) {
      if (atom->line_flag) ignoreline = 1;
      if (atom->tri_flag) ignoretri = 1;
      if (ignoreline || ignoretri) ignoreflag = 1;
      iarg += 1;

    } else if (strcmp(arg[iarg],"diam") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal fix pour command");
      if (strcmp(arg[iarg+1],"one") == 0) {
        if (iarg+3 > narg) error->all(FLERR,"Illegal fix pour command");
        dstyle = ONE;
        radius_one = 0.5 * utils::numeric(FLERR,arg[iarg+2],false,lmp);
        radius_max = radius_one;
        iarg += 3;
      } else if (strcmp(arg[iarg+1],"range") == 0) {
        if (iarg+4 > narg) error->all(FLERR,"Illegal fix pour command");
        dstyle = RANGE;
        radius_lo = 0.5 * utils::numeric(FLERR,arg[iarg+2],false,lmp);
        radius_hi = 0.5 * utils::numeric(FLERR,arg[iarg+3],false,lmp);
        if (radius_lo > radius_hi) error->all(FLERR,"Illegal fix pour command");
        radius_max = radius_hi;
        iarg += 4;
      } else if (strcmp(arg[iarg+1],"poly") == 0) {
        if (iarg+3 > narg) error->all(FLERR,"Illegal fix pour command");
        dstyle = POLY;
        npoly = utils::inumeric(FLERR,arg[iarg+2],false,lmp);
        if (npoly <= 0) error->all(FLERR,"Illegal fix pour command");
        if (iarg+3 + 2*npoly > narg)
          error->all(FLERR,"Illegal fix pour command");
        radius_poly = new double[npoly];
        frac_poly = new double[npoly];
        iarg += 3;
        radius_max = 0.0;
        for (int i = 0; i < npoly; i++) {
          radius_poly[i] = 0.5 * utils::numeric(FLERR,arg[iarg++],false,lmp);
          frac_poly[i] = utils::numeric(FLERR,arg[iarg++],false,lmp);
          if (radius_poly[i] <= 0.0 || frac_poly[i] < 0.0)
            error->all(FLERR,"Illegal fix pour command");
          radius_max = MAX(radius_max,radius_poly[i]);
        }
        double sum = 0.0;
        for (int i = 0; i < npoly; i++) sum += frac_poly[i];
        if (fabs(sum - 1.0) > SMALL)
          error->all(FLERR,"Fix pour polydisperse fractions do not sum to 1.0");
      } else error->all(FLERR,"Illegal fix pour command");

    } else if (strcmp(arg[iarg],"dens") == 0) {
      if (iarg+3 > narg) error->all(FLERR,"Illegal fix pour command");
      density_lo = utils::numeric(FLERR,arg[iarg+1],false,lmp);
      density_hi = utils::numeric(FLERR,arg[iarg+2],false,lmp);
      if (density_lo > density_hi) error->all(FLERR,"Illegal fix pour command");
      iarg += 3;
    } else if (strcmp(arg[iarg],"vol") == 0) {
      if (iarg+3 > narg) error->all(FLERR,"Illegal fix pour command");
      volfrac = utils::numeric(FLERR,arg[iarg+1],false,lmp);
      maxattempt = utils::inumeric(FLERR,arg[iarg+2],false,lmp);
      iarg += 3;
    } else if (strcmp(arg[iarg],"rate") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal fix pour command");
      rate = utils::numeric(FLERR,arg[iarg+1],false,lmp);
      iarg += 2;
    } else if (strcmp(arg[iarg],"vel") == 0) {
      if (domain->dimension == 3) {
        if (iarg+6 > narg) error->all(FLERR,"Illegal fix pour command");
        vxlo = utils::numeric(FLERR,arg[iarg+1],false,lmp);
        vxhi = utils::numeric(FLERR,arg[iarg+2],false,lmp);
        vylo = utils::numeric(FLERR,arg[iarg+3],false,lmp);
        vyhi = utils::numeric(FLERR,arg[iarg+4],false,lmp);
        if (vxlo > vxhi || vylo > vyhi)
          error->all(FLERR,"Illegal fix pour command");
        vz = utils::numeric(FLERR,arg[iarg+5],false,lmp);
        iarg += 6;
      } else {
        if (iarg+4 > narg) error->all(FLERR,"Illegal fix pour command");
        vxlo = utils::numeric(FLERR,arg[iarg+1],false,lmp);
        vxhi = utils::numeric(FLERR,arg[iarg+2],false,lmp);
        vy = utils::numeric(FLERR,arg[iarg+3],false,lmp);
        vz = 0.0;
        if (vxlo > vxhi) error->all(FLERR,"Illegal fix pour command");
        iarg += 4;
      }
    } else error->all(FLERR,"Illegal fix pour command");
  }
}

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

void FixPour::reset_dt()
{
  error->all(FLERR,"Cannot change timestep with fix pour");
}

/* ----------------------------------------------------------------------
   extract particle radius for atom type = itype
------------------------------------------------------------------------- */

void *FixPour::extract(const char *str, int &itype)
{
  if (strcmp(str,"radius") == 0) {
    if (mode == ATOM) {
      if (itype == ntype) oneradius = radius_max;
      else oneradius = 0.0;

    } else {

      // loop over onemols molecules
      // skip a molecule with no atoms as large as itype

      oneradius = 0.0;
      for (int i = 0; i < nmol; i++) {
        if (itype > ntype+onemols[i]->ntypes) continue;
        double *radius = onemols[i]->radius;
        int *type = onemols[i]->type;
        int natoms = onemols[i]->natoms;

        // check radii of atoms in Molecule with matching types
        // default to 0.5, if radii not defined in Molecule
        //   same as atom->avec->create_atom(), invoked in pre_exchange()

        for (int i = 0; i < natoms; i++)
          if (type[i]+ntype == itype) {
            if (radius) oneradius = MAX(oneradius,radius[i]);
            else oneradius = MAX(oneradius,0.5);
          }
      }
    }
    itype = 0;
    return &oneradius;
  }
  return nullptr;
}
