// clang-format off
/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   https://www.lammps.org/, Sandia National Laboratories
   LAMMPS development team: developers@lammps.org

   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: Julien Tranchida (SNL)

   Please cite the related publication:
   Bessarab, P. F., Uzdin, V. M., & Jonsson, H. (2015).
   Method for finding mechanism and activation energy of magnetic transitions,
   applied to skyrmion and antivortex annihilation.
   Computer Physics Communications, 196, 335-347.
------------------------------------------------------------------------- */

#include "neb_spin.h"

#include "atom.h"
#include "citeme.h"
#include "comm.h"
#include "domain.h"
#include "error.h"
#include "finish.h"
#include "fix.h"
#include "fix_neb_spin.h"
#include "memory.h"
#include "min.h"
#include "modify.h"
#include "output.h"
#include "thermo.h"
#include "timer.h"
#include "tokenizer.h"
#include "universe.h"
#include "update.h"

#include <cmath>
#include <cstring>
#include <exception>

using namespace LAMMPS_NS;

static const char cite_neb_spin[] =
  "neb/spin command: doi:10.1016/j.cpc.2015.07.001\n\n"
  "@article{bessarab2015method,\n"
  "title={Method for Finding Mechanism and Activation Energy of\n"
  "  Magnetic Transitions, Applied to Skyrmion and Antivortex\n"
  "  Annihilation},\n"
  "author={Bessarab, P. F. and Uzdin, V. M. and J{\'o}nsson, H.},\n"
  "journal={Computer Physics Communications},\n"
  "volume={196},\n"
  "pages={335--347},\n"
  "year={2015},\n"
  "publisher={Elsevier}\n"
  "doi={10.1016/j.cpc.2015.07.001}\n"
  "}\n\n";

static constexpr int MAXLINE = 256;
static constexpr int CHUNK = 1024;

// 8 attributes: tag, spin norm, position (3), spin direction (3)
static constexpr int ATTRIBUTE_PERLINE = 8;

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

NEBSpin::NEBSpin(LAMMPS *lmp) : Command(lmp), fp(nullptr) {
  if (lmp->citeme) lmp->citeme->add(cite_neb_spin);
}

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

NEBSpin::~NEBSpin()
{
  if (roots != MPI_COMM_NULL) MPI_Comm_free(&roots);
  memory->destroy(all);
  delete[] rdist;
  if (fp) {
    if (compressed) platform::pclose(fp);
    else fclose(fp);
  }
}

/* ----------------------------------------------------------------------
   perform NEBSpin on multiple replicas
------------------------------------------------------------------------- */

void NEBSpin::command(int narg, char **arg)
{
  if (domain->box_exist == 0)
    error->all(FLERR,"NEBSpin command before simulation box is defined" + utils::errorurl(33));

  if (narg < 6) error->universe_all(FLERR,"Illegal NEBSpin command");

  etol = utils::numeric(FLERR,arg[0],false,lmp);
  ttol = utils::numeric(FLERR,arg[1],false,lmp);
  n1steps = utils::inumeric(FLERR,arg[2],false,lmp);
  n2steps = utils::inumeric(FLERR,arg[3],false,lmp);
  nevery = utils::inumeric(FLERR,arg[4],false,lmp);

  // error checks

  if (etol < 0.0) error->all(FLERR,"Illegal NEBSpin command");
  if (ttol < 0.0) error->all(FLERR,"Illegal NEBSpin command");
  if (nevery <= 0) error->universe_all(FLERR,"Illegal NEBSpin command");
  if (n1steps % nevery || n2steps % nevery)
    error->universe_all(FLERR,"Illegal NEBSpin command");

  // replica info

  nreplica = universe->nworlds;
  ireplica = universe->iworld;
  me_universe = universe->me;
  uworld = universe->uworld;
  MPI_Comm_rank(world,&me);

  // check metal units and spin atom/style

  if (!atom->sp_flag)
    error->all(FLERR,"neb/spin requires atom/spin style");
  if (strcmp(update->unit_style,"metal") != 0)
    error->all(FLERR,"neb/spin simulation requires metal unit style");

  // error checks

  if (nreplica == 1) error->all(FLERR,"Cannot use NEBSpin with a single replica");
  if (atom->map_style == Atom::MAP_NONE)
    error->all(FLERR,"Cannot use NEBSpin unless atom map exists");

  // process file-style setting to setup initial configs for all replicas

  if (strcmp(arg[5],"final") == 0) {
    if (narg != 7 && narg !=8) error->universe_all(FLERR,"Illegal NEBSpin command");
    inpfile = arg[6];
    readfile(inpfile,0);
  } else if (strcmp(arg[5],"each") == 0) {
    if (narg != 7 && narg !=8) error->universe_all(FLERR,"Illegal NEBSpin command");
    inpfile = arg[6];
    readfile(inpfile,1);
  } else if (strcmp(arg[5],"none") == 0) {
    if (narg != 6 && narg !=7) error->universe_all(FLERR,"Illegal NEBSpin command");
  } else error->universe_all(FLERR,"Illegal NEBSpin command");

  verbose=false;
  if (strcmp(arg[narg-1],"verbose") == 0) verbose=true;
  // run the NEB calculation

  run();
}

/* ----------------------------------------------------------------------
   run NEBSpin on multiple replicas
------------------------------------------------------------------------- */

void NEBSpin::run()
{
  // create MPI communicator for root proc from each world

  int color;
  if (me == 0)
    color = 0;
  else
    color = MPI_UNDEFINED;
  MPI_Comm_split(uworld,color,0,&roots);

  // search for neb_spin fix, allocate it

  auto fixes = modify->get_fix_by_style("^neb/spin");
  if (fixes.size() != 1)
    error->all(FLERR,"NEBSpin requires use of exactly one fix neb/spin instance");

  fneb = dynamic_cast<FixNEBSpin *>(fixes[0]);
  if (verbose) numall =7;
  else  numall = 4;
  memory->create(all,nreplica,numall,"neb:all");
  rdist = new double[nreplica];

  // initialize LAMMPS

  update->whichflag = 2;
  update->etol = etol;
  update->ftol = ttol;          // update->ftol is a torque tolerance
  update->multireplica = 1;

  lmp->init();

  // check if correct minimizer is setup

  if (update->minimize->searchflag)
    error->all(FLERR,"NEBSpin requires damped dynamics minimizer");
  if (!utils::strmatch(update->minimize_style,"^spin"))
    error->all(FLERR,"NEBSpin requires a spin minimizer");

  // setup regular NEBSpin minimization

  FILE *uscreen = universe->uscreen;
  FILE *ulogfile = universe->ulogfile;

  if (me_universe == 0 && uscreen)
    fprintf(uscreen,"Setting up regular NEBSpin ...\n");

  update->beginstep = update->firststep = update->ntimestep;
  update->endstep = update->laststep = update->firststep + n1steps;
  update->nsteps = n1steps;
  update->max_eval = n1steps;
  if (update->laststep < 0)
    error->all(FLERR,"Too many timesteps for NEBSpin");

  update->minimize->setup();

  if (me_universe == 0) {
    if (uscreen) {
      if (verbose) {
        fprintf(uscreen,"Step MaxReplicaTorque MaxAtomTorque "
                "GradV0 GradV1 GradVc EBF EBR RDT "
                "RD1 PE1 RD2 PE2 ... RDN PEN "
                "GradV0dottan DN0 ... GradVNdottan DNN\n");
      } else {
        fprintf(uscreen,"Step MaxReplicaTorque MaxAtomTorque "
                "GradV0 GradV1 GradVc EBF EBR RDT RD1 PE1 RD2 PE2 ... "
                "RDN PEN\n");
      }
    }

    if (ulogfile) {
      if (verbose) {
        fprintf(ulogfile,"Step MaxReplicaTorque MaxAtomTorque "
            "GradV0 GradV1 GradVc EBF EBR RDT "
            "RD1 PE1 RD2 PE2 ... RDN PEN "
            "GradV0dottan DN0 ... GradVNdottan DNN\n");
      } else {
        fprintf(ulogfile,"Step MaxReplicaTorque MaxAtomTorque "
                "GradV0 GradV1 GradVc EBF EBR RDT RD1 PE1 RD2 PE2 ... "
                "RDN PEN\n");
      }
    }
  }
  print_status();

  // perform regular NEBSpin for n1steps or until replicas converge
  // retrieve PE values from fix NEBSpin and print every nevery iterations
  // break out of while loop early if converged
  // damped dynamic min styles ensure all replicas converge together

  timer->init();
  timer->barrier_start();

  // if (ireplica != 0 && ireplica != nreplica -1)

  while (update->minimize->niter < n1steps) {
    update->minimize->run(nevery);
    print_status();
    if (update->minimize->stop_condition) break;
  }

  timer->barrier_stop();

  update->minimize->cleanup();

  Finish finish(lmp);
  finish.end(1);

  // switch fix NEBSpin to climbing mode
  // top = replica that becomes hill climber

  double vmax = all[0][0];
  int top = 0;
  for (int m = 1; m < nreplica; m++)
    if (vmax < all[m][0]) {
      vmax = all[m][0];
      top = m;
    }

  // setup climbing NEBSpin minimization
  // must reinitialize minimizer so it re-creates its fix MINIMIZE

  if (me_universe == 0 && uscreen)
    fprintf(uscreen,"Setting up climbing ...\n");

  if (me_universe == 0) {
    if (uscreen)
      fprintf(uscreen,"Climbing replica = %d\n",top+1);
    if (ulogfile)
      fprintf(ulogfile,"Climbing replica = %d\n",top+1);
  }

  update->beginstep = update->firststep = update->ntimestep;
  update->endstep = update->laststep = update->firststep + n2steps;
  update->nsteps = n2steps;
  update->max_eval = n2steps;
  if (update->laststep < 0)
    error->all(FLERR,"Too many timesteps");

  update->minimize->init();
  fneb->rclimber = top;
  update->minimize->setup();

  if (me_universe == 0) {
    if (uscreen) {
      if (verbose) {
        fprintf(uscreen,"Step MaxReplicaTorque MaxAtomTorque "
                "GradV0 GradV1 GradVc EBF EBR RDT "
                "RD1 PE1 RD2 PE2 ... RDN PEN "
                "GradV0dottan DN0 ... GradVNdottan DNN\n");
      } else {
        fprintf(uscreen,"Step MaxReplicaTorque MaxAtomTorque "
                "GradV0 GradV1 GradVc "
                "EBF EBR RDT "
                "RD1 PE1 RD2 PE2 ... RDN PEN\n");
      }
    }
    if (ulogfile) {
      if (verbose) {
        fprintf(ulogfile,"Step MaxReplicaTorque MaxAtomTorque "
            "GradV0 GradV1 GradVc EBF EBR RDT "
            "RD1 PE1 RD2 PE2 ... RDN PEN "
            "GradV0dottan DN0 ... GradVNdottan DNN\n");
      } else {
        fprintf(ulogfile,"Step MaxReplicaTorque MaxAtomTorque "
                "GradV0 GradV1 GradVc "
                "EBF EBR RDT "
                "RD1 PE1 RD2 PE2 ... RDN PEN\n");
      }
    }
  }
  print_status();

  // perform climbing NEBSpin for n2steps or until replicas converge
  // retrieve PE values from fix NEBSpin and print every nevery iterations
  // break induced if converged
  // damped dynamic min styles ensure all replicas converge together

  timer->init();
  timer->barrier_start();

  while (update->minimize->niter < n2steps) {
    update->minimize->run(nevery);
    print_status();
    if (update->minimize->stop_condition) break;
  }

  timer->barrier_stop();

  update->minimize->cleanup();

  finish.end(1);

  update->whichflag = 0;
  update->multireplica = 0;
  update->firststep = update->laststep = 0;
  update->beginstep = update->endstep = 0;
}

/* ----------------------------------------------------------------------
   read initial config atom coords from file
   flag = 0
   only first replica opens file and reads it
   first replica bcasts lines to all replicas
   final replica stores coords
   intermediate replicas interpolate from coords
   new coord = replica fraction between current and final state
   initial replica does nothing
   flag = 1
   each replica (except first) opens file and reads it
   each replica stores coords
   initial replica does nothing
------------------------------------------------------------------------- */

void NEBSpin::readfile(char *file, int flag)
{
  int i,nchunk,eofflag,nlines;
  tagint tag;
  char *eof,*start,*next,*buf;
  char line[MAXLINE] = {'\0'};
  double musp,xx,yy,zz,spx,spy,spz;

  if (me_universe == 0 && universe->uscreen)
    fprintf(universe->uscreen,"Reading NEBSpin coordinate file(s) ...\n");

  // flag = 0, universe root reads header of file, bcast to universe
  // flag = 1, each replica's root reads header of file, bcast to world
  //   but explicitly skip first replica

  if (flag == 0) {
    if (me_universe == 0) {
      open(file);
      while (true) {
        eof = fgets(line,MAXLINE,fp);
        if (eof == nullptr) error->one(FLERR,"Unexpected end of neb/spin file");
        start = &line[strspn(line," \t\n\v\f\r")];
        if (*start != '\0' && *start != '#') break;
      }
      int rv = sscanf(line,"%d",&nlines);
      if (rv != 1) nlines = -1;
    }
    MPI_Bcast(&nlines,1,MPI_INT,0,uworld);
    if (nlines < 0)
      error->universe_all(FLERR,"Incorrectly formatted NEB file");
  } else {
    if (me == 0) {
      if (ireplica) {
        open(file);
        while (true) {
          eof = fgets(line,MAXLINE,fp);
          if (eof == nullptr) error->one(FLERR,"Unexpected end of neb/spin file");
          start = &line[strspn(line," \t\n\v\f\r")];
          if (*start != '\0' && *start != '#') break;
        }
        int rv = sscanf(line,"%d",&nlines);
        if (rv != 1) nlines = -1;
      } else nlines = 0;
    }
    MPI_Bcast(&nlines,1,MPI_INT,0,world);
    if (nlines < 0)
      error->all(FLERR,"Incorrectly formatted NEB file");
  }

  auto *buffer = new char[CHUNK*MAXLINE];
  double fraction = ireplica/(nreplica-1.0);
  double **x = atom->x;
  double **sp = atom->sp;
  double spinit[3],spfinal[3];
  int nlocal = atom->nlocal;

  // loop over chunks of lines read from file
  // two versions of read_lines_from_file() for world vs universe bcast
  // count # of atom coords changed so can check for invalid atom IDs in file

  int ncount=0, nread=0, temp_flag=0, rot_flag=0;
  while (nread < nlines) {
    nchunk = MIN(nlines-nread,CHUNK);
    if (flag == 0)
      eofflag = utils::read_lines_from_file(fp,nchunk,MAXLINE,buffer,
                                            universe->me,universe->uworld);
    else
      eofflag = utils::read_lines_from_file(fp,nchunk,MAXLINE,buffer,me,world);
    if (eofflag) error->all(FLERR,"Unexpected end of neb/spin file");

    buf = buffer;
    next = strchr(buf,'\n');
    *next = '\0';
    int nwords = utils::count_words(utils::trim_comment(buf));
    *next = '\n';

    if (nwords != ATTRIBUTE_PERLINE)
      error->all(FLERR,"Incorrect atom format in neb/spin file");

    // loop over lines of atom coords
    // tokenize the line into values

    for (i = 0; i < nchunk; i++) {
      next = strchr(buf,'\n');
      *next = '\0';

      try {
        ValueTokenizer values(buf," \t\n\r\f");

        // adjust spin coord based on replica fraction
        // for flag = 0, interpolate for intermediate and final replicas
        // for flag = 1, replace existing coord with new coord
        // ignore image flags of final x
        // for interpolation:
        //   new x is displacement from old x via minimum image convention
        //   if final x is across periodic boundary:
        //     new x may be outside box
        //     will be remapped back into box when simulation starts
        //     its image flags will then be adjusted

        tag = values.next_tagint();
        int m = atom->map(tag);
        if (m >= 0 && m < nlocal) {
          ncount++;

          musp = values.next_double();
          xx = values.next_double();
          yy = values.next_double();
          zz = values.next_double();
          spx = values.next_double();
          spy = values.next_double();
          spz = values.next_double();

          if (flag == 0) {

            spinit[0] = sp[m][0];
            spinit[1] = sp[m][1];
            spinit[2] = sp[m][2];
            spfinal[0] = spx;
            spfinal[1] = spy;
            spfinal[2] = spz;

            // interpolate intermediate spin states

            sp[m][3] = musp;
            if (fraction == 0.0) {
              sp[m][0] = spinit[0];
              sp[m][1] = spinit[1];
              sp[m][2] = spinit[2];
            } else if (fraction == 1.0) {
              sp[m][0] = spfinal[0];
              sp[m][1] = spfinal[1];
              sp[m][2] = spfinal[2];
            } else {
              temp_flag = initial_rotation(spinit,spfinal,fraction);
              rot_flag = MAX(temp_flag,rot_flag);
              sp[m][0] = spfinal[0];
              sp[m][1] = spfinal[1];
              sp[m][2] = spfinal[2];
            }
          } else {
            sp[m][3] = musp;
            x[m][0] = xx;
            x[m][1] = yy;
            x[m][2] = zz;
            sp[m][0] = spx;
            sp[m][1] = spy;
            sp[m][2] = spz;
          }
        }
      } catch (std::exception &e) {
        error->universe_one(FLERR,"Incorrectly formatted NEB file: " + std::string(e.what()));
      }
      buf = next + 1;
    }
    nread += nchunk;
  }

  // warning message if one or more couples (spi,spf) were aligned
  // this breaks Rodrigues' formula, and an arbitrary rotation
  // vector has to be chosen

  if ((rot_flag > 0) && (comm->me == 0))
    error->warning(FLERR,"arbitrary initial rotation of one or more spin(s)");

  // check that all atom IDs in file were found by a proc

  if (flag == 0) {
    int ntotal;
    MPI_Allreduce(&ncount,&ntotal,1,MPI_INT,MPI_SUM,uworld);
    if (ntotal != nreplica*nlines)
      error->universe_all(FLERR,"Invalid atom IDs in neb/spin file");
  } else {
    int ntotal;
    MPI_Allreduce(&ncount,&ntotal,1,MPI_INT,MPI_SUM,world);
    if (ntotal != nlines)
      error->all(FLERR,"Invalid atom IDs in neb/spin file");
  }

  // clean up
  delete[] buffer;

  if (flag == 0) {
    if (me_universe == 0) {
      if (compressed) platform::pclose(fp);
      else fclose(fp);
    }
  } else {
    if (me == 0 && ireplica) {
      if (compressed) platform::pclose(fp);
      else fclose(fp);
    }
  }
  fp = nullptr;
}

/* ----------------------------------------------------------------------
   initial configuration of intermediate spins using Rodrigues' formula
   interpolates between initial (spi) and final (stored in sploc)
------------------------------------------------------------------------- */

int NEBSpin::initial_rotation(double *spi, double *sploc, double fraction)
{

  // no interpolation for initial and final replica

  if (fraction == 0.0 || fraction == 1.0) return 0;

  int rot_flag = 0;
  double kx,ky,kz;
  double spix,spiy,spiz,spfx,spfy,spfz;
  double kcrossx,kcrossy,kcrossz,knormsq;
  double kdots;
  double spkx,spky,spkz;
  double sidotsf,omega,iknorm,isnorm;

  spix = spi[0];
  spiy = spi[1];
  spiz = spi[2];

  spfx = sploc[0];
  spfy = sploc[1];
  spfz = sploc[2];

  kx = spiy*spfz - spiz*spfy;
  ky = spiz*spfx - spix*spfz;
  kz = spix*spfy - spiy*spfx;

  knormsq = kx*kx+ky*ky+kz*kz;
  sidotsf = spix*spfx + spiy*spfy + spiz*spfz;

  // if knormsq == 0.0, init and final spins are aligned
  // Rodrigues' formula breaks, needs to define another axis k

  if (knormsq == 0.0) {
    if (sidotsf > 0.0) {        // spins aligned and in same direction
      return 0;
    } else if (sidotsf < 0.0) { // spins aligned and in opposite directions

      // defining a rotation axis
      // first guess, k = spi x [100]
      // second guess, k = spi x [010]

      if (spiy*spiy + spiz*spiz != 0.0) { // spin not along [100]
        kx = 0.0;
        ky = spiz;
        kz = -spiy;
        knormsq = ky*ky + kz*kz;
      } else if (spix*spix + spiz*spiz != 0.0) { // spin not along [010]
        kx = -spiz;
        ky = 0.0;
        kz = spix;
        knormsq = kx*kx + kz*kz;
      } else error->all(FLERR,"Incorrect initial rotation operation");
      rot_flag = 1;
    }
  }

  // knormsq should not be 0

  if (knormsq == 0.0)
    error->all(FLERR,"Incorrect initial rotation operation");

  // normalize k vector

  iknorm = 1.0/sqrt(knormsq);
  kx *= iknorm;
  ky *= iknorm;
  kz *= iknorm;

  // calc. k x spi and total rotation angle

  kcrossx = ky*spiz - kz*spiy;
  kcrossy = kz*spix - kx*spiz;
  kcrossz = kx*spiy - ky*spix;

  kdots = kx*spix + ky*spiy + kz*spiz;

  omega = acos(sidotsf);
  omega *= fraction;

  // apply Rodrigues' formula

  spkx = spix*cos(omega);
  spky = spiy*cos(omega);
  spkz = spiz*cos(omega);

  spkx += kcrossx*sin(omega);
  spky += kcrossy*sin(omega);
  spkz += kcrossz*sin(omega);

  spkx += kx*kdots*(1.0-cos(omega));
  spky += ky*kdots*(1.0-cos(omega));
  spkz += kz*kdots*(1.0-cos(omega));

  // normalizing resulting spin vector

  isnorm = 1.0/sqrt(spkx*spkx+spky*spky+spkz*spkz);
  if (isnorm == 0.0)
    error->all(FLERR,"Incorrect initial rotation operation");

  spkx *= isnorm;
  spky *= isnorm;
  spkz *= isnorm;

  // returns rotated spin

  sploc[0] = spkx;
  sploc[1] = spky;
  sploc[2] = spkz;

  return rot_flag;
}

/* ----------------------------------------------------------------------
   universe proc 0 opens NEBSpin data file
   test if compressed
------------------------------------------------------------------------- */

void NEBSpin::open(char *file)
{
  compressed = 0;
  if (platform::has_compress_extension(file)) {
    fp = platform::compressed_read(file);
    if (!fp) error->one(FLERR,"Cannot open compressed file");
  } else fp = fopen(file,"r");

  if (fp == nullptr)
    error->one(FLERR,"Cannot open file {}: {}",file,utils::getsyserror());
}

/* ----------------------------------------------------------------------
   query fix NEBSpin for info on each replica
   universe proc 0 prints current NEBSpin status
------------------------------------------------------------------------- */

void NEBSpin::print_status()
{
  int nlocal = atom->nlocal;
  double tx,ty,tz;
  double tnorm2,local_norm_inf,temp_inf;
  double **sp = atom->sp;
  double **fm = atom->fm;

  // calc. magnetic torques

  tnorm2 = local_norm_inf = temp_inf = 0.0;
  for (int i = 0; i < nlocal; i++) {
    tx = (fm[i][1]*sp[i][2] - fm[i][2]*sp[i][1]);
    ty = (fm[i][2]*sp[i][0] - fm[i][0]*sp[i][2]);
    tz = (fm[i][0]*sp[i][1] - fm[i][1]*sp[i][0]);
    tnorm2 += tx*tx + ty*ty + tz*tz;
    temp_inf = MAX(fabs(tx),fabs(ty));
    temp_inf = MAX(fabs(tz),temp_inf);
    local_norm_inf = MAX(temp_inf,local_norm_inf);
  }

  double fmaxreplica = 0.0;
  double fmaxatom = 0.0;

  double fnorminf = 0.0;
  MPI_Allreduce(&local_norm_inf,&fnorminf,1,MPI_DOUBLE,MPI_MAX,world);

  if (me == 0) {
    MPI_Allreduce(&tnorm2,&fmaxreplica,1,MPI_DOUBLE,MPI_MAX,roots);
    MPI_Allreduce(&fnorminf,&fmaxatom,1,MPI_DOUBLE,MPI_MAX,roots);
    if (verbose) {
      freplica = new double[nreplica];
      MPI_Allgather(&tnorm2,1,MPI_DOUBLE,&freplica[0],1,MPI_DOUBLE,roots);
      fmaxatomInRepl = new double[nreplica];
      MPI_Allgather(&fnorminf,1,MPI_DOUBLE,&fmaxatomInRepl[0],1,MPI_DOUBLE,roots);
    }
  }

  double one[7];
  one[0] = fneb->veng;
  one[1] = fneb->plen;
  one[2] = fneb->nlen;
  one[3] = fneb->gradlen;

  if (verbose) {
    one[4] = fneb->dotpath;
    one[5] = fneb->dottangrad;
    one[6] = fneb->dotgrad;
  }

  if (output->thermo->normflag) one[0] /= atom->natoms;
  if (me == 0)
    MPI_Allgather(one,numall,MPI_DOUBLE,&all[0][0],numall,MPI_DOUBLE,roots);
  MPI_Bcast(&all[0][0],numall*nreplica,MPI_DOUBLE,0,world);

  rdist[0] = 0.0;
  for (int i = 1; i < nreplica; i++)
    rdist[i] = rdist[i-1] + all[i][1];
  double endpt = rdist[nreplica-1] = rdist[nreplica-2] + all[nreplica-2][2];
  for (int i = 1; i < nreplica; i++)
    rdist[i] /= endpt;

  // look up GradV for the initial, final, and climbing replicas
  // these are identical to fnorm2, but to be safe we
  // take them straight from fix_neb

  double gradvnorm0, gradvnorm1, gradvnormc;

  int irep;
  irep = 0;
  gradvnorm0 = all[irep][3];
  irep = nreplica-1;
  gradvnorm1 = all[irep][3];
  irep = fneb->rclimber;
  if (irep > -1) {
    gradvnormc = all[irep][3];
    ebf = all[irep][0]-all[0][0];
    ebr = all[irep][0]-all[nreplica-1][0];
  } else {
    double vmax = all[0][0];
    int top = 0;
    for (int m = 1; m < nreplica; m++)
      if (vmax < all[m][0]) {
        vmax = all[m][0];
        top = m;
      }
    irep = top;
    gradvnormc = all[irep][3];
    ebf = all[irep][0]-all[0][0];
    ebr = all[irep][0]-all[nreplica-1][0];
  }

  if (me_universe == 0) {
    FILE *uscreen = universe->uscreen;
    FILE *ulogfile = universe->ulogfile;
    if (uscreen) {
      utils::print(uscreen,"{} {:12.8g} {:12.8g} ",update->ntimestep,fmaxreplica,fmaxatom);
      fprintf(uscreen,"%12.8g %12.8g %12.8g ",gradvnorm0,gradvnorm1,gradvnormc);
      fprintf(uscreen,"%12.8g %12.8g %12.8g ",ebf,ebr,endpt);
      for (int i = 0; i < nreplica; i++)
        fprintf(uscreen,"%12.8g %12.8g ",rdist[i],all[i][0]);
      if (verbose) {
        for (int i = 0; i < nreplica-1; i++)
          fprintf(uscreen,"%12.8g %12.8g ",all[i][2],all[i][5]);
        fprintf(uscreen,"%12.8g %12.8g ",NAN,all[nreplica-1][5]);
      }
      fprintf(uscreen,"\n");
    }

    if (ulogfile) {
      utils::print(ulogfile,"{} {:12.8} {:12.8g} ",update->ntimestep,fmaxreplica,fmaxatom);
      fprintf(ulogfile,"%12.8g %12.8g %12.8g ",gradvnorm0,gradvnorm1,gradvnormc);
      fprintf(ulogfile,"%12.8g %12.8g %12.8g ",ebf,ebr,endpt);
      for (int i = 0; i < nreplica; i++)
        fprintf(ulogfile,"%12.8g %12.8g ",rdist[i],all[i][0]);
      if (verbose) {
        for (int i = 0; i < nreplica-1; i++)
          fprintf(ulogfile,"%12.8g %12.8g ",all[i][2],all[i][5]);
        fprintf(ulogfile,"%12.8g %12.8g ",NAN,all[nreplica-1][5]);
      }
      fprintf(ulogfile,"\n");
      fflush(ulogfile);
    }
    if ((me == 0) && verbose) {
      delete[] freplica;
      delete[] fmaxatomInRepl;
    }
  }
}
